QOL updates for profile meta

implement filterable meta for issue #158
update translations and use humanizer lib with datetime/timespan for issue #80
This commit is contained in:
RaidMax 2020-08-17 21:21:11 -05:00
parent 1ef2ba5344
commit 778e339a61
78 changed files with 1800 additions and 775 deletions

View File

@ -25,20 +25,20 @@
<ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-1632" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
<PackageReference Include="RestEase" Version="1.4.10" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.7" />
<PackageReference Include="RestEase" Version="1.5.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.1" />
</ItemGroup>
<PropertyGroup>
<ServerGarbageCollection>false</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
<TieredCompilation>true</TieredCompilation>
<LangVersion>7.1</LangVersion>
<LangVersion>Latest</LangVersion>
<StartupObject></StartupObject>
</PropertyGroup>

View File

@ -13,6 +13,7 @@ using SharedLibraryCore.Dtos;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using SharedLibraryCore.Services;
using System;
using System.Collections;
@ -54,7 +55,7 @@ namespace IW4MAdmin.Application
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
readonly IPageList PageList;
private readonly Dictionary<long, ILogger> _loggers = new Dictionary<long, ILogger>();
private readonly MetaService _metaService;
private readonly IMetaService _metaService;
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
private readonly CancellationTokenSource _tokenSource;
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
@ -65,12 +66,14 @@ namespace IW4MAdmin.Application
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
private readonly IEventHandler _eventHandler;
private readonly IScriptCommandFactory _scriptCommandFactory;
private readonly IMetaRegistration _metaRegistration;
public ApplicationManager(ILogger logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory)
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService,
IMetaRegistration metaRegistration)
{
MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag<Server>();
@ -85,7 +88,7 @@ namespace IW4MAdmin.Application
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(parserRegexFactory) };
TokenAuthenticator = new TokenAuthentication();
_logger = logger;
_metaService = new MetaService();
_metaService = metaService;
_tokenSource = new CancellationTokenSource();
_loggers.Add(0, logger);
_commands = commands.ToList();
@ -96,6 +99,7 @@ namespace IW4MAdmin.Application
_customParserEvents = customParserEvents;
_eventHandler = eventHandler;
_scriptCommandFactory = scriptCommandFactory;
_metaRegistration = metaRegistration;
Plugins = plugins;
}
@ -453,133 +457,7 @@ namespace IW4MAdmin.Application
await _commandConfiguration.Save();
#endregion
#region META
async Task<List<ProfileMeta>> getProfileMeta(int clientId, int offset, int count, DateTime? startAt)
{
var metaList = new List<ProfileMeta>();
// we don't want to return anything because it means we're trying to retrieve paged meta data
if (count > 1)
{
return metaList;
}
var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = clientId });
if (lastMapMeta != null)
{
metaList.Add(new ProfileMeta()
{
Id = lastMapMeta.MetaId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"],
Value = lastMapMeta.Value,
Show = true,
Type = ProfileMeta.MetaType.Information,
});
}
var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = clientId });
if (lastServerMeta != null)
{
metaList.Add(new ProfileMeta()
{
Id = lastServerMeta.MetaId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"],
Value = lastServerMeta.Value,
Show = true,
Type = ProfileMeta.MetaType.Information
});
}
var client = await GetClientService().Get(clientId);
if (client == null)
{
_logger.WriteWarning($"No client found with id {clientId} when generating profile meta");
return metaList;
}
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]} {Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PLAYER"]}",
Value = Math.Round(client.TotalConnectionTime / 3600.0, 1).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Show = true,
Column = 1,
Order = 0,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_FSEEN"],
Value = Utilities.GetTimePassed(client.FirstConnection, false),
Show = true,
Column = 1,
Order = 1,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_LSEEN"],
Value = Utilities.GetTimePassed(client.LastConnection, false),
Show = true,
Column = 1,
Order = 2,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Id = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Show = true,
Column = 1,
Order = 3,
Type = ProfileMeta.MetaType.Information
});
metaList.Add(new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
Sensitive = true,
Column = 1,
Order = 4,
Type = ProfileMeta.MetaType.Information
});
return metaList;
}
async Task<List<ProfileMeta>> getPenaltyMeta(int clientId, int offset, int count, DateTime? startAt)
{
if (count <= 1)
{
return new List<ProfileMeta>();
}
var penalties = await GetPenaltyService().GetClientPenaltyForMetaAsync(clientId, count, offset, startAt);
return penalties.Select(_penalty => new ProfileMeta()
{
Id = _penalty.Id,
Type = _penalty.PunisherId == clientId ? ProfileMeta.MetaType.Penalized : ProfileMeta.MetaType.ReceivedPenalty,
Value = _penalty,
When = _penalty.TimePunished,
Sensitive = _penalty.Sensitive
})
.ToList();
}
MetaService.AddRuntimeMeta(getProfileMeta);
MetaService.AddRuntimeMeta(getPenaltyMeta);
#endregion
_metaRegistration.Register();
#region CUSTOM_EVENTS
foreach (var customEvent in _customParserEvents.SelectMany(_events => _events.Events))

View File

@ -13,17 +13,19 @@ namespace IW4MAdmin.Application.Factories
private readonly ITranslationLookup _translationLookup;
private readonly IRConConnectionFactory _rconConnectionFactory;
private readonly IGameLogReaderFactory _gameLogReaderFactory;
private readonly IMetaService _metaService;
/// <summary>
/// base constructor
/// </summary>
/// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory)
public GameServerInstanceFactory(ITranslationLookup translationLookup, IRConConnectionFactory rconConnectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService)
{
_translationLookup = translationLookup;
_rconConnectionFactory = rconConnectionFactory;
_gameLogReaderFactory = gameLogReaderFactory;
_metaService = metaService;
}
/// <summary>
@ -34,7 +36,7 @@ namespace IW4MAdmin.Application.Factories
/// <returns></returns>
public Server CreateServer(ServerConfiguration config, IManager manager)
{
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory);
return new IW4MServer(manager, config, _translationLookup, _rconConnectionFactory, _gameLogReaderFactory, _metaService);
}
}
}

View File

@ -7,7 +7,6 @@ using SharedLibraryCore.Dtos;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.IO;
@ -26,15 +25,17 @@ namespace IW4MAdmin
private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex;
public GameLogEventDetection LogEvent;
private readonly ITranslationLookup _translationLookup;
private readonly IMetaService _metaService;
private const int REPORT_FLAG_COUNT = 4;
private int lastGameTime = 0;
public int Id { get; private set; }
public IW4MServer(IManager mgr, ServerConfiguration cfg, ITranslationLookup lookup,
IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
IRConConnectionFactory connectionFactory, IGameLogReaderFactory gameLogReaderFactory, IMetaService metaService) : base(cfg, mgr, connectionFactory, gameLogReaderFactory)
{
_translationLookup = lookup;
_metaService = metaService;
}
override public async Task<EFClient> OnClientConnected(EFClient clientFromLog)
@ -475,8 +476,8 @@ namespace IW4MAdmin
Time = DateTime.UtcNow
});
await new MetaService().AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
await new MetaService().AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
await _metaService.AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
await _metaService.AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
}
else if (E.Type == GameEvent.EventType.PreDisconnect)
@ -610,13 +611,10 @@ namespace IW4MAdmin
if (E.Type == GameEvent.EventType.Broadcast)
{
#if DEBUG == false
// this is a little ugly but I don't want to change the abstract class
if (E.Data != null)
if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode
{
await E.Owner.ExecuteCommandAsync(E.Data);
}
#endif
}
lock (ChatHistory)
@ -1083,9 +1081,11 @@ namespace IW4MAdmin
Logger.WriteInfo($"Log file is {LogPath}");
_ = Task.Run(() => LogEvent.PollForChanges());
#if !DEBUG
Broadcast(loc["BROADCAST_ONLINE"]);
#endif
if (!Utilities.IsDevelopment)
{
Broadcast(loc["BROADCAST_ONLINE"]);
}
}
public Uri[] GenerateUriForLog(string logPath, string gameLogServerUrl)
@ -1233,6 +1233,7 @@ namespace IW4MAdmin
if (targetClient.IsIngame)
{
string formattedKick = string.Format(RconParser.Configuration.CommandPrefixes.Kick, targetClient.ClientNumber, $"^7{loc["SERVER_TB_TEXT"]}- ^5{Reason}");
Logger.WriteDebug($"Executing tempban kick command for {targetClient}");
await targetClient.CurrentServer.ExecuteCommandAsync(formattedKick);
}
}

View File

@ -2,16 +2,22 @@
using IW4MAdmin.Application.EventParsers;
using IW4MAdmin.Application.Factories;
using IW4MAdmin.Application.Helpers;
using IW4MAdmin.Application.Meta;
using IW4MAdmin.Application.Migration;
using IW4MAdmin.Application.Misc;
using Microsoft.Extensions.DependencyInjection;
using RestEase;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos.Meta.Requests;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using SharedLibraryCore.Repositories;
using SharedLibraryCore.Services;
using System;
using System.Linq;
using System.Text;
@ -128,7 +134,11 @@ namespace IW4MAdmin.Application
await ApplicationTask;
}
catch { }
catch (Exception e)
{
string failMessage = translationLookup == null ? "Failed to initalize IW4MAdmin" : translationLookup["MANAGER_INIT_FAIL"];
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
}
if (ServerManager.IsRestartRequested)
{
@ -235,6 +245,12 @@ namespace IW4MAdmin.Application
.AddSingleton<IGameLogReaderFactory, GameLogReaderFactory>()
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddSingleton<IEntityService<EFClient>, ClientService>()
.AddSingleton<IMetaService, MetaService>()
.AddSingleton<IMetaRegistration, MetaRegistration>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>, ReceivedPenaltyResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>, AdministeredPenaltyResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>, UpdatedAliasResourceQueryHelper>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton(_serviceProvider =>
{

View File

@ -0,0 +1,64 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
namespace IW4MAdmin.Application.Meta
{
/// <summary>
/// implementation of IResourceQueryHelper
/// query helper that retrieves administered penalties for provided client id
/// </summary>
public class AdministeredPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>
{
private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory;
public AdministeredPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
_logger = logger;
}
public async Task<ResourceQueryHelperResult<AdministeredPenaltyResponse>> QueryResource(ClientPaginationRequest query)
{
using var ctx = _contextFactory.CreateContext(enableTracking: false);
var iqPenalties = ctx.Penalties.AsNoTracking()
.Where(_penalty => query.ClientId == _penalty.PunisherId)
.Where(_penalty => _penalty.When < query.Before)
.OrderByDescending(_penalty => _penalty.When);
var penalties = await iqPenalties
.Take(query.Count)
.Select(_penalty => new AdministeredPenaltyResponse()
{
PenaltyId = _penalty.PenaltyId,
Offense = _penalty.Offense,
AutomatedOffense = _penalty.AutomatedOffense,
ClientId = _penalty.OffenderId,
OffenderName = _penalty.Offender.CurrentAlias.Name,
OffenderClientId = _penalty.Offender.ClientId,
PunisherClientId = _penalty.PunisherId,
PunisherName = _penalty.Punisher.CurrentAlias.Name,
PenaltyType = _penalty.Type,
When = _penalty.When,
ExpirationDate = _penalty.Expires,
IsLinked = _penalty.OffenderId != query.ClientId,
IsSensitive = _penalty.Type == EFPenalty.PenaltyType.Flag
})
.ToListAsync();
return new ResourceQueryHelperResult<AdministeredPenaltyResponse>
{
// todo: might need to do count at some point
RetrievedResultCount = penalties.Count,
Results = penalties
};
}
}
}

View File

@ -0,0 +1,165 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.Meta
{
public class MetaRegistration : IMetaRegistration
{
private readonly ILogger _logger;
private ITranslationLookup _transLookup;
private readonly IMetaService _metaService;
private readonly IEntityService<EFClient> _clientEntityService;
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
public MetaRegistration(ILogger logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper)
{
_logger = logger;
_transLookup = transLookup;
_metaService = metaService;
_clientEntityService = clientEntityService;
_receivedPenaltyHelper = receivedPenaltyHelper;
_administeredPenaltyHelper = administeredPenaltyHelper;
_updatedAliasHelper = updatedAliasHelper;
}
public void Register()
{
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetProfileMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta);
}
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request)
{
var metaList = new List<InformationResponse>();
var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = request.ClientId });
if (lastMapMeta != null)
{
metaList.Add(new InformationResponse()
{
ClientId = request.ClientId,
MetaId = lastMapMeta.MetaId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_MAP"],
Value = lastMapMeta.Value,
ShouldDisplay = true,
Type = MetaType.Information,
Column = 1,
Order = 6
});
}
var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = request.ClientId });
if (lastServerMeta != null)
{
metaList.Add(new InformationResponse()
{
ClientId = request.ClientId,
MetaId = lastServerMeta.MetaId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_LAST_SERVER"],
Value = lastServerMeta.Value,
ShouldDisplay = true,
Type = MetaType.Information,
Column = 0,
Order = 6
});
}
var client = await _clientEntityService.Get(request.ClientId);
if (client == null)
{
_logger.WriteWarning($"No client found with id {request.ClientId} when generating profile meta");
return metaList;
}
metaList.Add(new InformationResponse()
{
ClientId = client.ClientId,
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
ShouldDisplay = true,
Column = 1,
Order = 0,
Type = MetaType.Information
});
metaList.Add(new InformationResponse()
{
ClientId = client.ClientId,
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
ShouldDisplay = true,
Column = 1,
Order = 1,
Type = MetaType.Information
});
metaList.Add(new InformationResponse()
{
ClientId = client.ClientId,
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
ShouldDisplay = true,
Column = 1,
Order = 2,
Type = MetaType.Information
});
metaList.Add(new InformationResponse()
{
ClientId = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
ShouldDisplay = true,
Column = 1,
Order = 3,
Type = MetaType.Information
});
metaList.Add(new InformationResponse()
{
ClientId = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
IsSensitive = true,
Column = 1,
Order = 4,
Type = MetaType.Information
});
return metaList;
}
private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(ClientPaginationRequest request)
{
var penalties = await _receivedPenaltyHelper.QueryResource(request);
return penalties.Results;
}
private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(ClientPaginationRequest request)
{
var penalties = await _administeredPenaltyHelper.QueryResource(request);
return penalties.Results;
}
private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request)
{
var aliases = await _updatedAliasHelper.QueryResource(request);
return aliases.Results;
}
}
}

