From 3367c5c22f3be59ccb83afce53fa6e19c732591e Mon Sep 17 00:00:00 2001 From: RaidMax Date: Mon, 17 Oct 2022 09:17:43 -0500 Subject: [PATCH] add support for plugin generated pages (interactions). add disallow vpn command --- Application/Application.csproj | 2 +- Application/ApplicationManager.cs | 4 +- .../Extensions/ScriptPluginExtensions.cs | 21 +++ Application/Misc/InteractionRegistration.cs | 45 +++--- Application/Misc/ScriptPlugin.cs | 3 +- Plugins/Mute/Plugin.cs | 2 +- Plugins/ScriptPlugins/VPNDetection.js | 128 +++++++++++++++--- SharedLibraryCore/BaseController.cs | 9 +- SharedLibraryCore/Helpers/InteractionData.cs | 2 + .../Interfaces/IInteractionData.cs | 9 ++ .../Interfaces/IInteractionRegistration.cs | 2 +- SharedLibraryCore/Interfaces/IManager.cs | 1 + WebfrontCore/Controllers/ActionController.cs | 29 +++- .../Controllers/Client/ClientController.cs | 3 +- .../Controllers/InteractionController.cs | 37 +++++ WebfrontCore/Views/Interaction/Render.cshtml | 8 ++ WebfrontCore/Views/Shared/_LeftNavBar.cshtml | 51 +++++++ 17 files changed, 311 insertions(+), 45 deletions(-) create mode 100644 Application/Extensions/ScriptPluginExtensions.cs create mode 100644 WebfrontCore/Controllers/InteractionController.cs create mode 100644 WebfrontCore/Views/Interaction/Render.cshtml diff --git a/Application/Application.csproj b/Application/Application.csproj index b150b4b19..efff2a195 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -24,7 +24,7 @@ - + all diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 92ad710f6..600d08143 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -85,7 +85,7 @@ namespace IW4MAdmin.Application IEnumerable plugins, IParserRegexFactory parserRegexFactory, IEnumerable customParserEvents, IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, 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; _servers = new ConcurrentBag(); @@ -115,9 +115,11 @@ namespace IW4MAdmin.Application _changeHistoryService = changeHistoryService; _appConfig = appConfig; Plugins = plugins; + InteractionRegistration = interactionRegistration; } public IEnumerable Plugins { get; } + public IInteractionRegistration InteractionRegistration { get; } public async Task ExecuteEvent(GameEvent newEvent) { diff --git a/Application/Extensions/ScriptPluginExtensions.cs b/Application/Extensions/ScriptPluginExtensions.cs new file mode 100644 index 000000000..6e03dd388 --- /dev/null +++ b/Application/Extensions/ScriptPluginExtensions.cs @@ -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 GetClientsBasicData( + this DbSet set, int[] clientIds) + { + return set.Where(client => clientIds.Contains(client.ClientId)) + .Select(client => new + { + client.ClientId, + client.CurrentAlias, + client.Level, + client.NetworkId + }).ToList(); + } +} diff --git a/Application/Misc/InteractionRegistration.cs b/Application/Misc/InteractionRegistration.cs index fe49b04a6..31acf2fdc 100644 --- a/Application/Misc/InteractionRegistration.cs +++ b/Application/Misc/InteractionRegistration.cs @@ -70,23 +70,11 @@ public class InteractionRegistration : IInteractionRegistration } } - public async Task> GetInteractions(int? clientId = null, + public async Task> GetInteractions(string interactionPrefix = null, + 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); + return await GetInteractionsPrivate(interactionPrefix, clientId, game, token); } public async Task ProcessInteraction(string interactionId, int originId, int? targetId = null, @@ -115,17 +103,40 @@ public class InteractionRegistration : IInteractionRegistration continue; } - return scriptPlugin.ExecuteAction(interaction.ScriptAction, originId, targetId, game, meta, token); + return scriptPlugin.ExecuteAction(interaction.ScriptAction, originId, targetId, game, meta, + token); } } } catch (Exception 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); } return null; } + + private async Task> 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); + } } diff --git a/Application/Misc/ScriptPlugin.cs b/Application/Misc/ScriptPlugin.cs index 4ca319a08..58918cd7b 100644 --- a/Application/Misc/ScriptPlugin.cs +++ b/Application/Misc/ScriptPlugin.cs @@ -13,6 +13,7 @@ using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using IW4MAdmin.Application.Extensions; using Jint.Runtime.Interop; using Microsoft.Extensions.Logging; using Serilog.Context; @@ -112,7 +113,7 @@ namespace IW4MAdmin.Application.Misc } _scriptEngine = new Engine(cfg => - cfg.AddExtensionMethods(typeof(Utilities), typeof(Enumerable), typeof(Queryable)) + cfg.AddExtensionMethods(typeof(Utilities), typeof(Enumerable), typeof(Queryable), typeof(ScriptPluginExtensions)) .AllowClr(new[] { typeof(System.Net.Http.HttpClient).Assembly, diff --git a/Plugins/Mute/Plugin.cs b/Plugins/Mute/Plugin.cs index f5bd47dfc..d077d58d4 100644 --- a/Plugins/Mute/Plugin.cs +++ b/Plugins/Mute/Plugin.cs @@ -22,7 +22,7 @@ public class Plugin : IPlugin private static readonly string[] DisabledCommands = {nameof(PrivateMessageAdminsCommand), "PrivateMessageCommand"}; private readonly IInteractionRegistration _interactionRegistration; private readonly IRemoteCommandService _remoteCommandService; - private static readonly string MuteInteraction = nameof(MuteInteraction); + private static readonly string MuteInteraction = "Webfront::Profile::Mute"; public Plugin(ILogger logger, IMetaServiceV2 metaService, IInteractionRegistration interactionRegistration, ITranslationLookup translationLookup, IRemoteCommandService remoteCommandService) diff --git a/Plugins/ScriptPlugins/VPNDetection.js b/Plugins/ScriptPlugins/VPNDetection.js index 029d26bf4..4c0e63707 100644 --- a/Plugins/ScriptPlugins/VPNDetection.js +++ b/Plugins/ScriptPlugins/VPNDetection.js @@ -1,4 +1,7 @@ let vpnExceptionIds = []; +const vpnAllowListKey = 'Webfront::Nav::Admin::VPNAllowList'; +const vpnWhitelistKey = 'Webfront::Profile::VPNWhitelist'; + const commands = [{ name: 'whitelistvpn', description: 'whitelists a player\'s client id from VPN detection', @@ -15,8 +18,35 @@ const commands = [{ 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 = { author: 'RaidMax', version: 1.5, @@ -28,7 +58,7 @@ const plugin = { let exempt = false; // prevent players that are exempt from being kicked 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; return false; } @@ -83,33 +113,99 @@ const plugin = { this.logger.WriteInfo(`Loaded ${vpnExceptionIds.length} ids into whitelist`); this.interactionRegistration = _serviceResolver.ResolveService('IInteractionRegistration'); - this.interactionRegistration.RegisterScriptInteraction('WhitelistVPN', this.name, (targetId, game, token) => { - if (vpnExceptionIds.includes(targetId)) { - return; - } + // registers the profile action + this.interactionRegistration.RegisterScriptInteraction(vpnWhitelistKey, this.name, (targetId, game, token) => { const helpers = importNamespace('SharedLibraryCore.Helpers'); const interactionData = new helpers.InteractionData(); - interactionData.EntityId = targetId; - interactionData.Name = 'Whitelist VPN'; - interactionData.DisplayMeta = 'oi-circle-check'; - - interactionData.ActionMeta.Add('InteractionId', 'command'); - interactionData.ActionMeta.Add('Data', `whitelistvpn`); - interactionData.ActionMeta.Add('ActionButtonLabel', 'Allow'); - interactionData.ActionMeta.Add('Name', 'Allow VPN Connection'); - interactionData.ActionMeta.Add('ShouldRefresh', true.toString()); - 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)) { + 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 interactionData = new helpers.InteractionData(); + + interactionData.Name = _localization.LocalizationIndex['WEBFRONT_NAV_VPN_TITLE']; // navigation link name + interactionData.Description = _localization.LocalizationIndex['WEBFRONT_NAV_VPN_DESC']; // alt and title + interactionData.DisplayMeta = 'oi-circle-check'; // nav icon + interactionData.InteractionId = vpnAllowListKey; + interactionData.MinimumPermission = 3; // moderator + interactionData.InteractionType = 2; // 1 is RawContent for apis etc..., 2 is + interactionData.Source = this.name; + + interactionData.ScriptAction = (sourceId, targetId, game, meta, token) => { + const clientsData = getClientsData(vpnExceptionIds); + + let table = ''; + + 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 += `` + } + + clientsData.forEach(client => { + table += ` + + + `; + }); + + table += '
No players are whitelisted.
+ ${client.CurrentAlias.Name.StripColors()} + + +
+ + ${_localization.LocalizationIndex['WEBFRONT_VPN_BUTTON_DISALLOW']} +
+
+
'; + + return table; + } + return interactionData; }); }, onUnloadAsync: function () { - this.interactionRegistration.UnregisterInteraction('WhitelistVPN'); + this.interactionRegistration.UnregisterInteraction(vpnWhitelistKey); + this.interactionRegistration.UnregisterInteraction(vpnAllowListKey); }, onTickAsync: function (server) { diff --git a/SharedLibraryCore/BaseController.cs b/SharedLibraryCore/BaseController.cs index 0f44db4f6..2085878e5 100644 --- a/SharedLibraryCore/BaseController.cs +++ b/SharedLibraryCore/BaseController.cs @@ -19,6 +19,7 @@ namespace SharedLibraryCore { public class BaseController : Controller { + protected readonly IInteractionRegistration InteractionRegistration; protected readonly IAlertManager AlertManager; /// @@ -41,6 +42,7 @@ namespace SharedLibraryCore public BaseController(IManager manager) { + InteractionRegistration = manager.InteractionRegistration; AlertManager = manager.AlertManager; Manager = manager; Localization = Utilities.CurrentLocalization.LocalizationIndex; @@ -71,9 +73,7 @@ namespace SharedLibraryCore CurrentAlias = new EFAlias { Name = "Webfront Guest" } }; } - - - + protected async Task SignInAsync(ClaimsPrincipal 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)) { @@ -154,6 +154,7 @@ namespace SharedLibraryCore && !communityName.Contains("IW4MAdmin") && AppConfig.CommunityInformation.IsEnabled; + ViewBag.Interactions = await InteractionRegistration.GetInteractions("Webfront::Nav"); ViewBag.Authorized = Authorized; ViewBag.Url = AppConfig.WebfrontUrl; ViewBag.User = Client; diff --git a/SharedLibraryCore/Helpers/InteractionData.cs b/SharedLibraryCore/Helpers/InteractionData.cs index 68a5ab16d..e658f2cbd 100644 --- a/SharedLibraryCore/Helpers/InteractionData.cs +++ b/SharedLibraryCore/Helpers/InteractionData.cs @@ -10,6 +10,8 @@ namespace SharedLibraryCore.Helpers; public class InteractionData : IInteractionData { public int? EntityId { get; set; } + public string InteractionId { get; set; } + public InteractionType InteractionType { get; set; } public bool Enabled { get; set; } public string Name { get; set; } public string Description { get; set; } diff --git a/SharedLibraryCore/Interfaces/IInteractionData.cs b/SharedLibraryCore/Interfaces/IInteractionData.cs index aa92b2c4d..b1660b0a2 100644 --- a/SharedLibraryCore/Interfaces/IInteractionData.cs +++ b/SharedLibraryCore/Interfaces/IInteractionData.cs @@ -8,6 +8,8 @@ namespace SharedLibraryCore.Interfaces; public interface IInteractionData { int? EntityId { get; } + string InteractionId { get; } + InteractionType InteractionType { get; } bool Enabled { get; } string Name { get; } string Description { get; } @@ -22,3 +24,10 @@ public interface IInteractionData InteractionCallback Action { get; } Delegate ScriptAction { get; } } + +public enum InteractionType +{ + ActionButton, + RawContent, + TemplateContent +} diff --git a/SharedLibraryCore/Interfaces/IInteractionRegistration.cs b/SharedLibraryCore/Interfaces/IInteractionRegistration.cs index d24b3ad36..aefe30c7f 100644 --- a/SharedLibraryCore/Interfaces/IInteractionRegistration.cs +++ b/SharedLibraryCore/Interfaces/IInteractionRegistration.cs @@ -11,7 +11,7 @@ public interface IInteractionRegistration void RegisterScriptInteraction(string interactionName, string source, Delegate interactionRegistration); void RegisterInteraction(string interactionName, Func> interactionRegistration); void UnregisterInteraction(string interactionName); - Task> GetInteractions(int? clientId = null, + Task> GetInteractions(string interactionPrefix = null, int? clientId = null, Reference.Game? game = null, CancellationToken token = default); Task ProcessInteraction(string interactionId, int originId, int? targetId = null, Reference.Game? game = null, IDictionary meta = null, CancellationToken token = default); } diff --git a/SharedLibraryCore/Interfaces/IManager.cs b/SharedLibraryCore/Interfaces/IManager.cs index c87e70979..049266ae7 100644 --- a/SharedLibraryCore/Interfaces/IManager.cs +++ b/SharedLibraryCore/Interfaces/IManager.cs @@ -105,5 +105,6 @@ namespace SharedLibraryCore.Interfaces event EventHandler OnGameEventExecuted; IAlertManager AlertManager { get; } + IInteractionRegistration InteractionRegistration { get; } } } diff --git a/WebfrontCore/Controllers/ActionController.cs b/WebfrontCore/Controllers/ActionController.cs index 677e904c0..67bb890fa 100644 --- a/WebfrontCore/Controllers/ActionController.cs +++ b/WebfrontCore/Controllers/ActionController.cs @@ -27,6 +27,7 @@ namespace WebfrontCore.Controllers private readonly IMetaServiceV2 _metaService; private readonly IInteractionRegistration _interactionRegistration; private readonly IRemoteCommandService _remoteCommandService; + private readonly ITranslationLookup _translationLookup; private readonly string _banCommandName; private readonly string _tempbanCommandName; private readonly string _unbanCommandName; @@ -41,12 +42,14 @@ namespace WebfrontCore.Controllers public ActionController(IManager manager, IEnumerable registeredCommands, ApplicationConfiguration appConfig, IMetaServiceV2 metaService, - IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService) : base(manager) + IInteractionRegistration interactionRegistration, IRemoteCommandService remoteCommandService, + ITranslationLookup translationLookup) : base(manager) { _appConfig = appConfig; _metaService = metaService; _interactionRegistration = interactionRegistration; _remoteCommandService = remoteCommandService; + _translationLookup = translationLookup; foreach (var cmd in registeredCommands) { @@ -94,7 +97,18 @@ namespace WebfrontCore.Controllers public IActionResult DynamicActionForm(int? id, string meta) { - var metaDict = JsonSerializer.Deserialize>(meta); + if (Client.ClientId < 1) + { + return Ok(new[] + { + new CommandResponseInfo + { + Response = _translationLookup["SERVER_COMMANDS_INTERCEPTED"] + } + }); + } + + var metaDict = JsonSerializer.Deserialize>(meta.TrimEnd('"').TrimStart('"')); if (metaDict is null) { @@ -170,6 +184,17 @@ namespace WebfrontCore.Controllers public async Task 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("CustomInputKeys", out var inputKeys); HttpContext.Request.Query.TryGetValue("Data", out var data); diff --git a/WebfrontCore/Controllers/Client/ClientController.cs b/WebfrontCore/Controllers/Client/ClientController.cs index e08f4164d..9466bb598 100644 --- a/WebfrontCore/Controllers/Client/ClientController.cs +++ b/WebfrontCore/Controllers/Client/ClientController.cs @@ -77,7 +77,8 @@ namespace WebfrontCore.Controllers 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 // (ie they haven't reconnected with the infringing player identifier) diff --git a/WebfrontCore/Controllers/InteractionController.cs b/WebfrontCore/Controllers/InteractionController.cs new file mode 100644 index 000000000..5ec73df27 --- /dev/null +++ b/WebfrontCore/Controllers/InteractionController.cs @@ -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 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); + } +} diff --git a/WebfrontCore/Views/Interaction/Render.cshtml b/WebfrontCore/Views/Interaction/Render.cshtml new file mode 100644 index 000000000..67a2fad11 --- /dev/null +++ b/WebfrontCore/Views/Interaction/Render.cshtml @@ -0,0 +1,8 @@ +@model string + +
+

+ +

+ @Html.Raw(Model) +
diff --git a/WebfrontCore/Views/Shared/_LeftNavBar.cshtml b/WebfrontCore/Views/Shared/_LeftNavBar.cshtml index c51f4f0d0..0d59ad896 100644 --- a/WebfrontCore/Views/Shared/_LeftNavBar.cshtml +++ b/WebfrontCore/Views/Shared/_LeftNavBar.cshtml @@ -1,6 +1,7 @@ @using SharedLibraryCore.Configuration @using SharedLibraryCore.Dtos @using Data.Models.Client +@using SharedLibraryCore.Interfaces @@ -43,6 +44,23 @@ @ViewBag.Localization["WEBFRONT_NAV_HELP"] + + @foreach (IInteractionData interactionData in ViewBag.Interactions) + { + if (!interactionData.InteractionId.StartsWith("Webfront::Nav::Main")) + { + continue; + } + + if (ViewBag.User.Level >= interactionData.MinimumPermission) + { + + + @interactionData.Name + + } + } + @@ -104,6 +122,23 @@ } + + @foreach (IInteractionData interactionData in ViewBag.Interactions) + { + if (!interactionData.InteractionId.StartsWith("Webfront::Nav::Social")) + { + continue; + } + + if (ViewBag.User.Level >= interactionData.MinimumPermission) + { + + + @interactionData.Name + + } + } +
@@ -142,6 +177,22 @@ @ViewBag.Localization["WEBFRONT_ACTION_RECENT_CLIENTS"]
+ + @foreach (IInteractionData interactionData in ViewBag.Interactions) + { + if (!interactionData.InteractionId.StartsWith("Webfront::Nav::Admin")) + { + continue; + } + + if (ViewBag.User.Level >= interactionData.MinimumPermission) + { + + + @interactionData.Name + + } + } @if (ViewBag.Authorized) {