Compare commits

...

23 Commits

Author SHA1 Message Date
b7a76cc4a2 only send heartbeat when fully initialized 2022-02-01 18:31:55 -06:00
261da918c7 Allow either parser version or parser name to be used in server config block 2022-02-01 18:27:03 -06:00
2ed5e00bcb more profile loading optimizations 2022-02-01 18:20:29 -06:00
6ca94f8da8 only default to IPv4 when parsing
update postgres target version to 12.9
2022-02-01 14:27:16 -06:00
3b532cf1f7 don't try to load scoreboard if not on scoreboard page 2022-02-01 09:09:29 -06:00
40966ed74d modify update script on linux to set executable bit on itself after update 2022-02-01 09:04:40 -06:00
45eacabc28 actual fix now? 2022-01-31 17:56:43 -06:00
0b02b7627a fix again 2022-01-31 17:23:56 -06:00
fc3a24ca17 fix typo on pipeline 2022-01-31 17:00:24 -06:00
209cb6cdd0 use proper folder in post publish script 2022-01-31 16:47:51 -06:00
cfd4296f5c update webfront ip lookup for ssl connection 2022-01-31 16:37:44 -06:00
b275fbaced create update script for managing updates programatically
./UpdateIW4MAdmin.sh or ./UpdateIW4MAdmin.ps1
Co-authored-by: xerxes-at <xerxes-at@users.noreply.github.com>
2022-01-31 11:06:44 -06:00
b2a3625288 update IP lookup api 2022-01-31 08:16:12 -06:00
0d3e2cb0bc fix issue with writing config files 2022-01-29 13:30:48 -06:00
505a2c4c2d fix refactor issue 2022-01-28 17:28:49 -06:00
8730a3fab8 fix issue with certain penalties not linking 2022-01-28 15:33:21 -06:00
3539101a40 webfront profile loading optimizations 2022-01-28 14:33:08 -06:00
7ccdee7d1b disable some warnings 2022-01-28 09:37:04 -06:00
f4b160b735 small startup performance optimization 2022-01-28 09:35:01 -06:00
73036dc1c7 properly provide culture to welcome plugin ordinalize 2022-01-27 21:19:05 -06:00
6cfcce23cc tech debt 2022-01-27 21:18:35 -06:00
8649b0efe9 fix issue with configuration on new install 2022-01-27 13:37:38 -06:00
f554536b95 s This is a combination of 7 commits.
This is the 1st commit message:
2022-01-27 11:25:42 -06:00
55 changed files with 576 additions and 299 deletions

View File

@ -355,10 +355,10 @@ namespace IW4MAdmin.Application
// copy over default config if it doesn't exist
if (!_appConfig.Servers?.Any() ?? true)
{
var defaultConfig = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings").Configuration();
//ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
//var newConfig = ConfigHandler.Configuration();
var defaultHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
await defaultHandler.BuildAsync();
var defaultConfig = defaultHandler.Configuration();
_appConfig.AutoMessages = defaultConfig.AutoMessages;
_appConfig.GlobalRules = defaultConfig.GlobalRules;
_appConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;

View File

@ -23,6 +23,7 @@ echo setting up default folders
if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration"
move "%PublishDir%\DefaultSettings.json" "%PublishDir%\Configuration\"
if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\"
del "%PublishDir%\Microsoft.CodeAnalysis*.dll" /F /Q
move "%PublishDir%\*.dll" "%PublishDir%\Lib\"
move "%PublishDir%\*.json" "%PublishDir%\Lib\"
move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes"
@ -30,16 +31,37 @@ move "%PublishDir%\ru" "%PublishDir%\Lib\ru"
move "%PublishDir%\de" "%PublishDir%\Lib\de"
move "%PublishDir%\pt" "%PublishDir%\Lib\pt"
move "%PublishDir%\es" "%PublishDir%\Lib\es"
rmdir /Q /S "%PublishDir%\cs"
rmdir /Q /S "%PublishDir%\fr"
rmdir /Q /S "%PublishDir%\it"
rmdir /Q /S "%PublishDir%\ja"
rmdir /Q /S "%PublishDir%\ko"
rmdir /Q /S "%PublishDir%\pl"
rmdir /Q /S "%PublishDir%\pt-BR"
rmdir /Q /S "%PublishDir%\tr"
rmdir /Q /S "%PublishDir%\zh-Hans"
rmdir /Q /S "%PublishDir%\zh-Hant"
if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs"
echo making start scripts
@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd"
@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh"
echo copying update scripts
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.ps1" "%PublishDir%\UpdateIW4MAdmin.ps1"
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.sh" "%PublishDir%\UpdateIW4MAdmin.sh"
echo moving front-end library dependencies
if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font"
move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\"
if exist "%PublishDir%\wwwroot\lib" rd /s /q "%PublishDir%\wwwroot\lib"
if not exist "%PublishDir%\wwwroot\css" mkdir "%PublishDir%\wwwroot\css"
move "WebfrontCore\wwwroot\css\global.min.css" "%PublishDir%\wwwroot\css\global.min.css"
if not exist "%PublishDir%\wwwroot\js" mkdir "%PublishDir%\wwwroot\js"
move "%SourceDir%\WebfrontCore\wwwroot\js\global.min.js" "%PublishDir%\wwwroot\js\global.min.js"
if not exist "%PublishDir%\wwwroot\images" mkdir "%PublishDir%\wwwroot\images"
xcopy "%SourceDir%\WebfrontCore\wwwroot\images" "%PublishDir%\wwwroot\images" /E /H /C /I
echo setting permissions...
cacls "%PublishDir%" /t /e /p Everyone:F
cacls "%PublishDir%" /t /e /p Everyone:F

View File

@ -94,7 +94,7 @@ namespace IW4MAdmin.Application.Extensions
postgresqlOptions =>
{
postgresqlOptions.EnableRetryOnFailure();
postgresqlOptions.SetPostgresVersion(new Version("9.4"));
postgresqlOptions.SetPostgresVersion(new Version("12.9"));
})
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
return services;

View File

@ -1,4 +1,5 @@
using IW4MAdmin.Application.Misc;
using System.Threading.Tasks;
using IW4MAdmin.Application.Misc;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Factories
@ -17,7 +18,17 @@ namespace IW4MAdmin.Application.Factories
/// <returns></returns>
public IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration
{
return new BaseConfigurationHandler<T>(name);
var handler = new BaseConfigurationHandler<T>(name);
handler.BuildAsync().Wait();
return handler;
}
/// <inheritdoc/>
public async Task<IConfigurationHandler<T>> GetConfigurationHandlerAsync<T>(string name) where T : IBaseConfiguration
{
var handler = new BaseConfigurationHandler<T>(name);
await handler.BuildAsync();
return handler;
}
}
}

View File