View File

@ -0,0 +1,72 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
namespace IW4MAdmin.Application.Meta
{
/// <summary>
/// implementation of IResourceQueryHelper
/// used to pull in penalties applied to a given client id
/// </summary>
public class ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>
{
private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory;
public ReceivedPenaltyResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
_logger = logger;
}
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
{
var linkedPenaltyType = Utilities.LinkedPenaltyTypes();
using var ctx = _contextFactory.CreateContext(enableTracking: false);
var linkId = await ctx.Clients.AsNoTracking()
.Where(_client => _client.ClientId == query.ClientId)
.Select(_client => _client.AliasLinkId)
.FirstOrDefaultAsync();
var iqPenalties = ctx.Penalties.AsNoTracking()
.Where(_penalty => _penalty.OffenderId == query.ClientId || (linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId))
.Where(_penalty => _penalty.When < query.Before)
.OrderByDescending(_penalty => _penalty.When);
var penalties = await iqPenalties
.Take(query.Count)
.Select(_penalty => new ReceivedPenaltyResponse()
{
PenaltyId = _penalty.PenaltyId,
ClientId = query.ClientId,
Offense = _penalty.Offense,
AutomatedOffense = _penalty.AutomatedOffense,
OffenderClientId = _penalty.OffenderId,
OffenderName = _penalty.Offender.CurrentAlias.Name,
PunisherClientId = _penalty.PunisherId,
PunisherName = _penalty.Punisher.CurrentAlias.Name,
PenaltyType = _penalty.Type,
When = _penalty.When,
ExpirationDate = _penalty.Expires,
IsLinked = _penalty.OffenderId != query.ClientId,
IsSensitive = _penalty.Type == EFPenalty.PenaltyType.Flag
})
.ToListAsync();
return new ResourceQueryHelperResult<ReceivedPenaltyResponse>
{
// todo: maybe actually count
RetrievedResultCount = penalties.Count,
Results = penalties
};
}
}
}

View File

@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using System.Linq;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.Meta
{
/// <summary>
/// implementation if IResrouceQueryHerlp
/// used to pull alias changes for given client id
/// </summary>
public class UpdatedAliasResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse>
{
private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory;
public UpdatedAliasResourceQueryHelper(ILogger logger, IDatabaseContextFactory contextFactory)
{
_logger = logger;
_contextFactory = contextFactory;
}
public async Task<ResourceQueryHelperResult<UpdatedAliasResponse>> QueryResource(ClientPaginationRequest query)
{
using var ctx = _contextFactory.CreateContext(enableTracking: false);
int linkId = ctx.Clients.First(_client => _client.ClientId == query.ClientId).AliasLinkId;
var iqAliasUpdates = ctx.Aliases
.Where(_alias => _alias.LinkId == linkId)
.Where(_alias => _alias.DateAdded < query.Before)
.Where(_alias => _alias.IPAddress != null)
.OrderByDescending(_alias => _alias.DateAdded)
.Select(_alias => new UpdatedAliasResponse
{
MetaId = _alias.AliasId,
Name = _alias.Name,
IPAddress = _alias.IPAddress.ConvertIPtoString(),
When = _alias.DateAdded,
Type = MetaType.AliasUpdate,
IsSensitive = true
});
var result = (await iqAliasUpdates
.Take(query.Count)
.ToListAsync())
.Distinct();
return new ResourceQueryHelperResult<UpdatedAliasResponse>
{
Results = result, // we can potentially have duplicates
RetrievedResultCount = result.Count()
};
}
}
}

View File

@ -0,0 +1,187 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace IW4MAdmin.Application.Misc
{
/// <summary>
/// implementation of IMetaService
/// used to add and retrieve runtime and persistent meta
/// </summary>
public class MetaService : IMetaService
{
private readonly IDictionary<MetaType, List<dynamic>> _metaActions;
private readonly IDatabaseContextFactory _contextFactory;
private readonly ILogger _logger;
public MetaService(ILogger logger, IDatabaseContextFactory contextFactory)
{
_logger = logger;
_metaActions = new Dictionary<MetaType, List<dynamic>>();
_contextFactory = contextFactory;
}
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client)
{
// this seems to happen if the client disconnects before they've had time to authenticate and be added
if (client.ClientId < 1)
{
return;
}
using var ctx = _contextFactory.CreateContext();
var existingMeta = await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.FirstOrDefaultAsync();
if (existingMeta != null)
{
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
}
else
{
ctx.EFMeta.Add(new EFMeta()
{
ClientId = client.ClientId,
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue
});
}
await ctx.SaveChangesAsync();
}
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
{
using var ctx = _contextFactory.CreateContext(enableTracking: false);
return await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.Select(_meta => new EFMeta()
{
MetaId = _meta.MetaId,
Key = _meta.Key,
ClientId = _meta.ClientId,
Value = _meta.Value
})
.FirstOrDefaultAsync();
}
public void AddRuntimeMeta<T, V>(MetaType metaKey, Func<T, Task<IEnumerable<V>>> metaAction) where T : PaginationRequest where V : IClientMeta
{
if (!_metaActions.ContainsKey(metaKey))
{
_metaActions.Add(metaKey, new List<dynamic>() { metaAction });
}
else
{
_metaActions[metaKey].Add(metaAction);
}
}
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
{
var meta = new List<IClientMeta>();
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)
.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));
}
return ProcessInformationMeta(allMeta);
}
else
{
meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
}
return meta;
}
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
{
var table = new List<List<T>>();
var metaWithColumn = meta
.Where(_meta => _meta.Column != null);
var columnGrouping = metaWithColumn
.GroupBy(_meta => _meta.Column);
var metaToSort = meta.Except(metaWithColumn).ToList();
foreach (var metaItem in columnGrouping)
{
table.Add(new List<T>(metaItem));
}
while (metaToSort.Count > 0)
{
var sortingMeta = metaToSort.First();
int indexOfSmallestColumn()
{
int index = 0;
int smallestColumnSize = int.MaxValue;
for (int i = 0; i < table.Count; i++)
{
if (table[i].Count < smallestColumnSize)
{
smallestColumnSize = table[i].Count;
index = i;
}
}
return index;
}
int columnIndex = indexOfSmallestColumn();
sortingMeta.Column = columnIndex;
sortingMeta.Order = columnGrouping
.First(_group => _group.Key == columnIndex)
.Count();
table[columnIndex].Add(sortingMeta);
metaToSort.Remove(sortingMeta);
}
return meta;
}
}
}

View File

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

View File

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

View File

@ -16,7 +16,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.6" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -23,7 +23,7 @@
</Target>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.6" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

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

View File

@ -22,7 +22,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
List<string> mostPlayed = new List<string>()
{
$"^5--{Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
$"^5--{translationLookup["PLUGINS_STATS_COMMANDS_MOSTPLAYED_TEXT"]}--"
};
using (var db = new DatabaseContext(true))
@ -51,8 +51,7 @@ namespace IW4MAdmin.Plugins.Stats.Commands
var iqList = await iqStats.ToListAsync();
mostPlayed.AddRange(iqList.Select(stats =>
$"^3{stats.Name}^7 - ^5{stats.Kills} ^7{translationLookup["PLUGINS_STATS_TEXT_KILLS"]} | ^5{Utilities.GetTimePassed(DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime), false)} ^7{translationLookup["WEBFRONT_PROFILE_PLAYER"].ToLower()}"));
mostPlayed.AddRange(iqList.Select(stats => translationLookup["COMMANDS_MOST_PLAYED_FORMAT"].FormatExt(stats.Name, (DateTime.UtcNow - DateTime.UtcNow.AddSeconds(-stats.TotalConnectionTime)).HumanizeForCurrentCulture())));
}

View File

@ -156,7 +156,7 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
Deaths = s.Deaths,
Kills = s.Kills,
KDR = Math.Round(s.KDR, 2),
LastSeen = Utilities.GetTimePassed(clientRatingsDict[s.ClientId].LastConnection, false),
LastSeen = (DateTime.UtcNow - clientRatingsDict[s.ClientId].LastConnection).HumanizeForCurrentCulture(),
Name = clientRatingsDict[s.ClientId].Name,
Performance = Math.Round(clientRatingsDict[s.ClientId].Performance, 2),
RatingChange = ratingInfo.First(r => r.Key == s.ClientId).Ratings.First().Ranking - ratingInfo.First(r => r.Key == s.ClientId).Ratings.Last().Ranking,
@ -663,6 +663,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
$"{penalty.Type}-{(int)penalty.Location}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}" :
$"{penalty.Type}-{Math.Round(penalty.Value, 2)}@{penalty.HitCount}";
penaltyClient.AdministeredPenalties = new List<EFPenalty>()
{
new EFPenalty()
{
AutomatedOffense = flagReason
}
};
await attacker.Flag(flagReason, penaltyClient, new TimeSpan(168, 0, 0)).WaitAsync(Utilities.DefaultCommandTimeout, attacker.CurrentServer.Manager.CancellationToken);
break;
}

View File

