add support for plugin generated pages (interactions). add disallow vpn command

This commit is contained in:
RaidMax 2022-10-17 09:17:43 -05:00
parent 3295315339
commit 3367c5c22f
17 changed files with 311 additions and 45 deletions

View File

@ -24,7 +24,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-2038" /> <PackageReference Include="Jint" Version="3.0.0-beta-2041" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" /> <PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@ -85,7 +85,7 @@ namespace IW4MAdmin.Application
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents, IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory,
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider, IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager) ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration)
{ {
MiddlewareActionHandler = actionHandler; MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag<Server>(); _servers = new ConcurrentBag<Server>();
@ -115,9 +115,11 @@ namespace IW4MAdmin.Application
_changeHistoryService = changeHistoryService; _changeHistoryService = changeHistoryService;
_appConfig = appConfig; _appConfig = appConfig;
Plugins = plugins; Plugins = plugins;
InteractionRegistration = interactionRegistration;
} }
public IEnumerable<IPlugin> Plugins { get; } public IEnumerable<IPlugin> Plugins { get; }
public IInteractionRegistration InteractionRegistration { get; }
public async Task ExecuteEvent(GameEvent newEvent) public async Task ExecuteEvent(GameEvent newEvent)
{ {

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace IW4MAdmin.Application.Extensions;
public static class ScriptPluginExtensions
{
public static IEnumerable<object> GetClientsBasicData(
this DbSet<Data.Models.Client.EFClient> set, int[] clientIds)
{
return set.Where(client => clientIds.Contains(client.ClientId))
.Select(client => new
{
client.ClientId,
client.CurrentAlias,
client.Level,
client.NetworkId
}).ToList();
}
}

View File

@ -70,23 +70,11 @@ public class InteractionRegistration : IInteractionRegistration
} }
} }
public async Task<IEnumerable<IInteractionData>> GetInteractions(int? clientId = null, public async Task<IEnumerable<IInteractionData>> GetInteractions(string interactionPrefix = null,
int? clientId = null,
Reference.Game? game = null, CancellationToken token = default) Reference.Game? game = null, CancellationToken token = default)
{ {
return (await Task.WhenAll(_interactions.Select(async kvp => return await GetInteractionsPrivate(interactionPrefix, clientId, game, token);
{
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 originId, int? targetId = null, public async Task<string> ProcessInteraction(string interactionId, int originId, int? targetId = null,
@ -115,17 +103,40 @@ public class InteractionRegistration : IInteractionRegistration
continue; continue;
} }
return scriptPlugin.ExecuteAction<string>(interaction.ScriptAction, originId, targetId, game, meta, token); return scriptPlugin.ExecuteAction<string>(interaction.ScriptAction, originId, targetId, game, meta,
token);
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning(ex, _logger.LogWarning(ex,
"Could not process interaction for interaction {InteractionName} and OriginId {ClientId}", "Could not process interaction for {InteractionName} and OriginId {ClientId}",
interactionId, originId); interactionId, originId);
} }
return null; return null;
} }
private async Task<IEnumerable<IInteractionData>> GetInteractionsPrivate(string prefix = null, int? clientId = null,
Reference.Game? game = null, CancellationToken token = default)
{
return (await Task.WhenAll(_interactions
.Where(interaction => string.IsNullOrWhiteSpace(prefix) || interaction.Key.StartsWith(prefix)).Select(
async kvp =>
{
try
{
return await kvp.Value(clientId, game, token);
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"Could not get interaction for {InteractionName} and ClientId {ClientId}",
kvp.Key,
clientId);
return null;
}
}))).Where(interaction => interaction is not null);
}
} }

View File

