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
This commit is contained in:
parent
3a1cfba251
commit
697a752be0
2
.gitignore
vendored
2
.gitignore
vendored
@ -238,3 +238,5 @@ launchSettings.json
|
||||
/GameLogServer/log_env
|
||||
**/*.css
|
||||
/Master/master/persistence
|
||||
/WebfrontCore/wwwroot/fonts
|
||||
/WebfrontCore/wwwroot/font
|
||||
|
@ -24,11 +24,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
|
||||
<PackageReference Include="RestEase" Version="1.4.10" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0" />
|
||||
</ItemGroup>
|
||||
|
@ -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<ServerConfiguration>();
|
||||
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"]))
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -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],
|
||||
|
@ -23,7 +23,7 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -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,
|
||||
|
@ -92,9 +92,13 @@ namespace IW4MAdmin.Plugins.Stats.Models
|
||||
{
|
||||
SessionScores[SessionScores.Count - 1] = value;
|
||||
}
|
||||
|
||||
get
|
||||
{
|
||||
return SessionScores.Sum();
|
||||
lock (SessionScores)
|
||||
{
|
||||
return SessionScores.Sum();
|
||||
}
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -24,23 +24,18 @@ namespace Tests
|
||||
|
||||
var config = new ApplicationConfiguration
|
||||
{
|
||||
Servers = new List<ServerConfiguration>()
|
||||
Servers = new[]
|
||||
{
|
||||
new ServerConfiguration()
|
||||
{
|
||||
AutoMessages = new List<string>(),
|
||||
IPAddress = "127.0.0.1",
|
||||
Password = "test",
|
||||
Port = 28960,
|
||||
Rules = new List<string>(),
|
||||
RConParserVersion = "test",
|
||||
EventParserVersion = "IW4x (v0.6.0)",
|
||||
ManualLogPath = logFile
|
||||
}
|
||||
},
|
||||
AutoMessages = new List<string>(),
|
||||
GlobalRules = new List<string>(),
|
||||
Maps = new List<MapConfiguration>(),
|
||||
RConPollRate = int.MaxValue
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -16,7 +16,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.4" PrivateAssets="All" />
|
||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.2.5" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
@ -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"]) :
|
||||
|
@ -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<string> 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<string> AutoMessages { get; set; }
|
||||
public string[] AutoMessages { get; set; } = new string[0];
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_GLOBAL_RULES")]
|
||||
public List<string> GlobalRules { get; set; }
|
||||
public string[] GlobalRules { get; set; } = new string[0];
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_DISALLOWED_NAMES")]
|
||||
public List<string> DisallowedClientNames { get; set; }
|
||||
public string[] DisallowedClientNames { get; set; } = new string[0];
|
||||
[UIHint("ServerConfiguration")]
|
||||
public List<ServerConfiguration> Servers { get; set; }
|
||||
|
||||
public ServerConfiguration[] Servers { get; set; }
|
||||
|
||||
[ConfigurationIgnore]
|
||||
public string Id { get; set; }
|
||||
[ConfigurationIgnore]
|
||||
public List<MapConfiguration> Maps { get; set; }
|
||||
public MapConfiguration[] Maps { get; set; }
|
||||
[ConfigurationIgnore]
|
||||
public List<QuickMessageConfiguration> 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;
|
||||
|
||||
|
@ -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<string> AutoMessages { get; set; }
|
||||
public List<string> GlobalRules { get; set; }
|
||||
public List<MapConfiguration> Maps { get; set; }
|
||||
public List<QuickMessageConfiguration> QuickMessages {get; set;}
|
||||
public List<string> 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;
|
||||
|
||||
|
@ -15,9 +15,9 @@ namespace SharedLibraryCore.Configuration
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_PASSWORD")]
|
||||
public string Password { get; set; }
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_RULES")]
|
||||
public List<string> Rules { get; set; }
|
||||
public string[] Rules { get; set; } = new string[0];
|
||||
[LocalizedDisplayName("WEBFRONT_CONFIGURATION_SERVER_AUTO_MESSAGES")]
|
||||
public List<string> 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<IRConParser> rconParsers;
|
||||
private readonly IList<IEventParser> eventParsers;
|
||||
|
||||
@ -38,8 +41,8 @@ namespace SharedLibraryCore.Configuration
|
||||
{
|
||||
rconParsers = new List<IRConParser>();
|
||||
eventParsers = new List<IEventParser>();
|
||||
Rules = new List<string>();
|
||||
AutoMessages = new List<string>();
|
||||
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<string>();
|
||||
Rules = new List<string>();
|
||||
AutoMessages = new string[0];
|
||||
Rules = new string[0];
|
||||
ReservedSlotNumber = loc["SETUP_SERVER_RESERVEDSLOT"].PromptInt(null, 0, 32);
|
||||
ManualLogPath = null;
|
||||
|
||||
|
@ -0,0 +1,72 @@
|
||||
using FluentValidation;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharedLibraryCore.Configuration.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Validation class for main application configuration
|
||||
/// </summary>
|
||||
public class ApplicationConfigurationValidator : AbstractValidator<ApplicationConfiguration>
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using FluentValidation;
|
||||
using System.Net;
|
||||
|
||||
namespace SharedLibraryCore.Configuration.Validation
|
||||
{
|
||||
/// <summary>
|
||||
/// Validation class for server configuration
|
||||
/// </summary>
|
||||
public class ServerConfigurationValidator : AbstractValidator<ServerConfiguration>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
11
SharedLibraryCore/Exceptions/ConfigurationException.cs
Normal file
11
SharedLibraryCore/Exceptions/ConfigurationException.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace SharedLibraryCore.Exceptions
|
||||
{
|
||||
public class ConfigurationException : Exception
|
||||
{
|
||||
public string[] Errors { get; set; }
|
||||
|
||||
public ConfigurationException(string message) : base(message) { }
|
||||
}
|
||||
}
|
@ -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<T>(configContent);
|
||||
}
|
||||
catch
|
||||
catch (Exception e)
|
||||
{
|
||||
_configuration = default(T);
|
||||
throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR")
|
||||
{
|
||||
Errors = new[] { e.Message }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,8 @@ namespace SharedLibraryCore
|
||||
{
|
||||
typeof(System.Net.Http.HttpClient).Assembly,
|
||||
typeof(EFClient).Assembly,
|
||||
typeof(Utilities).Assembly,
|
||||
typeof(Encoding).Assembly
|
||||
})
|
||||
.CatchClrExceptions());
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||
<Version>2.2.4</Version>
|
||||
<Version>2.2.5</Version>
|
||||
<Authors>RaidMax</Authors>
|
||||
<Company>Forever None</Company>
|
||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||
@ -20,8 +20,8 @@
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Shared Library for IW4MAdmin</Description>
|
||||
<AssemblyVersion>2.2.4.0</AssemblyVersion>
|
||||
<FileVersion>2.2.4.0</FileVersion>
|
||||
<AssemblyVersion>2.2.5.0</AssemblyVersion>
|
||||
<FileVersion>2.2.5.0</FileVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||
@ -46,21 +46,22 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentValidation" Version="8.6.1" />
|
||||
<PackageReference Include="Jint" Version="2.11.58" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.0">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Npgsql" Version="4.1.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint to get the current configuration view
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint for the save action
|
||||
/// </summary>
|
||||
/// <param name="newConfiguration">bound configuration</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Edit(ApplicationConfiguration newConfiguration, bool addNewServer = false, bool shouldSave = false)
|
||||
public async Task<IActionResult> 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) } });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans the configuration by removing empty items from from the array
|
||||
/// </summary>
|
||||
/// <param name="newConfiguration"></param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies required config fields from new to old
|
||||
/// </summary>
|
||||
/// <param name="newConfiguration">Source config</param>
|
||||
/// <param name="oldConfiguration">Destination config</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the partial view for a new list item
|
||||
/// </summary>
|
||||
/// <param name="propertyName">name of the property the input element is generated for</param>
|
||||
/// <param name="itemCount">how many items exist already</param>
|
||||
/// <param name="serverIndex">if it's a server property, which one</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the property should be ignored when cleaning/copying from one config to another
|
||||
/// </summary>
|
||||
/// <param name="info">property info of the current property</param>
|
||||
/// <returns></returns>
|
||||
private bool ShouldIgnoreProperty(PropertyInfo info) => (info.GetCustomAttributes(false)
|
||||
.Where(_attr => _attr.GetType() == typeof(ConfigurationIgnore))
|
||||
.FirstOrDefault() as ConfigurationIgnore) != null;
|
||||
}
|
||||
}
|
@ -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
|
||||
/// </summary>
|
||||
internal sealed class IPWhitelist
|
||||
{
|
||||
private readonly List<byte[]> whitelistedIps;
|
||||
private readonly RequestDelegate nextRequest;
|
||||
private readonly byte[][] _whitelistedIps;
|
||||
private readonly RequestDelegate _nextRequest;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// constructor
|
||||
/// </summary>
|
||||
/// <param name="nextRequest"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="whitelistedIps">list of textual ip addresses</param>
|
||||
public IPWhitelist(RequestDelegate nextRequest, ILogger<IPWhitelist> logger, List<string> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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<IPWhitelist>(Program.Manager.GetApplicationSettings().Configuration().WebfrontConnectionWhitelist);
|
||||
app.UseMiddleware<IPWhitelist>(manager.GetLogger(0), manager.GetApplicationSettings().Configuration().WebfrontConnectionWhitelist);
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
23
WebfrontCore/ViewModels/BindingHelper.cs
Normal file
23
WebfrontCore/ViewModels/BindingHelper.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace WebfrontCore.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class that hold information to assist with binding lists of items
|
||||
/// </summary>
|
||||
public class BindingHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Sequential property mapping items
|
||||
/// </summary>
|
||||
public string[] Properties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index in the array this new item lives
|
||||
/// </summary>
|
||||
public int ItemIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Index in the array of the parent item
|
||||
/// </summary>
|
||||
public int ParentItemIndex { get; set; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
@using SharedLibraryCore.Configuration.Attributes
|
||||
@using SharedLibraryCore.Configuration;
|
||||
@model SharedLibraryCore.Configuration.ApplicationConfiguration
|
||||
|
||||
@{
|
||||
@ -39,7 +40,7 @@
|
||||
<div class="col-12 text-white-50 ">
|
||||
<h3 class="text-white">@ViewData["Title"]</h3>
|
||||
<h5 class="mb-4">@noticeText</h5>
|
||||
<form method="post">
|
||||
<form id="configurationForm" asp-controller="Configuration" asp-action="Save" method="post">
|
||||
@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))
|
||||
{
|
||||
<div class="form-group form-check bg-primary mb-0 pl-3 pr-3 p-2">
|
||||
@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" })
|
||||
</div>
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
<div id="@($"{property.Name}_content")" class="pl-3 pr-3 pt-2 pb-3 bg-dark">
|
||||
@for (int i = 0; i < Model.Servers.Length; i++)
|
||||
{
|
||||
@Html.EditorFor(model => model.Servers[i]);
|
||||
}
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-server-add-new">@addServerText</a>
|
||||
</div>
|
||||
}
|
||||
|
||||
else if (hasLinkedParent(property))
|
||||
{
|
||||
<div id="@($"{property.Name}_content")" class="@(linkedPropertyNames.Length == 0 ? "hide" : "hide") bg-dark pl-3 pr-3 pb-2">
|
||||
@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")}" } })
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-add-new">@addText</a>
|
||||
@ -76,14 +91,7 @@
|
||||
@Html.Label(property.Name, null, new { @class = "bg-primary pl-3 pr-3 p-2 mb-0 w-100" })
|
||||
<div id="@($"{property.Name}_content")" class="pl-3 pr-3 pt-2 pb-3 bg-dark">
|
||||
@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")
|
||||
{
|
||||
<button asp-action="Edit" asp-route-addNewServer="true" class="btn btn-primary">@addServerText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-add-new">@addText</a>
|
||||
}
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@property.Name" class="btn btn-primary configuration-add-new">@addText</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -107,7 +115,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
<button asp-controller="Configuration" asp-action="Edit" asp-route-shouldSave="true" class="btn btn-primary btn-block">@saveText</button>
|
||||
<button class="btn btn-primary btn-block">@saveText</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,13 @@
|
||||
@model WebfrontCore.ViewModels.ConfigurationInfo
|
||||
@model WebfrontCore.ViewModels.BindingHelper
|
||||
|
||||
<input class="form-control bg-dark text-white-50 mb-2 text-box single-line" id="@($"{Model.PropertyName.Replace('[', '_').Replace(']', '_').Replace('.', '_')}_{Model.NewItemCount - 1}_")" name="@($"{Model.PropertyName}[{Model.NewItemCount- 1}]")" type="text" />
|
||||
@if (Model.Properties.Length == 1)
|
||||
{
|
||||
<!-- we're working off that top level model -->
|
||||
@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)
|
||||
{
|
||||
<!-- we're working off child model -->
|
||||
@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" } })
|
||||
}
|
||||
|
10
WebfrontCore/Views/Configuration/_ServerItem.cshtml
Normal file
10
WebfrontCore/Views/Configuration/_ServerItem.cshtml
Normal file
@ -0,0 +1,10 @@
|
||||
@model SharedLibraryCore.Configuration.ApplicationConfiguration
|
||||
@{
|
||||
int start = Model.Servers.Length - 1;
|
||||
int end = start + 1;
|
||||
}
|
||||
<!-- this is an ugly hack that allows us to create new server layout with correct array indices -->
|
||||
@for (int i = start; i < end; i++)
|
||||
{
|
||||
@Html.EditorFor(model => model.Servers[i]);
|
||||
}
|
@ -1,52 +1,59 @@
|
||||
@model IList<SharedLibraryCore.Configuration.ServerConfiguration>
|
||||
@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++)
|
||||
{
|
||||
<div class="h4 text-white mb-0">@Model[i].IPAddress:@Model[i].Port</div>
|
||||
<div class="server-configuration-header">
|
||||
<div class="d-flex h4">
|
||||
<div class="text-white mb-0">@Model.IPAddress:@Model.Port</div>
|
||||
<!--span class="oi oi-trash link-inverse ml-auto delete-server-button"></!--span>-->
|
||||
</div>
|
||||
<div class="border-bottom mb-3">
|
||||
@Html.LabelFor(model => model[i].IPAddress, null, new { @class = labelClass })
|
||||
@Html.EditorFor(model => model[i].IPAddress, new { htmlAttributes = new { @class = editorClass } })
|
||||
<label asp-for="@Model.IPAddress" class="@labelClass"></label>
|
||||
<input asp-for="@Model.IPAddress" class="@editorClass" />
|
||||
|
||||
@Html.LabelFor(model => model[i].Port, null, new { @class = labelClass })
|
||||
@Html.EditorFor(model => model[i].Port, new { htmlAttributes = new { @class = editorClass } })
|
||||
<label asp-for="@Model.Port" class="@labelClass"></label>
|
||||
<input asp-for="@Model.Port" class="@editorClass" />
|
||||
|
||||
@Html.LabelFor(model => model[i].Password, null, new { @class = labelClass })
|
||||
@Html.EditorFor(model => model[i].Password, new { htmlAttributes = new { @class = editorClass } })
|
||||
<label asp-for="@Model.Password" class="@labelClass"></label>
|
||||
<input asp-for="@Model.Password" 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 } })
|
||||
<label asp-for="@Model.ManualLogPath" class="@labelClass"></label>
|
||||
<input asp-for="@Model.ManualLogPath" 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 } })
|
||||
<label asp-for="@Model.GameLogServerUrl" class="@labelClass"></label>
|
||||
<input asp-for="@Model.GameLogServerUrl" 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 } })
|
||||
<label asp-for="@Model.RConParserVersion" class="@labelClass"></label>
|
||||
<input asp-for="@Model.RConParserVersion" class="@editorClass" />
|
||||
|
||||
@Html.LabelFor(model => model[i].EventParserVersion, null, new { @class = labelClass })
|
||||
@Html.EditorFor(model => model[i].EventParserVersion, new { htmlAttributes = new { @class = editorClass } })
|
||||
<label asp-for="@Model.EventParserVersion" class="@labelClass"></label>
|
||||
<input asp-for="@Model.EventParserVersion" class="@editorClass" />
|
||||
|
||||
@Html.LabelFor(model => model[i].ReservedSlotNumber, null, new { @class = labelClass })
|
||||
@Html.EditorFor(model => model[i].ReservedSlotNumber, new { htmlAttributes = new { @class = editorClass } })
|
||||
<label asp-for="@Model.ReservedSlotNumber" class="@labelClass"></label>
|
||||
<input asp-for="@Model.ReservedSlotNumber" class="@editorClass" />
|
||||
|
||||
<div>
|
||||
@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 } })
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@($"Servers[{i}].Rules")" class="btn btn-primary configuration-add-new mt-2">@addText</a>
|
||||
<label asp-for="@Model.Rules" class="bg-primary pl-3 pr-3 p-2 w-100 mt-3"></label>
|
||||
@for(i = 0; i < Model.Rules.Length; i++)
|
||||
{
|
||||
<input asp-for="@Model.Rules[i]" class="@editorClass" />
|
||||
}
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="Servers.Rules" class="btn btn-primary configuration-add-new mt-2">@addText</a>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@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 } })
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="@($"Servers[{i}].Automessages")" class="btn btn-primary configuration-add-new mt-2">@addText</a>
|
||||
<label asp-for="@Model.AutoMessages" class="bg-primary pl-3 pr-3 p-2 w-100 mt-3"></label>
|
||||
@for(i = 0; i < Model.AutoMessages.Length; i++)
|
||||
{
|
||||
<input asp-for="@Model.AutoMessages[i]" class="@editorClass" />
|
||||
}
|
||||
<a asp-controller="Configuration" asp-action="GetNewListItem" asp-route-propertyName="Servers.Automessages" class="btn btn-primary configuration-add-new mt-2">@addText</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@*<button asp-controller="Configuration" asp-action="Edit" asp-route-addNewServer="true" class="btn btn-primary">@addServerText</button>*@
|
||||
</div>
|
||||
|
@ -70,11 +70,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(CONFIG)'=='Debug'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -98,6 +94,6 @@
|
||||
</ProjectExtensions>
|
||||
|
||||
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
|
||||
<Exec Command="if $(ConfigurationName) == Debug ( 
powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap.scss
)" />
|
||||
<Exec Command="if $(ConfigurationName) == Debug ( 
powershell -Command wget https://raw.githubusercontent.com/iconic/open-iconic/master/font/css/open-iconic-bootstrap.scss -o $(ProjectDir)wwwroot\lib\open-iconic\font\css\open-iconic-bootstrap.scss
echo d | xcopy /f /y $(ProjectDir)wwwroot\lib\open-iconic\font\fonts $(ProjectDir)wwwroot\font\
)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -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('<br/>');
|
||||
}
|
||||
message = response.responseJSON.message;
|
||||
$('#actionModal .modal-message').html(message + '<br/>' + errors);
|
||||
$('#actionModal').modal();
|
||||
$('#actionModal .modal-message').fadeIn('fast');
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user