@ -6,8 +6,10 @@ using SharedLibraryCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
@ -33,13 +35,15 @@ namespace IW4MAdmin.Plugins.Stats
#endif
private readonly IDatabaseContextFactory _databaseContextFactory;
private readonly ITranslationLookup _translationLookup;
private readonly IMetaService _metaService;
public Plugin(IConfigurationHandlerFactory configurationHandlerFactory, IDatabaseContextFactory databaseContextFactory,
ITranslationLookup translationLookup)
ITranslationLookup translationLookup, IMetaService metaService)
{
Config = configurationHandlerFactory.GetConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
_databaseContextFactory = databaseContextFactory;
_translationLookup = translationLookup;
_metaService = metaService;
}
public async Task OnEventAsync(GameEvent E, Server S)
@ -188,17 +192,14 @@ namespace IW4MAdmin.Plugins.Stats
"/Stats/TopPlayersAsync");
// meta data info
async Task<List<ProfileMeta>> getStats(int clientId, int offset, int count, DateTime? startAt)
async Task<IEnumerable<InformationResponse>> getStats(ClientPaginationRequest request)
{
if (count > 1)
{
return new List<ProfileMeta>();
}
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
int messageCount = 0;
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
{
clientStats = await ctx.Set<EFClientStatistics>().Where(c => c.ClientId == clientId).ToListAsync();
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);
@ -209,73 +210,76 @@ namespace IW4MAdmin.Plugins.Stats
double performance = Math.Round(validPerformanceValues.Sum(c => c.Performance * c.TimePlayed / performancePlayTime), 2);
double spm = Math.Round(clientStats.Sum(c => c.SPM) / clientStats.Where(c => c.SPM > 0).Count(), 1);
return new List<ProfileMeta>()
return new List<InformationResponse>()
{
new ProfileMeta()
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_RANKING"],
Value = "#" + (await Manager.GetClientOverallRanking(clientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Value = "#" + (await Manager.GetClientOverallRanking(request.ClientId)).ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 0,
Type = ProfileMeta.MetaType.Information
Type = MetaType.Information
},
new ProfileMeta()
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KILLS"],
Value = kills.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 1,
Type = ProfileMeta.MetaType.Information
Type = MetaType.Information
},
new ProfileMeta()
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_DEATHS"],
Value = deaths.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 2,
Type = ProfileMeta.MetaType.Information
Type = MetaType.Information
},
new ProfileMeta()
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_TEXT_KDR"],
Value = kdr.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 3,
Type = ProfileMeta.MetaType.Information
Type = MetaType.Information
},
new ProfileMeta()
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_COMMANDS_PERFORMANCE"],
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_PERFORMANCE"],
Value = performance.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 4,
Type = ProfileMeta.MetaType.Information
Type = MetaType.Information
},
new ProfileMeta()
new InformationResponse()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["PLUGINS_STATS_META_SPM"],
Value = spm.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 0,
Order = 5,
Type = ProfileMeta.MetaType.Information
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
}
};
}
async Task<List<ProfileMeta>> getAnticheatInfo(int clientId, int offset, int count, DateTime? startAt)
async Task<IEnumerable<InformationResponse>> getAnticheatInfo(ClientPaginationRequest request)
{
if (count > 1)
{
return new List<ProfileMeta>();
}
IList<EFClientStatistics> clientStats;
using (var ctx = new DatabaseContext(disableTracking: true))
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
{
clientStats = await ctx.Set<EFClientStatistics>()
.Include(c => c.HitLocations)
.Where(c => c.ClientId == clientId)
.Where(c => c.ClientId == request.ClientId)
.ToListAsync();
}
@ -310,147 +314,120 @@ namespace IW4MAdmin.Plugins.Stats
averageSnapValue = clientStats.Any(_stats => _stats.AverageSnapValue > 0) ? clientStats.Where(_stats => _stats.AverageSnapValue > 0).Average(_stat => _stat.AverageSnapValue) : 0;
}
return new List<ProfileMeta>()
return new List<InformationResponse>()
{
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 1",
Value = chestRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 0,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM1"],
IsSensitive = true
},
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 2",
Value = abdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 1,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM2"],
IsSensitive = true
},
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 3",
Value = chestAbdomenRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 2,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM3"],
IsSensitive = true
},
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 4",
Value = headRatio.ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)) + '%',
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 3,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM4"],
IsSensitive = true
},
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 5",
// todo: make sure this is wrapped somewhere else
Value = $"{Math.Round(((float)hitOffsetAverage), 4).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName))}°",
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 4,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM5"],
IsSensitive = true
},
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 6",
Value = Math.Round(maxStrain, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 5,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM6"],
IsSensitive = true
},
new ProfileMeta()
new InformationResponse()
{
Key = $"{Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_AC_METRIC"]} 7",
Value = Math.Round(averageSnapValue, 3).ToString(new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Type = ProfileMeta.MetaType.Information,
Type = MetaType.Information,
Column = 2,
Order = 6,
Extra = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
Sensitive = true
ToolTipText = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_TITLE_ACM7"],
IsSensitive = true
}
};
}
async Task<List<ProfileMeta>> getMessages(int clientId, int offset, int count, DateTime? startAt)
async Task<IEnumerable<MessageResponse>> getMessages(ClientPaginationRequest request)
{
if (count <= 1)
{
using (var ctx = new DatabaseContext(true))
{
return new List<ProfileMeta>
{
new ProfileMeta()
{
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGES"],
Value = (await ctx.Set<EFClientMessage>()
.CountAsync(_message => _message.ClientId == clientId))
.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
Column = 1,
Order= 4,
Type = ProfileMeta.MetaType.Information
}
};
}
}
List<ProfileMeta> messageMeta;
using (var ctx = new DatabaseContext(disableTracking: true))
List<MessageResponse> messageMeta;
using (var ctx = _databaseContextFactory.CreateContext(enableTracking: false))
{
var messages = ctx.Set<EFClientMessage>()
.Where(m => m.ClientId == clientId)
.Where(_message => _message.TimeSent < startAt)
.Where(m => m.ClientId == request.ClientId)
.Where(_message => _message.TimeSent < request.Before)
.OrderByDescending(_message => _message.TimeSent)
.Skip(offset)
.Take(count);
.Take(request.Count);
messageMeta = await messages.Select(m => new ProfileMeta()
messageMeta = await messages.Select(m => new MessageResponse()
{
Key = null,
Value = new { m.Message, m.Server.GameName },
// todo: game name
Message = m.Message,
When = m.TimeSent,
Extra = m.ServerId.ToString(),
Type = ProfileMeta.MetaType.ChatMessage
ServerId = m.ServerId,
Type = MetaType.ChatMessage
}).ToListAsync();
foreach (var message in messageMeta)
foreach (var meta in messageMeta)
{
if ((message.Value.Message as string).IsQuickMessage())
if ((meta.Message).IsQuickMessage())
{
try
{
var quickMessages = ServerManager.GetApplicationSettings().Configuration()
.QuickMessages
.First(_qm => _qm.Game == message.Value.GameName);
message.Value = quickMessages.Messages[(message.Value.Message as string).Substring(1)];
message.Type = ProfileMeta.MetaType.QuickMessage;
.First(/*_qm => _qm.Game == meta.GameName*/);
meta.Message = quickMessages.Messages[(meta.Message as string).Substring(1)];
meta.Type = MetaType.QuickMessage;
}
catch
{
message.Value = message.Value.Message;
}
}
else
{
message.Value = message.Value.Message;
}
}
}
return messageMeta;
@ -458,11 +435,11 @@ namespace IW4MAdmin.Plugins.Stats
if (Config.Configuration().EnableAntiCheat)
{
MetaService.AddRuntimeMeta(getAnticheatInfo);
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getAnticheatInfo);
}
MetaService.AddRuntimeMeta(getStats);
MetaService.AddRuntimeMeta(getMessages);
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, getStats);
_metaService.AddRuntimeMeta<ClientPaginationRequest, MessageResponse>(MetaType.ChatMessage, getMessages);
async Task<string> totalKills(Server server)
{

View File

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

View File

@ -36,6 +36,7 @@ namespace IW4MAdmin.Plugins.Web.StatsWeb.Controllers
ViewBag.Title = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_TITLE"];
ViewBag.Description = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_STATS_INDEX_DESC"];
ViewBag.Servers = _manager.GetServers().Select(_server => new ServerInfo() { Name = _server.Hostname, ID = _server.EndPoint });
ViewBag.Localization = _translationLookup;
return View("Index");
}

View File

@ -1,9 +1,10 @@
using SharedLibraryCore.Dtos;
using SharedLibraryCore.QueryHelper;
using System;
namespace StatsWeb.Dtos
{
public class ChatSearchQuery : PaginationInfo
public class ChatSearchQuery : ClientPaginationRequest
{
/// <summary>
/// specifies the partial content of the message to search for
@ -18,7 +19,7 @@ namespace StatsWeb.Dtos
/// <summary>
/// identifier for the client
/// </summary>
public int? ClientId { get; set; }
public new int? ClientId { get; set; }
/// <summary>
/// only look for messages sent after this date

View File

@ -14,7 +14,7 @@
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.3" PrivateAssets="All" />
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2.4.6" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -1,6 +1,6 @@
<ul class="nav nav-tabs border-top border-bottom nav-fill row" role="tablist" id="stats_top_players">
<li class="nav-item">
<a class="nav-link active top-players-link" href="#server_0" role="tab" data-toggle="tab" aria-selected="true" data-serverid="0">All Servers</a>
<a class="nav-link active top-players-link" href="#server_0" role="tab" data-toggle="tab" aria-selected="true" data-serverid="0">@ViewBag.Localization["WEBFRONT_STATS_INDEX_ALL_SERVERS"]</a>
</li>
@foreach (var server in ViewBag.Servers)

View File

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

View File

@ -53,7 +53,6 @@ namespace SharedLibraryCore.Commands
public override Task ExecuteAsync(GameEvent E)
{
MetaService.Clear();
E.Owner.Manager.Restart();
E.Origin.Tell(_translationLookup["COMMANDS_RESTART_SUCCESS"]);
return Task.CompletedTask;
@ -292,7 +291,7 @@ namespace SharedLibraryCore.Commands
switch ((await E.Target.TempBan(tempbanReason, length, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
{
case GameEvent.EventFailReason.None:
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.TimeSpanText()));
E.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(E.Target, length.HumanizeForCurrentCulture()));
break;
case GameEvent.EventFailReason.Exception:
E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
@ -890,14 +889,9 @@ namespace SharedLibraryCore.Commands
return;
}
foreach (var P in db_players)
foreach (var client in db_players)
{
// they're not going by another alias
// /*P.AliasLink.Children.FirstOrDefault(a => a.Name.ToLower().Contains(E.Data.ToLower()))?.Name*/
string msg = P.Name.ToLower().Contains(E.Data.ToLower()) ?
$"[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor((Permission)P.LevelInt, P.Level)}^7] - {P.IPAddress} | last seen {Utilities.GetTimePassed(P.LastConnection)}" :
$"()->[^3{P.Name}^7] [^3@{P.ClientId}^7] - [{ Utilities.ConvertLevelToColor((Permission)P.LevelInt, P.Level)}^7] - {P.IPAddress} | last seen {Utilities.GetTimePassed(P.LastConnection)}";
E.Origin.Tell(msg);
E.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT"].FormatExt(client.Name, client.ClientId, Utilities.ConvertLevelToColor((Permission)client.LevelInt, client.Level), client.IPAddress, client.LastConnectionText));
}
}
}
@ -1259,7 +1253,7 @@ namespace SharedLibraryCore.Commands
else
{
string remainingTime = (penalty.Expires.Value - DateTime.UtcNow).TimeSpanText();
string remainingTime = (penalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture();
E.Origin.Tell(_translationLookup["COMMANDS_BANINFO_TB_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense, remainingTime));
}
}
@ -1543,7 +1537,9 @@ namespace SharedLibraryCore.Commands
/// </summary>
public class SetGravatarCommand : Command
{
public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
private readonly IMetaService _metaService;
public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, IMetaService metaService) : base(config, translationLookup)
{
Name = "setgravatar";
Description = _translationLookup["COMMANDS_GRAVATAR_DESC"];
@ -1558,17 +1554,17 @@ namespace SharedLibraryCore.Commands
Required = true
}
};
_metaService = metaService;
}
public override async Task ExecuteAsync(GameEvent E)
{
var metaSvc = new MetaService();
using (var md5 = MD5.Create())
{
string gravatarEmail = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
.Select(h => h.ToString("x2")));
await metaSvc.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin);
await _metaService.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin);
}
E.Origin.Tell(_translationLookup["COMMANDS_GRAVATAR_SUCCESS_NEW"]);

View File