@ -12,6 +12,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
@ -25,7 +26,6 @@ using Data.Models;
using Data.Models.Server;
using IW4MAdmin.Application.Commands;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Formatting;
using static Data.Models.Client.EFClient;
namespace IW4MAdmin
@ -355,7 +355,7 @@ namespace IW4MAdmin
try
{
var factory = _serviceProvider.GetRequiredService<IDatabaseContextFactory>();
await using var context = factory.CreateContext();
await using var context = factory.CreateContext(enableTracking: false);
var messageCount = await context.InboxMessages
.CountAsync(msg => msg.DestinationClientId == E.Origin.ClientId && !msg.IsDelivered);
@ -1076,19 +1076,26 @@ namespace IW4MAdmin
{
try
{
ResolvedIpEndPoint = new IPEndPoint((await Dns.GetHostAddressesAsync(IP)).First(), Port);
ResolvedIpEndPoint =
new IPEndPoint(
(await Dns.GetHostAddressesAsync(IP)).First(address =>
address.AddressFamily == AddressFamily.InterNetwork), Port);
}
catch (Exception ex)
{
ServerLogger.LogWarning(ex, "Could not resolve hostname or IP for RCon connection {IP}:{Port}", IP, Port);
ResolvedIpEndPoint = new IPEndPoint(IPAddress.Parse(IP), Port);
}
RconParser = Manager.AdditionalRConParsers
.FirstOrDefault(_parser => _parser.Version == ServerConfig.RConParserVersion);
.FirstOrDefault(parser =>
parser.Version == ServerConfig.RConParserVersion ||
parser.Name == ServerConfig.RConParserVersion);
EventParser = Manager.AdditionalEventParsers
.FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
.FirstOrDefault(parser =>
parser.Version == ServerConfig.EventParserVersion ||
parser.Name == ServerConfig.RConParserVersion);
RconParser ??= Manager.AdditionalRConParsers[0];
EventParser ??= Manager.AdditionalEventParsers[0];
@ -1105,7 +1112,7 @@ namespace IW4MAdmin
GameName = RconParser.GameName;
}
if (version?.Value?.Length != 0)
if (version.Value?.Length != 0)
{
var matchedRconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => _parser.Version == version.Value);
RconParser.Configuration = matchedRconParser != null ? matchedRconParser.Configuration : RconParser.Configuration;

View File

@ -34,6 +34,7 @@ using IW4MAdmin.Plugins.Stats.Client.Abstractions;
using IW4MAdmin.Plugins.Stats.Client;
using Stats.Client.Abstractions;
using Stats.Client;
using Stats.Config;
using Stats.Helpers;
namespace IW4MAdmin.Application
@ -41,9 +42,9 @@ namespace IW4MAdmin.Application
public class Program
{
public static BuildNumber Version { get; } = BuildNumber.Parse(Utilities.GetVersionAsString());
public static ApplicationManager ServerManager;
private static Task ApplicationTask;
private static ServiceProvider serviceProvider;
private static ApplicationManager _serverManager;
private static Task _applicationTask;
private static ServiceProvider _serviceProvider;
/// <summary>
/// entrypoint of the application
@ -75,10 +76,10 @@ namespace IW4MAdmin.Application
/// <param name="e"></param>
private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e)
{
ServerManager?.Stop();
if (ApplicationTask != null)
_serverManager?.Stop();
if (_applicationTask != null)
{
await ApplicationTask;
await _applicationTask;
}
}
@ -92,7 +93,6 @@ namespace IW4MAdmin.Application
ITranslationLookup translationLookup = null;
var logger = BuildDefaultLogger<Program>(new ApplicationConfiguration());
Utilities.DefaultLogger = logger;
IServiceCollection services = null;
logger.LogInformation("Begin IW4MAdmin startup. Version is {Version} {@Args}", Version, args);
try
@ -102,18 +102,18 @@ namespace IW4MAdmin.Application
ConfigurationMigration.CheckDirectories();
ConfigurationMigration.RemoveObsoletePlugins20210322();
logger.LogDebug("Configuring services...");
services = ConfigureServices(args);
serviceProvider = services.BuildServiceProvider();
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
ServerManager = (ApplicationManager) serviceProvider.GetRequiredService<IManager>();
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
var services = await ConfigureServices(args);
_serviceProvider = services.BuildServiceProvider();
var versionChecker = _serviceProvider.GetRequiredService<IMasterCommunication>();
_serverManager = (ApplicationManager) _serviceProvider.GetRequiredService<IManager>();
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>();
ApplicationTask = RunApplicationTasksAsync(logger, services);
_applicationTask = RunApplicationTasksAsync(logger, services);
var tasks = new[]
{
versionChecker.CheckVersion(),
ServerManager.Init(),
ApplicationTask
_serverManager.Init(),
_applicationTask
};
await Task.WhenAll(tasks);
@ -160,12 +160,12 @@ namespace IW4MAdmin.Application
return;
}
if (ServerManager.IsRestartRequested)
if (_serverManager.IsRestartRequested)
{
goto restart;
}
await serviceProvider.DisposeAsync();
await _serviceProvider.DisposeAsync();
}
/// <summary>
@ -174,11 +174,11 @@ namespace IW4MAdmin.Application
/// <returns></returns>
private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services)
{
var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront
? WebfrontCore.Program.Init(ServerManager, serviceProvider, services, ServerManager.CancellationToken)
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
? WebfrontCore.Program.Init(_serverManager, _serviceProvider, services, _serverManager.CancellationToken)
: Task.CompletedTask;
var collectionService = serviceProvider.GetRequiredService<IServerDataCollector>();
var collectionService = _serviceProvider.GetRequiredService<IServerDataCollector>();
// we want to run this one on a manual thread instead of letting the thread pool handle it,
// because we can't exit early from waiting on console input, and it prevents us from restarting
@ -190,10 +190,10 @@ namespace IW4MAdmin.Application
var tasks = new[]
{
webfrontTask,
ServerManager.Start(),
serviceProvider.GetRequiredService<IMasterCommunication>()
.RunUploadStatus(ServerManager.CancellationToken),
collectionService.BeginCollectionAsync(cancellationToken: ServerManager.CancellationToken)
_serverManager.Start(),
_serviceProvider.GetRequiredService<IMasterCommunication>()
.RunUploadStatus(_serverManager.CancellationToken),
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
};
logger.LogDebug("Starting webfront and input tasks");
@ -215,14 +215,19 @@ namespace IW4MAdmin.Application
return;
}
string lastCommand;
EFClient origin = null;
try
{
while (!ServerManager.CancellationToken.IsCancellationRequested)
while (!_serverManager.CancellationToken.IsCancellationRequested)
{
lastCommand = await Console.In.ReadLineAsync();
if (!_serverManager.IsInitialized)
{
await Task.Delay(1000);
continue;
}
var lastCommand = await Console.In.ReadLineAsync();
if (lastCommand == null)
{
@ -238,12 +243,12 @@ namespace IW4MAdmin.Application
{
Type = GameEvent.EventType.Command,
Data = lastCommand,
Origin = origin ??= Utilities.IW4MAdminClient(ServerManager.Servers.FirstOrDefault()),
Owner = ServerManager.Servers[0]
Origin = origin ??= Utilities.IW4MAdminClient(_serverManager.Servers.FirstOrDefault()),
Owner = _serverManager.Servers[0]
};
ServerManager.AddEvent(gameEvent);
await gameEvent.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken);
_serverManager.AddEvent(gameEvent);
await gameEvent.WaitAsync(Utilities.DefaultCommandTimeout, _serverManager.CancellationToken);
Console.Write('>');
}
}
@ -273,9 +278,9 @@ namespace IW4MAdmin.Application
// register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace == "IW4MAdmin.Application.Commands"))
.Where(_command => _command.BaseType == typeof(Command)))
.Where(command => command.BaseType == typeof(Command)))
{
defaultLogger.LogDebug("Registered native command type {name}", commandType.Name);
defaultLogger.LogDebug("Registered native command type {Name}", commandType.Name);
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
}
@ -283,23 +288,23 @@ namespace IW4MAdmin.Application
var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations();
foreach (var pluginType in plugins)
{
defaultLogger.LogDebug("Registered plugin type {name}", pluginType.FullName);
defaultLogger.LogDebug("Registered plugin type {Name}", pluginType.FullName);
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
}
// register the plugin commands
foreach (var commandType in commands)
{
defaultLogger.LogDebug("Registered plugin command type {name}", commandType.FullName);
defaultLogger.LogDebug("Registered plugin command type {Name}", commandType.FullName);
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
}
foreach (var configurationType in configurations)
{
defaultLogger.LogDebug("Registered plugin config type {name}", configurationType.Name);
defaultLogger.LogDebug("Registered plugin config type {Name}", configurationType.Name);
var configInstance = (IBaseConfiguration) Activator.CreateInstance(configurationType);
var handlerType = typeof(BaseConfigurationHandler<>).MakeGenericType(configurationType);
var handlerInstance = Activator.CreateInstance(handlerType, new[] {configInstance.Name()});
var handlerInstance = Activator.CreateInstance(handlerType, configInstance.Name());
var genericInterfaceType = typeof(IConfigurationHandler<>).MakeGenericType(configurationType);
serviceCollection.AddSingleton(genericInterfaceType, handlerInstance);
@ -313,10 +318,10 @@ namespace IW4MAdmin.Application
// register any eventable types
foreach (var assemblyType in typeof(Program).Assembly.GetTypes()
.Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType))
.Union(plugins.SelectMany(_asm => _asm.Assembly.GetTypes())
.Where(asmType => typeof(IRegisterEvent).IsAssignableFrom(asmType))
.Union(plugins.SelectMany(asm => asm.Assembly.GetTypes())
.Distinct()
.Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType))))
.Where(asmType => typeof(IRegisterEvent).IsAssignableFrom(asmType))))
{
var instance = Activator.CreateInstance(assemblyType) as IRegisterEvent;
serviceCollection.AddSingleton(instance);
@ -329,12 +334,21 @@ namespace IW4MAdmin.Application
/// <summary>
/// Configures the dependency injection services
/// </summary>
private static IServiceCollection ConfigureServices(string[] args)
private static async Task<IServiceCollection> ConfigureServices(string[] args)
{
// todo: this is a quick fix
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
// setup the static resources (config/master api/translations)
var serviceCollection = new ServiceCollection();
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
await appConfigHandler.BuildAsync();
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
await defaultConfigHandler.BuildAsync();
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
await commandConfigHandler.BuildAsync();
var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>();
await statsCommandHandler.BuildAsync();
var defaultConfig = defaultConfigHandler.Configuration();
var appConfig = appConfigHandler.Configuration();
var masterUri = Utilities.IsDevelopment
@ -352,7 +366,7 @@ namespace IW4MAdmin.Application
{
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
appConfigHandler.Set(appConfig);
appConfigHandler.Save().RunSynchronously();
await appConfigHandler.Save();
}
// register override level names
@ -370,15 +384,14 @@ namespace IW4MAdmin.Application
serviceCollection
.AddBaseLogger(appConfig)
.AddSingleton(defaultConfig)
.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
.AddSingleton<IServiceCollection>(serviceCollection)
.AddSingleton<IConfigurationHandler<DefaultSettings>, BaseConfigurationHandler<DefaultSettings>>()
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
.AddSingleton(
new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as
IConfigurationHandler<CommandConfiguration>)
.AddSingleton<IConfigurationHandler<CommandConfiguration>>(commandConfigHandler)
.AddSingleton(appConfig)
.AddSingleton(_serviceProvider =>
_serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
.AddSingleton(statsCommandHandler.Configuration() ?? new StatsConfiguration())
.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
.Configuration() ?? new CommandConfiguration())
.AddSingleton<IPluginImporter, PluginImporter>()
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()

View File

@ -1,11 +1,13 @@
using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces;
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace IW4MAdmin.Application.Misc
{
@ -15,19 +17,24 @@ namespace IW4MAdmin.Application.Misc
/// <typeparam name="T">base configuration type</typeparam>
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
{
T _configuration;
private T _configuration;
private readonly SemaphoreSlim _onSaving;
private readonly JsonSerializerOptions _serializerOptions;
public BaseConfigurationHandler(string fn)
public BaseConfigurationHandler(string fileName)
{
_serializerOptions = new JsonSerializerOptions
{
WriteIndented = true,
};
_serializerOptions.Converters.Add(new JsonStringEnumConverter());
_onSaving = new SemaphoreSlim(1, 1);
FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fn}.json");
Build();
FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fileName}.json");
}
public BaseConfigurationHandler() : this(typeof(T).Name)
{
_onSaving = new SemaphoreSlim(1, 1);
}
~BaseConfigurationHandler()
@ -37,12 +44,12 @@ namespace IW4MAdmin.Application.Misc
public string FileName { get; }
public void Build()
public async Task BuildAsync()
{
try
{
var configContent = File.ReadAllText(FileName);
_configuration = JsonConvert.DeserializeObject<T>(configContent);
await using var fileStream = File.OpenRead(FileName);
_configuration = await JsonSerializer.DeserializeAsync<T>(fileStream, _serializerOptions);
}
catch (FileNotFoundException)
@ -65,14 +72,9 @@ namespace IW4MAdmin.Application.Misc
try
{
await _onSaving.WaitAsync();
var settings = new JsonSerializerSettings()
{
Formatting = Formatting.Indented
};
settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
var appConfigJson = JsonConvert.SerializeObject(_configuration, settings);
await File.WriteAllTextAsync(FileName, appConfigJson);
await using var fileStream = File.Create(FileName);
await JsonSerializer.SerializeAsync(fileStream, _configuration, _serializerOptions);
}
finally

View File

@ -26,7 +26,8 @@ namespace IW4MAdmin.Application.Misc
private readonly ApplicationConfiguration _appConfig;
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
private readonly int _apiVersion = 1;
private bool firstHeartBeat = true;
private bool _firstHeartBeat = true;
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(30);
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
{
@ -97,7 +98,10 @@ namespace IW4MAdmin.Application.Misc
{
try
{
await UploadStatus();
if (_manager.IsRunning)
{
await UploadStatus();
}
}
catch (Exception ex)
@ -107,7 +111,7 @@ namespace IW4MAdmin.Application.Misc
try
{
await Task.Delay(30000, token);
await Task.Delay(Interval, token);
}
catch
@ -119,7 +123,7 @@ namespace IW4MAdmin.Application.Misc
private async Task UploadStatus()
{
if (firstHeartBeat)
if (_firstHeartBeat)
{
var token = await _apiInstance.Authenticate(new AuthenticationId
{
@ -153,7 +157,7 @@ namespace IW4MAdmin.Application.Misc
Response<ResultMessage> response = null;
if (firstHeartBeat)
if (_firstHeartBeat)
{
response = await _apiInstance.AddInstance(instance);
}
@ -161,7 +165,7 @@ namespace IW4MAdmin.Application.Misc
else
{
response = await _apiInstance.UpdateInstance(instance.Id, instance);
firstHeartBeat = false;
_firstHeartBeat = false;
}
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)

View File

@ -207,42 +207,30 @@ namespace IW4MAdmin.Application.Misc
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
{
var meta = new List<IClientMeta>();
var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information)
.Select(async kvp => await kvp.Value[0](request)));
foreach (var (type, actions) in _metaActions)
{
// information is not listed chronologically
if (type != MetaType.Information)
{
var metaItems = await actions[0](request);
meta.AddRange(metaItems);
}
}
return meta.OrderByDescending(_meta => _meta.When)
return metas.SelectMany(m => (IEnumerable<IClientMeta>)m)
.OrderByDescending(m => m.When)
.Take(request.Count)
.ToList();
}
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta
{
IEnumerable<T> meta;
if (metaType == MetaType.Information)
{
var allMeta = new List<T>();
foreach (var individualMetaRegistration in _metaActions[metaType])
{
allMeta.AddRange(await individualMetaRegistration(request));
}
var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration =>
(IEnumerable<T>)await individualMetaRegistration(request)));
allMeta.AddRange(completedMeta.SelectMany(meta => meta));
return ProcessInformationMeta(allMeta);
}
else
{
meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
}
var meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
return meta;
}

View File

@ -168,22 +168,26 @@ namespace IW4MAdmin.Application.Misc
}
}
_scriptEngine.SetValue("_configHandler", new ScriptPluginConfigurationWrapper(Name, _scriptEngine));
await OnLoadAsync(manager);
try
{
if (pluginObject.isParser)
{
await OnLoadAsync(manager);
IsParser = true;
IEventParser eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject();
IRConParser rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject();
var eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject();
var rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject();
manager.AdditionalEventParsers.Add(eventParser);
manager.AdditionalRConParsers.Add(rconParser);
}
}
catch (RuntimeBinderException) { }
catch (RuntimeBinderException)
{
var configWrapper = new ScriptPluginConfigurationWrapper(Name, _scriptEngine);
await configWrapper.InitializeAsync();
_scriptEngine.SetValue("_configHandler", configWrapper);
await OnLoadAsync(manager);
}
if (!firstRun)
{

View File

@ -12,19 +12,24 @@ namespace IW4MAdmin.Application.Misc
public class ScriptPluginConfigurationWrapper
{
private readonly BaseConfigurationHandler<ScriptPluginConfiguration> _handler;
private readonly ScriptPluginConfiguration _config;
private ScriptPluginConfiguration _config;
private readonly string _pluginName;
private readonly Engine _scriptEngine;
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine)
{
_handler = new BaseConfigurationHandler<ScriptPluginConfiguration>("ScriptPluginSettings");
_config = _handler.Configuration() ??
(ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate();
_pluginName = pluginName;
_scriptEngine = scriptEngine;
}
public async Task InitializeAsync()
{
await _handler.BuildAsync();
_config = _handler.Configuration() ??
(ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate();
}
private static int? AsInteger(double d)
{
return int.TryParse(d.ToString(CultureInfo.InvariantCulture), out var parsed) ? parsed : (int?) null;
@ -87,4 +92,4 @@ namespace IW4MAdmin.Application.Misc
return JsValue.FromObject(_scriptEngine, item);
}
}
}
}

View File

@ -23,7 +23,7 @@ namespace Data.Context
{
var link = new EFAliasLink();
context.Clients.Add(new EFClient()
context.Clients.Add(new EFClient
{
Active = false,
Connections = 0,
@ -33,7 +33,7 @@ namespace Data.Context
Masked = true,
NetworkId = 0,
AliasLink = link,
CurrentAlias = new EFAlias()
CurrentAlias = new EFAlias
{
Link = link,
Active = true,
@ -46,4 +46,4 @@ namespace Data.Context
}
}
}
}
}

View File

@ -102,7 +102,7 @@ namespace Data.Helpers
{
try
{
await using var context = _contextFactory.CreateContext();
await using var context = _contextFactory.CreateContext(false);
_cachedItems = await context.Set<T>().ToDictionaryAsync(item => item.Id);
}
catch (Exception ex)
@ -111,4 +111,4 @@ namespace Data.Helpers
}
}
}
}
}