@ -13,6 +13,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using IW4MAdmin.Application.Extensions;
using Jint.Runtime.Interop; using Jint.Runtime.Interop;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog.Context; using Serilog.Context;
@ -112,7 +113,7 @@ namespace IW4MAdmin.Application.Misc
} }
_scriptEngine = new Engine(cfg => _scriptEngine = new Engine(cfg =>
cfg.AddExtensionMethods(typeof(Utilities), typeof(Enumerable), typeof(Queryable)) cfg.AddExtensionMethods(typeof(Utilities), typeof(Enumerable), typeof(Queryable), typeof(ScriptPluginExtensions))
.AllowClr(new[] .AllowClr(new[]
{ {
typeof(System.Net.Http.HttpClient).Assembly, typeof(System.Net.Http.HttpClient).Assembly,

View File

@ -22,7 +22,7 @@ public class Plugin : IPlugin
private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"}; private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"};
private readonly IInteractionRegistration _interactionRegistration; private readonly IInteractionRegistration _interactionRegistration;
private readonly IRemoteCommandService _remoteCommandService; private readonly IRemoteCommandService _remoteCommandService;
private static readonly string MuteInteraction = nameof(MuteInteraction); private static readonly string MuteInteraction = "Webfront::Profile::Mute";
public Plugin(ILogger<Plugin> logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration, public Plugin(ILogger<Plugin> logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration,
ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService) ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService)

View File

@ -1,4 +1,7 @@
let vpnExceptionIds = []; let vpnExceptionIds = [];
const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList';
const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist';
const commands = [{ const commands = [{
name: 'whitelistvpn', name: 'whitelistvpn',
description: 'whitelists a player\'s client id from VPN detection', description: 'whitelists a player\'s client id from VPN detection',
@ -15,8 +18,35 @@ const commands = [{
gameEvent.Origin.Tell(`Successfully whitelisted ${gameEvent.Target.Name}`); gameEvent.Origin.Tell(`Successfully whitelisted ${gameEvent.Target.Name}`);
} }
},
{
name: 'disallowvpn',
description: 'disallows a player from connecting with a VPN',
alias: 'dv',
permission: 'SeniorAdmin',
targetRequired: true,
arguments: [{
name: 'player',
required: true
}],
execute: (gameEvent) => {
vpnExceptionIds = vpnExceptionIds.filter(exception => parseInt(exception) !== parseInt(gameEvent.Target.ClientId));
plugin.configHandler.SetValue('vpnExceptionIds', vpnExceptionIds);
gameEvent.Origin.Tell(`Successfully disallowed ${gameEvent.Target.Name} from connecting with VPN`);
}
}]; }];
const getClientsData = (clientIds) => {
const contextFactory = _serviceResolver.ResolveService('IDatabaseContextFactory');
const context = contextFactory.CreateContext(false);
const clientSet = context.Clients;
const clients = clientSet.GetClientsBasicData(clientIds);
context.Dispose();
return clients;
}
const plugin = { const plugin = {
author: 'RaidMax', author: 'RaidMax',
version: 1.5, version: 1.5,
@ -28,7 +58,7 @@ const plugin = {
let exempt = false; let exempt = false;
// prevent players that are exempt from being kicked // prevent players that are exempt from being kicked
vpnExceptionIds.forEach(function (id) { vpnExceptionIds.forEach(function (id) {
if (id == origin.ClientId) { // when loaded from the config the "id" type is not the same as the ClientId type if (parseInt(id) === parseInt(origin.ClientId)) {
exempt = true; exempt = true;
return false; return false;
} }
@ -83,33 +113,99 @@ const plugin = {
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 = _serviceResolver.ResolveService('IInteractionRegistration');
this.interactionRegistration.RegisterScriptInteraction('WhitelistVPN', this.name, (targetId, game, token) => {
// registers the profile action
this.interactionRegistration.RegisterScriptInteraction(vpnWhitelistKey, this.name, (targetId, game, token) => {
const helpers = importNamespace('SharedLibraryCore.Helpers');
const interactionData = new helpers.InteractionData();
interactionData.ActionPath = 'DynamicAction';
interactionData.InteractionId = vpnWhitelistKey;
interactionData.EntityId = targetId;
interactionData.MinimumPermission = 3;
interactionData.Source = this.name;
interactionData.ActionMeta.Add('InteractionId', 'command'); // indicate we're wanting to execute a command
interactionData.ActionMeta.Add('ShouldRefresh', true.toString()); // indicates that the page should refresh after performing the action
if (vpnExceptionIds.includes(targetId)) { if (vpnExceptionIds.includes(targetId)) {
return; interactionData.Name = _localization.LocalizationIndex['WEBFRONT_VPN_BUTTON_DISALLOW']; // text for the profile button
interactionData.DisplayMeta = 'oi-circle-x';
interactionData.ActionMeta.Add('Data', `disallowvpn`); // command to execute
interactionData.ActionMeta.Add('ActionButtonLabel', _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_DISALLOW_CONFIRM']); // confirm button on the dialog
interactionData.ActionMeta.Add('Name', _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_DISALLOW_TITLE']); // title on the confirm dialog
} else {
interactionData.Name = _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_ALLOW']; // text for the profile button
interactionData.DisplayMeta = 'oi-circle-check';
interactionData.ActionMeta.Add('Data', `whitelistvpn`); // command to execute
interactionData.ActionMeta.Add('ActionButtonLabel', _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_ALLOW_CONFIRM']); // confirm button on the dialog
interactionData.ActionMeta.Add('Name', _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_ALLOW_TITLE']); // title on the confirm dialog
} }
return interactionData;
});
// registers the navigation/page
this.interactionRegistration.RegisterScriptInteraction(vpnAllowListKey, this.name, (targetId, game, token) => {
const helpers = importNamespace('SharedLibraryCore.Helpers'); const helpers = importNamespace('SharedLibraryCore.Helpers');
const interactionData = new helpers.InteractionData(); const interactionData = new helpers.InteractionData();
interactionData.EntityId = targetId; interactionData.Name = _localization.LocalizationIndex['WEBFRONT_NAV_VPN_TITLE']; // navigation link name
interactionData.Name = 'Whitelist VPN'; interactionData.Description = _localization.LocalizationIndex['WEBFRONT_NAV_VPN_DESC']; // alt and title
interactionData.DisplayMeta = 'oi-circle-check'; interactionData.DisplayMeta = 'oi-circle-check'; // nav icon
interactionData.InteractionId = vpnAllowListKey;
interactionData.ActionMeta.Add('InteractionId', 'command'); interactionData.MinimumPermission = 3; // moderator
interactionData.ActionMeta.Add('Data', `whitelistvpn`); interactionData.InteractionType = 2; // 1 is RawContent for apis etc..., 2 is
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; interactionData.Source = this.name;
interactionData.ScriptAction = (sourceId, targetId, game, meta, token) => {
const clientsData = getClientsData(vpnExceptionIds);
let table = '<table class="table bg-dark-dm bg-light-lm">';
const disallowInteraction = {
InteractionId: 'command',
Data: 'disallowvpn',
ActionButtonLabel: _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_DISALLOW_CONFIRM'],
Name: _localization.LocalizationIndex['WEBFRONT_VPN_ACTION_DISALLOW_TITLE']
};
if (clientsData.length === 0)
{
table += `<tr><td>No players are whitelisted.</td></tr>`
}
clientsData.forEach(client => {
table += `<tr>
<td>
<a href="/Client/Profile/${client.ClientId}" class="level-color-${client.Level.toLowerCase()} no-decoration">${client.CurrentAlias.Name.StripColors()}</a>
</td>
<td>
<a href="#" class="profile-action no-decoration float-right" data-action="DynamicAction" data-action-id="${client.ClientId}"
data-action-meta="${encodeURI(JSON.stringify(disallowInteraction))}">
<div class="btn">
<i class="oi oi-circle-x mr-5 font-size-12"></i>
<span class="text-truncate">${_localization.LocalizationIndex['WEBFRONT_VPN_BUTTON_DISALLOW']}</span>
</div>
</a>
</td>
</tr>`;
});
table += '</table>';
return table;
}
return interactionData; return interactionData;
}); });
}, },
onUnloadAsync: function () { onUnloadAsync: function () {
this.interactionRegistration.UnregisterInteraction('WhitelistVPN'); this.interactionRegistration.UnregisterInteraction(vpnWhitelistKey);
this.interactionRegistration.UnregisterInteraction(vpnAllowListKey);
}, },
onTickAsync: function (server) { onTickAsync: function (server) {

View File

@ -19,6 +19,7 @@ namespace SharedLibraryCore
{ {
public class BaseController : Controller public class BaseController : Controller
{ {
protected readonly IInteractionRegistration InteractionRegistration;
protected readonly IAlertManager AlertManager; protected readonly IAlertManager AlertManager;
/// <summary> /// <summary>
@ -41,6 +42,7 @@ namespace SharedLibraryCore
public BaseController(IManager manager) public BaseController(IManager manager)
{ {
InteractionRegistration = manager.InteractionRegistration;
AlertManager = manager.AlertManager; AlertManager = manager.AlertManager;
Manager = manager; Manager = manager;
Localization = Utilities.CurrentLocalization.LocalizationIndex; Localization = Utilities.CurrentLocalization.LocalizationIndex;
@ -72,8 +74,6 @@ namespace SharedLibraryCore
}; };
} }
protected async Task SignInAsync(ClaimsPrincipal claimsPrinciple) protected async Task SignInAsync(ClaimsPrincipal claimsPrinciple)
{ {
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrinciple, await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrinciple,
@ -86,7 +86,7 @@ namespace SharedLibraryCore
}); });
} }
public override void OnActionExecuting(ActionExecutingContext context) public override async void OnActionExecuting(ActionExecutingContext context)
{ {
if (!HttpContext.Connection.RemoteIpAddress.GetAddressBytes().SequenceEqual(LocalHost)) if (!HttpContext.Connection.RemoteIpAddress.GetAddressBytes().SequenceEqual(LocalHost))
{ {
@ -154,6 +154,7 @@ namespace SharedLibraryCore
&& !communityName.Contains("IW4MAdmin") && !communityName.Contains("IW4MAdmin")
&& AppConfig.CommunityInformation.IsEnabled; && AppConfig.CommunityInformation.IsEnabled;
ViewBag.Interactions = await InteractionRegistration.GetInteractions("Webfront::Nav");
ViewBag.Authorized = Authorized; ViewBag.Authorized = Authorized;
ViewBag.Url = AppConfig.WebfrontUrl; ViewBag.Url = AppConfig.WebfrontUrl;
ViewBag.User = Client; ViewBag.User = Client;

View File

@ -10,6 +10,8 @@ namespace SharedLibraryCore.Helpers;
public class InteractionData : IInteractionData public class InteractionData : IInteractionData
{ {
public int? EntityId { get; set; } public int? EntityId { get; set; }
public string InteractionId { get; set; }
public InteractionType InteractionType { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }

View File

@ -8,6 +8,8 @@ namespace SharedLibraryCore.Interfaces;
public interface IInteractionData public interface IInteractionData
{ {
int? EntityId { get; } int? EntityId { get; }
string InteractionId { get; }
InteractionType InteractionType { get; }
bool Enabled { get; } bool Enabled { get; }
string Name { get; } string Name { get; }
string Description { get; } string Description { get; }
@ -22,3 +24,10 @@ public interface IInteractionData
InteractionCallback Action { get; } InteractionCallback Action { get; }
Delegate ScriptAction { get; } Delegate ScriptAction { get; }
} }
public enum InteractionType
{
ActionButton,
RawContent,
TemplateContent
}

View File

@ -11,7 +11,7 @@ public interface IInteractionRegistration
void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration); void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration);
void RegisterInteraction(string interactionName, Func<int?, Reference.Game?, CancellationToken, Task<IInteractionData>> interactionRegistration); void RegisterInteraction(string interactionName, Func<int?, Reference.Game?, CancellationToken, Task<IInteractionData>> interactionRegistration);
void UnregisterInteraction(string interactionName); void UnregisterInteraction(string interactionName);
Task<IEnumerable<IInteractionData>> GetInteractions(int? clientId = null, Task<IEnumerable<IInteractionData>> GetInteractions(string interactionPrefix = null, int? clientId = null,
Reference.Game? game = null, CancellationToken token = default); Reference.Game? game = null, CancellationToken token = default);
Task<string> ProcessInteraction(string interactionId, int originId, int? targetId = null, Reference.Game? game = null, IDictionary<string, string> meta = null, CancellationToken token = default); Task<string> ProcessInteraction(string interactionId, int originId, int? targetId = null, Reference.Game? game = null, IDictionary<string, string> meta = null, CancellationToken token = default);
} }

View File

@ -105,5 +105,6 @@ namespace SharedLibraryCore.Interfaces
event EventHandler<GameEvent> OnGameEventExecuted; event EventHandler<GameEvent> OnGameEventExecuted;
IAlertManager AlertManager { get; } IAlertManager AlertManager { get; }
IInteractionRegistration InteractionRegistration { get; }
} }
} }

View File

@ -27,6 +27,7 @@ namespace WebfrontCore.Controllers
private readonly IMetaServiceV2 _metaService; private readonly IMetaServiceV2 _metaService;
private readonly IInteractionRegistration _interactionRegistration; private readonly IInteractionRegistration _interactionRegistration;
private readonly IRemoteCommandService _remoteCommandService; private readonly IRemoteCommandService _remoteCommandService;
private readonly ITranslationLookup _translationLookup;
private readonly string _banCommandName; private readonly string _banCommandName;
private readonly string _tempbanCommandName; private readonly string _tempbanCommandName;
private readonly string _unbanCommandName; private readonly string _unbanCommandName;
@ -41,12 +42,14 @@ namespace WebfrontCore.Controllers
public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands, public ActionController(IManager manager, IEnumerable<IManagerCommand> registeredCommands,
ApplicationConfiguration appConfig, IMetaServiceV2 metaService, ApplicationConfiguration appConfig, IMetaServiceV2 metaService,
IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService) : base(manager) IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService,
ITranslationLookup translationLookup) : base(manager)
{ {
_appConfig = appConfig; _appConfig = appConfig;
_metaService = metaService; _metaService = metaService;
_interactionRegistration = interactionRegistration; _interactionRegistration = interactionRegistration;
_remoteCommandService = remoteCommandService; _remoteCommandService = remoteCommandService;
_translationLookup = translationLookup;
foreach (var cmd in registeredCommands) foreach (var cmd in registeredCommands)
{ {
@ -94,7 +97,18 @@ namespace WebfrontCore.Controllers
public IActionResult DynamicActionForm(int? id, string meta) public IActionResult DynamicActionForm(int? id, string meta)
{ {
var metaDict = JsonSerializer.Deserialize<Dictionary<string, string>>(meta); if (Client.ClientId < 1)
{
return Ok(new[]
{
new CommandResponseInfo
{
Response = _translationLookup["SERVER_COMMANDS_INTERCEPTED"]
}
});
}
var metaDict = JsonSerializer.Deserialize<Dictionary<string, string>>(meta.TrimEnd('"').TrimStart('"'));
if (metaDict is null) if (metaDict is null)
{ {
@ -170,6 +184,17 @@ namespace WebfrontCore.Controllers
public async Task<IActionResult> DynamicActionAsync(CancellationToken token = default) public async Task<IActionResult> DynamicActionAsync(CancellationToken token = default)
{ {
if (Client.ClientId < 1)
{
return Ok(new[]
{
new CommandResponseInfo
{
Response = _translationLookup["SERVER_COMMANDS_INTERCEPTED"]
}
});
}
HttpContext.Request.Query.TryGetValue("InteractionId", out var interactionId); HttpContext.Request.Query.TryGetValue("InteractionId", out var interactionId);
HttpContext.Request.Query.TryGetValue("CustomInputKeys", out var inputKeys); HttpContext.Request.Query.TryGetValue("CustomInputKeys", out var inputKeys);
HttpContext.Request.Query.TryGetValue("Data", out var data); HttpContext.Request.Query.TryGetValue("Data", out var data);

View File

@ -77,7 +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); var interactions =
await _interactionRegistration.GetInteractions("Webfront::Profile", 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)

View File

@ -0,0 +1,37 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
namespace WebfrontCore.Controllers;
public class InteractionController : BaseController
{
private readonly IInteractionRegistration _interactionRegistration;
public InteractionController(IManager manager, IInteractionRegistration interactionRegistration) : base(manager)
{
_interactionRegistration = interactionRegistration;
}
[HttpGet("[controller]/[action]/{interactionName}")]
public async Task<IActionResult> Render([FromRoute]string interactionName, CancellationToken token)
{
var interactionData = (await _interactionRegistration.GetInteractions(interactionName, token: token)).FirstOrDefault();
if (interactionData is null)
{
return NotFound();
}
ViewBag.Title = interactionData.Description;
var result = await _interactionRegistration.ProcessInteraction(interactionName, Client.ClientId, token: token);
return interactionData.InteractionType == InteractionType.TemplateContent
? View("Render", result ?? "")
: Ok(result);
}
}

View File

@ -0,0 +1,8 @@
@model string
<div class="content text-wrap mt-20">
<h2 class="content-title">
<color-code value="@ViewBag.Title"></color-code>
</h2>
@Html.Raw(Model)
</div>

View File

@ -1,6 +1,7 @@
@using SharedLibraryCore.Configuration @using SharedLibraryCore.Configuration
@using SharedLibraryCore.Dtos @using SharedLibraryCore.Dtos
@using Data.Models.Client @using Data.Models.Client
@using SharedLibraryCore.Interfaces
<!-- left side navigation --> <!-- left side navigation -->
<div class="sidebar-overlay" onclick="halfmoon.toggleSidebar()"></div> <div class="sidebar-overlay" onclick="halfmoon.toggleSidebar()"></div>
@ -43,6 +44,23 @@
<span class="name">@ViewBag.Localization["WEBFRONT_NAV_HELP"]</span> <span class="name">@ViewBag.Localization["WEBFRONT_NAV_HELP"]</span>
</a> </a>
</has-permission> </has-permission>
@foreach (IInteractionData interactionData in ViewBag.Interactions)
{
if (!interactionData.InteractionId.StartsWith("Webfront::Nav::Main"))
{
continue;
}
if (ViewBag.User.Level >= interactionData.MinimumPermission)
{
<a asp-controller="Interaction" asp-action="Render" asp-route-interactionName="@interactionData.InteractionId" class="sidebar-link">
<i class="oi @interactionData.DisplayMeta mr-5"></i>
<span class="name">@interactionData.Name</span>
</a>
}
}
<!-- profile --> <!-- profile -->
<has-permission entity="ProfilePage" required-permission="Read"> <has-permission entity="ProfilePage" required-permission="Read">
<a asp-controller="Client" asp-action="Profile" asp-route-id="@ViewBag.User.ClientId" class="sidebar-link"> <a asp-controller="Client" asp-action="Profile" asp-route-id="@ViewBag.User.ClientId" class="sidebar-link">
@ -104,6 +122,23 @@
</div> </div>
</a> </a>
} }
@foreach (IInteractionData interactionData in ViewBag.Interactions)
{
if (!interactionData.InteractionId.StartsWith("Webfront::Nav::Social"))
{
continue;
}
if (ViewBag.User.Level >= interactionData.MinimumPermission)
{
<a asp-controller="Interaction" asp-action="Render" asp-route-interactionName="@interactionData.InteractionId" class="sidebar-link">
<i class="oi @interactionData.DisplayMeta mr-5"></i>
<span class="name">@interactionData.Name</span>
</a>
}
}
<br/> <br/>
<!-- admin --> <!-- admin -->
@ -143,6 +178,22 @@
</a> </a>
</has-permission> </has-permission>
@foreach (IInteractionData interactionData in ViewBag.Interactions)
{
if (!interactionData.InteractionId.StartsWith("Webfront::Nav::Admin"))
{
continue;
}
if (ViewBag.User.Level >= interactionData.MinimumPermission)
{
<a asp-controller="Interaction" asp-action="Render" asp-route-interactionName="@interactionData.InteractionId" class="sidebar-link">
<i class="oi @interactionData.DisplayMeta mr-5"></i>
<span class="name">@interactionData.Name</span>
</a>
}
}
@if (ViewBag.Authorized) @if (ViewBag.Authorized)
{ {
<a class="sidebar-link profile-action" href="#actionModal" data-action="GenerateLoginToken" data-response-duration="30000" title="@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]"> <a class="sidebar-link profile-action" href="#actionModal" data-action="GenerateLoginToken" data-response-duration="30000" title="@ViewBag.Localization["WEBFRONT_ACTION_TOKEN"]">