@ -1,6 +1,6 @@
namespace SharedLibraryCore.Dtos
{
public class FindClientRequest : PaginationInfo
public class FindClientRequest : PaginationRequest
{
/// <summary>
/// name of client

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Requests
{
public class BaseClientMetaRequest : PaginationRequest
{
public int ClientId { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using SharedLibraryCore.QueryHelper;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Requests
{
public class ReceivedPenaltyRequest : BaseClientMetaRequest
{
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class AdministeredPenaltyResponse : ReceivedPenaltyResponse
{
}
}

View File

@ -0,0 +1,19 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class BaseMetaResponse : IClientMeta, IClientMetaResponse
{
public int MetaId { get; set; }
public int ClientId { get; set; }
public MetaType Type { get; set; }
public DateTime When { get; set; }
public bool IsSensitive { get; set; }
public bool ShouldDisplay { get; set; }
public int? Column { get; set; }
public int? Order { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class InformationResponse : BaseMetaResponse
{
public string Key { get; set; }
public string Value { get; set; }
public string ToolTipText { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class MessageResponse : BaseMetaResponse
{
public long ServerId { get; set; }
public string Message { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
using static SharedLibraryCore.Database.Models.EFPenalty;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class ReceivedPenaltyResponse : BaseMetaResponse
{
public int PenaltyId { get; set; }
public int OffenderClientId { get; set; }
public string OffenderName { get; set; }
public string PunisherName { get; set; }
public int PunisherClientId { get; set; }
public PenaltyType PenaltyType { get; set; }
public string Offense { get; set; }
public string AutomatedOffense { get; set; }
public DateTime? ExpirationDate { get; set; }
public string ExpiresInText => ExpirationDate.HasValue && ExpirationDate.Value > DateTime.UtcNow ? (ExpirationDate - DateTime.UtcNow).Value.HumanizeForCurrentCulture() : "";
public string LengthText => ExpirationDate.HasValue ? (ExpirationDate.Value.AddMinutes(1) - When).HumanizeForCurrentCulture() : "";
public bool IsLinked { get; set; }
public int LinkedClientId { get; set; }
}
}

View File

@ -0,0 +1,22 @@
using System;
namespace SharedLibraryCore.Dtos.Meta.Responses
{
public class UpdatedAliasResponse : BaseMetaResponse
{
public string Name { get; set; }
public string IPAddress { get; set; } = "--";
public override bool Equals(object obj)
{
if (obj is UpdatedAliasResponse resp)
{
return resp.Name.StripColors() == Name.StripColors() && resp.IPAddress == IPAddress;
}
return false;
}
public override int GetHashCode() => HashCode.Combine(Name.StripColors(), IPAddress);
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Dtos.Meta
{
public class WebfrontTranslationHelper
{
public bool IsInterpolation { get; set; }
public string MatchValue { get; set; }
public string TranslationValue { get; set; }
}
}

View File

@ -1,9 +1,11 @@
namespace SharedLibraryCore.Dtos
using System;
namespace SharedLibraryCore.Dtos
{
/// <summary>
/// pagination information holder class
/// </summary>
public class PaginationInfo
public class PaginationRequest
{
/// <summary>
/// how many items to skip
@ -24,6 +26,8 @@
/// direction of ordering
/// </summary>
public SortDirection Direction { get; set; } = SortDirection.Descending;
public DateTime? Before { get; set; }
}
public enum SortDirection

View File

@ -21,8 +21,8 @@ namespace SharedLibraryCore.Dtos
public PenaltyType PenaltyType { get; set; }
public string PenaltyTypeText => PenaltyType.ToString();
public DateTime TimePunished { get; set; }
public string TimePunishedString => Utilities.GetTimePassed(TimePunished, true);
public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(TimePunished, true) : Utilities.TimeSpanText((Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}";
public string TimePunishedString => TimePunished.HumanizeForCurrentCulture();
public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? TimePunishedString : ((Expires ?? DateTime.MaxValue) - DateTime.UtcNow).HumanizeForCurrentCulture())}";
public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow;
public DateTime? Expires { get; set; }
public override bool Sensitive => PenaltyType == PenaltyType.Flag || PenaltyType == PenaltyType.Unflag;

View File

@ -1,8 +1,8 @@
using System;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
@ -19,11 +19,13 @@ namespace SharedLibraryCore.Dtos
public bool HasActivePenalty { get; set; }
public string ActivePenaltyType { get; set; }
public bool Authenticated { get; set; }
public List<ProfileMeta> Meta { get; set; }
public List<InformationResponse> Meta { get; set; }
public EFPenalty ActivePenalty { get; set; }
public bool Online { get; set; }
public string TimeOnline { get; set; }
public DateTime LastConnection { get; set; }
public string LastConnectionText => Utilities.GetTimePassed(LastConnection, true);
public string LastConnectionText => (DateTime.UtcNow - LastConnection).HumanizeForCurrentCulture();
public IDictionary<int, long> LinkedAccounts { get; set; }
public MetaType? MetaFilterType { get; set; }
}
}

View File

@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharedLibraryCore.Dtos
{
public class ProfileMeta : SharedInfo
{
public enum MetaType
{
Other,
Information,
AliasUpdate,
ChatMessage,
Penalized,
ReceivedPenalty,
QuickMessage
}
public DateTime When { get; set; }
public string WhenString => Utilities.GetTimePassed(When, false);
public string Key { get; set; }
public dynamic Value { get; set; }
public string Extra { get; set; }
public virtual string Class => Value.GetType().ToString();
public MetaType Type { get; set; }
public int? Column { get; set; }
public int? Order { get; set; }
}
}

View File

@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using SharedLibraryCore.Dtos.Meta.Responses;
namespace SharedLibraryCore.Helpers
{

View File

@ -14,6 +14,6 @@ namespace SharedLibraryCore.Interfaces
/// </summary>
/// <param name="paginationInfo">pagination info</param>
/// <returns></returns>
Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo);
Task<IList<AuditInfo>> ListAuditInformation(PaginationRequest paginationInfo);
}
}

View File

@ -0,0 +1,31 @@
using System;
namespace SharedLibraryCore.Interfaces
{
/// <summary>
/// describes all the base attributes of a client meta object
/// </summary>
public interface IClientMeta
{
MetaType Type { get; }
DateTime When { get; }
bool IsSensitive { get; }
bool ShouldDisplay { get; }
// sorting purposes
public int? Column { get; set; }
public int? Order { get; set; }
}
public enum MetaType
{
Other,
Information,
AliasUpdate,
ChatMessage,
Penalized,
ReceivedPenalty,
QuickMessage
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
public interface IClientMetaResponse
{
int ClientId { get;}
int MetaId { get; }
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.Interfaces
{
public interface IMetaRegistration
{
void Register();
}
}

View File

@ -0,0 +1,51 @@
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.QueryHelper;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SharedLibraryCore.Interfaces
{
public interface IMetaService
{
/// <summary>
/// adds or updates meta key and value to the database
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="metaValue">value of the meta data</param>
/// <param name="client">client to save the meta for</param>
/// <returns></returns>
Task AddPersistentMeta(string metaKey, string metaValue, EFClient client);
/// <summary>
/// retrieves meta data for given client and key
/// </summary>
/// <param name="metaKey">key to retrieve value for</param>
/// <param name="client">client to retrieve meta for</param>
/// <returns></returns>
Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client);
/// <summary>
/// adds a meta task to the runtime meta list
/// </summary>
/// <param name="metaKey">type of meta</param>
/// <param name="metaAction">action to perform</param>
void AddRuntimeMeta<T,V>(MetaType metaKey, Func<T, Task<IEnumerable<V>>> metaAction) where V : IClientMeta where T: PaginationRequest;
/// <summary>
/// retrieves all the runtime meta information for given client idea
/// </summary>
/// <param name="request">request information</param>
/// <returns></returns>
Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request);
/// <summary>
/// retreives all the runtime of provided type
/// </summary>
/// <param name="request">>request information</param>
/// <param name="metaType">type of meta to retreive</param>
/// <returns></returns>
Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta;
}
}

View File

@ -1,12 +1,23 @@
using SharedLibraryCore.Interfaces;
using System.Collections.Generic;
using System.Globalization;
namespace SharedLibraryCore.Localization
{
public class Layout
{
public string LocalizationName { get; set; }
private string localizationName;
public string LocalizationName
{
get => localizationName;
set
{
localizationName = value;
Culture = new CultureInfo(value);
}
}
public TranslationLookup LocalizationIndex { get; set; }
public CultureInfo Culture { get; private set; }
public Layout(Dictionary<string, string> set)
{

View File

@ -618,7 +618,7 @@ namespace SharedLibraryCore.Database.Models
if (tempbanPenalty != null)
{
CurrentServer.Logger.WriteDebug($"Kicking {this} because their GUID is temporarily banned");
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempbanPenalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
return false;
}

View File

@ -0,0 +1,12 @@
using SharedLibraryCore.Dtos;
using System;
using System.Collections.Generic;
using System.Text;
namespace SharedLibraryCore.QueryHelper
{
public class ClientPaginationRequest : PaginationRequest
{
public int ClientId { get; set; }
}
}

View File

@ -20,7 +20,7 @@ namespace SharedLibraryCore.Repositories
}
/// <inheritdoc/>
public async Task<IList<AuditInfo>> ListAuditInformation(PaginationInfo paginationInfo)
public async Task<IList<AuditInfo>> ListAuditInformation(PaginationRequest paginationInfo)
{
using (var ctx = _contextFactory.CreateContext(enableTracking: false))
{

View File

@ -1,169 +0,0 @@
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Database;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SharedLibraryCore.Services
{
public class MetaService
{
private static List<Func<int, int, int, DateTime?, Task<List<ProfileMeta>>>> _metaActions = new List<Func<int, int, int, DateTime?, Task<List<ProfileMeta>>>>();
/// <summary>
/// adds or updates meta key and value to the database
/// </summary>
/// <param name="metaKey">key of meta data</param>
/// <param name="metaValue">value of the meta data</param>
/// <param name="client">client to save the meta for</param>
/// <returns></returns>
public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client)
{
// this seems to happen if the client disconnects before they've had time to authenticate and be added
if (client.ClientId < 1)
{
return;
}
using (var ctx = new DatabaseContext())
{
var existingMeta = await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.FirstOrDefaultAsync();
if (existingMeta != null)
{
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
}
else
{
ctx.EFMeta.Add(new EFMeta()
{
ClientId = client.ClientId,
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue
});
}
await ctx.SaveChangesAsync();
}
}
internal static void Clear()
{
_metaActions.Clear();
}
/// <summary>
/// retrieves meta data for given client and key
/// </summary>
/// <param name="metaKey">key to retrieve value for</param>
/// <param name="client">client to retrieve meta for</param>
/// <returns></returns>
public async Task<EFMeta> GetPersistentMeta(string metaKey, EFClient client)
{
using (var ctx = new DatabaseContext(disableTracking: true))
{
return await ctx.EFMeta
.Where(_meta => _meta.Key == metaKey)
.Where(_meta => _meta.ClientId == client.ClientId)
.Select(_meta => new EFMeta()
{
MetaId = _meta.MetaId,
Key = _meta.Key,
ClientId = _meta.ClientId,
Value = _meta.Value
})
.FirstOrDefaultAsync();
}
}
/// <summary>
/// aads a meta task to the runtime meta list
/// </summary>
/// <param name="metaAction"></param>
public static void AddRuntimeMeta(Func<int, int, int, DateTime?, Task<List<ProfileMeta>>> metaAction)
{
_metaActions.Add(metaAction);
}
/// <summary>
/// retrieves all the runtime meta information for given client idea
/// </summary>
/// <param name="clientId">id of the client</param>
/// <param name="count">number of meta items to retrieve</param>
/// <param name="offset">offset from the first item</param>
/// <returns></returns>
public static async Task<List<ProfileMeta>> GetRuntimeMeta(int clientId, int offset = 0, int count = int.MaxValue, DateTime? startAt = null)
{
var meta = new List<ProfileMeta>();
foreach (var action in _metaActions)
{
var metaItems = await action(clientId, offset, count, startAt);
meta.AddRange(metaItems);
}
if (count == 1)
{
var table = new List<List<ProfileMeta>>();
var metaWithColumn = meta
.Where(_meta => _meta.Column != null);
var columnGrouping = metaWithColumn
.GroupBy(_meta => _meta.Column);
var metaToSort = meta.Except(metaWithColumn).ToList();
foreach (var metaItem in columnGrouping)
{
table.Add(new List<ProfileMeta>(metaItem));
}
while (metaToSort.Count > 0)
{
var sortingMeta = metaToSort.First();
int indexOfSmallestColumn()
{
int index = 0;
int smallestColumnSize = int.MaxValue;
for (int i = 0; i < table.Count; i++)
{
if (table[i].Count < smallestColumnSize)
{
smallestColumnSize = table[i].Count;
index = i;
}
}
return index;
}
int columnIndex = indexOfSmallestColumn();
sortingMeta.Column = columnIndex;
sortingMeta.Order = columnGrouping
.First(_group => _group.Key == columnIndex)
.Count();
table[columnIndex].Add(sortingMeta);
metaToSort.Remove(sortingMeta);
}
return meta;
}
return meta.OrderByDescending(_meta => _meta.When)
.Take(count)
.ToList();
}
}
}

View File

@ -6,7 +6,7 @@
<ApplicationIcon />
<StartupObject />
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
<Version>2.4.3</Version>
<Version>2.4.6</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.4.3.0</AssemblyVersion>
<FileVersion>2.4.3.0</FileVersion>
<AssemblyVersion>2.4.6.0</AssemblyVersion>
<FileVersion>2.4.6.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
@ -30,30 +30,33 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentValidation" Version="8.6.2" />
<PackageReference Include="FluentValidation" Version="9.1.2" />
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
<PackageReference Include="Humanizer.Core.pt" Version="2.8.26" />
<PackageReference Include="Humanizer.Core.ru" Version="2.8.26" />
<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.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.7" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Npgsql" Version="4.1.3.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
<PackageReference Include="Npgsql" Version="4.1.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.2" />
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.7" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -1,5 +1,8 @@

using Humanizer;
using Humanizer.Localisation;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos.Meta;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System;
@ -381,57 +384,6 @@ namespace SharedLibraryCore
return !ip.HasValue ? "" : new IPAddress(BitConverter.GetBytes(ip.Value)).ToString();
}
public static string GetTimePassed(DateTime start)
{
return GetTimePassed(start, true);
}
public static string GetTimePassed(DateTime start, bool includeAgo)
{
TimeSpan Elapsed = DateTime.UtcNow - start;
string ago = includeAgo ? $" {CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_TEMPLATE_AGO"]}" : "";
if (Elapsed.TotalSeconds < 30)
{
return CurrentLocalization.LocalizationIndex["GLOBAL_TIME_JUSTNOW"] + ago;
}
if (Elapsed.TotalMinutes < 120)
{
if (Elapsed.TotalMinutes < 1.5)
{
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}";
}
return Math.Round(Elapsed.TotalMinutes, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MINUTES"]}{ago}";
}
if (Elapsed.TotalHours <= 24)
{
if (Elapsed.TotalHours < 1.5)
{
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}";
}
return Math.Round(Elapsed.TotalHours, 0) + $" { CurrentLocalization.LocalizationIndex["GLOBAL_TIME_HOURS"]}{ago}";
}
if (Elapsed.TotalDays <= 90)
{
if (Elapsed.TotalDays < 1.5)
{
return $"1 {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}";
}
return Math.Round(Elapsed.TotalDays, 0) + $" {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_DAYS"]}{ago}";
}
if (Elapsed.TotalDays <= 365)
{
return $"{Math.Round(Elapsed.TotalDays / 7)} {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_WEEKS"]}{ago}";
}
else
{
return $"{Math.Round(Elapsed.TotalDays / 30, 0)} {CurrentLocalization.LocalizationIndex["GLOBAL_TIME_MONTHS"]}{ago}";
}
}
public static Game GetGame(string gameName)
{
if (string.IsNullOrEmpty(gameName))
@ -519,42 +471,6 @@ namespace SharedLibraryCore
return new TimeSpan(1, 0, 0);
}
public static string TimeSpanText(this TimeSpan span)
{
var loc = CurrentLocalization.LocalizationIndex;
if (span.TotalMinutes < 60)
{
return $"{span.Minutes} {loc["GLOBAL_TIME_MINUTES"]}";
}
else if (span.Hours >= 1 && span.TotalHours < 24)
{
return $"{span.Hours} {loc["GLOBAL_TIME_HOURS"]}";
}
else if (span.TotalDays >= 1 && span.TotalDays < 7)
{
return $"{span.Days} {loc["GLOBAL_TIME_DAYS"]}";
}
else if (span.TotalDays >= 7 && span.TotalDays < 90)
{
return $"{Math.Round(span.Days / 7.0, 0)} {loc["GLOBAL_TIME_WEEKS"]}";
}
else if (span.TotalDays >= 90 && span.TotalDays < 365)
{
return $"{Math.Round(span.Days / 30.0, 0)} {loc["GLOBAL_TIME_MONTHS"]}";
}
else if (span.TotalDays >= 365 && span.TotalDays < 36500)
{
return $"{Math.Round(span.Days / 365.0, 0)} {loc["GLOBAL_TIME_YEARS"]}";
}
else if (span.TotalDays >= 36500)
{
return loc["GLOBAL_TIME_FOREVER"];
}
return "unknown";
}
/// <summary>
/// returns a list of penalty types that should be shown across all profiles
/// </summary>
@ -932,7 +848,7 @@ namespace SharedLibraryCore
/// <param name="penalty"></param>
/// <param name="penaltyService"></param>
/// <param name="logger"></param>
/// <returns>true of the creat succeeds, false otherwise</returns>
/// <returns>true of the create succeeds, false otherwise</returns>
public static async Task<bool> TryCreatePenalty(this EFPenalty penalty, IEntityService<EFPenalty> penaltyService, ILogger logger)
{
try
@ -969,7 +885,50 @@ namespace SharedLibraryCore
}
public static bool ShouldHideLevel(this Permission perm) => perm == Permission.Flagged;
/// <summary>
/// parses translation string into tokens that are able to be formatted by the webfront
/// </summary>
/// <param name="translationKey">key for translation lookup</param>
/// <returns></returns>
public static WebfrontTranslationHelper[] SplitTranslationTokens(string translationKey)
{
string translationString = CurrentLocalization.LocalizationIndex[translationKey];
var builder = new StringBuilder();
var results = new List<WebfrontTranslationHelper>();
foreach (string word in translationString.Split(' '))
{
string finalWord = word;
if ((word.StartsWith("{{") && !word.EndsWith("}}")) ||
(builder.Length > 0 && !word.EndsWith("}}")))
{
builder.Append($"{word} ");
continue;
}
if (builder.Length > 0)
{
builder.Append(word);
finalWord = builder.ToString();
builder.Clear();
}
var match = Regex.Match(finalWord, @"{{([^}|^-]+)(?:->)([^}]+)}}|{{([^}]+)}}");
bool isInterpolation = match.Success;
results.Add(new WebfrontTranslationHelper
{
IsInterpolation = isInterpolation,
MatchValue = isInterpolation ? match.Groups[3].Length > 0 ? match.Groups[3].ToString() : match.Groups[1].ToString() : finalWord,
TranslationValue = isInterpolation && match.Groups[2].Length > 0 ? match.Groups[2].ToString() : ""
});
}
return results.ToArray();
}
/// <summary>
/// indicates if running in development mode
/// </summary>
@ -991,5 +950,27 @@ namespace SharedLibraryCore
return path;
}
/// <summary>
/// wrapper method for humanizee that uses current current culture
/// </summary>
public static string HumanizeForCurrentCulture(this TimeSpan timeSpan, int precision = 1, TimeUnit maxUnit = TimeUnit.Week,
TimeUnit minUnit = TimeUnit.Millisecond, string collectionSeparator = ", ", bool toWords = false)
{
return timeSpan.Humanize(precision, CurrentLocalization.Culture, maxUnit, minUnit, collectionSeparator, toWords);
}
/// <summary>
/// wrapper method for humanizee that uses current current culture
/// </summary>
public static string HumanizeForCurrentCulture(this DateTime input, bool utcDate = true, DateTime? dateToCompareAgainst = null, CultureInfo culture = null)
{
return input.Humanize(utcDate, dateToCompareAgainst, CurrentLocalization.Culture);
}
public static string ToTranslatedName(this MetaType metaType)
{
return CurrentLocalization.LocalizationIndex[$"META_TYPE_{metaType.ToString().ToUpper()}_NAME"];
}
}
}

View File

@ -6,10 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FakeItEasy" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="FakeItEasy" Version="6.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -43,13 +43,14 @@ namespace ApplicationTests
.AddSingleton(A.Fake<IParserRegexFactory>())
.AddSingleton<DataFileLoader>()
.AddSingleton(A.Fake<IGameLogReaderFactory>())
.AddSingleton(A.Fake<IMetaService>())
.AddSingleton(eventHandler)
.AddSingleton(ConfigurationGenerators.CreateApplicationConfiguration())
.AddSingleton(ConfigurationGenerators.CreateCommandConfiguration())
.AddSingleton<IConfigurationHandler<ApplicationConfiguration>, ApplicationConfigurationHandlerMock>();
serviceCollection.AddSingleton(_sp => new IW4MServer(_sp.GetRequiredService<IManager>(), ConfigurationGenerators.CreateServerConfiguration(),
_sp.GetRequiredService<ITranslationLookup>(), _sp.GetRequiredService<IRConConnectionFactory>(), _sp.GetRequiredService<IGameLogReaderFactory>())
_sp.GetRequiredService<ITranslationLookup>(), _sp.GetRequiredService<IRConConnectionFactory>(), _sp.GetRequiredService<IGameLogReaderFactory>(), _sp.GetRequiredService<IMetaService>())
{
RconParser = _sp.GetRequiredService<IRConParser>()
});

View File

@ -21,9 +21,9 @@ namespace ApplicationTests.Fixtures
CurrentServer = currentServer
};
public static EFClient CreateDatabaseClient(bool hasIp = true) => new EFClient()
public static EFClient CreateDatabaseClient(bool hasIp = true, int clientId = 1) => new EFClient()
{
ClientId = 1,
ClientId = clientId,
ClientNumber = -1,
AliasLinkId = 1,
Level = EFClient.Permission.User,

View File

@ -0,0 +1,28 @@
using SharedLibraryCore.Database.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace ApplicationTests.Fixtures
{
class PenaltyGenerators
{
public static EFPenalty Create(EFPenalty.PenaltyType type = EFPenalty.PenaltyType.Ban, EFClient originClient = null, EFClient targetClient = null, DateTime? occurs = null, string reason = null)
{
originClient ??= ClientGenerators.CreateDatabaseClient(clientId: 1);
targetClient ??= ClientGenerators.CreateDatabaseClient(clientId: 2);
occurs ??= DateTime.UtcNow;
reason ??= "test";
return new EFPenalty()
{
Offender = targetClient,
Punisher = originClient,
When = occurs.Value,
Offense = reason,
Type = type,
LinkId = targetClient.AliasLinkId
};
}
}
}

View File

@ -0,0 +1,209 @@
using ApplicationTests.Fixtures;
using IW4MAdmin.Application.Meta;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace ApplicationTests
{
public class QueryHelperTests
{
private IServiceProvider serviceProvider;
[SetUp]
public void Setup()
{
serviceProvider = new ServiceCollection().BuildBase()
.AddSingleton<AdministeredPenaltyResourceQueryHelper>()
.AddSingleton<ReceivedPenaltyResourceQueryHelper>()
.AddSingleton<UpdatedAliasResourceQueryHelper>()
.BuildServiceProvider();
SetupPenalties();
SetupAliases();
}
private void SetupAliases()
{
using var ctx = serviceProvider.GetRequiredService<IDatabaseContextFactory>().CreateContext();
var client = ClientGenerators.CreateDatabaseClient();
var aliases = new[]
{
new EFAlias()
{
LinkId = client.AliasLinkId,
Name = "Test1",
IPAddress = -1,
DateAdded = DateTime.UtcNow.AddMinutes(-1)
},
new EFAlias()
{
LinkId = client.AliasLinkId,
Name = "Test2",
IPAddress = -1,
DateAdded = DateTime.UtcNow
}
};
ctx.Aliases.AddRange(aliases);
ctx.SaveChanges();
}
private void SetupPenalties()
{
using var ctx = serviceProvider.GetRequiredService<IDatabaseContextFactory>().CreateContext();
var firstPenalty = PenaltyGenerators.Create(occurs: DateTime.UtcNow.AddMinutes(-2), reason: "first");
var secondPenalty = PenaltyGenerators.Create(occurs: DateTime.UtcNow.AddMinutes(-1), reason: "second", originClient: firstPenalty.Punisher, targetClient: firstPenalty.Offender);
var linkedPenalty = PenaltyGenerators.Create(occurs: DateTime.UtcNow, reason: "linked", originClient: firstPenalty.Punisher, targetClient: ClientGenerators.CreateDatabaseClient(clientId: 3));
ctx.Add(firstPenalty);
ctx.Add(secondPenalty);
ctx.Add(linkedPenalty);
ctx.SaveChanges();
}
[TearDown]
public void Teardown()
{
using var ctx = serviceProvider.GetRequiredService<IDatabaseContextFactory>().CreateContext();
ctx.Database.EnsureDeleted();
}
#region ADMINISTERED PENALTIES
[Test]
public async Task Test_AdministeredPenaltyResourceQueryHelper_QueryResource_TakesAppropriateCount()
{
var queryHelper = serviceProvider.GetRequiredService<AdministeredPenaltyResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 1,
Before = DateTime.UtcNow,
ClientId = 1
};
var result = await queryHelper.QueryResource(request);
Assert.AreEqual(request.Count, result.RetrievedResultCount);
}
[Test]
public async Task Test_AdministeredPenaltyResourceQueryHelper_QueryResource_OrdersDescending()
{
var queryHelper = serviceProvider.GetRequiredService<AdministeredPenaltyResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 2,
Before = DateTime.UtcNow,
ClientId = 1,
Direction = SortDirection.Descending
};
var result = await queryHelper.QueryResource(request);
Assert.Less(result.Results.Last().When.ToFileTimeUtc(), result.Results.First().When.ToFileTimeUtc());
}
#endregion
#region RECEIVED PENALTIES
[Test]
public async Task Test_ReceivedPenaltyResourceQueryHelper_QueryResource_TakesAppropriateCount()
{
var queryHelper = serviceProvider.GetRequiredService<ReceivedPenaltyResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 1,
Before = DateTime.UtcNow,
ClientId = 2
};
var result = await queryHelper.QueryResource(request);
Assert.AreEqual(request.Count, result.RetrievedResultCount);
}
[Test]
public async Task Test_ReceivedPenaltyResourceQueryHelper_QueryResource_OrdersDescending()
{
var queryHelper = serviceProvider.GetRequiredService<ReceivedPenaltyResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 2,
Before = DateTime.UtcNow,
ClientId = 2,
Direction = SortDirection.Descending
};
var result = await queryHelper.QueryResource(request);
Assert.Less(result.Results.Last().When.ToFileTimeUtc(), result.Results.First().When.ToFileTimeUtc());
}
[Test]
public async Task Test_ReceivedPenaltyResourceQueryHelper_QueryResource_IncludesLinkedPenalty()
{
var queryHelper = serviceProvider.GetRequiredService<ReceivedPenaltyResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 3,
Before = DateTime.UtcNow,
ClientId = 3,
};
var result = await queryHelper.QueryResource(request);
Assert.AreEqual(request.Count, result.RetrievedResultCount);
}
#endregion
#region ALIAS UPDATE
[Test]
public async Task Test_UpdatedAliasResourceQueryHelper_QueryResource_TakesAppropriateCount()
{
var queryHelper = serviceProvider.GetRequiredService<UpdatedAliasResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 1,
Before = DateTime.UtcNow,
ClientId = 1
};
var result = await queryHelper.QueryResource(request);
Assert.AreEqual(request.Count, result.RetrievedResultCount);
}
[Test]
public async Task Test_UpdatedAliasResourceQueryHelper_QueryResource_OrdersDescending()
{
var queryHelper = serviceProvider.GetRequiredService<UpdatedAliasResourceQueryHelper>();
var request = new ClientPaginationRequest
{
Count = 2,
Before = DateTime.UtcNow,
ClientId = 1,
Direction = SortDirection.Descending
};
var result = await queryHelper.QueryResource(request);
Assert.Less(result.Results.Last().When.ToFileTimeUtc(), result.Results.First().When.ToFileTimeUtc());
}
#endregion
}
}