View File

@ -25,10 +25,10 @@ namespace Data.MigrationContext
{
optionsBuilder.UseNpgsql(
"Host=127.0.0.1;Database=IW4MAdmin_Migration;Username=postgres;Password=password;",
options => options.SetPostgresVersion(new Version("9.4")))
options => options.SetPostgresVersion(new Version("12.9")))
.EnableDetailedErrors(true)
.EnableSensitiveDataLogging(true);
}
}
}
}
}

View File

@ -2,6 +2,7 @@
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
// ReSharper disable CompareOfFloatsByEqualityOperator
#pragma warning disable CS0659
namespace Data.Models
{
@ -29,9 +30,7 @@ namespace Data.Models
return $"({X}, {Y}, {Z})";
}
#pragma warning disable CS0659
public override bool Equals(object obj)
#pragma warning restore CS0659
{
if (obj is Vector3 vec)
{

View File

@ -0,0 +1,118 @@
param (
[Parameter(HelpMessage = "Do not prompt for any user input")]
[switch]$Silent = $False,
[Parameter(HelpMessage = "Clean unneeded files listed in _delete.txt after update")]
[switch]$Clean = $False,
[Parameter(HelpMessage = "Only update releases in the verified stream")]
[switch]$Verified = $False,
[Parameter(HelpMessage = "Directory to install to")]
[ValidateScript({
if (-Not($_ | Test-Path))
{
throw "File or folder does not exist"
} return $true
})]
[System.IO.FileInfo]$Directory
)
Write-Output "======================================="
Write-Output " IW4MAdmin Updater v1 "
Write-Output " by XERXES & RaidMax "
Write-Output "======================================="
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
$repoName = "RaidMax/IW4M-Admin"
$assetPattern = "IW4MAdmin-20*.zip"
if ($Verified)
{
$releasesUri = "https://api.github.com/repos/$repoName/releases/latest"
}
else
{
$releasesUri = "https://api.github.com/repos/$repoName/releases"
}
Write-Output "Retrieving latest version info..."
$releaseInfo = (Invoke-WebRequest $releasesUri | ConvertFrom-Json) | Select -First 1
$asset = $releaseInfo.assets | Where-Object name -like $assetPattern | Select -First 1
$downloadUri = $asset.browser_download_url
$filename = Split-Path $downloadUri -leaf
Write-Output "The latest version is $( $releaseInfo.tag_name ) released $( $releaseInfo.published_at )"
if (!$Silent)
{
$stopwatch.Stop()
Write-Warning "All IW4MAdmin files will be updated. Your database and configuration will not be modified. Are you sure you want to continue?" -WarningAction Inquire
$stopwatch.Start()
}
Write-Output "Downloading update. This might take a moment..."
$fileDownload = Invoke-WebRequest -Uri $downloadUri
if ($fileDownload.StatusDescription -ne "OK")
{
throw "Could not update IW4MAdmin. ($fileDownload.StatusDescription)"
}
$remoteHash = $fileDownload.Headers['Content-MD5']
$decodedHash = [System.BitConverter]::ToString([System.Convert]::FromBase64String($remoteHash)).replace('-', '')
$directoryPath = Get-Location
$fullPath = "$directoryPath\$filename"
$outputFile = [System.IO.File]::Open($fullPath, 2)
$stream = [System.IO.BinaryWriter]::new($outputFile)
if ($Directory)
{
$outputDir = $Directory
}
else
{
$outputDir = Get-Location
}
try
{
$stream.Write($fileDownload.Content)
}
finally
{
$stream.Dispose()
$outputFile.Dispose()
}
$localHash = (Get-FileHash -Path $fullPath -Algorithm MD5).Hash
if ($localHash -ne $decodedHash)
{
throw "Failed to update. File hashes don't match!"
}
Write-Output "Extracting $filename to $outputDir"
Expand-Archive -Path $fullPath -DestinationPath $outputDir -Force
if ($Clean)
{
Write-Output "Running post-update clean..."
$DeleteList = Get-Content -Path ./_delete.txt
ForEach ($file in $DeleteList)
{
Write-Output "Deleting $file"
Remove-Item -Path $file
}
}
Write-Output "Removing temporary files..."
Remove-Item -Force $fullPath
$stopwatch.Stop()
$executionTime = [math]::Round($stopwatch.Elapsed.TotalSeconds, 0)
Write-Output "Update completed successfully in $executionTime seconds!"

View File

@ -0,0 +1,106 @@
#!/bin/bash
echo "======================================="
echo " IW4MAdmin Updater v1 "
echo "======================================="
while getopts scvd: flag
do
case "${flag}" in
s) silent='true';;
c) clean='true';;
v) verified='true';;
d) directory=${OPTARG};;
*) exit 1;;
esac
done
start=$SECONDS
repoName="RaidMax/IW4M-Admin"
releaseUri="https://api.github.com/repos/$repoName/releases"
echo "Retrieving latest version info..."
if [ ! "$directory" ]
then
directory=$(pwd)
else
if [ ! -d "$directory" ]
then
mkdir "$directory"
fi
fi
if [ "$verified" ]
then
releaseUri="https://api.github.com/repos/$repoName/releases/latest"
fi
releaseInfo=$(curl -s "${releaseUri}")
downloadUri=$(echo "$releaseInfo" | grep "browser_download_url" | cut -d '"' -f 4"" | head -n1)
publishDate=$(echo "$releaseInfo"| grep "published_at" | cut -d '"' -f 4"" | head -n1)
releaseTitle=$(echo "$releaseInfo" | grep "tag_name" | cut -d '"' -f 4"" | head -n1)
filename=$(basename $downloadUri)
fullpath="$directory/$filename"
echo "The latest version is $releaseTitle released $publishDate"
if [[ ! "$silent" ]]
then
echo -e "\033[33mAll IW4MAdmin files will be updated.\033[0m"
echo -e "\033[33mYour database and configuration will not be modified.\033[0m"
read -p "Are you sure you want to continue [Y/N]? " -n 1 -r
echo
if ! [[ $REPLY =~ ^[Yy]$ ]]
then
exit 0
fi
fi
echo "Downloading update. This might take a moment..."
wget -q "$downloadUri" -O "$fullpath"
if [[ $? -ne 0 ]]
then
echo "Could not download update files!"
exit 1
fi
echo "Extracting $filename to $directory"
unzip -o -q "$fullpath" -d "$directory"
if [[ $? -ne 0 ]]
then
echo "Could not extract update files!"
exit 1
fi
if [[ "$clean" ]]
then
echo "Running post-update clean..."
cat "_delete.txt" | while read -r line || [[ -n $line ]];
do
rm -f "$directory/$line"
if [[ $? -ne 0 ]]
then
echo "Could not clean $directory/$line!"
exit 1
fi
done
fi
echo "Removing temporary files..."
rm -f "$fullpath"
if [[ $? -ne 0 ]]
then
echo "Could not remove update files!"
exit 1
fi
chmod +x "$directory/StartIW4MAdmin.sh"
chmod +x "$directory/UpdateIW4MAdmin.sh"
executionTime=$(($SECONDS - start))
echo "Update completed successfully in $executionTime seconds!"

