From 697a752be0ca1359be5c0e277ca157961d0cb145 Mon Sep 17 00:00:00 2001 From: RaidMax Date: Fri, 17 Jan 2020 17:31:53 -0600 Subject: [PATCH] make the version name match the actual name for FTP deployment fix rare issue with summing session scores copy font to expected wwwroot dir in debug mode so we get pretty icons when developing upgrade some packages pretty much reworked the entire server web config to support better validation and stuff.. not really a small fix finish web configuration changes (I think) finish up configuration changes and update shared library nuget --- .gitignore | 2 + Application/Application.csproj | 4 +- Application/ApplicationManager.cs | 20 ++- Application/Main.cs | 10 ++ .../AutomessageFeed/AutomessageFeed.csproj | 2 +- Plugins/LiveRadar/LiveRadar.csproj | 2 +- Plugins/LiveRadar/RadarEvent.cs | 2 +- Plugins/Login/Login.csproj | 2 +- .../ProfanityDeterment.csproj | 2 +- Plugins/Stats/Cheat/Detection.cs | 11 +- Plugins/Stats/Models/EFClientStatistics.cs | 6 +- Plugins/Stats/Plugin.cs | 12 +- Plugins/Stats/Stats.csproj | 2 +- Plugins/Tests/ManagerFixture.cs | 7 +- Plugins/Web/StatsWeb/StatsWeb.csproj | 2 +- Plugins/Welcome/Welcome.csproj | 2 +- SharedLibraryCore/Commands/NativeCommands.cs | 4 +- .../Configuration/ApplicationConfiguration.cs | 20 +-- .../Configuration/DefaultConfiguration.cs | 13 +- .../Configuration/ServerConfiguration.cs | 15 +- .../ApplicationConfigurationValidator.cs | 72 ++++++++ .../ServerConfigurationValidator.cs | 39 +++++ .../Exceptions/ConfigurationException.cs | 11 ++ .../Helpers/BaseConfigurationHandler.cs | 8 +- SharedLibraryCore/ScriptPlugin.cs | 2 + SharedLibraryCore/SharedLibraryCore.csproj | 25 +-- .../Controllers/ConfigurationController.cs | 161 +++++++++++++----- WebfrontCore/Middleware/IPWhitelist.cs | 25 ++- WebfrontCore/Startup.cs | 4 +- WebfrontCore/ViewModels/BindingHelper.cs | 23 +++ WebfrontCore/ViewModels/ConfigurationInfo.cs | 19 --- WebfrontCore/Views/Configuration/Index.cshtml | 38 +++-- .../Views/Configuration/_ListItem.cshtml | 14 +- .../Views/Configuration/_ServerItem.cshtml | 10 ++ .../ServerConfiguration.cshtml | 65 +++---- WebfrontCore/WebfrontCore.csproj | 8 +- WebfrontCore/wwwroot/js/configuration.js | 65 ++++++- azure-pipelines.yml | 2 +- 38 files changed, 531 insertions(+), 200 deletions(-) create mode 100644 SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs create mode 100644 SharedLibraryCore/Configuration/Validation/ServerConfigurationValidator.cs create mode 100644 SharedLibraryCore/Exceptions/ConfigurationException.cs create mode 100644 WebfrontCore/ViewModels/BindingHelper.cs delete mode 100644 WebfrontCore/ViewModels/ConfigurationInfo.cs create mode 100644 WebfrontCore/Views/Configuration/_ServerItem.cshtml diff --git a/.gitignore b/.gitignore index 7cf87e782..8baefa9b1 100644 --- a/.gitignore +++ b/.gitignore @@ -238,3 +238,5 @@ launchSettings.json /GameLogServer/log_env **/*.css /Master/master/persistence +/WebfrontCore/wwwroot/fonts +/WebfrontCore/wwwroot/font diff --git a/Application/Application.csproj b/Application/Application.csproj index 1140f03b7..9abb26c63 100644 --- a/Application/Application.csproj +++ b/Application/Application.csproj @@ -24,11 +24,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Application/ApplicationManager.cs b/Application/ApplicationManager.cs index 5d1973eea..4bea75fc5 100644 --- a/Application/ApplicationManager.cs +++ b/Application/ApplicationManager.cs @@ -5,6 +5,7 @@ using IW4MAdmin.Application.RconParsers; using SharedLibraryCore; using SharedLibraryCore.Commands; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Configuration.Validation; using SharedLibraryCore.Database; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; @@ -277,7 +278,7 @@ namespace IW4MAdmin.Application if (newConfig.Servers == null) { ConfigHandler.Set(newConfig); - newConfig.Servers = new List(); + newConfig.Servers = new ServerConfiguration[1]; do { @@ -292,7 +293,7 @@ namespace IW4MAdmin.Application serverConfig.AddEventParser(parser); } - newConfig.Servers.Add((ServerConfiguration)serverConfig.Generate()); + newConfig.Servers[0] = (ServerConfiguration)serverConfig.Generate(); } while (Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["SETUP_SERVER_SAVE"])); config = newConfig; @@ -314,6 +315,17 @@ namespace IW4MAdmin.Application await ConfigHandler.Save(); } + var validator = new ApplicationConfigurationValidator(); + var validationResult = validator.Validate(config); + + if (!validationResult.IsValid) + { + throw new ConfigurationException(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_CONFIGURATION_ERROR"]) + { + Errors = validationResult.Errors.Select(_error => _error.ErrorMessage).ToArray() + }; + } + foreach (var serverConfig in config.Servers) { Migration.ConfigurationMigration.ModifyLogPath020919(serverConfig); @@ -336,7 +348,7 @@ namespace IW4MAdmin.Application } } - if (config.Servers.Count == 0) + if (config.Servers.Length == 0) { throw new ServerException("A server configuration in IW4MAdminSettings.json is invalid"); } @@ -584,7 +596,7 @@ namespace IW4MAdmin.Application throw lastException; } - if (successServers != config.Servers.Count) + if (successServers != config.Servers.Length) { if (!Utilities.PromptBool(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_START_WITH_ERRORS"])) { diff --git a/Application/Main.cs b/Application/Main.cs index 0c1b31af5..5bf79a111 100644 --- a/Application/Main.cs +++ b/Application/Main.cs @@ -2,6 +2,7 @@ using IW4MAdmin.Application.Misc; using Microsoft.Extensions.DependencyInjection; using SharedLibraryCore; +using SharedLibraryCore.Exceptions; using SharedLibraryCore.Helpers; using SharedLibraryCore.Interfaces; using System; @@ -96,6 +97,15 @@ namespace IW4MAdmin.Application } Console.WriteLine(e.Message); + + if (e is ConfigurationException cfgE) + { + foreach (string error in cfgE.Errors) + { + Console.WriteLine(error); + } + } + Console.WriteLine(exitMessage); Console.ReadKey(); return; diff --git a/Plugins/AutomessageFeed/AutomessageFeed.csproj b/Plugins/AutomessageFeed/AutomessageFeed.csproj index 6e46fd422..58e8e9437 100644 --- a/Plugins/AutomessageFeed/AutomessageFeed.csproj +++ b/Plugins/AutomessageFeed/AutomessageFeed.csproj @@ -10,7 +10,7 @@ - + diff --git a/Plugins/LiveRadar/LiveRadar.csproj b/Plugins/LiveRadar/LiveRadar.csproj index 292ba9613..2bf620294 100644 --- a/Plugins/LiveRadar/LiveRadar.csproj +++ b/Plugins/LiveRadar/LiveRadar.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/LiveRadar/RadarEvent.cs b/Plugins/LiveRadar/RadarEvent.cs index 056c2bd66..ef166be6f 100644 --- a/Plugins/LiveRadar/RadarEvent.cs +++ b/Plugins/LiveRadar/RadarEvent.cs @@ -45,7 +45,7 @@ namespace LiveRadar var parsedEvent = new RadarEvent() { - Guid = items[0].ConvertGuidToLong(), + Guid = items[0].ConvertGuidToLong(System.Globalization.NumberStyles.HexNumber), Location = Vector3.Parse(items[1]), ViewAngles = Vector3.Parse(items[2]).FixIW4Angles(), Team = items[3], diff --git a/Plugins/Login/Login.csproj b/Plugins/Login/Login.csproj index 1536f3a64..da15e2ed5 100644 --- a/Plugins/Login/Login.csproj +++ b/Plugins/Login/Login.csproj @@ -23,7 +23,7 @@ - + diff --git a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj index 354838c49..50e7aabab 100644 --- a/Plugins/ProfanityDeterment/ProfanityDeterment.csproj +++ b/Plugins/ProfanityDeterment/ProfanityDeterment.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Stats/Cheat/Detection.cs b/Plugins/Stats/Cheat/Detection.cs index 808198ee1..a66ca7806 100644 --- a/Plugins/Stats/Cheat/Detection.cs +++ b/Plugins/Stats/Cheat/Detection.cs @@ -92,7 +92,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat HitLocationCount[hit.HitLoc].Count++; HitCount++; - if (!isDamage) { Kills++; @@ -200,12 +199,6 @@ namespace IW4MAdmin.Plugins.Stats.Cheat if (weightedLifetimeAverage > Thresholds.MaxOffset(totalHits) && hitLoc.HitCount > 100) { - //Log.WriteDebug("*** Reached Max Lifetime Average for Angle Difference ***"); - //Log.WriteDebug($"Lifetime Average = {newAverage}"); - //Log.WriteDebug($"Bone = {hitLoc.Location}"); - //Log.WriteDebug($"HitCount = {hitLoc.HitCount}"); - //Log.WriteDebug($"ID = {hit.AttackerId}"); - results.Add(new DetectionPenaltyResult() { ClientPenalty = EFPenalty.PenaltyType.Ban, @@ -437,7 +430,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat ClientId = ClientStats.ClientId, SessionAngleOffset = AngleDifferenceAverage, RecoilOffset = hitRecoilAverage, - CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalSeconds, + CurrentSessionLength = (int)(DateTime.UtcNow - ConnectionTime).TotalMinutes, CurrentStrain = currentStrain, CurrentViewAngle = new Vector3(hit.ViewAngles.X, hit.ViewAngles.Y, hit.ViewAngles.Z), Hits = HitCount, @@ -453,7 +446,7 @@ namespace IW4MAdmin.Plugins.Stats.Cheat Distance = hit.Distance, SessionScore = ClientStats.SessionScore, HitType = hit.DeathType, - SessionSPM = ClientStats.SessionSPM, + SessionSPM = Math.Round(ClientStats.SessionSPM, 0), StrainAngleBetween = Strain.LastDistance, TimeSinceLastEvent = (int)Strain.LastDeltaTime, WeaponId = hit.Weapon, diff --git a/Plugins/Stats/Models/EFClientStatistics.cs b/Plugins/Stats/Models/EFClientStatistics.cs index 8bcb518f7..c406956c7 100644 --- a/Plugins/Stats/Models/EFClientStatistics.cs +++ b/Plugins/Stats/Models/EFClientStatistics.cs @@ -92,9 +92,13 @@ namespace IW4MAdmin.Plugins.Stats.Models { SessionScores[SessionScores.Count - 1] = value; } + get { - return SessionScores.Sum(); + lock (SessionScores) + { + return SessionScores.Sum(); + } } } [NotMapped] diff --git a/Plugins/Stats/Plugin.cs b/Plugins/Stats/Plugin.cs index b0fa519f1..3cae668f0 100644 --- a/Plugins/Stats/Plugin.cs +++ b/Plugins/Stats/Plugin.cs @@ -22,7 +22,7 @@ namespace IW4MAdmin.Plugins.Stats { public string Name => "Simple Stats"; - public float Version => Assembly.GetExecutingAssembly().GetName().Version.Major + Assembly.GetExecutingAssembly().GetName().Version.Minor / 10.0f; + public float Version => (float)Utilities.GetVersionAsDouble(); public string Author => "RaidMax"; @@ -102,6 +102,11 @@ namespace IW4MAdmin.Plugins.Stats S.Logger.WriteInfo($"End ScriptKill {scriptKillCount}"); #endif } + + else + { + E.Owner.Logger.WriteDebug("Skipping script kill as it is ignored or data in customcallbacks is outdated/missing"); + } break; case GameEvent.EventType.Kill: if (!ShouldIgnoreEvent(E.Origin, E.Target)) @@ -149,6 +154,11 @@ namespace IW4MAdmin.Plugins.Stats S.Logger.WriteInfo($"End ScriptDamage {scriptDamageCount}"); #endif } + + else + { + E.Owner.Logger.WriteDebug("Skipping script damage as it is ignored or data in customcallbacks is outdated/missing"); + } break; } } diff --git a/Plugins/Stats/Stats.csproj b/Plugins/Stats/Stats.csproj index 031f5fd5a..5684a8f6d 100644 --- a/Plugins/Stats/Stats.csproj +++ b/Plugins/Stats/Stats.csproj @@ -16,7 +16,7 @@ - + diff --git a/Plugins/Tests/ManagerFixture.cs b/Plugins/Tests/ManagerFixture.cs index 353ce3f7c..fbe1cb82c 100644 --- a/Plugins/Tests/ManagerFixture.cs +++ b/Plugins/Tests/ManagerFixture.cs @@ -24,23 +24,18 @@ namespace Tests var config = new ApplicationConfiguration { - Servers = new List() + Servers = new[] { new ServerConfiguration() { - AutoMessages = new List(), IPAddress = "127.0.0.1", Password = "test", Port = 28960, - Rules = new List(), RConParserVersion = "test", EventParserVersion = "IW4x (v0.6.0)", ManualLogPath = logFile } }, - AutoMessages = new List(), - GlobalRules = new List(), - Maps = new List(), RConPollRate = int.MaxValue }; diff --git a/Plugins/Web/StatsWeb/StatsWeb.csproj b/Plugins/Web/StatsWeb/StatsWeb.csproj index ca48f2716..c0bb83423 100644 --- a/Plugins/Web/StatsWeb/StatsWeb.csproj +++ b/Plugins/Web/StatsWeb/StatsWeb.csproj @@ -14,7 +14,7 @@ Always - + diff --git a/Plugins/Welcome/Welcome.csproj b/Plugins/Welcome/Welcome.csproj index 82258f0b4..1464736ce 100644 --- a/Plugins/Welcome/Welcome.csproj +++ b/Plugins/Welcome/Welcome.csproj @@ -16,7 +16,7 @@ - + diff --git a/SharedLibraryCore/Commands/NativeCommands.cs b/SharedLibraryCore/Commands/NativeCommands.cs index d51a51011..7c64ae9cf 100644 --- a/SharedLibraryCore/Commands/NativeCommands.cs +++ b/SharedLibraryCore/Commands/NativeCommands.cs @@ -701,8 +701,8 @@ namespace SharedLibraryCore.Commands public override Task ExecuteAsync(GameEvent E) { - if (E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules?.Count < 1 && - E.Owner.ServerConfig.Rules?.Count < 1) + if (E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules?.Length < 1 && + E.Owner.ServerConfig.Rules?.Length < 1) { var _ = E.Message.IsBroadcastCommand() ? E.Owner.Broadcast(Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_RULES_NONE"]) : diff --git a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs index 629410d1a..83b049cc0 100644 --- a/SharedLibraryCore/Configuration/ApplicationConfiguration.cs +++ b/SharedLibraryCore/Configuration/ApplicationConfiguration.cs @@ -1,14 +1,12 @@ using SharedLibraryCore.Configuration.Attributes; using SharedLibraryCore.Interfaces; using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace SharedLibraryCore.Configuration { public class ApplicationConfiguration : IBaseConfiguration { - [LocalizedDisplayName("SETUP_ENABLE_WEBFRONT")] [ConfigurationLinked("WebfrontBindUrl", "ManualWebfrontUrl", "WebfrontPrimaryColor", "WebfrontSecondaryColor", "WebfrontCustomBranding")] public bool EnableWebFront { get; set; } @@ -60,7 +58,7 @@ namespace SharedLibraryCore.Configuration [ConfigurationLinked("WebfrontConnectionWhitelist")] public bool EnableWebfrontConnectionWhitelist { get; set; } [LocalizedDisplayName("WEBFRONT_CONFIGURATION_WHITELIST_LIST")] - public List WebfrontConnectionWhitelist { get; set; } + public string[] WebfrontConnectionWhitelist { get; set; } = new string[0]; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")] [ConfigurationLinked("CustomLocale")] @@ -68,7 +66,6 @@ namespace SharedLibraryCore.Configuration [LocalizedDisplayName("WEBFRONT_CONFIGURATION_CUSTOM_LOCALE")] public string CustomLocale { get; set; } - [ConfigurationOptional] [LocalizedDisplayName("WEBFRONT_CONFIGURATION_DB_PROVIDER")] public string DatabaseProvider { get; set; } = "sqlite"; [ConfigurationOptional] @@ -78,26 +75,25 @@ namespace SharedLibraryCore.Configuration public int RConPollRate { get; set; } = 5000; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_MAX_TB")] public TimeSpan MaximumTempBanTime { get; set; } = new TimeSpan(24 * 30, 0, 0); - [LocalizedDisplayName("SETUP_ENABLE_COLOR_CODES")] + [LocalizedDisplayName("WEBFRONT_CONFIGURATION_ENABLE_COLOR_CODES")] public bool EnableColorCodes { get; set; } [LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGE_PERIOD")] public int AutoMessagePeriod { get; set; } [LocalizedDisplayName("WEBFRONT_CONFIGURATION_AUTOMESSAGES")] - public List AutoMessages { get; set; } + public string[] AutoMessages { get; set; } = new string[0]; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_GLOBAL_RULES")] - public List GlobalRules { get; set; } + public string[] GlobalRules { get; set; } = new string[0]; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")] - public List DisallowedClientNames { get; set; } + public string[] DisallowedClientNames { get; set; } = new string[0]; [UIHint("ServerConfiguration")] - public List Servers { get; set; } - + public ServerConfiguration[] Servers { get; set; } [ConfigurationIgnore] public string Id { get; set; } [ConfigurationIgnore] - public List Maps { get; set; } + public MapConfiguration[] Maps { get; set; } [ConfigurationIgnore] - public List QuickMessages { get; set; } + public QuickMessageConfiguration[] QuickMessages { get; set; } [ConfigurationIgnore] public string WebfrontUrl => string.IsNullOrEmpty(ManualWebfrontUrl) ? WebfrontBindUrl?.Replace("0.0.0.0", "127.0.0.1") : ManualWebfrontUrl; diff --git a/SharedLibraryCore/Configuration/DefaultConfiguration.cs b/SharedLibraryCore/Configuration/DefaultConfiguration.cs index 9a2b17dfb..d6f560906 100644 --- a/SharedLibraryCore/Configuration/DefaultConfiguration.cs +++ b/SharedLibraryCore/Configuration/DefaultConfiguration.cs @@ -1,17 +1,14 @@ using SharedLibraryCore.Interfaces; -using System; -using System.Collections.Generic; -using System.Text; namespace SharedLibraryCore.Configuration { public class DefaultConfiguration : IBaseConfiguration { - public List AutoMessages { get; set; } - public List GlobalRules { get; set; } - public List Maps { get; set; } - public List QuickMessages {get; set;} - public List DisallowedClientNames { get; set; } + public string[] AutoMessages { get; set; } + public string[] GlobalRules { get; set; } + public MapConfiguration[] Maps { get; set; } + public QuickMessageConfiguration[] QuickMessages {get; set;} + public string[] DisallowedClientNames { get; set; } public IBaseConfiguration Generate() => this; diff --git a/SharedLibraryCore/Configuration/ServerConfiguration.cs b/SharedLibraryCore/Configuration/ServerConfiguration.cs index 082bf71b8..f00e0ec4e 100644 --- a/SharedLibraryCore/Configuration/ServerConfiguration.cs +++ b/SharedLibraryCore/Configuration/ServerConfiguration.cs @@ -15,9 +15,9 @@ namespace SharedLibraryCore.Configuration [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PASSWORD")] public string Password { get; set; } [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RULES")] - public List Rules { get; set; } + public string[] Rules { get; set; } = new string[0]; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_AUTO_MESSAGES")] - public List AutoMessages { get; set; } + public string[] AutoMessages { get; set; } = new string[0]; [LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PATH")] [ConfigurationOptional] public string ManualLogPath { get; set; } @@ -31,6 +31,9 @@ namespace SharedLibraryCore.Configuration [ConfigurationOptional] public Uri GameLogServerUrl { get; set; } + [ConfigurationIgnore] + public int Index { get; set; } + private readonly IList rconParsers; private readonly IList eventParsers; @@ -38,8 +41,8 @@ namespace SharedLibraryCore.Configuration { rconParsers = new List(); eventParsers = new List(); - Rules = new List(); - AutoMessages = new List(); + Rules = new string[0]; + AutoMessages = new string[0]; } public void AddRConParser(IRConParser parser) @@ -94,8 +97,8 @@ namespace SharedLibraryCore.Configuration Port = Utilities.PromptInt(loc["SETUP_SERVER_PORT"], null, 1, ushort.MaxValue); Password = Utilities.PromptString(loc["SETUP_SERVER_RCON"]); - AutoMessages = new List(); - Rules = new List(); + AutoMessages = new string[0]; + Rules = new string[0]; ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32); ManualLogPath = null; diff --git a/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs b/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs new file mode 100644 index 000000000..24c7de9b3 --- /dev/null +++ b/SharedLibraryCore/Configuration/Validation/ApplicationConfigurationValidator.cs @@ -0,0 +1,72 @@ +using FluentValidation; +using System; +using System.Linq; + +namespace SharedLibraryCore.Configuration.Validation +{ + /// + /// Validation class for main application configuration + /// + public class ApplicationConfigurationValidator : AbstractValidator + { + public ApplicationConfigurationValidator() + { + RuleFor(_app => _app.WebfrontBindUrl) + .NotEmpty(); + + RuleFor(_app => _app.CustomSayName) + .NotEmpty() + .When(_app => _app.EnableCustomSayName); + + RuleFor(_app => _app.SocialLinkAddress) + .NotEmpty() + .When(_app => _app.EnableSocialLink); + + RuleFor(_app => _app.SocialLinkTitle) + .NotEmpty() + .When(_app => _app.EnableSocialLink); + + RuleFor(_app => _app.CustomParserEncoding) + .NotEmpty() + .When(_app => _app.EnableCustomParserEncoding); + + RuleFor(_app => _app.WebfrontConnectionWhitelist) + .NotEmpty() + .When(_app => _app.EnableWebfrontConnectionWhitelist); + + RuleForEach(_app => _app.WebfrontConnectionWhitelist) + .Must(_address => System.Net.IPAddress.TryParse(_address, out _)); + + RuleFor(_app => _app.CustomLocale) + .NotEmpty() + .When(_app => _app.EnableCustomLocale); + + RuleFor(_app => _app.DatabaseProvider) + .NotEmpty() + .Must(_provider => new[] { "sqlite", "mysql", "postgresql" }.Contains(_provider)); + + RuleFor(_app => _app.ConnectionString) + .NotEmpty() + .When(_app => _app.DatabaseProvider != "sqlite"); + + RuleFor(_app => _app.RConPollRate) + .GreaterThanOrEqualTo(1000); + + RuleFor(_app => _app.AutoMessagePeriod) + .GreaterThanOrEqualTo(60); + + RuleFor(_app => _app.Servers) + .NotEmpty(); + + RuleFor(_app => _app.AutoMessages) + .NotNull(); + + RuleFor(_app => _app.GlobalRules) + .NotNull(); + + RuleForEach(_app => _app.Servers) + .NotEmpty() + .SetValidator(new ServerConfigurationValidator()); + } + } +} diff --git a/SharedLibraryCore/Configuration/Validation/ServerConfigurationValidator.cs b/SharedLibraryCore/Configuration/Validation/ServerConfigurationValidator.cs new file mode 100644 index 000000000..a5877013f --- /dev/null +++ b/SharedLibraryCore/Configuration/Validation/ServerConfigurationValidator.cs @@ -0,0 +1,39 @@ +using FluentValidation; +using System.Net; + +namespace SharedLibraryCore.Configuration.Validation +{ + /// + /// Validation class for server configuration + /// + public class ServerConfigurationValidator : AbstractValidator + { + public ServerConfigurationValidator() + { + RuleFor(_server => _server.IPAddress) + .NotEmpty() + .Must(_address => IPAddress.TryParse(_address, out _)); + + RuleFor(_server => _server.Port) + .InclusiveBetween(0, ushort.MaxValue); + + RuleFor(_server => _server.Password) + .NotEmpty(); + + RuleForEach(_server => _server.Rules) + .NotEmpty(); + + RuleForEach(_server => _server.AutoMessages) + .NotEmpty(); + + RuleFor(_server => _server.RConParserVersion) + .NotEmpty(); + + RuleFor(_server => _server.EventParserVersion) + .NotEmpty(); + + RuleFor(_server => _server.ReservedSlotNumber) + .InclusiveBetween(0, 32); + } + } +} diff --git a/SharedLibraryCore/Exceptions/ConfigurationException.cs b/SharedLibraryCore/Exceptions/ConfigurationException.cs new file mode 100644 index 000000000..009f52403 --- /dev/null +++ b/SharedLibraryCore/Exceptions/ConfigurationException.cs @@ -0,0 +1,11 @@ +using System; + +namespace SharedLibraryCore.Exceptions +{ + public class ConfigurationException : Exception + { + public string[] Errors { get; set; } + + public ConfigurationException(string message) : base(message) { } + } +} diff --git a/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs b/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs index 6790daf0f..90d726ba9 100644 --- a/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs +++ b/SharedLibraryCore/Helpers/BaseConfigurationHandler.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using SharedLibraryCore.Exceptions; using SharedLibraryCore.Interfaces; using System; using System.IO; @@ -24,9 +25,12 @@ namespace SharedLibraryCore.Configuration var configContent = File.ReadAllText(_configurationPath); _configuration = JsonConvert.DeserializeObject(configContent); } - catch + catch (Exception e) { - _configuration = default(T); + throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR") + { + Errors = new[] { e.Message } + }; } } diff --git a/SharedLibraryCore/ScriptPlugin.cs b/SharedLibraryCore/ScriptPlugin.cs index b59b94898..5428b43b9 100644 --- a/SharedLibraryCore/ScriptPlugin.cs +++ b/SharedLibraryCore/ScriptPlugin.cs @@ -104,6 +104,8 @@ namespace SharedLibraryCore { typeof(System.Net.Http.HttpClient).Assembly, typeof(EFClient).Assembly, + typeof(Utilities).Assembly, + typeof(Encoding).Assembly }) .CatchClrExceptions()); diff --git a/SharedLibraryCore/SharedLibraryCore.csproj b/SharedLibraryCore/SharedLibraryCore.csproj index 18516f3d4..c1bc09f6b 100644 --- a/SharedLibraryCore/SharedLibraryCore.csproj +++ b/SharedLibraryCore/SharedLibraryCore.csproj @@ -6,7 +6,7 @@ RaidMax.IW4MAdmin.SharedLibraryCore - 2.2.4 + 2.2.5 RaidMax Forever None Debug;Release;Prerelease @@ -20,8 +20,8 @@ true MIT Shared Library for IW4MAdmin - 2.2.4.0 - 2.2.4.0 + 2.2.5.0 + 2.2.5.0 @@ -46,21 +46,22 @@ + - - - + + + all runtime; build; native; contentfiles - - - - - - + + + + + + diff --git a/WebfrontCore/Controllers/ConfigurationController.cs b/WebfrontCore/Controllers/ConfigurationController.cs index 301b02bbe..2e6edf489 100644 --- a/WebfrontCore/Controllers/ConfigurationController.cs +++ b/WebfrontCore/Controllers/ConfigurationController.cs @@ -2,8 +2,11 @@ using Microsoft.AspNetCore.Mvc; using SharedLibraryCore; using SharedLibraryCore.Configuration; +using SharedLibraryCore.Configuration.Attributes; +using SharedLibraryCore.Configuration.Validation; using SharedLibraryCore.Interfaces; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using WebfrontCore.ViewModels; @@ -12,11 +15,17 @@ namespace WebfrontCore.Controllers [Authorize] public class ConfigurationController : BaseController { - public ConfigurationController(IManager manager) : base (manager) - { + private readonly ApplicationConfigurationValidator _validator; + public ConfigurationController(IManager manager) : base(manager) + { + _validator = new ApplicationConfigurationValidator(); } + /// + /// Endpoint to get the current configuration view + /// + /// public IActionResult Edit() { if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner) @@ -27,56 +36,132 @@ namespace WebfrontCore.Controllers return View("Index", Manager.GetApplicationSettings().Configuration()); } + /// + /// Endpoint for the save action + /// + /// bound configuration + /// [HttpPost] - public async Task Edit(ApplicationConfiguration newConfiguration, bool addNewServer = false, bool shouldSave = false) + public async Task Save(ApplicationConfiguration newConfiguration) + { + // todo: make this authorization middleware instead of these checks + if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner) + { + return Unauthorized(); + } + + CleanConfiguration(newConfiguration); + var validationResult = _validator.Validate(newConfiguration); + + if (validationResult.IsValid) + { + var currentConfiguration = Manager.GetApplicationSettings().Configuration(); + CopyConfiguration(newConfiguration, currentConfiguration); + await Manager.GetApplicationSettings().Save(); + return Ok(new { message = new[] { Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVED"] } }); + } + + else + { + return BadRequest(new { message = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_SAVE_FAILED"], errors = new[] { validationResult.Errors.Select(_error => _error.ErrorMessage) } }); + } + } + + /// + /// Cleans the configuration by removing empty items from from the array + /// + /// + private void CleanConfiguration(ApplicationConfiguration newConfiguration) + { + void cleanProperties(object config) + { + foreach (var property in config.GetType() + .GetProperties().Where(_prop => _prop.CanWrite)) + { + var newPropValue = property.GetValue(config); + + if (newPropValue is ServerConfiguration[] serverConfig) + { + foreach (var c in serverConfig) + { + cleanProperties(c); + } + } + + // this clears out any null or empty items in the string array + if (newPropValue is string[] configArray) + { + newPropValue = configArray.Where(_str => !string.IsNullOrWhiteSpace(_str)).ToArray(); + } + + property.SetValue(config, newPropValue); + } + } + + cleanProperties(newConfiguration); + } + + /// + /// Copies required config fields from new to old + /// + /// Source config + /// Destination config + private void CopyConfiguration(ApplicationConfiguration newConfiguration, ApplicationConfiguration oldConfiguration) + { + foreach (var property in newConfiguration.GetType() + .GetProperties().Where(_prop => _prop.CanWrite)) + { + var newPropValue = property.GetValue(newConfiguration); + bool isPropNullArray = property.PropertyType.IsArray && newPropValue == null; + + // this prevents us from setting a null array as that could screw reading up + if (!ShouldIgnoreProperty(property) && !isPropNullArray) + { + property.SetValue(oldConfiguration, newPropValue); + } + } + } + + /// + /// Generates the partial view for a new list item + /// + /// name of the property the input element is generated for + /// how many items exist already + /// if it's a server property, which one + /// + public IActionResult GetNewListItem(string propertyName, int itemCount, int serverIndex = -1) { if (Client.Level < SharedLibraryCore.Database.Models.EFClient.Permission.Owner) { return Unauthorized(); } - if (shouldSave) + // todo: maybe make this cleaner in the future + if (propertyName.StartsWith("Servers") && serverIndex < 0) { - var currentConfiguration = Manager.GetApplicationSettings().Configuration(); - - var newConfigurationProperties = newConfiguration.GetType().GetProperties(); - foreach (var property in currentConfiguration.GetType().GetProperties()) + return PartialView("_ServerItem", new ApplicationConfiguration() { - var newProp = newConfigurationProperties.First(_prop => _prop.Name == property.Name); - var newPropValue = newProp.GetValue(newConfiguration); - - if (newPropValue != null && newProp.CanWrite) - { - property.SetValue(currentConfiguration, newPropValue); - } - } - - await Manager.GetApplicationSettings().Save(); + Servers = Enumerable.Repeat(new ServerConfiguration(), itemCount + 1).ToArray() + }); } - if (addNewServer) + var model = new BindingHelper() { - newConfiguration.Servers.Add(new ServerConfiguration()); - } - - - return View("Index", newConfiguration); - } - - public IActionResult GetNewListItem(string propertyName, int itemCount) - { - if (Client.Level != SharedLibraryCore.Database.Models.EFClient.Permission.Owner) - { - return Unauthorized(); - } - - var configInfo = new ConfigurationInfo() - { - NewItemCount = itemCount, - PropertyName = propertyName + Properties = propertyName.Split("."), + ItemIndex = itemCount, + ParentItemIndex = serverIndex }; - return PartialView("_ListItem", configInfo); + return PartialView("_ListItem", model); } + + /// + /// Indicates if the property should be ignored when cleaning/copying from one config to another + /// + /// property info of the current property + /// + private bool ShouldIgnoreProperty(PropertyInfo info) => (info.GetCustomAttributes(false) + .Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore)) + .FirstOrDefault() as ConfigurationIgnore) != null; } } \ No newline at end of file diff --git a/WebfrontCore/Middleware/IPWhitelist.cs b/WebfrontCore/Middleware/IPWhitelist.cs index 16b34cb82..85d8c1634 100644 --- a/WebfrontCore/Middleware/IPWhitelist.cs +++ b/WebfrontCore/Middleware/IPWhitelist.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; +using SharedLibraryCore.Interfaces; using System; using System.Collections.Generic; using System.Linq; @@ -13,32 +13,39 @@ namespace WebfrontCore.Middleware /// internal sealed class IPWhitelist { - private readonly List whitelistedIps; - private readonly RequestDelegate nextRequest; + private readonly byte[][] _whitelistedIps; + private readonly RequestDelegate _nextRequest; + private readonly ILogger _logger; /// /// constructor /// /// - /// /// list of textual ip addresses - public IPWhitelist(RequestDelegate nextRequest, ILogger logger, List whitelistedIps) + public IPWhitelist(RequestDelegate nextRequest, ILogger logger, string[] whitelistedIps) { - this.whitelistedIps = whitelistedIps.Select(_ip => System.Net.IPAddress.Parse(_ip).GetAddressBytes()).ToList(); - this.nextRequest = nextRequest; + _whitelistedIps = whitelistedIps.Select(_ip => System.Net.IPAddress.Parse(_ip).GetAddressBytes()).ToArray(); + _nextRequest = nextRequest; + _logger = logger; } public async Task Invoke(HttpContext context) { - bool isAlllowed = whitelistedIps.Any(_ip => _ip.SequenceEqual(context.Connection.RemoteIpAddress.GetAddressBytes())); + bool isAlllowed = true; + + if (_whitelistedIps.Length > 0) + { + isAlllowed = _whitelistedIps.Any(_ip => _ip.SequenceEqual(context.Connection.RemoteIpAddress.GetAddressBytes())); + } if (isAlllowed) { - await nextRequest.Invoke(context); + await _nextRequest.Invoke(context); } else { + _logger.WriteInfo($"Blocking HTTP request from {context.Connection.RemoteIpAddress.ToString()}"); context.Abort(); } } diff --git a/WebfrontCore/Startup.cs b/WebfrontCore/Startup.cs index 42cc5cfba..1ffedd01a 100644 --- a/WebfrontCore/Startup.cs +++ b/WebfrontCore/Startup.cs @@ -96,7 +96,7 @@ namespace WebfrontCore } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IManager manager) { app.UseStatusCodePages(_context => { @@ -120,7 +120,7 @@ namespace WebfrontCore if (Program.Manager.GetApplicationSettings().Configuration().EnableWebfrontConnectionWhitelist) { - app.UseMiddleware(Program.Manager.GetApplicationSettings().Configuration().WebfrontConnectionWhitelist); + app.UseMiddleware(manager.GetLogger(0), manager.GetApplicationSettings().Configuration().WebfrontConnectionWhitelist); } app.UseStaticFiles(); diff --git a/WebfrontCore/ViewModels/BindingHelper.cs b/WebfrontCore/ViewModels/BindingHelper.cs new file mode 100644 index 000000000..d45e89b45 --- /dev/null +++ b/WebfrontCore/ViewModels/BindingHelper.cs @@ -0,0 +1,23 @@ +namespace WebfrontCore.ViewModels +{ + /// + /// Helper class that hold information to assist with binding lists of items + /// + public class BindingHelper + { + /// + /// Sequential property mapping items + /// + public string[] Properties { get; set; } + + /// + /// Index in the array this new item lives + /// + public int ItemIndex { get; set; } + + /// + /// Index in the array of the parent item + /// + public int ParentItemIndex { get; set; } + } +} diff --git a/WebfrontCore/ViewModels/ConfigurationInfo.cs b/WebfrontCore/ViewModels/ConfigurationInfo.cs deleted file mode 100644 index da5901d09..000000000 --- a/WebfrontCore/ViewModels/ConfigurationInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -using SharedLibraryCore.Interfaces; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace WebfrontCore.ViewModels -{ - public class ConfigurationInfo - { - public string PropertyName { get; set; } - public PropertyInfo PropertyInfo { get; set; } - public IList PropertyValue { get; set; } - public IBaseConfiguration Configuration { get; set; } - public int NewItemCount { get; set; } - } -} diff --git a/WebfrontCore/Views/Configuration/Index.cshtml b/WebfrontCore/Views/Configuration/Index.cshtml index 4035d736e..1037aa9c9 100644 --- a/WebfrontCore/Views/Configuration/Index.cshtml +++ b/WebfrontCore/Views/Configuration/Index.cshtml @@ -1,4 +1,5 @@ @using SharedLibraryCore.Configuration.Attributes +@using SharedLibraryCore.Configuration; @model SharedLibraryCore.Configuration.ApplicationConfiguration @{ @@ -39,7 +40,7 @@

@ViewData["Title"]

@noticeText
-
+ @foreach (var property in properties) { if (shouldIgnore(property)) @@ -49,22 +50,36 @@ string[] linkedPropertyNames = getLinkedPropertyName(property); - if (property.PropertyType.Name == typeof(System.Boolean).Name) + // bool type + if (property.PropertyType == typeof(bool)) {
- @Html.Editor(property.Name, linkedPropertyNames.Length > 0 ? new { htmlAttributes = new { @class= "has-related-content mb-0", data_related_content = string.Join(',', linkedPropertyNames.Select(_id => $"#{_id}_content")) } } : null) + @Html.Editor(property.Name, linkedPropertyNames.Length > 0 ? new { htmlAttributes = new { @class = "has-related-content mb-0", data_related_content = string.Join(',', linkedPropertyNames.Select(_id => $"#{_id}_content")) } } : null) @Html.Label(property.Name, null, new { @class = "form-check-label ml-1" })
} - else if (property.PropertyType.Name.Contains("List")) + // array type + else if (property.PropertyType.IsArray) { - if (hasLinkedParent(property)) + // special type for server config, I don't like this but for now it's ok + @if (property.PropertyType.GetElementType() == typeof(ServerConfiguration)) + { +
+ @for (int i = 0; i < Model.Servers.Length; i++) + { + @Html.EditorFor(model => model.Servers[i]); + } + @addServerText +
+ } + + else if (hasLinkedParent(property)) {
@if (linkedPropertyNames.Length == 0) { - @Html.Label(property.Name, null, new { @class = "mt-2" }) + @Html.Label(property.Name, null, new { @class = "mt-2 d-block" }) } @Html.Editor(property.Name, new { htmlAttributes = new { @class = $"form-group form-control bg-dark text-white-50 {(linkedPropertyNames.Length == 0 ? "mb-3" : "mb-0")}" } }) @addText @@ -76,14 +91,7 @@ @Html.Label(property.Name, null, new { @class = "bg-primary pl-3 pr-3 p-2 mb-0 w-100" })
@Html.Editor(property.Name, new { htmlAttributes = new { @class = "form-control bg-dark text-white-50 mt-3 mb-3", placeholder = isOptional(property) ? optionalText : "" } }) - @if (property.PropertyType.GenericTypeArguments[0].Name == "ServerConfiguration") - { - - } - else - { - @addText - } + @addText
} } @@ -107,7 +115,7 @@ } } } - +
diff --git a/WebfrontCore/Views/Configuration/_ListItem.cshtml b/WebfrontCore/Views/Configuration/_ListItem.cshtml index 47a0d9767..57af4b2d4 100644 --- a/WebfrontCore/Views/Configuration/_ListItem.cshtml +++ b/WebfrontCore/Views/Configuration/_ListItem.cshtml @@ -1,3 +1,13 @@ -@model WebfrontCore.ViewModels.ConfigurationInfo +@model WebfrontCore.ViewModels.BindingHelper - \ No newline at end of file +@if (Model.Properties.Length == 1) +{ + + @Html.Editor($"{Model.Properties.Last()}[{Model.ItemIndex}]", new { htmlAttributes = new { @class = "form-control bg-dark text-white-50 mb-2 text-box single-line" } }) +} + +@if (Model.Properties.Length > 1) +{ + + @Html.Editor($"{Model.Properties.First()}[{Model.ParentItemIndex}].{Model.Properties.Last()}[{Model.ItemIndex}]", new { htmlAttributes = new { @class = "form-control bg-dark text-white-50 mb-2 text-box single-line" } }) +} diff --git a/WebfrontCore/Views/Configuration/_ServerItem.cshtml b/WebfrontCore/Views/Configuration/_ServerItem.cshtml new file mode 100644 index 000000000..44ea3fb3d --- /dev/null +++ b/WebfrontCore/Views/Configuration/_ServerItem.cshtml @@ -0,0 +1,10 @@ +@model SharedLibraryCore.Configuration.ApplicationConfiguration +@{ + int start = Model.Servers.Length - 1; + int end = start + 1; +} + +@for (int i = start; i < end; i++) +{ + @Html.EditorFor(model => model.Servers[i]); +} \ No newline at end of file diff --git a/WebfrontCore/Views/Shared/EditorTemplates/ServerConfiguration.cshtml b/WebfrontCore/Views/Shared/EditorTemplates/ServerConfiguration.cshtml index aab157a81..4f3dd008a 100644 --- a/WebfrontCore/Views/Shared/EditorTemplates/ServerConfiguration.cshtml +++ b/WebfrontCore/Views/Shared/EditorTemplates/ServerConfiguration.cshtml @@ -1,52 +1,59 @@ -@model IList +@model SharedLibraryCore.Configuration.ServerConfiguration @{ string labelClass = "mb-2 mt-1"; string editorClass = "form-control bg-dark text-white-50 text-box single-line mb-2 mt-0"; string addText = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CONFIGURATION_ADD"]; string optionalText = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["COMMAND_HELP_OPTIONAL"]; - int startAt = ViewBag.AddNew ?? false ? Model.Count - 1 : 0; + int i = 0; } -@for (int i = startAt; i < Model.Count; i++) -{ -
@Model[i].IPAddress:@Model[i].Port
+
+
+
@Model.IPAddress:@Model.Port
+ +
- @Html.LabelFor(model => model[i].IPAddress, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].IPAddress, new { htmlAttributes = new { @class = editorClass } }) + + - @Html.LabelFor(model => model[i].Port, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].Port, new { htmlAttributes = new { @class = editorClass } }) + + - @Html.LabelFor(model => model[i].Password, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].Password, new { htmlAttributes = new { @class = editorClass } }) + + - @Html.LabelFor(model => model[i].ManualLogPath, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].ManualLogPath, new { htmlAttributes = new { @class = editorClass, placeholder = optionalText } }) + + - @Html.LabelFor(model => model[i].GameLogServerUrl, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].GameLogServerUrl, new { htmlAttributes = new { @class = editorClass, placeholder = optionalText } }) + + - @Html.LabelFor(model => model[i].RConParserVersion, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].RConParserVersion, new { htmlAttributes = new { @class = editorClass } }) + + - @Html.LabelFor(model => model[i].EventParserVersion, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].EventParserVersion, new { htmlAttributes = new { @class = editorClass } }) + + - @Html.LabelFor(model => model[i].ReservedSlotNumber, null, new { @class = labelClass }) - @Html.EditorFor(model => model[i].ReservedSlotNumber, new { htmlAttributes = new { @class = editorClass } }) + +
- @Html.LabelFor(model => model[i].Rules, null, new { @class = "bg-primary pl-3 pr-3 p-2 w-100 mt-3" }) - @Html.EditorFor(model => model[i].Rules, new { htmlAttributes = new { @class = editorClass } }) - @addText + + @for(i = 0; i < Model.Rules.Length; i++) + { + + } + @addText
- @Html.LabelFor(model => model[i].AutoMessages, null, new { @class = "bg-primary pl-3 pr-3 p-2 w-100 mt-3" }) - @Html.EditorFor(model => model[i].AutoMessages, new { htmlAttributes = new { @class = editorClass } }) - @addText + + @for(i = 0; i < Model.AutoMessages.Length; i++) + { + + } + @addText
-} -@**@ +
diff --git a/WebfrontCore/WebfrontCore.csproj b/WebfrontCore/WebfrontCore.csproj index d3d8ddbe7..a8e9b0fe1 100644 --- a/WebfrontCore/WebfrontCore.csproj +++ b/WebfrontCore/WebfrontCore.csproj @@ -70,11 +70,7 @@
- - - - - + @@ -98,6 +94,6 @@ - + diff --git a/WebfrontCore/wwwroot/js/configuration.js b/WebfrontCore/wwwroot/js/configuration.js index 37028847b..3839874f4 100644 --- a/WebfrontCore/wwwroot/js/configuration.js +++ b/WebfrontCore/wwwroot/js/configuration.js @@ -1,23 +1,76 @@ $(document).ready(function() { - $.each($('.has-related-content'), function (key, value) { + $.each($('.has-related-content'), function(key, value) { value = $(value); if (value.attr('checked') !== undefined && value.attr('checked').length > 0) { $(value.data('related-content')).slideDown(); } }); - $('input:checkbox').change(function () { + $('input:checkbox').change(function() { var isChecked = $(this).is(':checked'); isChecked ? $($(this).data('related-content')).slideDown() : $($(this).data('related-content')).slideUp(); }); - $('.configuration-add-new').click(function (e) { + // this is used for regular simple form adds + $(document).on('click', '.configuration-add-new', function(e) { e.preventDefault(); - - let parentElement = $(this).parent(); - $.get($(this).attr('href') + '&itemCount=' + $(this).siblings().length, function (response) { + let parentElement = $(this).parent(); + let label = $(this).siblings('label'); + let forAttr = $(label).attr('for'); + let match = /Servers_+([0-9+])_+.*/g.exec(forAttr); + let additionalData = ''; + if (match !== null && match.length === 2) { + additionalData = '&serverIndex=' + match[1].toString(); + } + + $.get($(this).attr('href') + '&itemCount=' + $(this).siblings('input').length.toString() + additionalData, function (response) { $(response).insertBefore(parentElement.children().last()); }); }); + + // this is used for server adds which are little more complex + $(document).on('click', '.configuration-server-add-new', function (e) { + e.preventDefault(); + + let parentElement = $(this).parent(); + + $.get($(this).attr('href') + '&itemCount=' + $('.server-configuration-header').length.toString(), function (response) { + $(response).insertBefore(parentElement.children().last()); + }); + }); + + // removes the server when clicking the delete button + $(document).on('click', '.delete-server-button', function (e) { + $(this).parents('.server-configuration-header').remove(); + }); + + $('#configurationForm').submit(function (e) { + $.ajax({ + data: $(this).serialize(), + type: $(this).attr('method'), + url: $(this).attr('action'), + complete: function(response) { + if (response.status === 200) { + $('#actionModal .modal-message').removeClass('text-danger'); + $('#actionModal').data('should-refresh', true); + } + else { + $('#actionModal .modal-message').addClass('text-danger'); + } + $('#actionModal .modal-body-content').html(''); + let errors = ''; + + if (response.responseJSON.errors !== undefined) { + errors = response.responseJSON.errors[0].join('
'); + } + message = response.responseJSON.message; + $('#actionModal .modal-message').html(message + '
' + errors); + $('#actionModal').modal(); + $('#actionModal .modal-message').fadeIn('fast'); + } + }); + + return false; + }); }); \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4554dc903..99af89479 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -85,7 +85,7 @@ steps: echo changing to encoding for linux start script dos2unix $(outputFolder)\StartIW4MAdmin.sh echo creating website version filename - @echo $(Version.Major).$(Version.Minor).$(Version.Build).$(Build.BuildId) > $(Build.ArtifactStagingDirectory)\version_prerelease.txt + @echo IW4MAdmin-$(Version.Major).$(Version.Minor)-$(buildConfiguration)$(Version.Build)b$(Build.BuildId) > $(Build.ArtifactStagingDirectory)\version_prerelease.txt workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts' - task: CopyFiles@2