View File

@ -34,7 +34,7 @@ namespace ApplicationTests
var mgr = A.Fake<IManager>();
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>(), A.Fake<IMetaService>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>(), A.Fake<ApplicationConfiguration>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;
@ -60,7 +60,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
A.Fake<ITranslationLookup>(), A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>(), A.Fake<IMetaService>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>(), A.Fake<ApplicationConfiguration>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;

View File

@ -72,7 +72,7 @@ namespace ApplicationTests
var server = new IW4MServer(mgr,
new SharedLibraryCore.Configuration.ServerConfiguration() { IPAddress = "127.0.0.1", Port = 28960 },
A.Fake<ITranslationLookup>(),
A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>());
A.Fake<IRConConnectionFactory>(), A.Fake<IGameLogReaderFactory>(), A.Fake<IMetaService>());
var parser = new BaseEventParser(A.Fake<IParserRegexFactory>(), A.Fake<ILogger>(), A.Fake<ApplicationConfiguration>());
parser.Configuration.GuidNumberStyle = System.Globalization.NumberStyles.Integer;

View File

@ -242,7 +242,7 @@ namespace WebfrontCore.Controllers
{
var info = new ActionInfo()
{
ActionButtonLabel = "Generate",
ActionButtonLabel = Localization["WEBFRONT_ACTION_LABEL_GENERATE_TOKEN"],
Name = "GenerateLoginToken",
Action = "GenerateLoginTokenAsync",
Inputs = new List<InputInfo>()

View File

@ -27,7 +27,7 @@ namespace WebfrontCore.Controllers
ViewBag.Title = _translationLookup["WEBFRONT_NAV_AUDIT_LOG"];
ViewBag.InitialOffset = DEFAULT_COUNT;
var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationInfo()
var auditItems = await _auditInformationRepository.ListAuditInformation(new PaginationRequest()
{
Count = DEFAULT_COUNT
});
@ -35,7 +35,7 @@ namespace WebfrontCore.Controllers
return View(auditItems);
}
public async Task<IActionResult> ListAuditLog([FromQuery] PaginationInfo paginationInfo)
public async Task<IActionResult> ListAuditLog([FromQuery] PaginationRequest paginationInfo)
{
ViewBag.EnableColorCodes = Manager.GetApplicationSettings().Configuration().EnableColorCodes;
var auditItems = await _auditInformationRepository.ListAuditInformation(paginationInfo);

View File

@ -2,12 +2,15 @@
using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebfrontCore.ViewComponents;
using static SharedLibraryCore.Database.Models.EFClient;
using static SharedLibraryCore.Database.Models.EFPenalty;
@ -15,12 +18,14 @@ namespace WebfrontCore.Controllers
{
public class ClientController : BaseController
{
public ClientController(IManager manager) : base(manager)
{
private readonly IMetaService _metaService;
public ClientController(IManager manager, IMetaService metaService) : base(manager)
{
_metaService = metaService;
}
public async Task<IActionResult> ProfileAsync(int id)
public async Task<IActionResult> ProfileAsync(int id, MetaType? metaFilterType)
{
var client = await Manager.GetClientService().Get(id);
@ -29,8 +34,8 @@ namespace WebfrontCore.Controllers
return NotFound();
}
var activePenalties = (await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.IPAddress))
.Where(_penalty => _penalty.Type != PenaltyType.Flag);
var activePenalties = (await Manager.GetPenaltyService().GetActivePenaltiesAsync(client.AliasLinkId, client.IPAddress));
int displayLevelInt = (int)client.Level;
string displayLevel = client.Level.ToLocalizedLevelName();
@ -49,7 +54,7 @@ namespace WebfrontCore.Controllers
ClientId = client.ClientId,
IPAddress = client.IPAddressString,
NetworkId = client.NetworkId,
Meta = new List<ProfileMeta>(),
Meta = new List<InformationResponse>(),
Aliases = client.AliasLink.Children
.Select(_alias => _alias.Name)
.GroupBy(_alias => _alias.StripColors())
@ -65,38 +70,32 @@ namespace WebfrontCore.Controllers
.Prepend(client.CurrentAlias.IPAddress.ConvertIPtoString())
.Distinct()
.ToList(),
HasActivePenalty = activePenalties.Count() > 0,
ActivePenaltyType = activePenalties.Count() > 0 ? activePenalties.First().Type.ToString() : null,
HasActivePenalty = activePenalties.Any(_penalty => _penalty.Type != PenaltyType.Flag),
Online = Manager.GetActiveClients().FirstOrDefault(c => c.ClientId == client.ClientId) != null,
TimeOnline = (DateTime.UtcNow - client.LastConnection).TimeSpanText(),
LinkedAccounts = client.LinkedAccounts
TimeOnline = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
LinkedAccounts = client.LinkedAccounts,
MetaFilterType = metaFilterType
};
var meta = await MetaService.GetRuntimeMeta(client.ClientId, 0, 1, DateTime.UtcNow);
var gravatar = await new MetaService().GetPersistentMeta("GravatarEmail", client);
var meta = await _metaService.GetRuntimeMeta<InformationResponse>(new ClientPaginationRequest
{
ClientId = client.ClientId,
Before = DateTime.UtcNow
}, MetaType.Information);
var gravatar = await _metaService.GetPersistentMeta("GravatarEmail", client);
if (gravatar != null)
{
clientDto.Meta.Add(new ProfileMeta()
clientDto.Meta.Add(new InformationResponse()
{
Key = "GravatarEmail",
Type = ProfileMeta.MetaType.Other,
Type = MetaType.Other,
Value = gravatar.Value
});
}
var currentPenalty = activePenalties.FirstOrDefault();
if (currentPenalty != null && currentPenalty.Type == PenaltyType.TempBan)
{
clientDto.Meta.Add(new ProfileMeta()
{
Key = Localization["WEBFRONT_CLIENT_META_REMAINING_BAN"],
Value = ((currentPenalty.Expires - DateTime.UtcNow) ?? new TimeSpan()).TimeSpanText(),
When = currentPenalty.When
});
}
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.Sensitive));
clientDto.ActivePenalty = activePenalties.OrderByDescending(_penalty => _penalty.Type).FirstOrDefault();
clientDto.Meta.AddRange(Authorized ? meta : meta.Where(m => !m.IsSensitive));
string strippedName = clientDto.Name.StripColors();
ViewBag.Title = strippedName.Substring(strippedName.Length - 1).ToLower()[0] == 's' ?
@ -160,14 +159,17 @@ namespace WebfrontCore.Controllers
return View("Find/Index", clientsDto);
}
public async Task<IActionResult> Meta(int id, int count, int offset, DateTime? startAt)
public async Task<IActionResult> Meta(int id, int count, int offset, long? startAt, MetaType? metaFilterType)
{
IEnumerable<ProfileMeta> meta = await MetaService.GetRuntimeMeta(id, startAt == null ? offset : 0, count, startAt ?? DateTime.UtcNow);
if (!Authorized)
var request = new ClientPaginationRequest
{
meta = meta.Where(_meta => !_meta.Sensitive);
}
ClientId = id,
Count = count,
Offset = offset,
Before = DateTime.FromFileTimeUtc(startAt ?? DateTime.UtcNow.ToFileTimeUtc())
};
var meta = await ProfileMetaListViewComponent.GetClientMeta(_metaService, metaFilterType, Client.Level, request);
if (meta.Count() == 0)
{

View File

@ -19,6 +19,10 @@ using Stats.Dtos;
using Stats.Helpers;
using StatsWeb;
using StatsWeb.Dtos;
/*using Stats.Dtos;
using Stats.Helpers;
using StatsWeb;
using StatsWeb.Dtos;*/
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -125,6 +129,7 @@ namespace WebfrontCore
services.AddSingleton(Program.ApplicationServiceProvider.GetService<ITranslationLookup>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<SharedLibraryCore.Interfaces.ILogger>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IEnumerable<IManagerCommand>>());
services.AddSingleton(Program.ApplicationServiceProvider.GetService<IMetaService>());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@ -1,7 +1,8 @@
using Microsoft.AspNetCore.Mvc;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Services;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using System;
using System.Collections.Generic;
using System.Linq;
@ -12,18 +13,70 @@ namespace WebfrontCore.ViewComponents
{
public class ProfileMetaListViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync(int clientId, int count, int offset, DateTime? startAt)
private readonly IMetaService _metaService;
public ProfileMetaListViewComponent(IMetaService metaService)
{
_metaService = metaService;
}
public async Task<IViewComponentResult> InvokeAsync(int clientId, int count, int offset, DateTime? startAt, MetaType? metaType)
{
var level = (EFClient.Permission)Enum.Parse(typeof(EFClient.Permission), UserClaimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value ?? "User");
IEnumerable<ProfileMeta> meta = await MetaService.GetRuntimeMeta(clientId, offset, count, startAt ?? DateTime.UtcNow);
if (level < EFClient.Permission.Trusted)
var request = new ClientPaginationRequest
{
meta = meta.Where(_meta => !_meta.Sensitive);
}
ClientId = clientId,
Count = count,
Offset = offset,
Before = startAt,
};
var meta = await GetClientMeta(_metaService, metaType, level, request);
ViewBag.Localization = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
return View("_List", meta);
}
public static async Task<IEnumerable<IClientMeta>> GetClientMeta(IMetaService metaService, MetaType? metaType, EFClient.Permission level, ClientPaginationRequest request)
{
IEnumerable<IClientMeta> meta = null;
if (metaType == null) // all types
{
meta = await metaService.GetRuntimeMeta(request);
}
else
{
switch (metaType)
{
case MetaType.Information:
meta = await metaService.GetRuntimeMeta<InformationResponse>(request, metaType.Value);
break;
case MetaType.AliasUpdate:
meta = await metaService.GetRuntimeMeta<UpdatedAliasResponse>(request, metaType.Value);
break;
case MetaType.ChatMessage:
meta = await metaService.GetRuntimeMeta<MessageResponse>(request, metaType.Value);
break;
case MetaType.Penalized:
meta = await metaService.GetRuntimeMeta<AdministeredPenaltyResponse>(request, metaType.Value);
break;
case MetaType.ReceivedPenalty:
meta = await metaService.GetRuntimeMeta<ReceivedPenaltyResponse>(request, metaType.Value);
break;
default:
break;
}
}
if (level < EFClient.Permission.Trusted)
{
meta = meta.Where(_meta => !_meta.IsSensitive);
}
return meta;
}
}
}