View File

@ -71,11 +71,11 @@ steps:
targetType: 'inline'
script: |
Write-Host 'Getting dotnet bundle'
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\WebfrontCore\dotnet-bundle.zip
wget http://raidmax.org/IW4MAdmin/res/dotnet-bundle.zip -o $(Build.Repository.LocalPath)\dotnet-bundle.zip
Write-Host 'Unzipping download'
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\WebfrontCore\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)\WebfrontCore
Expand-Archive -LiteralPath $(Build.Repository.LocalPath)\dotnet-bundle.zip -DestinationPath $(Build.Repository.LocalPath)
Write-Host 'Executing dotnet-bundle'
$(Build.Repository.LocalPath)\WebfrontCore\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
$(Build.Repository.LocalPath)\dotnet-bundle.exe $(Build.Repository.LocalPath)\WebfrontCore\bundleconfig.json
failOnStderr: true
workingDirectory: '$(Build.Repository.LocalPath)\WebfrontCore'
@ -104,7 +104,7 @@ steps:
inputs:
filename: 'Application\BuildScripts\PostPublish.bat'
workingFolder: '$(Build.Repository.LocalPath)'
arguments: '$(outputFolder)'
arguments: '$(outputFolder) $(Build.Repository.LocalPath)'
failOnStandardError: true
- task: PowerShell@2
@ -121,6 +121,7 @@ steps:
script: |
echo changing to encoding for linux start script
dos2unix $(outputFolder)\StartIW4MAdmin.sh
dos2unix $(outputFolder)\UpdateIW4MAdmin.sh
echo creating website version filename
@echo IW4MAdmin-$(Build.BuildNumber) > $(Build.ArtifactStagingDirectory)\version_$(releaseType).txt
workingDirectory: '$(Build.Repository.LocalPath)\Application\BuildScripts'
@ -162,6 +163,11 @@ steps:
archiveFile: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
replaceExistingArchive: true
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/IW4MAdmin-$(Build.BuildNumber).zip'
artifact: 'IW4MAdmin-$(Build.BuildNumber).zip'
- task: FtpUpload@2
displayName: 'Upload zip file to website'
inputs:

