Merge pull request #101 from RaidMax/bugfix/issue-0-misc-small-fixes
web configuration changes for issue #100
This commit is contained in:
commit
ad64540bb6
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();
|
||||
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=' + $(this).siblings().length, function (response) {
|
||||
$.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