View File

@ -1,17 +1,17 @@
@model SharedLibraryCore.Dtos.PlayerInfo
@using SharedLibraryCore.Database.Models
@using SharedLibraryCore.Interfaces
@using SharedLibraryCore
@model SharedLibraryCore.Dtos.PlayerInfo
@{
string match = System.Text.RegularExpressions.Regex.Match(Model.Name.ToUpper(), "[A-Z]").Value;
string shortCode = match == string.Empty ? "?" : match;
var loc = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex;
string gravatarUrl = Model.Meta.FirstOrDefault(m => m.Key == "GravatarEmail")?.Value;
bool isTempBanned = Model.ActivePenaltyType == "TempBan";
bool isFlagged = Model.LevelInt == (int)SharedLibraryCore.Database.Models.EFClient.Permission.Flagged;
bool isPermBanned = Model.LevelInt == (int)SharedLibraryCore.Database.Models.EFClient.Permission.Banned;
var informationMeta = Model.Meta
.Where(_meta => _meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.Information)
.OrderBy(_meta => _meta.Order)
.GroupBy(_meta => _meta.Column)
.OrderBy(_grouping => _grouping.Key);
bool isTempBanned = Model.ActivePenalty?.Type == EFPenalty.PenaltyType.TempBan;
string translationKey = $"WEBFRONT_PROFILE_{Model.ActivePenalty?.Type.ToString().ToUpper()}_INFO";
var ignoredMetaTypes = new[] { MetaType.Information, MetaType.Other, MetaType.QuickMessage };
}
<div id="profile_wrapper" class="pb-3 row d-flex flex-column flex-lg-row">
@ -23,7 +23,7 @@
}
</div>
<!-- Name/Level Column -->
<div class="d-block d-lg-inline-flex flex-column flex-fill text-center text-lg-left pb-3 pb-lg-0 pt-3 pt-lg-0 pl-3 pr-3">
<div class="w-75 d-block d-lg-inline-flex flex-column flex-fill text-center text-lg-left pb-3 pb-lg-0 pt-3 pt-lg-0 pl-3 pr-3 ml-auto mr-auto" style="overflow-wrap: anywhere">
<div class="mt-n2 flex-fill d-block d-lg-inline-flex">
<div id="profile_name" class="client-name h1 mb-0"><color-code value="@Model.Name" allow="@ViewBag.EnableColorCodes"></color-code></div>
@if (ViewBag.Authorized)
@ -31,6 +31,7 @@
<div id="profile_aliases_btn" class="oi oi-caret-bottom h3 ml-0 ml-lg-2 mb-0 pt-lg-2 mt-lg-1"></div>
}
</div>
@if (ViewBag.Authorized)
{
<div id="profile_aliases" class="text-muted pt-0 pt-lg-2 pb-2">
@ -49,9 +50,34 @@
}
</div>
}
<div id="profile_level" class="font-weight-bold h4 mb-0 level-color-@Model.LevelInt @(isTempBanned ? "penalties-color-tempban" : "")">
@Model.Level @(isTempBanned ? $"({loc["WEBFRONT_PROFILE_TEMPBAN"]})" : "")
</div>
@if (Model.ActivePenalty != null)
{
<div class="font-weight-bold h4 mb-0 penalties-color-@Model.ActivePenalty.Type.ToString().ToLower()">
@foreach (var result in Utilities.SplitTranslationTokens(translationKey))
{
switch (result.MatchValue)
{
case "reason":
<span class="text-white font-weight-lighter">@(ViewBag.Authorized ? !string.IsNullOrEmpty(Model.ActivePenalty.AutomatedOffense) && Model.ActivePenalty.Type != EFPenalty.PenaltyType.Warning ? Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.ActivePenalty.AutomatedOffense) : Model.ActivePenalty.Offense : Model.ActivePenalty.Offense)</span>
break;
case "time":
<span class="text-white font-weight-lighter">
@Utilities.HumanizeForCurrentCulture(Model.ActivePenalty.Expires.Value - DateTime.UtcNow)
</span>
break;
default:
<span>@result.MatchValue</span>
break;
}
}
</div>
}
else
{
<div id="profile_level" class="font-weight-bold h4 mb-0 level-color-@Model.LevelInt">
@Model.Level
</div>
}
</div>
@if (ViewBag.Authorized)
{
@ -89,23 +115,32 @@
</div>
<div id="profile_info" class="row d-block d-lg-flex flex-row border-bottom border-top pt-2 pb-2">
@foreach (var metaColumn in informationMeta)
{
<div class="text-center text-lg-left mr-0 mr-lg-4">
@foreach (var meta in metaColumn)
<partial name="Meta/_Information.cshtml" model="@Model.Meta" />
</div>
<div class="row border-bottom">
<div class="text-center bg-dark p-2 pl-3 pr-4 text-muted col-12 col-md-auto" id="filter_meta_container_button">
<span class="oi oi-sort-ascending"></span>
<a>Filter Meta</a>
</div>
<div class="d-none d-md-flex flex-fill" id="filter_meta_container">
@foreach (MetaType type in Enum.GetValues(typeof(MetaType)))
{
if (!ignoredMetaTypes.Contains(type))
{
<div class="profile-meta-entry" title="@(string.IsNullOrEmpty(meta.Extra) ? meta.Key : meta.Extra)">
<span class="profile-meta-value text-primary"><color-code value="@meta.Value" allow="@ViewBag.EnableColorCodes"></color-code></span>
<span class="profile-meta-title text-muted"> @meta.Key</span>
</div>
<a asp-action="ProfileAsync" asp-controller="Client"
class="nav-link p-2 pl-3 pr-3 text-center col-12 col-md-auto text-md-left @(Model.MetaFilterType.HasValue && Model.MetaFilterType.Value.ToString() == type.ToString() ? "btn-primary text-white" : "text-muted")"
asp-route-id="@Model.ClientId"
asp-route-metaFilterType="@type"
data-meta-type="@type">@type.ToTranslatedName()</a>
}
</div>
}
}
</div>
</div>
<div class="row d-md-flex pt-2">
<div id="profile_events" class="text-muted text-left ml-sm-0">
@await Component.InvokeAsync("ProfileMetaList", new { clientId = Model.ClientId, count = 30, offset = 0 })
<div id="profile_events" class="text-muted text-left pl-4 pr-4 pl-md-0 pr-md-0">
@await Component.InvokeAsync("ProfileMetaList", new { clientId = Model.ClientId, count = 30, offset = 0, startAt = DateTime.UtcNow, metaType = Model.MetaFilterType })
</div>
</div>
@ -122,5 +157,5 @@
<script type="text/javascript" src="~/js/loader.js"></script>
<script type="text/javascript" src="~/js/profile.js"></script>
</environment>
<script>initLoader('/Client/Meta/@Model.ClientId', '#profile_events', 30);</script>
<script>initLoader('/Client/Meta/@Model.ClientId', '#profile_events', 30, 30, [{ 'name': 'metaFilterType', 'value': '@Model.MetaFilterType' }]);</script>
}