View File

@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
DeploymentFiles\PostPublish.ps1 = DeploymentFiles\PostPublish.ps1
README.md = README.md
version.txt = version.txt
DeploymentFiles\UpdateIW4MAdmin.ps1 = DeploymentFiles\UpdateIW4MAdmin.ps1
DeploymentFiles\UpdateIW4MAdmin.sh = DeploymentFiles\UpdateIW4MAdmin.sh
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedLibraryCore", "SharedLibraryCore\SharedLibraryCore.csproj", "{AA0541A2-8D51-4AD9-B0AC-3D1F5B162481}"

View File

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -65,6 +65,7 @@ namespace AutomessageFeed
public async Task OnLoadAsync(IManager manager)
{
await _configurationHandler.BuildAsync();
if (_configurationHandler.Configuration() == null)
{
_configurationHandler.Set((Configuration)new Configuration().Generate());

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -1,29 +1,25 @@
using LiveRadar.Configuration;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace LiveRadar.Web.Controllers
{
public class RadarController : BaseController
{
private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
private readonly IManager _manager;
private readonly LiveRadarConfiguration _config;
private static LiveRadarConfiguration _config;
private readonly IConfigurationHandler<LiveRadarConfiguration> _configurationHandler;
public RadarController(IManager manager, IConfigurationHandlerFactory configurationHandlerFactory) : base(manager)
{
_manager = manager;
_config = configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration").Configuration() ?? new LiveRadarConfiguration();
_configurationHandler =
configurationHandlerFactory.GetConfigurationHandler<LiveRadarConfiguration>("LiveRadarConfiguration");
}
[HttpGet]
@ -46,7 +42,7 @@ namespace LiveRadar.Web.Controllers
[HttpGet]
[Route("Radar/{serverId}/Map")]
public IActionResult Map(long? serverId = null)
public async Task<IActionResult> Map(long? serverId = null)
{
var server = serverId == null ? _manager.GetServers().FirstOrDefault() : _manager.GetServers().FirstOrDefault(_server => _server.EndPoint == serverId);
@ -54,6 +50,12 @@ namespace LiveRadar.Web.Controllers
{
return NotFound();
}
if (_config == null)
{
await _configurationHandler.BuildAsync();
_config = _configurationHandler.Configuration() ?? new LiveRadarConfiguration();
}
var map = _config.Maps.FirstOrDefault(_map => _map.Name == server.CurrentMap.Name);
@ -93,4 +95,4 @@ namespace LiveRadar.Web.Controllers
return Ok();
}
}
}
}

View File

@ -23,7 +23,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -103,6 +103,7 @@ namespace LiveRadar
public async Task OnLoadAsync(IManager manager)
{
await _configurationHandler.BuildAsync();
if (_configurationHandler.Configuration() == null)
{
_configurationHandler.Set((LiveRadarConfiguration)new LiveRadarConfiguration().Generate());

View File

@ -3,6 +3,7 @@ using SharedLibraryCore;
using System;
using System.Linq;
// ReSharper disable CompareOfFloatsByEqualityOperator
#pragma warning disable CS0659
namespace LiveRadar
{
@ -23,9 +24,7 @@ namespace LiveRadar
public Vector3 RadianAngles => new Vector3(ViewAngles.X.ToRadians(), ViewAngles.Y.ToRadians(), ViewAngles.Z.ToRadians());
public int Id => GetHashCode();
#pragma warning disable CS0659
public override bool Equals(object obj)
#pragma warning restore CS0659
{
if (obj is RadarEvent re)
{

View File

@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -76,6 +76,7 @@ namespace IW4MAdmin.Plugins.Login
{
AuthorizedClients = new ConcurrentDictionary<int, bool>();
await _configHandler.BuildAsync();
if (_configHandler.Configuration() == null)
{
_configHandler.Set((Configuration)new Configuration().Generate());

View File

@ -109,6 +109,7 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
public async Task OnLoadAsync(IManager manager)
{
await _configHandler.BuildAsync();
if (_configHandler.Configuration() == null)
{
_configHandler.Set((Configuration)new Configuration().Generate());

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -48,6 +48,7 @@ namespace Stats.Client
await LoadServers();
_distributionCache.SetCacheItem((async (set, token) =>
{
await _configurationHandler.BuildAsync();
var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3;
var distributions = new Dictionary<long, Extensions.LogParams>();
@ -73,6 +74,7 @@ namespace Stats.Client
_maxZScoreCache.SetCacheItem(async (set, token) =>
{
await _configurationHandler.BuildAsync();
var validPlayTime = _configurationHandler.Configuration()?.TopPlayersMinPlayTime ?? 3600 * 3;
var zScore = await set

View File

@ -3,9 +3,7 @@ using Stats.Client.Abstractions;
using Stats.Client.Game;
using System.Collections.Generic;
using System.Linq;
using IW4MAdmin.Plugins.Stats.Config;
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using Stats.Config;
using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -16,10 +14,10 @@ namespace Stats.Client
private readonly ILogger _logger;
private readonly StatsConfiguration _config;
public WeaponNameParser(ILogger<WeaponNameParser> logger, IConfigurationHandler<StatsConfiguration> config)
public WeaponNameParser(ILogger<WeaponNameParser> logger, StatsConfiguration config)
{
_logger = logger;
_config = config.Configuration();
_config = config;
}
public WeaponInfo Parse(string weaponName, Server.Game gameName)
@ -67,4 +65,4 @@ namespace Stats.Client
return weaponInfo;
}
}
}
}

View File

@ -38,7 +38,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
private readonly ConcurrentDictionary<long, ServerStats> _servers;
private readonly ILogger _log;
private readonly IDatabaseContextFactory _contextFactory;
private readonly IConfigurationHandler<StatsConfiguration> _configHandler;
private readonly StatsConfiguration _config;
private static List<EFServer> serverModels;
public static string CLIENT_STATS_KEY = "ClientStats";
public static string CLIENT_DETECTIONS_KEY = "ClientDetections";
@ -47,13 +47,13 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
private readonly IServerDistributionCalculator _serverDistributionCalculator;
public StatManager(ILogger<StatManager> logger, IManager mgr, IDatabaseContextFactory contextFactory,
IConfigurationHandler<StatsConfiguration> configHandler,
StatsConfiguration statsConfig,
IServerDistributionCalculator serverDistributionCalculator)
{
_servers = new ConcurrentDictionary<long, ServerStats>();
_log = logger;
_contextFactory = contextFactory;
_configHandler = configHandler;
_config = statsConfig;
_serverDistributionCalculator = serverDistributionCalculator;
}
@ -75,7 +75,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
r.When > fifteenDaysAgo &&
r.RatingHistory.Client.Level != EFClient.Permission.Banned &&
r.Newest &&
r.ActivityAmount >= _configHandler.Configuration().TopPlayersMinPlayTime;
r.ActivityAmount >= _config.TopPlayersMinPlayTime;
}
/// <summary>
@ -87,7 +87,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
await using var context = _contextFactory.CreateContext(enableTracking: false);
if (_configHandler.Configuration().EnableAdvancedMetrics)
if (_config.EnableAdvancedMetrics)
{
var clientRanking = await context.Set<EFClientRankingHistory>()
.Where(r => r.ClientId == clientId)
@ -126,7 +126,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
&& ranking.PerformanceMetric != null
&& ranking.Newest
&& ranking.Client.TotalConnectionTime >=
_configHandler.Configuration().TopPlayersMinPlayTime;
_config.TopPlayersMinPlayTime;
}
public async Task<int> GetTotalRankedPlayers(long serverId)
@ -217,7 +217,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task<List<TopStatsInfo>> GetTopStats(int start, int count, long? serverId = null)
{
if (_configHandler.Configuration().EnableAdvancedMetrics)
if (_config.EnableAdvancedMetrics)
{
return await GetNewTopStats(start, count, serverId);
}
@ -570,7 +570,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
{
clientStats = UpdateStats(clientStats, pl);
await SaveClientStats(clientStats);
if (_configHandler.Configuration().EnableAdvancedMetrics)
if (_config.EnableAdvancedMetrics)
{
await UpdateHistoricalRanking(pl.ClientId, clientStats, serverId);
}
@ -973,7 +973,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// update their performance
if ((DateTime.UtcNow - attackerStats.LastStatHistoryUpdate).TotalMinutes >=
(Utilities.IsDevelopment ? 0.5 : _configHandler.Configuration().EnableAdvancedMetrics ? 5.0 : 2.5))
(Utilities.IsDevelopment ? 0.5 : _config.EnableAdvancedMetrics ? 5.0 : 2.5))
{
try
{
@ -982,7 +982,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// for stat history update, but one is already processing that invalidates the original
await attackerStats.ProcessingHit.WaitAsync(Utilities.DefaultCommandTimeout,
Plugin.ServerManager.CancellationToken);
if (_configHandler.Configuration().EnableAdvancedMetrics)
if (_config.EnableAdvancedMetrics)
{
await UpdateHistoricalRanking(attacker.ClientId, attackerStats, serverId);
}
@ -1190,7 +1190,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
public async Task UpdateHistoricalRanking(int clientId, EFClientStatistics clientStats, long serverId)
{
await using var context = _contextFactory.CreateContext();
var minPlayTime = _configHandler.Configuration().TopPlayersMinPlayTime;
var minPlayTime = _config.TopPlayersMinPlayTime;
var performances = await context.Set<EFClientStatistics>()
.AsNoTracking()
@ -1208,7 +1208,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
var serverRanking = await context.Set<EFClientStatistics>()
.Where(stats => stats.ClientId != clientStats.ClientId)
.Where(AdvancedClientStatsResourceQueryHelper.GetRankingFunc(
_configHandler.Configuration().TopPlayersMinPlayTime, clientStats.ZScore, serverId))
_config.TopPlayersMinPlayTime, clientStats.ZScore, serverId))
.CountAsync();
var serverRankingSnapshot = new EFClientRankingHistory

View File

@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models.Client;
using Data.Models.Client.Stats;
using Data.Models.Server;
using Microsoft.Extensions.Logging;
@ -172,6 +171,7 @@ namespace IW4MAdmin.Plugins.Stats
public async Task OnLoadAsync(IManager manager)
{
await Config.BuildAsync();
// load custom configuration
if (Config.Configuration() == null)
{
@ -191,10 +191,8 @@ namespace IW4MAdmin.Plugins.Stats
async Task<IEnumerable<InformationResponse>> getStats(ClientPaginationRequest request)
{
IList<EFClientStatistics> clientStats;
int messageCount = 0;
await using var ctx = _databaseContextFactory.CreateContext(enableTracking: false);
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == request.ClientId).ToListAsync();
messageCount = await ctx.Set<EFClientMessage>().CountAsync(_message => _message.ClientId == request.ClientId);
int kills = clientStats.Sum(c => c.Kills);
int deaths = clientStats.Sum(c => c.Deaths);
@ -253,14 +251,6 @@ namespace IW4MAdmin.Plugins.Stats
Column = 0,
Order = 5,
Type = MetaType.Information
},
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = messageCount.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 1,
Order = 4,
Type = MetaType.Information
}
};
}
@ -451,7 +441,7 @@ namespace IW4MAdmin.Plugins.Stats
}
ServerManager = manager;
Manager = new StatManager(_managerLogger, manager, _databaseContextFactory, Config, _serverDistributionCalculator);
Manager = new StatManager(_managerLogger, manager, _databaseContextFactory, Config.Configuration(), _serverDistributionCalculator);
await _serverDistributionCalculator.Initialize();
}

View File

@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">

View File

@ -35,6 +35,7 @@ namespace IW4MAdmin.Plugins.Welcome
public async Task OnLoadAsync(IManager manager)
{
await _configHandler.BuildAsync();
if (_configHandler.Configuration() == null)
{
_configHandler.Set((WelcomeConfiguration) new WelcomeConfiguration().Generate());
@ -94,7 +95,7 @@ namespace IW4MAdmin.Plugins.Welcome
msg = msg.Replace("{{ClientLocation}}", await GetCountryName(joining.IPAddressString));
}
msg = msg.Replace("{{TimesConnected}}", joining.Connections.Ordinalize());
msg = msg.Replace("{{TimesConnected}}", joining.Connections.Ordinalize(Utilities.CurrentLocalization.Culture));
return msg;
}
@ -110,7 +111,7 @@ namespace IW4MAdmin.Plugins.Welcome
try
{
var response =
await wc.GetStringAsync(new Uri($"http://extreme-ip-lookup.com/json/{ip}?key=demo"));
await wc.GetStringAsync(new Uri($"http://ip-api.com/json/{ip}"));
var responseObj = JObject.Parse(response);
response = responseObj["country"]?.ToString();

View File

@ -20,7 +20,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.25.2" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2022.1.28.1" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -35,11 +35,22 @@ Linux
* You will need to retrieve your login credentials by typing `!rt` ingame
### Updating
**Manually**
1. Download the latest version of **IW4MAdmin**
2. Extract the newer version of **IW4MAdmin** into pre-existing **IW4MAdmin** folder and overwrite existing files
_Your configuration and database will be saved_
**OR**
Use the provided `UpdateIW4MAdmin` script to download and install automatically
| Argument Windows (Linux) | Description |
|--------------------------|-----------------------------------------------------------|
| -Silent (s) | Do not prompt for any user input |
| -Clean (c) | Clean unneeded files listed in `_delete.txt` after update |
| -Verified (v) | Only update releases in the verified stream |
| -Directory (d) | Directory to install to |
## Help
Feel free to join the **IW4MAdmin** [Discord](https://discord.gg/ZZFK5p3)
If you come across an issue, bug, or feature request please post an [issue](https://github.com/RaidMax/IW4M-Admin/issues)

View File

@ -381,9 +381,10 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
// todo: don't do the lookup here
var penalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId);
if (penalties.Where(p => p.Type == EFPenalty.PenaltyType.Ban || p.Type == EFPenalty.PenaltyType.TempBan)
.FirstOrDefault() != null)
var penalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.CurrentAliasId);
if (penalties
.FirstOrDefault(p =>
p.Type == EFPenalty.PenaltyType.Ban || p.Type == EFPenalty.PenaltyType.TempBan) != null)
{
switch ((await E.Target.Unban(E.Data, E.Origin)
.WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
@ -897,7 +898,7 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E)
{
var existingPenalties = await E.Owner.Manager.GetPenaltyService()
.GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.IPAddress);
.GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.CurrentAliasId, E.Target.IPAddress);
var penalty = existingPenalties.FirstOrDefault(b => b.Type > EFPenalty.PenaltyType.Kick);
if (penalty == null)
@ -1247,4 +1248,4 @@ namespace SharedLibraryCore.Commands
E.Origin.Tell(await GetNextMap(E.Owner, _translationLookup));
}
}
}
}

View File

@ -147,8 +147,8 @@ namespace SharedLibraryCore.Configuration
}
_selectedParser = _rconParsers.FirstOrDefault(p => p.Name == parser);
RConParserVersion = _selectedParser?.Version;
EventParserVersion = _selectedParser?.Version;
RConParserVersion = _selectedParser?.Name;
EventParserVersion = _selectedParser?.Name;
if (index <= 0 || _rconParsers[index].CanGenerateLogPath)
{

View File

@ -5,4 +5,4 @@
string Name();
IBaseConfiguration Generate();
}
}
}

View File

@ -6,8 +6,8 @@ namespace SharedLibraryCore.Interfaces
{
string FileName { get; }
Task Save();
void Build();
Task BuildAsync();
T Configuration();
void Set(T config);
}
}
}

View File

@ -1,17 +1,27 @@
namespace SharedLibraryCore.Interfaces
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// defines the capabilities of the configuration handler factory
/// used to generate new instance of configuration handlers
/// defines the capabilities of the configuration handler factory
/// used to generate new instance of configuration handlers
/// </summary>
public interface IConfigurationHandlerFactory
{
/// <summary>
/// generates a new configuration handler
/// generates a new configuration handler
/// </summary>
/// <typeparam name="T">base configuration type</typeparam>
/// <param name="name">file name of configuration</param>
/// <returns>new configuration handler instance</returns>
IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration;
/// <summary>
/// generates a new configuration handler and builds the configuration automatically
/// </summary>
/// <typeparam name="T">base configuration type</typeparam>
/// <param name="name">file name of configuration</param>
/// <returns>new configuration handler instance</returns>
Task<IConfigurationHandler<T>> GetConfigurationHandlerAsync<T>(string name) where T : IBaseConfiguration;
}
}
}

