implement profile interaction registration through plugins (mute and vpn detection implementation)
This commit is contained in:
parent
3cffdfdd9d
commit
2380f23dbe
@ -451,6 +451,7 @@ namespace IW4MAdmin.Application
|
|||||||
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
|
||||||
.AddSingleton<IAlertManager, AlertManager>()
|
.AddSingleton<IAlertManager, AlertManager>()
|
||||||
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
|
||||||
|
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
|
||||||
.AddSingleton(translationLookup)
|
.AddSingleton(translationLookup)
|
||||||
.AddDatabaseContextOptions(appConfig);
|
.AddDatabaseContextOptions(appConfig);
|
||||||
|
|
||||||
|
132
Application/Misc/InteractionRegistration.cs
Normal file
132
Application/Misc/InteractionRegistration.cs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Data.Models;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using InteractionRegistrationCallback =
|
||||||
|
System.Func<int?, Data.Models.Reference.Game?, System.Threading.CancellationToken,
|
||||||
|
System.Threading.Tasks.Task<SharedLibraryCore.Interfaces.IInteractionData>>;
|
||||||
|
|
||||||
|
namespace IW4MAdmin.Application.Misc;
|
||||||
|
|
||||||
|
public class InteractionRegistration : IInteractionRegistration
|
||||||
|
{
|
||||||
|
private readonly ILogger<InteractionRegistration> _logger;
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly ConcurrentDictionary<string, InteractionRegistrationCallback> _interactions = new();
|
||||||
|
|
||||||
|
public InteractionRegistration(ILogger<InteractionRegistration> logger, IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration)
|
||||||
|
{
|
||||||
|
var plugin = _serviceProvider.GetRequiredService<IEnumerable<IPlugin>>()
|
||||||
|
.FirstOrDefault(plugin => plugin.Name == source);
|
||||||
|
|
||||||
|
if (plugin is not ScriptPlugin scriptPlugin)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var wrappedDelegate = (int? clientId, Reference.Game? game, CancellationToken token) =>
|
||||||
|
Task.FromResult(
|
||||||
|
scriptPlugin.WrapDelegate<IInteractionData>(interactionRegistration, clientId, game, token));
|
||||||
|
|
||||||
|
if (!_interactions.ContainsKey(interactionName))
|
||||||
|
{
|
||||||
|
_interactions.TryAdd(interactionName, wrappedDelegate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_interactions[interactionName] = wrappedDelegate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterInteraction(string interactionName, InteractionRegistrationCallback interactionRegistration)
|
||||||
|
{
|
||||||
|
if (!_interactions.ContainsKey(interactionName))
|
||||||
|
{
|
||||||
|
_interactions.TryAdd(interactionName, interactionRegistration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_interactions[interactionName] = interactionRegistration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterInteraction(string interactionName)
|
||||||
|
{
|
||||||
|
if (_interactions.ContainsKey(interactionName))
|
||||||
|
{
|
||||||
|
_interactions.TryRemove(interactionName, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<IInteractionData>> GetInteractions(int? clientId = null,
|
||||||
|
Reference.Game? game = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return (await Task.WhenAll(_interactions.Select(async kvp =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await kvp.Value(clientId, game, token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"Could not get interaction for interaction {InteractionName} and ClientId {ClientId}", kvp.Key,
|
||||||
|
clientId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}))).Where(interaction => interaction is not null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ProcessInteraction(string interactionId, int? clientId = null,
|
||||||
|
Reference.Game? game = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (!_interactions.ContainsKey(interactionId))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Interaction with ID {interactionId} has not been registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var interaction = await _interactions[interactionId](clientId, game, token);
|
||||||
|
|
||||||
|
if (interaction.Action is not null)
|
||||||
|
{
|
||||||
|
return await interaction.Action(clientId, game, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.ScriptAction is not null)
|
||||||
|
{
|
||||||
|
foreach (var plugin in _serviceProvider.GetRequiredService<IEnumerable<IPlugin>>())
|
||||||
|
{
|
||||||
|
if (plugin is not ScriptPlugin scriptPlugin || scriptPlugin.Name != interaction.Source)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptPlugin.ExecuteAction<string>(interaction.ScriptAction, clientId, game, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex,
|
||||||
|
"Could not process interaction for interaction {InteractionName} and ClientId {ClientId}",
|
||||||
|
interactionId,
|
||||||
|
clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -339,6 +339,41 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T ExecuteAction<T>(Delegate action, params object[] param)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_onProcessing.Wait();
|
||||||
|
var args = param.Select(p => JsValue.FromObject(_scriptEngine, p)).ToArray();
|
||||||
|
var result = action.DynamicInvoke(JsValue.Undefined, args);
|
||||||
|
return (T)(result as JsValue)?.ToObject();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onProcessing.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onProcessing.Release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T WrapDelegate<T>(Delegate act, params object[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_onProcessing.Wait();
|
||||||
|
return (T)(act.DynamicInvoke(JsValue.Null,
|
||||||
|
args.Select(arg => JsValue.FromObject(_scriptEngine, arg)).ToArray()) as ObjectWrapper)?.ToObject();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (_onProcessing.CurrentCount == 0)
|
||||||
|
{
|
||||||
|
_onProcessing.Release(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// finds declared script commands in the script plugin
|
/// finds declared script commands in the script plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1"/>
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies"/>
|
<Exec Command="dotnet publish $(ProjectPath) -c $(ConfigurationName) -o $(ProjectDir)..\..\Build\Plugins --no-build --no-restore --no-dependencies" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Database.Models;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
namespace Mute;
|
namespace Mute;
|
||||||
|
|
||||||
public class Plugin : IPlugin
|
public class Plugin : IPlugin
|
||||||
{
|
{
|
||||||
public Plugin(IMetaServiceV2 metaService)
|
private readonly IInteractionRegistration _interactionRegistration;
|
||||||
|
private static readonly string MuteInteraction = nameof(MuteInteraction);
|
||||||
|
|
||||||
|
public Plugin(IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration)
|
||||||
{
|
{
|
||||||
|
_interactionRegistration = interactionRegistration;
|
||||||
DataManager = new DataManager(metaService);
|
DataManager = new DataManager(metaService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,11 +51,58 @@ public class Plugin : IPlugin
|
|||||||
|
|
||||||
public Task OnLoadAsync(IManager manager)
|
public Task OnLoadAsync(IManager manager)
|
||||||
{
|
{
|
||||||
|
_interactionRegistration.RegisterInteraction(MuteInteraction, async (clientId, game, token) =>
|
||||||
|
{
|
||||||
|
if (!clientId.HasValue || game.HasValue && !SupportedGames.Contains((Server.Game)game.Value))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var muteState = await DataManager.ReadPersistentData(new EFClient { ClientId = clientId.Value });
|
||||||
|
|
||||||
|
return muteState is MuteState.Unmuted or MuteState.Unmuting
|
||||||
|
? new InteractionData
|
||||||
|
{
|
||||||
|
EntityId = clientId,
|
||||||
|
Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"],
|
||||||
|
DisplayMeta = "oi-volume-off",
|
||||||
|
ActionPath = "DynamicAction",
|
||||||
|
ActionMeta = new()
|
||||||
|
{
|
||||||
|
{ "InteractionId", "command" },
|
||||||
|
{ "Data", $"mute @{clientId.Value}" },
|
||||||
|
{ "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] },
|
||||||
|
{ "Name", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_MUTE"] },
|
||||||
|
{ "ShouldRefresh", true.ToString() }
|
||||||
|
},
|
||||||
|
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||||
|
Source = Name
|
||||||
|
}
|
||||||
|
: new InteractionData
|
||||||
|
{
|
||||||
|
EntityId = clientId,
|
||||||
|
Name = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"],
|
||||||
|
DisplayMeta = "oi-volume-high",
|
||||||
|
ActionPath = "DynamicAction",
|
||||||
|
ActionMeta = new()
|
||||||
|
{
|
||||||
|
{ "InteractionId", "command" },
|
||||||
|
{ "Data", $"mute @{clientId.Value}" },
|
||||||
|
{ "ActionButtonLabel", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"] },
|
||||||
|
{ "Name", Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_CONTEXT_MENU_ACTION_UNMUTE"] },
|
||||||
|
{ "ShouldRefresh", true.ToString() }
|
||||||
|
},
|
||||||
|
MinimumPermission = Data.Models.Client.EFClient.Permission.Moderator,
|
||||||
|
Source = Name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task OnUnloadAsync()
|
public Task OnUnloadAsync()
|
||||||
{
|
{
|
||||||
|
_interactionRegistration.UnregisterInteraction(MuteInteraction);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -19,7 +19,7 @@ const commands = [{
|
|||||||
|
|
||||||
const plugin = {
|
const plugin = {
|
||||||
author: 'RaidMax',
|
author: 'RaidMax',
|
||||||
version: 1.3,
|
version: 1.4,
|
||||||
name: 'VPN Detection Plugin',
|
name: 'VPN Detection Plugin',
|
||||||
manager: null,
|
manager: null,
|
||||||
logger: null,
|
logger: null,
|
||||||
@ -82,9 +82,35 @@ const plugin = {
|
|||||||
this.configHandler = _configHandler;
|
this.configHandler = _configHandler;
|
||||||
this.configHandler.GetValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(element));
|
this.configHandler.GetValue('vpnExceptionIds').forEach(element => vpnExceptionIds.push(element));
|
||||||
this.logger.WriteInfo(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
this.logger.WriteInfo(`Loaded ${vpnExceptionIds.length} ids into whitelist`);
|
||||||
|
|
||||||
|
this.interactionRegistration = _serviceResolver.ResolveService('IInteractionRegistration');
|
||||||
|
this.interactionRegistration.RegisterScriptInteraction('WhitelistVPN', this.name, (clientId, game, token) => {
|
||||||
|
if (vpnExceptionIds.includes(clientId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const helpers = importNamespace('SharedLibraryCore.Helpers');
|
||||||
|
const interactionData = new helpers.InteractionData();
|
||||||
|
|
||||||
|
interactionData.EntityId = clientId;
|
||||||
|
interactionData.Name = 'Whitelist VPN';
|
||||||
|
interactionData.DisplayMeta = 'oi-circle-check';
|
||||||
|
|
||||||
|
interactionData.ActionMeta.Add('InteractionId', 'command');
|
||||||
|
interactionData.ActionMeta.Add('Data', `whitelistvpn @${clientId}`);
|
||||||
|
interactionData.ActionMeta.Add('ActionButtonLabel', 'Allow');
|
||||||
|
interactionData.ActionMeta.Add('Name', 'Allow VPN Connection');
|
||||||
|
interactionData.ActionMeta.Add('ShouldRefresh', true.toString());
|
||||||
|
|
||||||
|
interactionData.ActionPath = 'DynamicAction';
|
||||||
|
interactionData.MinimumPermission = 3;
|
||||||
|
interactionData.Source = this.name;
|
||||||
|
return interactionData;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnloadAsync: function () {
|
onUnloadAsync: function () {
|
||||||
|
this.interactionRegistration.UnregisterInteraction('WhitelistVPN');
|
||||||
},
|
},
|
||||||
|
|
||||||
onTickAsync: function (server) {
|
onTickAsync: function (server) {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.6.16.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.9.8.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -34,5 +34,6 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public string CurrentServerName { get; set; }
|
public string CurrentServerName { get; set; }
|
||||||
public IGeoLocationResult GeoLocationInfo { get; set; }
|
public IGeoLocationResult GeoLocationInfo { get; set; }
|
||||||
public ClientNoteMetaResponse NoteMeta { get; set; }
|
public ClientNoteMetaResponse NoteMeta { get; set; }
|
||||||
|
public List<IInteractionData> Interactions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
SharedLibraryCore/Helpers/InteractionData.cs
Normal file
27
SharedLibraryCore/Helpers/InteractionData.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Data.Models.Client;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
|
using InteractionCallback = System.Func<int?, Data.Models.Reference.Game?, System.Threading.CancellationToken, System.Threading.Tasks.Task<string>>;
|
||||||
|
using ScriptInteractionCallback = System.Func<int?, Data.Models.Reference.Game?, System.Threading.CancellationToken, System.Threading.Tasks.Task<string>>;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Helpers;
|
||||||
|
|
||||||
|
public class InteractionData : IInteractionData
|
||||||
|
{
|
||||||
|
public int? EntityId { get; set; }
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
public string DisplayMeta { get; set; }
|
||||||
|
public string ActionPath { get; set; }
|
||||||
|
public Dictionary<string, string> ActionMeta { get; set; } = new();
|
||||||
|
public string ActionUri => ActionPath + "?" + string.Join('&', ActionMeta.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||||
|
public EFClient.Permission? MinimumPermission { get; set; }
|
||||||
|
public string PermissionEntity { get; set; } = "Interaction";
|
||||||
|
public string PermissionAccess { get; set; } = "Read";
|
||||||
|
public string Source { get; set; }
|
||||||
|
public InteractionCallback Action { get; set; }
|
||||||
|
public Delegate ScriptAction { get; set; }
|
||||||
|
}
|
25
SharedLibraryCore/Interfaces/IInteractionData.cs
Normal file
25
SharedLibraryCore/Interfaces/IInteractionData.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Data.Models.Client;
|
||||||
|
using InteractionCallback = System.Func<int?, Data.Models.Reference.Game?, System.Threading.CancellationToken, System.Threading.Tasks.Task<string>>;
|
||||||
|
using ScriptInteractionCallback = System.Func<int?, Data.Models.Reference.Game?, System.Threading.CancellationToken, System.Threading.Tasks.Task<string>>;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
public interface IInteractionData
|
||||||
|
{
|
||||||
|
int? EntityId { get; }
|
||||||
|
bool Enabled { get; }
|
||||||
|
string Name { get; }
|
||||||
|
string Description { get; }
|
||||||
|
string DisplayMeta { get; }
|
||||||
|
string ActionPath { get; }
|
||||||
|
Dictionary<string, string> ActionMeta { get; }
|
||||||
|
string ActionUri { get; }
|
||||||
|
EFClient.Permission? MinimumPermission { get; }
|
||||||
|
string PermissionEntity { get; }
|
||||||
|
string PermissionAccess { get; }
|
||||||
|
string Source { get; }
|
||||||
|
InteractionCallback Action { get; }
|
||||||
|
Delegate ScriptAction { get; }
|
||||||
|
}
|
17
SharedLibraryCore/Interfaces/IInteractionRegistration.cs
Normal file
17
SharedLibraryCore/Interfaces/IInteractionRegistration.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Data.Models;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
|
public interface IInteractionRegistration
|
||||||
|
{
|
||||||
|
void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration);
|
||||||
|
void RegisterInteraction(string interactionName, Func<int?, Reference.Game?, CancellationToken, Task<IInteractionData>> interactionRegistration);
|
||||||
|
void UnregisterInteraction(string interactionName);
|
||||||
|
Task<IEnumerable<IInteractionData>> GetInteractions(int? clientId = null,
|
||||||
|
Reference.Game? game = null, CancellationToken token = default);
|
||||||
|
Task<string> ProcessInteraction(string interactionId, int? clientId = null, Reference.Game? game = null, CancellationToken token = default);
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||||
<Version>2022.6.16.1</Version>
|
<Version>2022.9.8.1</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Description>Shared Library for IW4MAdmin</Description>
|
<Description>Shared Library for IW4MAdmin</Description>
|
||||||
<PackageVersion>2022.6.16.1</PackageVersion>
|
<PackageVersion>2022.9.8.1</PackageVersion>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Models;
|
using Data.Models;
|
||||||
@ -24,6 +25,7 @@ namespace WebfrontCore.Controllers
|
|||||||
{
|
{
|
||||||
private readonly ApplicationConfiguration _appConfig;
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
private readonly IMetaServiceV2 _metaService;
|
private readonly IMetaServiceV2 _metaService;
|
||||||
|
private readonly IInteractionRegistration _interactionRegistration;
|
||||||
private readonly string _banCommandName;
|
private readonly string _banCommandName;
|
||||||
private readonly string _tempbanCommandName;
|
private readonly string _tempbanCommandName;
|
||||||
private readonly string _unbanCommandName;
|
private readonly string _unbanCommandName;
|
||||||
@ -37,10 +39,12 @@ namespace WebfrontCore.Controllers
|
|||||||
private readonly string _addClientNoteCommandName;
|
private readonly string _addClientNoteCommandName;
|
||||||
|
|
||||||
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
|
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
|
||||||
ApplicationConfiguration appConfig, IMetaServiceV2 metaService) : base(manager)
|
ApplicationConfiguration appConfig, IMetaServiceV2 metaService,
|
||||||
|
IInteractionRegistration interactionRegistration) : base(manager)
|
||||||
{
|
{
|
||||||
_appConfig = appConfig;
|
_appConfig = appConfig;
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
|
_interactionRegistration = interactionRegistration;
|
||||||
|
|
||||||
foreach (var cmd in registeredCommands)
|
foreach (var cmd in registeredCommands)
|
||||||
{
|
{
|
||||||
@ -86,6 +90,81 @@ namespace WebfrontCore.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IActionResult DynamicActionForm(int? id, string meta)
|
||||||
|
{
|
||||||
|
var metaDict = JsonSerializer.Deserialize<Dictionary<string, string>>(meta);
|
||||||
|
|
||||||
|
if (metaDict is null)
|
||||||
|
{
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
metaDict.TryGetValue(nameof(ActionInfo.ActionButtonLabel), out var label);
|
||||||
|
metaDict.TryGetValue(nameof(ActionInfo.Name), out var name);
|
||||||
|
metaDict.TryGetValue(nameof(ActionInfo.ShouldRefresh), out var refresh);
|
||||||
|
metaDict.TryGetValue("Data", out var data);
|
||||||
|
metaDict.TryGetValue("InteractionId", out var interactionId);
|
||||||
|
|
||||||
|
bool.TryParse(refresh, out var shouldRefresh);
|
||||||
|
|
||||||
|
var inputs = new List<InputInfo>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "InteractionId",
|
||||||
|
Value = interactionId,
|
||||||
|
Type = "hidden"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "data",
|
||||||
|
Value = data,
|
||||||
|
Type = "hidden"
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "TargetId",
|
||||||
|
Value = id?.ToString(),
|
||||||
|
Type = "hidden"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var info = new ActionInfo
|
||||||
|
{
|
||||||
|
ActionButtonLabel = label,
|
||||||
|
Name = name,
|
||||||
|
Action = nameof(DynamicActionAsync),
|
||||||
|
ShouldRefresh = shouldRefresh,
|
||||||
|
Inputs = inputs
|
||||||
|
};
|
||||||
|
|
||||||
|
return View("_ActionForm", info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> DynamicActionAsync(string interactionId, string data, int? targetId,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (interactionId == "command")
|
||||||
|
{
|
||||||
|
var server = Manager.GetServers().First();
|
||||||
|
|
||||||
|
return await Task.FromResult(RedirectToAction("Execute", "Console", new
|
||||||
|
{
|
||||||
|
serverId = server.EndPoint,
|
||||||
|
command = $"{_appConfig.CommandPrefix}{data}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var game = (Reference.Game?)null;
|
||||||
|
|
||||||
|
if (targetId.HasValue)
|
||||||
|
{
|
||||||
|
game = (await Manager.GetClientService().Get(targetId.Value))?.GameName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(await _interactionRegistration.ProcessInteraction(interactionId, targetId, game, token));
|
||||||
|
}
|
||||||
|
|
||||||
public IActionResult BanForm()
|
public IActionResult BanForm()
|
||||||
{
|
{
|
||||||
var info = new ActionInfo
|
var info = new ActionInfo
|
||||||
|
@ -25,14 +25,16 @@ namespace WebfrontCore.Controllers
|
|||||||
private readonly StatsConfiguration _config;
|
private readonly StatsConfiguration _config;
|
||||||
private readonly IGeoLocationService _geoLocationService;
|
private readonly IGeoLocationService _geoLocationService;
|
||||||
private readonly ClientService _clientService;
|
private readonly ClientService _clientService;
|
||||||
|
private readonly IInteractionRegistration _interactionRegistration;
|
||||||
|
|
||||||
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
public ClientController(IManager manager, IMetaServiceV2 metaService, StatsConfiguration config,
|
||||||
IGeoLocationService geoLocationService, ClientService clientService) : base(manager)
|
IGeoLocationService geoLocationService, ClientService clientService, IInteractionRegistration interactionRegistration) : base(manager)
|
||||||
{
|
{
|
||||||
_metaService = metaService;
|
_metaService = metaService;
|
||||||
_config = config;
|
_config = config;
|
||||||
_geoLocationService = geoLocationService;
|
_geoLocationService = geoLocationService;
|
||||||
_clientService = clientService;
|
_clientService = clientService;
|
||||||
|
_interactionRegistration = interactionRegistration;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
@ -75,6 +77,8 @@ namespace WebfrontCore.Controllers
|
|||||||
note.OriginEntityName = await _clientService.GetClientNameById(note.OriginEntityId);
|
note.OriginEntityName = await _clientService.GetClientNameById(note.OriginEntityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var interactions = await _interactionRegistration.GetInteractions(id, client.GameName, token);
|
||||||
|
|
||||||
// even though we haven't set their level to "banned" yet
|
// even though we haven't set their level to "banned" yet
|
||||||
// (ie they haven't reconnected with the infringing player identifier)
|
// (ie they haven't reconnected with the infringing player identifier)
|
||||||
// we want to show them as banned as to not confuse people.
|
// we want to show them as banned as to not confuse people.
|
||||||
@ -137,7 +141,8 @@ namespace WebfrontCore.Controllers
|
|||||||
ingameClient.CurrentServer.Port),
|
ingameClient.CurrentServer.Port),
|
||||||
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
|
CurrentServerName = ingameClient?.CurrentServer?.Hostname,
|
||||||
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString),
|
GeoLocationInfo = await _geoLocationService.Locate(client.IPAddressString),
|
||||||
NoteMeta = string.IsNullOrWhiteSpace(note?.Note) ? null: note
|
NoteMeta = string.IsNullOrWhiteSpace(note?.Note) ? null: note,
|
||||||
|
Interactions = interactions.ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
|
||||||
|
@ -15,7 +15,8 @@ public enum WebfrontEntity
|
|||||||
RecentPlayersPage,
|
RecentPlayersPage,
|
||||||
ProfilePage,
|
ProfilePage,
|
||||||
AdminMenu,
|
AdminMenu,
|
||||||
ClientNote
|
ClientNote,
|
||||||
|
Interaction
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WebfrontPermission
|
public enum WebfrontPermission
|
||||||
|
@ -105,6 +105,8 @@ namespace WebfrontCore
|
|||||||
{
|
{
|
||||||
options.AccessDeniedPath = "/";
|
options.AccessDeniedPath = "/";
|
||||||
options.LoginPath = "/";
|
options.LoginPath = "/";
|
||||||
|
options.Events.OnValidatePrincipal += ClaimsPermissionRemoval.ValidateAsync;
|
||||||
|
options.Events.OnSignedIn += ClaimsPermissionRemoval.OnSignedIn;
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton(Program.Manager);
|
services.AddSingleton(Program.Manager);
|
||||||
@ -138,6 +140,7 @@ namespace WebfrontCore
|
|||||||
services.AddSingleton(Program.ApplicationServiceProvider
|
services.AddSingleton(Program.ApplicationServiceProvider
|
||||||
.GetRequiredService<StatsConfiguration>());
|
.GetRequiredService<StatsConfiguration>());
|
||||||
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
|
||||||
|
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IInteractionRegistration>());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
@ -12,6 +12,7 @@ public class SideContextMenuItem
|
|||||||
public string Icon { get; set; }
|
public string Icon { get; set; }
|
||||||
public string Tooltip { get; set; }
|
public string Tooltip { get; set; }
|
||||||
public int? EntityId { get; set; }
|
public int? EntityId { get; set; }
|
||||||
|
public string Meta { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
@{
|
@{
|
||||||
Layout = null;
|
Layout = null;
|
||||||
}
|
}
|
||||||
<h5 class="modal-title mb-10">@Model.Name.Titleize()</h5>
|
<h5 class="modal-title mb-10">@Model.Name?.Titleize()</h5>
|
||||||
@if (Model.Inputs.Any(input => input.Type != "hidden"))
|
@if (Model.Inputs.Any(input => input.Type != "hidden"))
|
||||||
{
|
{
|
||||||
<hr class="mb-10"/>
|
<hr class="mb-10"/>
|
||||||
|
@ -391,6 +391,20 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var interaction in Model.Interactions.Where(i => (int)ViewBag.User.Level >= ((int?)i.MinimumPermission ?? 0)))
|
||||||
|
{
|
||||||
|
menuItems.Items.Add(new SideContextMenuItem
|
||||||
|
{
|
||||||
|
Title = interaction.Name,
|
||||||
|
Tooltip = interaction.Description,
|
||||||
|
EntityId = interaction.EntityId,
|
||||||
|
Icon = interaction.DisplayMeta,
|
||||||
|
Reference = interaction.ActionPath,
|
||||||
|
Meta = System.Text.Json.JsonSerializer.Serialize(interaction.ActionMeta),
|
||||||
|
IsButton = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
<partial name="_SideContextMenu" for="@menuItems"></partial>
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
@foreach (var item in Model.Items)
|
@foreach (var item in Model.Items)
|
||||||
{
|
{
|
||||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId">
|
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action")" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId" data-action-meta="@item.Meta">
|
||||||
<div class="@(item.IsButton ? "btn btn-block" : "")" data-title="@item.Tooltip" data-placement="left" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
<div class="@(item.IsButton ? "btn btn-block" : "")" data-title="@item.Tooltip" data-placement="left" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
||||||
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
||||||
<span class="@(item.IsActive ? "text-primary" : "") text-truncate">@item.Title</span>
|
<span class="@(item.IsActive ? "text-primary" : "") text-truncate">@item.Title</span>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
@foreach (var item in Model.Items)
|
@foreach (var item in Model.Items)
|
||||||
{
|
{
|
||||||
<div class="mt-15 mb-15">
|
<div class="mt-15 mb-15">
|
||||||
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action") no-decoration" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId">
|
<a href="@(item.IsLink ? item.Reference : "#")" class="@(item.IsLink ? "" : "profile-action") no-decoration" data-action="@(item.IsLink ? "" : item.Reference)" data-action-id="@item.EntityId" data-action-meta="@item.Meta">
|
||||||
<div class="btn btn-block btn-lg @(item.IsActive ? "btn-primary" : "") text-truncate" data-title="@item.Tooltip" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
<div class="btn btn-block btn-lg @(item.IsActive ? "btn-primary" : "") text-truncate" data-title="@item.Tooltip" data-toggle="@(string.IsNullOrEmpty(item.Tooltip) ? "" : "tooltip")">
|
||||||
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
<i class="@(string.IsNullOrEmpty(item.Icon) ? "" : $"oi {item.Icon}") mr-5 font-size-12"></i>
|
||||||
<span>@item.Title</span>
|
<span>@item.Title</span>
|
||||||
|
@ -91,12 +91,18 @@ $(document).ready(function () {
|
|||||||
$(document).off('click', '.profile-action');
|
$(document).off('click', '.profile-action');
|
||||||
$(document).on('click', '.profile-action', function (e) {
|
$(document).on('click', '.profile-action', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const actionType = $(this).data('action');
|
const action = $(this).data('action');
|
||||||
const actionId = $(this).data('action-id');
|
const actionId = $(this).data('action-id');
|
||||||
|
const actionMeta = $(this).data('action-meta');
|
||||||
const responseDuration = $(this).data('response-duration') || 5000;
|
const responseDuration = $(this).data('response-duration') || 5000;
|
||||||
const actionIdKey = actionId === undefined ? '' : `?id=${actionId}`;
|
let actionKeys = actionId === undefined ? '' : `?id=${actionId}`;
|
||||||
|
|
||||||
|
if (actionMeta !== undefined) {
|
||||||
|
actionKeys = actionKeys + '&meta=' + JSON.stringify(actionMeta);
|
||||||
|
}
|
||||||
showLoader();
|
showLoader();
|
||||||
$.get(`/Action/${actionType}Form/${actionIdKey}`)
|
|
||||||
|
$.get(`/Action/${action}Form/${actionKeys}`)
|
||||||
.done(function (response) {
|
.done(function (response) {
|
||||||
$('#actionModal .modal-message').fadeOut('fast')
|
$('#actionModal .modal-message').fadeOut('fast')
|
||||||
$('#actionModal').attr('data-response-duration', responseDuration);
|
$('#actionModal').attr('data-response-duration', responseDuration);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user