View File

@ -0,0 +1,53 @@
@using SharedLibraryCore.Dtos.Meta.Responses
@model AdministeredPenaltyResponse
@{
string localizationKey = $"WEBFRONT_CLIENT_META_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
}
<div class="d-inline">
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
{
if (match.IsInterpolation)
{
if (match.MatchValue == "action")
{
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
}
else if (match.MatchValue == "offender")
{
<span class="text-highlight">
<a class="link-inverse" href="@Model.OffenderClientId">
<color-code value="@Model.OffenderName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</span>
}
else if (match.MatchValue == "reason")
{
<span class="text-white">
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Warning)
{
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
}
else
{
<color-code value="@Model.Offense" allow="@ViewBag.EnableColorCodes"></color-code>
}
</span>
}
else if (match.MatchValue == "time")
{
<span class="text-white">@Model.LengthText</span>
}
}
else
{
<span>@match.MatchValue</span>
}
}
</div>

View File

@ -0,0 +1,43 @@
@model IEnumerable<SharedLibraryCore.Dtos.Meta.Responses.InformationResponse>
@{
var informationMeta = Model
.Where(_meta => _meta.Type == SharedLibraryCore.Interfaces.MetaType.Information)
.OrderBy(_meta => _meta.Order)
.GroupBy(_meta => _meta.Column)
.OrderBy(_grouping => _grouping.Key);
}
@foreach (var metaColumn in informationMeta)
{
<div class="text-center text-lg-left mr-0 mr-lg-4">
@foreach (var meta in metaColumn)
{
<div class="profile-meta-entry" title="@meta.ToolTipText">
@{var results = Utilities.SplitTranslationTokens(meta.Key);}
@if (results.Any(_result => _result.IsInterpolation))
{
foreach (var result in results)
{
if (result.IsInterpolation)
{
<span class="profile-meta-value text-primary"><color-code value="@meta.Value" allow="@ViewBag.EnableColorCodes"></color-code></span>
}
else
{
<span class="profile-meta-title text-muted">@result.MatchValue</span>
}
}
}
else
{
<span class="profile-meta-value text-primary"><color-code value="@meta.Value" allow="@ViewBag.EnableColorCodes"></color-code></span>
<span class="profile-meta-title text-muted"> @meta.Key</span>
}
</div>
}
</div>
}

View File

@ -0,0 +1,9 @@
@using SharedLibraryCore.Dtos.Meta.Responses;
@model MessageResponse
<span class="client-message" data-serverid="@Model.ServerId" data-when="@Model.When.ToFileTimeUtc()">
<span class="oi oi-chevron-right text-white-50 align-middle client-message-prefix" title="@ViewBag.Localization["WEBFRONT_PROFILE_MESSAGE_CONTEXT"]" style="font-size: 0.75rem; margin-top: -0.256rem"></span>
<span class="text-muted">
<color-code value="@Model.Message" allow="@ViewBag.EnableColorCodes"></color-code>
</span>
</span>

View File