View File

@ -653,7 +653,7 @@ namespace SharedLibraryCore.Database.Models
// we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID)
var activePenalties = await CurrentServer.Manager.GetPenaltyService()
.GetActivePenaltiesAsync(AliasLinkId, ipAddress);
.GetActivePenaltiesAsync(AliasLinkId, CurrentAliasId, ipAddress);
var banPenalty = activePenalties.FirstOrDefault(_penalty => _penalty.Type == EFPenalty.PenaltyType.Ban);
var tempbanPenalty =
activePenalties.FirstOrDefault(_penalty => _penalty.Type == EFPenalty.PenaltyType.TempBan);
@ -740,4 +740,4 @@ namespace SharedLibraryCore.Database.Models
return IsBot ? ClientNumber : (int)NetworkId;
}
}
}
}

View File

@ -168,10 +168,9 @@ namespace SharedLibraryCore.Services
public async Task<EFClient> Get(int entityId)
{
// todo: this needs to be optimized for large linked accounts
await using var context = _contextFactory.CreateContext(false);
var client = context.Clients
var client = await context.Clients
.Select(_client => new EFClient
{
ClientId = _client.ClientId,
@ -187,26 +186,28 @@ namespace SharedLibraryCore.Services
Name = _client.CurrentAlias.Name,
IPAddress = _client.CurrentAlias.IPAddress
},
TotalConnectionTime = _client.TotalConnectionTime
TotalConnectionTime = _client.TotalConnectionTime,
AliasLink = new EFAliasLink
{
AliasLinkId = _client.AliasLinkId,
Children = _client.AliasLink.Children
},
LinkedAccounts = new Dictionary<int, long>()
{
{_client.ClientId, _client.NetworkId}
}
})
.FirstOrDefault(_client => _client.ClientId == entityId);
.FirstOrDefaultAsync(_client => _client.ClientId == entityId);
if (client == null)
{
return null;
}
client.AliasLink = new EFAliasLink
if (!_appConfig.EnableImplicitAccountLinking)
{
AliasLinkId = client.AliasLinkId,
Children = await context.Aliases
.Where(_alias => _alias.LinkId == client.AliasLinkId)
.Select(_alias => new EFAlias
{
Name = _alias.Name,
IPAddress = _alias.IPAddress
}).ToListAsync()
};
return client;
}
var foundClient = new
{
@ -220,11 +221,6 @@ namespace SharedLibraryCore.Services
.ToListAsync()
};
if (foundClient == null)
{
return null;
}
foundClient.Client.LinkedAccounts = new Dictionary<int, long>();
// todo: find out the best way to do this
// I'm doing this here because I don't know the best way to have multiple awaits in the query

View File

@ -148,7 +148,7 @@ namespace SharedLibraryCore.Services
return await iqPenalties.Distinct().ToListAsync();
}
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int? ip = null,
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int currentAliasId, int? ip = null,
bool includePunisherName = false)
{
var now = DateTime.UtcNow;
@ -163,9 +163,6 @@ namespace SharedLibraryCore.Services
(p.Expires == null || p.Expires > now);
await using var context = _contextFactory.CreateContext(false);
var iqLinkPenalties = context.Penalties
.Where(p => p.LinkId == linkId)
.Where(filter);
IQueryable<EFPenalty> iqIpPenalties;
@ -178,22 +175,21 @@ namespace SharedLibraryCore.Services
}
else
{
var aliasIps = await context.Aliases.Where(alias => alias.LinkId == linkId && alias.IPAddress != null)
.Select(alias => alias.IPAddress)
.ToListAsync();
if (ip != null)
{
aliasIps.Add(ip);
}
var usedIps = await context.Aliases.AsNoTracking()
.Where(alias => (alias.LinkId == linkId || alias.AliasId == currentAliasId) && alias.IPAddress != null)
.Select(alias => alias.IPAddress).ToListAsync();
iqIpPenalties = context.Penalties
.Where(penalty => aliasIps.Contains(penalty.Offender.CurrentAlias.IPAddress))
var aliasedIds = await context.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress))
.Select(alias => alias.LinkId)
.ToListAsync();
iqIpPenalties = context.Penalties.AsNoTracking()
.Where(penalty => aliasedIds.Contains(penalty.LinkId) || penalty.LinkId == linkId)
.Where(filter);
}
var activePenalties = (await iqLinkPenalties.ToListAsync())
.Union(await iqIpPenalties.ToListAsync())
.Distinct();
var activeIpPenalties = await iqIpPenalties.ToListAsync();
var activePenalties = activeIpPenalties.Distinct();
// this is a bit more performant in memory (ordering)
return activePenalties.OrderByDescending(p => p.When).ToList();
@ -221,4 +217,4 @@ namespace SharedLibraryCore.Services
await context.SaveChangesAsync();
}
}
}
}

View File

@ -4,12 +4,12 @@
<OutputType>Library</OutputType>
<TargetFramework>net6.0</TargetFramework>
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2022.01.25.2</Version>
<Version>2022.01.28.1</Version>
<Authors>RaidMax</Authors>
<Company>Forever None</Company>
<Configurations>Debug;Release;Prerelease</Configurations>
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
<LangVersion>8.0</LangVersion>
<LangVersion>default</LangVersion>
<PackageTags>IW4MAdmin</PackageTags>
<RepositoryUrl>https://github.com/RaidMax/IW4M-Admin/</RepositoryUrl>
<PackageProjectUrl>https://www.raidmax.org/IW4MAdmin/</PackageProjectUrl>
@ -19,7 +19,7 @@
<IsPackable>true</IsPackable>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Description>Shared Library for IW4MAdmin</Description>
<PackageVersion>2022.01.25.2</PackageVersion>
<PackageVersion>2022.01.28.1</PackageVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">

View File

@ -125,28 +125,6 @@ namespace SharedLibraryCore
return str.Length > maxLength ? $"{str.Substring(0, maxLength - 3)}..." : str;
}
/// <summary>
/// helper method to get the information about an exception and inner exceptions
/// </summary>
/// <param name="ex"></param>
/// <returns></returns>
public static string GetExceptionInfo(this Exception ex)
{
var sb = new StringBuilder();
var depth = 0;
while (ex != null)
{
sb.AppendLine($"Exception[{depth}] Name: {ex.GetType().FullName}");
sb.AppendLine($"Exception[{depth}] Message: {ex.Message}");
sb.AppendLine($"Exception[{depth}] Call Stack: {ex.StackTrace}");
sb.AppendLine($"Exception[{depth}] Source: {ex.Source}");
depth++;
ex = ex.InnerException;
}
return sb.ToString();
}
public static Permission MatchPermission(string str)
{
var lookingFor = str.ToLower();
@ -1190,4 +1168,4 @@ namespace SharedLibraryCore
return allRules[index];
}
}
}
}