@ -0,0 +1,74 @@
@using SharedLibraryCore.Dtos.Meta.Responses
@using SharedLibraryCore
@model ReceivedPenaltyResponse
@{
string localizationKey = $"WEBFRONT_CLIENT_META_WAS_PENALIZED_{Model.PenaltyType.ToString().ToUpper()}_V2";
}
<div class="d-inline">
@foreach (var match in Utilities.SplitTranslationTokens(localizationKey))
{
if (match.IsInterpolation)
{
if (match.MatchValue == "action")
{
<span class="penalties-color-@Model.PenaltyType.ToString().ToLower()">@match.TranslationValue</span>
}
else if (match.MatchValue == "punisher")
{
<span class="text-highlight">
<a class="link-inverse" href="@Model.PunisherClientId">
<color-code value="@Model.PunisherName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</span>
}
else if (match.MatchValue == "reason")
{
<span class="text-white">
@if (ViewBag.Authorized && !string.IsNullOrEmpty(Model.AutomatedOffense) && Model.PenaltyType != SharedLibraryCore.Database.Models.EFPenalty.PenaltyType.Warning)
{
<span>@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_ANTICHEAT_DETECTION"], Model.AutomatedOffense)</span>
<span class="oi oi-list-rich align-top text-primary automated-penalty-info-detailed" data-penalty-id="@Model.PenaltyId" style="margin-top: 0.125rem;" title="@ViewBag.Localization["WEBFRONT_CLIENT_META_AC_METRIC"]"></span>
}
else
{
<color-code value="@Model.Offense" allow="@ViewBag.EnableColorCodes"></color-code>
}
</span>
}
else if (match.MatchValue == "time")
{
<span class="text-white">@Model.LengthText</span>
}
}
else
{
<span>@match.MatchValue</span>
}
}
@if (Model.ClientId != Model.OffenderClientId)
{
<span>&mdash;</span>
@foreach (var helperResult in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_LINKED_ACCOUNT"))
{
if (!helperResult.IsInterpolation)
{
<span>@helperResult.MatchValue</span>
}
else
{
<a class="link-inverse" href="@Model.OffenderClientId">
<color-code value="@Model.OffenderName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
}
}
}
</div>

View File

@ -0,0 +1,27 @@
@using SharedLibraryCore.Dtos.Meta.Responses
@using SharedLibraryCore
@model UpdatedAliasResponse
@foreach (var token in Utilities.SplitTranslationTokens("WEBFRONT_PROFILE_META_CONNECT_ALIAS"))
{
if (token.IsInterpolation)
{
switch (token.MatchValue)
{
case "action":
<span class="text-warning">@token.TranslationValue</span>
break;
case "alias":
<span class="text-white">
<color-code value="@Model.Name" allow="@ViewBag.EnableColorCodes"></color-code>
[@Model.IPAddress]
</span>
break;
}
}
else
{
<span class="text-muted">@token.MatchValue</span>
}
}

View File

@ -1,94 +1,66 @@
@model IEnumerable<SharedLibraryCore.Dtos.ProfileMeta>
@using SharedLibraryCore.Interfaces;
@model IEnumerable<IClientMeta>
@{
Layout = null;
var timeSinceLastEvent = DateTime.MinValue;
var lastHeaderEventDate = DateTime.UtcNow;
dynamic formatPenaltyInfo(SharedLibraryCore.Dtos.ProfileMeta meta)
TimeSpan timeSpanForEvent(DateTime When)
{
var penalty = meta.Value as SharedLibraryCore.Dtos.PenaltyInfo;
var timePassed = (DateTime.UtcNow - When);
var daysPassed = timePassed.TotalDays;
var minutesPassed = timePassed.TotalMinutes;
string localizationKey = meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.Penalized ?
$"WEBFRONT_CLIENT_META_PENALIZED_{penalty.PenaltyTypeText.ToUpper()}" :
$"WEBFRONT_CLIENT_META_WAS_PENALIZED_{penalty.PenaltyTypeText.ToUpper()}";
string localizationMessage = SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex[localizationKey];
var regexMatch = System.Text.RegularExpressions.Regex.Match(localizationMessage, @"^.*{{([^{}]+)}}.+$");
string penaltyType = regexMatch.Groups[1].Value.ToString();
var secondMatch = System.Text.RegularExpressions.Regex.Match(localizationMessage, @"\{\{.+\}\}(.+)\{0\}(.+)\{1\}");
return new
if (minutesPassed <= 60)
{
Type = meta.Type,
Match = secondMatch,
Penalty = penalty,
PenaltyType = penaltyType
};
return TimeSpan.FromMinutes(5);
}
if (minutesPassed > 60 && daysPassed <= 1)
{
return TimeSpan.FromHours(1);
}
if (daysPassed > 1 && daysPassed <= 7)
{
return TimeSpan.FromDays(1);
}
if (daysPassed > 7 && daysPassed <= 31)
{
return TimeSpan.FromDays(31);
}
if (daysPassed > 31 && daysPassed <= 365)
{
return TimeSpan.FromDays(31);
}
else
{
return TimeSpan.FromDays(365);
}
}
}
@if (Model.Count() == 0)
{
<div class="p2 text-muted profile-event-timestep">@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_NONE"]</div>
<div class="p2 text-muted profile-event-timestep">@ViewBag.Localization["WEBFRONT_CLIENT_META_NONE"]</div>
}
@foreach (var meta in Model.OrderByDescending(_meta => _meta.When))
{
@if (Math.Abs((meta.When - timeSinceLastEvent).TotalDays) >= 1)
@if ((lastHeaderEventDate - meta.When) > timeSpanForEvent(lastHeaderEventDate))
{
<div class="p2 text-white profile-event-timestep">
<span class="text-primary">&mdash;</span>
<span>@SharedLibraryCore.Utilities.GetTimePassed(meta.When, true)</span>
<span>@meta.When.HumanizeForCurrentCulture()</span>
</div>
timeSinceLastEvent = meta.When;
lastHeaderEventDate = meta.When;
}
@switch (meta.Type)
{
case SharedLibraryCore.Dtos.ProfileMeta.MetaType.ChatMessage:
case SharedLibraryCore.Dtos.ProfileMeta.MetaType.QuickMessage:
<div class="profile-meta-entry loader-data-time" data-time="@meta.When">
<span style="color:white;">></span>
<span class="client-message text-muted @(meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.QuickMessage ? "font-italic" : "")" data-serverid="@meta.Extra" data-when="@meta.When.ToFileTimeUtc()" title="@SharedLibraryCore.Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PROFILE_MESSAGE_CONTEXT"]"> <color-code value="@meta.Value" allow="@ViewBag.EnableColorCodes"></color-code></span>
</div>
break;
case SharedLibraryCore.Dtos.ProfileMeta.MetaType.ReceivedPenalty:
case SharedLibraryCore.Dtos.ProfileMeta.MetaType.Penalized:
<div class="profile-meta-entry loader-data-time" data-time="@meta.When">
@{ var penaltyInfo = formatPenaltyInfo(meta); }
@if (meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.Penalized)
{
<span class="penalties-color-@penaltyInfo.Penalty.PenaltyTypeText.ToLower()">@penaltyInfo.PenaltyType</span>
<span>@penaltyInfo.Match.Groups[1].ToString()</span> <!-- by -->
<span class="text-highlight">
<!-- punisher -->
<a class="link-inverse" href="@penaltyInfo.Penalty.OffenderId">
<color-code value="@penaltyInfo.Penalty.OffenderName" allow="@ViewBag.EnableColorCodes"></color-code>
</a>
</span>
<span>@penaltyInfo.Match.Groups[2].ToString()</span> <!-- for -->
<span class="@(ViewBag.Authorized ? "automated-penalty-info-detailed" : "")} text-white"
data-penalty-id="@penaltyInfo.Penalty.Id"><color-code value="@penaltyInfo.Penalty.Offense" allow="@ViewBag.EnableColorCodes"></color-code> @(ViewBag.Authorized ? penaltyInfo.Penalty.AdditionalPenaltyInformation : "")</span>
}
@if (meta.Type == SharedLibraryCore.Dtos.ProfileMeta.MetaType.ReceivedPenalty)
{
<span class="penalties-color-@penaltyInfo.Penalty.PenaltyTypeText.ToLower()">@penaltyInfo.PenaltyType</span> <!-- actioned -->
<span>@penaltyInfo.Match.Groups[1].ToString()</span> <!-- by -->
<span class="text-highlight">
<a class="link-inverse" href="@penaltyInfo.Penalty.PunisherId">
<color-code value="@penaltyInfo.Penalty.PunisherName" allow="@ViewBag.EnableColorCodes"></color-code>
</a> <!-- punisher -->
</span>
<span>@penaltyInfo.Match.Groups[2]</span>
<span class="@(ViewBag.Authorized ? "automated-penalty-info-detailed" : "") text-white"
data-penalty-id="@penaltyInfo.Penalty.Id">
<color-code value="@penaltyInfo.Penalty.Offense" allow="@ViewBag.EnableColorCodes"></color-code> @(ViewBag.Authorized ? penaltyInfo.Penalty.AdditionalPenaltyInformation : "")
</span>
}
</div>
break;
}
}
<div class="profile-meta-entry loader-data-time" data-time="@meta.When.ToFileTimeUtc()" title="@Utilities.FormatExt(ViewBag.Localization["WEBFRONT_PROFILE_META_DATE_OCCURRED"], meta.When.ToString())">
<partial name="~/Views/Client/Profile/Meta/_@(meta.GetType().Name).cshtml" model="meta" />
</div>
}

View File

@ -1,4 +1,5 @@
@using SharedLibraryCore
@using WebfrontCore
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, SharedLibraryCore
@addTagHelper *, SharedLibraryCore
@addTagHelper *, WebfrontCore

View File

@ -64,20 +64,20 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
<PackageReference Include="BuildWebCompiler" Version="1.12.405" />
<PackageReference Include="FluentValidation.AspNetCore" Version="8.6.2" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.0.96" />
<PackageReference Include="FluentValidation.AspNetCore" Version="9.1.2" />
<PackageReference Include="Microsoft.Web.LibraryManager.Build" Version="2.1.76" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.7" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\lib\canvas.js\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Plugins\Web\StatsWeb\StatsWeb.csproj" />
<ProjectReference Include="..\SharedLibraryCore\SharedLibraryCore.csproj" />

View File

@ -1,7 +1,10 @@
[
[
{
"outputFile": "wwwroot/css/global.css",
"inputFile": "wwwroot/css/src/main.scss",
"sourceMap": false
"inputFile": "wwwroot/css/src/main.scss"
},
{
"outputFile": "wwwroot/css/src/profile.css",
"inputFile": "wwwroot/css/src/profile.scss"
}
]

View File

@ -1,5 +1,4 @@
@import 'bootstrap-custom.scss';
@import 'profile.scss';
@import 'profile.scss';
$icon-font-path: '/font/' !default;
@import '../../lib/open-iconic/font/css/open-iconic-bootstrap.scss';
@ -195,7 +194,7 @@ form *, select {
font-size: 1rem;
}
.client-message, .automated-penalty-info-detailed, .oi {
.oi {
cursor: pointer;
}

View File

@ -1,4 +1,6 @@
.level-bgcolor-console {
@import 'bootstrap-custom.scss';
.level-bgcolor-console {
background-color: grey;
}
@ -194,3 +196,8 @@
#profile_events span {
word-break: break-all;
}
#filter_meta_container .nav-link:hover {
background-color: $dark;
color: $white !important;
}

View File

@ -5,12 +5,14 @@ let startAt = null;
let isLoaderLoading = false;
let loadUri = '';
let loaderResponseId = '';
let additionalParams = [];
function initLoader(location, loaderId, count = 10, start = count) {
function initLoader(location, loaderId, count = 10, start = count, additional) {
loadUri = location;
loaderResponseId = loaderId;
loadCount = count;
loaderOffset = start;
additionalParams = additional;
setupListeners();
}
@ -21,7 +23,13 @@ function loadMoreItems() {
showLoader();
isLoaderLoading = true;
$.get(loadUri, { offset: loaderOffset, count: loadCount, startAt: startAt })
let params = { offset: loaderOffset, count: loadCount, startAt: startAt };
for (i = 0; i < additionalParams.length; i++) {
let param = additionalParams[i];
params[param.name] = param.value;
}
$.get(loadUri, params)
.done(function (response) {
$(loaderResponseId).append(response);
if (response.trim().length === 0) {

View File

@ -11,7 +11,15 @@
});
/* set the end time for initial event query */
startAt = $('#profile_events').children().last().data('time');
startAt = $('.loader-data-time').last().data('time');
$('#filter_meta_container_button').click(function () {
$('#filter_meta_container').hide();
$('#filter_meta_container').removeClass('d-none');
$('#filter_meta_container').addClass('d-block');
$('#filter_meta_container').slideDown();
});
/*
* load context of chat
@ -20,6 +28,14 @@
$(document).on('click', '.client-message', function (e) {
showLoader();
const location = $(this);
$('.client-message-prefix').removeClass('oi-chevron-bottom');
$('.client-message-prefix').removeClass('oi-chevron-right');
$('.client-message-prefix').addClass('oi-chevron-right');
$(this).children().filter('.client-message-prefix').removeClass('oi-chevron-right');
$(this).children().filter('.client-message-prefix').addClass('oi-chevron-bottom');
$.get('/Stats/GetMessageAsync', {
'serverId': $(this).data('serverid'),
'when': $(this).data('when')