View File

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Models;
using IW4MAdmin.Plugins.Stats.Config;
using Stats.Config;
using WebfrontCore.ViewComponents;
@ -19,13 +18,12 @@ namespace WebfrontCore.Controllers
public class ClientController : BaseController
{
private readonly IMetaService _metaService;
private readonly IConfigurationHandler<StatsConfiguration> _configurationHandler;
private readonly StatsConfiguration _config;
public ClientController(IManager manager, IMetaService metaService,
IConfigurationHandler<StatsConfiguration> configurationHandler) : base(manager)
public ClientController(IManager manager, IMetaService metaService, StatsConfiguration config) : base(manager)
{
_metaService = metaService;
_configurationHandler = configurationHandler;
_config = config;
}
public async Task<IActionResult> ProfileAsync(int id, MetaType? metaFilterType)
@ -37,9 +35,18 @@ namespace WebfrontCore.Controllers
return NotFound();
}
var activePenalties = (await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.IPAddress));
var activePenalties = await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.CurrentAliasId, client.IPAddress);
var persistentMetaTask = new[]
{
_metaService.GetPersistentMeta(EFMeta.ClientTag, client),
_metaService.GetPersistentMeta("GravatarEmail", client)
};
var persistentMeta = await Task.WhenAll(persistentMetaTask);
var tag = persistentMeta[0];
var gravatar = persistentMeta[1];
var tag = await _metaService.GetPersistentMeta(EFMeta.ClientTag, client);
if (tag?.LinkedMeta != null)
{
client.SetAdditionalProperty(EFMeta.ClientTag, tag.LinkedMeta.Value);
@ -56,7 +63,7 @@ namespace WebfrontCore.Controllers
displayLevel = string.IsNullOrEmpty(client.Tag) ? displayLevel : $"{displayLevel} ({client.Tag})";
var clientDto = new PlayerInfo()
var clientDto = new PlayerInfo
{
Name = client.Name,
Level = displayLevel,
@ -93,7 +100,6 @@ namespace WebfrontCore.Controllers
Before = DateTime.UtcNow
}, MetaType.Information);
var gravatar = await _metaService.GetPersistentMeta("GravatarEmail", client);
if (gravatar != null)
{
clientDto.Meta.Add(new InformationResponse()
@ -107,14 +113,14 @@ namespace WebfrontCore.Controllers
clientDto.ActivePenalty = activePenalties.OrderByDescending(_penalty => _penalty.Type).FirstOrDefault();
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
string strippedName = clientDto.Name.StripColors();
var strippedName = clientDto.Name.StripColors();
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ?
strippedName + "'" :
strippedName + "'s";
ViewBag.Title += " " + Localization["WEBFRONT_CLIENT_PROFILE_TITLE"];
ViewBag.Description = $"Client information for {strippedName}";
ViewBag.Keywords = $"IW4MAdmin, client, profile, {strippedName}";
ViewBag.UseNewStats = _configurationHandler.Configuration()?.EnableAdvancedMetrics ?? true;
ViewBag.UseNewStats = _config?.EnableAdvancedMetrics ?? true;
return View("Profile/Index", clientDto);
}

View File

@ -16,10 +16,10 @@ namespace WebfrontCore.Controllers
public ClientStatisticsController(IManager manager,
IResourceQueryHelper<StatsInfoRequest, AdvancedStatsInfo> queryHelper,
IConfigurationHandler<DefaultSettings> configurationHandler) : base(manager)
DefaultSettings defaultConfig) : base(manager)
{
_queryHelper = queryHelper;
_defaultConfig = configurationHandler.Configuration();
_defaultConfig = defaultConfig;
}
[HttpGet("{id:int}/advanced")]
@ -35,4 +35,4 @@ namespace WebfrontCore.Controllers
return View("~/Views/Client/Statistics/Advanced.cshtml", hitInfo.Results.First());
}
}
}
}

View File

@ -27,19 +27,18 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
private readonly IResourceQueryHelper<ChatSearchQuery, MessageResponse> _chatResourceQueryHelper;
private readonly ITranslationLookup _translationLookup;
private readonly IDatabaseContextFactory _contextFactory;
private readonly IConfigurationHandler<StatsConfiguration> _configurationHandler;
private readonly StatsConfiguration _config;
public StatsController(ILogger<StatsController> logger, IManager manager, IResourceQueryHelper<ChatSearchQuery,
MessageResponse> resourceQueryHelper, ITranslationLookup translationLookup,
IDatabaseContextFactory contextFactory,
IConfigurationHandler<StatsConfiguration> configurationHandler) : base(manager)
IDatabaseContextFactory contextFactory, StatsConfiguration config) : base(manager)
{
_logger = logger;
_manager = manager;
_chatResourceQueryHelper = resourceQueryHelper;
_translationLookup = translationLookup;
_contextFactory = contextFactory;
_configurationHandler = configurationHandler;
_config = config;
}
[HttpGet]
@ -70,7 +69,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
serverId = StatManager.GetIdForServer(server);
}
var results = _configurationHandler.Configuration().EnableAdvancedMetrics
var results = _config?.EnableAdvancedMetrics ?? true
? await Plugin.Manager.GetNewTopStats(offset, count, serverId)
: await Plugin.Manager.GetTopStats(offset, count, serverId);
@ -80,7 +79,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
return Ok();
}
ViewBag.UseNewStats = _configurationHandler.Configuration().EnableAdvancedMetrics;
ViewBag.UseNewStats = _config?.EnableAdvancedMetrics;
return View("~/Views/Client/Statistics/Components/TopPlayers/_List.cshtml", results);
}

View File

@ -129,7 +129,7 @@ namespace WebfrontCore
services.AddSingleton(Program.ApplicationServiceProvider
.GetRequiredService<IConfigurationHandler<DefaultSettings>>());
services.AddSingleton(Program.ApplicationServiceProvider
.GetRequiredService<IConfigurationHandler<StatsConfiguration>>());
.GetRequiredService<StatsConfiguration>());
services.AddSingleton(Program.ApplicationServiceProvider.GetRequiredService<IServerDataViewer>());
}

View File

@ -3,18 +3,17 @@ using System.Threading.Tasks;
using IW4MAdmin.Plugins.Stats;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore.Interfaces;
using Stats.Config;
namespace WebfrontCore.ViewComponents
{
public class TopPlayersViewComponent : ViewComponent
{
private readonly IConfigurationHandler<StatsConfiguration> _configurationHandler;
private readonly StatsConfiguration _config;
public TopPlayersViewComponent(IConfigurationHandler<StatsConfiguration> configurationHandler)
public TopPlayersViewComponent(StatsConfiguration config)
{
_configurationHandler = configurationHandler;
_config = config;
}
public async Task<IViewComponentResult> InvokeAsync(int count, int offset, long? serverId = null)
@ -32,7 +31,7 @@ namespace WebfrontCore.ViewComponents
}
ViewBag.UseNewStats = _configurationHandler.Configuration()?.EnableAdvancedMetrics ?? true;
ViewBag.UseNewStats = _config?.EnableAdvancedMetrics ?? true;
return View("~/Views/Client/Statistics/Components/TopPlayers/_List.cshtml",
ViewBag.UseNewStats
? await Plugin.Manager.GetNewTopStats(offset, count, serverId)

View File

@ -96,37 +96,31 @@
$('.ip-locate-link').click(function (e) {
e.preventDefault();
const ip = $(this).data("ip");
$.getJSON('https://extreme-ip-lookup.com/json/' + ip + '?key=demo')
$.getJSON(`https://ipwhois.app/json/${ip}`)
.done(function (response) {
$('#mainModal .modal-title').text(ip);
$('#mainModal .modal-body').text('');
if (response.ipName.length > 0) {
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_HOSTNAME']} &mdash; ${response.ipName}<br/>`);
}
if (response.isp.length > 0) {
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ISP']} &mdash; ${response.isp}<br/>`);
}
if (response.org.length > 0) {
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_ORG']} &mdash; ${response.org}<br/>`);
}
if (response['businessName'].length > 0) {
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_BUSINESS']} &mdash; ${response.businessName}<br/>`);
}
if (response['businessWebsite'].length > 0) {
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_WEBSITE']} &mdash; ${response.businessWebsite}<br/>`);
}
if (response.city.length > 0 || response.region.length > 0 || response.country.length > 0) {
if (response.region.length > 0 || response.city.length > 0 || response.country.length > 0 || response.timezone_gmt.length > 0) {
$('#mainModal .modal-body').append(`${_localization['WEBFRONT_PROFILE_LOOKUP_LOCATION']} &mdash; `);
}
if (response.city.length > 0) {
$('#mainModal .modal-body').append(response.city);
}
if (response.region.length > 0) {
$('#mainModal .modal-body').append((response.city.length > 0 ? ', ' : '') + response.region);
$('#mainModal .modal-body').append((response.region.length > 0 ? ', ' : '') + response.region);
}
if (response.country.length > 0) {
$('#mainModal .modal-body').append((response.country.length > 0 ? ', ' : '') + response.country);
}
if (response.timezone_gmt.length > 0) {
$('#mainModal .modal-body').append((response.timezone_gmt.length > 0 ? ', ' : '') + response.timezone_gmt);
}
$('#mainModal').modal();
})

View File

@ -11,6 +11,10 @@
}
$(document).ready(() => {
if ($('.scoreboard-container').length === 0) {
return 0;
}
$(window.location.hash).tab('show');
$(`${window.location.hash}_nav`).addClass('active');