persist client count history data across reboots and allow for configurable timespan
This commit is contained in:
parent
02e5e78f67
commit
deff4f2947
@ -1002,10 +1002,13 @@ namespace IW4MAdmin
|
|||||||
LastMessage = DateTime.Now - start;
|
LastMessage = DateTime.Now - start;
|
||||||
lastCount = DateTime.Now;
|
lastCount = DateTime.Now;
|
||||||
|
|
||||||
|
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
|
||||||
// update the player history
|
// update the player history
|
||||||
if ((lastCount - playerCountStart).TotalMinutes >= PlayerHistory.UpdateInterval)
|
if (lastCount - playerCountStart >= appConfig.ServerDataCollectionInterval)
|
||||||
{
|
{
|
||||||
while (ClientHistory.Count > ((60 / PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours
|
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
|
||||||
|
appConfig.ServerDataCollectionInterval.TotalMinutes);
|
||||||
|
while ( ClientHistory.Count > maxItems)
|
||||||
{
|
{
|
||||||
ClientHistory.Dequeue();
|
ClientHistory.Dequeue();
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,6 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Data.Abstractions;
|
using Data.Abstractions;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
using SharedLibraryCore.Database;
|
|
||||||
using SharedLibraryCore.Interfaces;
|
|
||||||
|
|
||||||
namespace IW4MAdmin.Application.Migration
|
namespace IW4MAdmin.Application.Migration
|
||||||
{
|
{
|
||||||
|
@ -10,6 +10,7 @@ using Data.Models.Server;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
|
|
||||||
@ -21,14 +22,16 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IManager _manager;
|
private readonly IManager _manager;
|
||||||
private readonly IDatabaseContextFactory _contextFactory;
|
private readonly IDatabaseContextFactory _contextFactory;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
private bool _inProgress;
|
private bool _inProgress;
|
||||||
private TimeSpan _period;
|
private TimeSpan _period;
|
||||||
|
|
||||||
public ServerDataCollector(ILogger<ServerDataCollector> logger, IManager manager,
|
public ServerDataCollector(ILogger<ServerDataCollector> logger, ApplicationConfiguration appConfig,
|
||||||
IDatabaseContextFactory contextFactory)
|
IManager manager, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_appConfig = appConfig;
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
@ -42,7 +45,9 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
|
|
||||||
_logger.LogDebug("Initializing data collection with {Name}", nameof(ServerDataCollector));
|
_logger.LogDebug("Initializing data collection with {Name}", nameof(ServerDataCollector));
|
||||||
_inProgress = true;
|
_inProgress = true;
|
||||||
_period = period ?? TimeSpan.FromMinutes(Utilities.IsDevelopment ? 1 : 5);
|
_period = period ?? (Utilities.IsDevelopment
|
||||||
|
? TimeSpan.FromMinutes(1)
|
||||||
|
: _appConfig.ServerDataCollectionInterval);
|
||||||
|
|
||||||
while (!cancellationToken.IsCancellationRequested)
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -8,6 +9,8 @@ using Data.Models.Server;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
using SharedLibraryCore.Interfaces;
|
using SharedLibraryCore.Interfaces;
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
||||||
|
|
||||||
@ -19,16 +22,19 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IDataValueCache<EFServerSnapshot, int> _snapshotCache;
|
private readonly IDataValueCache<EFServerSnapshot, int> _snapshotCache;
|
||||||
private readonly IDataValueCache<EFClient, (int, int)> _serverStatsCache;
|
private readonly IDataValueCache<EFClient, (int, int)> _serverStatsCache;
|
||||||
|
private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache;
|
||||||
|
|
||||||
private readonly TimeSpan? _cacheTimeSpan =
|
private readonly TimeSpan? _cacheTimeSpan =
|
||||||
Utilities.IsDevelopment ? TimeSpan.FromSeconds(1) : (TimeSpan?) TimeSpan.FromMinutes(1);
|
Utilities.IsDevelopment ? TimeSpan.FromSeconds(1) : (TimeSpan?) TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, int> snapshotCache,
|
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, int> snapshotCache,
|
||||||
IDataValueCache<EFClient, (int, int)> serverStatsCache)
|
IDataValueCache<EFClient, (int, int)> serverStatsCache,
|
||||||
|
IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> clientHistoryCache)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_snapshotCache = snapshotCache;
|
_snapshotCache = snapshotCache;
|
||||||
_serverStatsCache = serverStatsCache;
|
_serverStatsCache = serverStatsCache;
|
||||||
|
_clientHistoryCache = clientHistoryCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
public async Task<int> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
|
||||||
@ -45,14 +51,14 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
{
|
{
|
||||||
maxClients = await snapshots.Where(snapshot => snapshot.ServerId == serverId)
|
maxClients = await snapshots.Where(snapshot => snapshot.ServerId == serverId)
|
||||||
.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||||
.MaxAsync(snapshot => (int?)snapshot.ClientCount, cancellationToken) ?? 0;
|
.MaxAsync(snapshot => (int?) snapshot.ClientCount, cancellationToken) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
maxClients = await snapshots.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
maxClients = await snapshots.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||||
.GroupBy(snapshot => snapshot.PeriodBlock)
|
.GroupBy(snapshot => snapshot.PeriodBlock)
|
||||||
.Select(grp => grp.Sum(snapshot => (int?)snapshot.ClientCount))
|
.Select(grp => grp.Sum(snapshot => (int?) snapshot.ClientCount))
|
||||||
.MaxAsync(cancellationToken) ?? 0;
|
.MaxAsync(cancellationToken) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,5 +101,43 @@ namespace IW4MAdmin.Application.Misc
|
|||||||
return (0, 0);
|
return (0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<ClientHistoryInfo>> ClientHistoryAsync(TimeSpan? overPeriod = null, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
_clientHistoryCache.SetCacheItem(async (set, cancellationToken) =>
|
||||||
|
{
|
||||||
|
var oldestEntry = overPeriod.HasValue
|
||||||
|
? DateTime.UtcNow - overPeriod.Value
|
||||||
|
: DateTime.UtcNow.AddHours(-12);
|
||||||
|
|
||||||
|
var history = await set.Where(snapshot => snapshot.CapturedAt >= oldestEntry)
|
||||||
|
.Select(snapshot =>
|
||||||
|
new
|
||||||
|
{
|
||||||
|
snapshot.ServerId,
|
||||||
|
snapshot.CapturedAt,
|
||||||
|
snapshot.ClientCount
|
||||||
|
})
|
||||||
|
.OrderBy(snapshot => snapshot.CapturedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return history.GroupBy(snapshot => snapshot.ServerId).Select(byServer => new ClientHistoryInfo
|
||||||
|
{
|
||||||
|
ServerId = byServer.Key,
|
||||||
|
ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot()
|
||||||
|
{Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount}).ToList()
|
||||||
|
}).ToList();
|
||||||
|
}, nameof(_clientHistoryCache), TimeSpan.MaxValue);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await _clientHistoryCache.GetCacheItem(nameof(_clientHistoryCache), token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Could not retrieve data for {Name}", nameof(ClientHistoryAsync));
|
||||||
|
return Enumerable.Empty<ClientHistoryInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,7 +8,7 @@
|
|||||||
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.Data</PackageId>
|
||||||
<Title>RaidMax.IW4MAdmin.Data</Title>
|
<Title>RaidMax.IW4MAdmin.Data</Title>
|
||||||
<Authors />
|
<Authors />
|
||||||
<PackageVersion>1.0.5</PackageVersion>
|
<PackageVersion>1.0.6</PackageVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -22,33 +22,36 @@ namespace Data.Helpers
|
|||||||
public TimeSpan ExpirationTime { get; set; }
|
public TimeSpan ExpirationTime { get; set; }
|
||||||
public Func<DbSet<T>, CancellationToken, Task<V>> Getter { get; set; }
|
public Func<DbSet<T>, CancellationToken, Task<V>> Getter { get; set; }
|
||||||
public V Value { get; set; }
|
public V Value { get; set; }
|
||||||
public bool IsExpired => (DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
|
|
||||||
|
public bool IsExpired => ExpirationTime != TimeSpan.MaxValue &&
|
||||||
|
(DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public DataValueCache(ILogger<DataValueCache<T, V>> logger, IDatabaseContextFactory contextFactory)
|
public DataValueCache(ILogger<DataValueCache<T, V>> logger, IDatabaseContextFactory contextFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_contextFactory = contextFactory;
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> getter, string key, TimeSpan? expirationTime = null)
|
public void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> getter, string key,
|
||||||
|
TimeSpan? expirationTime = null)
|
||||||
{
|
{
|
||||||
if (_cacheStates.ContainsKey(key))
|
if (_cacheStates.ContainsKey(key))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Cache key {key} is already added", key);
|
_logger.LogDebug("Cache key {key} is already added", key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = new CacheState()
|
var state = new CacheState()
|
||||||
{
|
{
|
||||||
Key = key,
|
Key = key,
|
||||||
Getter = getter,
|
Getter = getter,
|
||||||
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
|
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
|
||||||
};
|
};
|
||||||
|
|
||||||
_cacheStates.Add(key, state);
|
_cacheStates.Add(key, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<V> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
|
public async Task<V> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!_cacheStates.ContainsKey(keyName))
|
if (!_cacheStates.ContainsKey(keyName))
|
||||||
@ -58,7 +61,7 @@ namespace Data.Helpers
|
|||||||
|
|
||||||
var state = _cacheStates[keyName];
|
var state = _cacheStates[keyName];
|
||||||
|
|
||||||
if (state.IsExpired)
|
if (state.IsExpired || state.Value == null)
|
||||||
{
|
{
|
||||||
await RunCacheUpdate(state, cancellationToken);
|
await RunCacheUpdate(state, cancellationToken);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.27.1" PrivateAssets="All" />
|
<PackageReference Include="RaidMax.IW4MAdmin.SharedLibraryCore" Version="2021.8.29.1" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -141,7 +141,15 @@ namespace SharedLibraryCore.Configuration
|
|||||||
[LocalizedDisplayName(("WEBFRONT_CONFIGURATION_ENABLE_PRIVILEGED_USER_PRIVACY"))]
|
[LocalizedDisplayName(("WEBFRONT_CONFIGURATION_ENABLE_PRIVILEGED_USER_PRIVACY"))]
|
||||||
public bool EnablePrivilegedUserPrivacy { get; set; }
|
public bool EnablePrivilegedUserPrivacy { get; set; }
|
||||||
|
|
||||||
|
[ConfigurationIgnore]
|
||||||
public bool EnableImplicitAccountLinking { get; set; } = false;
|
public bool EnableImplicitAccountLinking { get; set; } = false;
|
||||||
|
|
||||||
|
[ConfigurationIgnore]
|
||||||
|
public TimeSpan MaxClientHistoryTime { get; set; } = TimeSpan.FromHours(12);
|
||||||
|
|
||||||
|
[ConfigurationIgnore]
|
||||||
|
public TimeSpan ServerDataCollectionInterval { get; set; } = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
public Dictionary<Permission, string> OverridePermissionLevelNames { get; set; } = Enum
|
public Dictionary<Permission, string> OverridePermissionLevelNames { get; set; } = Enum
|
||||||
.GetValues(typeof(Permission))
|
.GetValues(typeof(Permission))
|
||||||
.Cast<Permission>()
|
.Cast<Permission>()
|
||||||
|
18
SharedLibraryCore/Dtos/ClientHistoryInfo.cs
Normal file
18
SharedLibraryCore/Dtos/ClientHistoryInfo.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SharedLibraryCore.Dtos
|
||||||
|
{
|
||||||
|
public class ClientHistoryInfo
|
||||||
|
{
|
||||||
|
public long ServerId { get; set; }
|
||||||
|
public List<ClientCountSnapshot> ClientCounts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClientCountSnapshot
|
||||||
|
{
|
||||||
|
public DateTime Time { get; set; }
|
||||||
|
public string TimeString => Time.ToString("yyyy-MM-ddTHH:mm:ssZ");
|
||||||
|
public int ClientCount { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,7 @@ namespace SharedLibraryCore.Dtos
|
|||||||
public List<ChatInfo> ChatHistory { get; set; }
|
public List<ChatInfo> ChatHistory { get; set; }
|
||||||
public List<PlayerInfo> Players { get; set; }
|
public List<PlayerInfo> Players { get; set; }
|
||||||
public Helpers.PlayerHistory[] PlayerHistory { get; set; }
|
public Helpers.PlayerHistory[] PlayerHistory { get; set; }
|
||||||
|
public List<ClientCountSnapshot> ClientCountHistory { get; set; }
|
||||||
public long ID { get; set; }
|
public long ID { get; set; }
|
||||||
public bool Online { get; set; }
|
public bool Online { get; set; }
|
||||||
public string ConnectProtocolUrl { get; set; }
|
public string ConnectProtocolUrl { get; set; }
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Helpers
|
namespace SharedLibraryCore.Helpers
|
||||||
{
|
{
|
||||||
@ -31,5 +32,14 @@ namespace SharedLibraryCore.Helpers
|
|||||||
/// Used by CanvasJS as a point on the y axis
|
/// Used by CanvasJS as a point on the y axis
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int y { get; }
|
public int y { get; }
|
||||||
|
|
||||||
|
public ClientCountSnapshot ToClientCountSnapshot()
|
||||||
|
{
|
||||||
|
return new ClientCountSnapshot
|
||||||
|
{
|
||||||
|
ClientCount = y,
|
||||||
|
Time = When
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using SharedLibraryCore.Dtos;
|
||||||
|
using SharedLibraryCore.Helpers;
|
||||||
|
|
||||||
namespace SharedLibraryCore.Interfaces
|
namespace SharedLibraryCore.Interfaces
|
||||||
{
|
{
|
||||||
@ -22,8 +25,16 @@ namespace SharedLibraryCore.Interfaces
|
|||||||
/// Gets the total number of clients connected and total clients connected in the given time frame
|
/// Gets the total number of clients connected and total clients connected in the given time frame
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="overPeriod">how far in the past to search</param>
|
/// <param name="overPeriod">how far in the past to search</param>
|
||||||
/// <param name="token"></param>
|
/// <param name="token">CancellationToken</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, CancellationToken token = default);
|
Task<(int, int)> ClientCountsAsync(TimeSpan? overPeriod = null, CancellationToken token = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the client count and history over the given period
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="overPeriod">how far in the past to search</param>
|
||||||
|
/// <param name="token">CancellationToken</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<IEnumerable<ClientHistoryInfo>> ClientHistoryAsync(TimeSpan? overPeriod = null, CancellationToken token = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
<PackageId>RaidMax.IW4MAdmin.SharedLibraryCore</PackageId>
|
||||||
<Version>2021.8.27.1</Version>
|
<Version>2021.8.29.1</Version>
|
||||||
<Authors>RaidMax</Authors>
|
<Authors>RaidMax</Authors>
|
||||||
<Company>Forever None</Company>
|
<Company>Forever None</Company>
|
||||||
<Configurations>Debug;Release;Prerelease</Configurations>
|
<Configurations>Debug;Release;Prerelease</Configurations>
|
||||||
@ -19,7 +19,7 @@
|
|||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
<Description>Shared Library for IW4MAdmin</Description>
|
<Description>Shared Library for IW4MAdmin</Description>
|
||||||
<PackageVersion>2021.8.27.1</PackageVersion>
|
<PackageVersion>2021.8.29.1</PackageVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Prerelease|AnyCPU'">
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.10" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="3.1.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.10" />
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="3.1.10" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.5" />
|
<PackageReference Include="RaidMax.IW4MAdmin.Data" Version="1.0.6" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
|
||||||
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
<PackageReference Include="SimpleCrypto.NetCore" Version="1.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,45 +1,94 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SharedLibraryCore;
|
using SharedLibraryCore;
|
||||||
using SharedLibraryCore.Dtos;
|
using SharedLibraryCore.Dtos;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Data.Models.Client.Stats;
|
using Data.Models.Client.Stats;
|
||||||
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
using SharedLibraryCore.Configuration;
|
||||||
|
using SharedLibraryCore.Interfaces;
|
||||||
using static SharedLibraryCore.Server;
|
using static SharedLibraryCore.Server;
|
||||||
|
|
||||||
namespace WebfrontCore.ViewComponents
|
namespace WebfrontCore.ViewComponents
|
||||||
{
|
{
|
||||||
public class ServerListViewComponent : ViewComponent
|
public class ServerListViewComponent : ViewComponent
|
||||||
{
|
{
|
||||||
|
private readonly IServerDataViewer _serverDataViewer;
|
||||||
|
private readonly ApplicationConfiguration _appConfig;
|
||||||
|
|
||||||
|
public ServerListViewComponent(IServerDataViewer serverDataViewer,
|
||||||
|
ApplicationConfiguration applicationConfiguration)
|
||||||
|
{
|
||||||
|
_serverDataViewer = serverDataViewer;
|
||||||
|
_appConfig = applicationConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
public IViewComponentResult Invoke(Game? game)
|
public IViewComponentResult Invoke(Game? game)
|
||||||
{
|
{
|
||||||
var servers = Program.Manager.GetServers().Where(_server => !game.HasValue || _server.GameName == game);
|
var servers = Program.Manager.GetServers().Where(server => !game.HasValue || server.GameName == game);
|
||||||
|
|
||||||
var serverInfo = servers.Select(s => new ServerInfo()
|
var serverInfo = new List<ServerInfo>();
|
||||||
|
|
||||||
|
foreach (var server in servers)
|
||||||
{
|
{
|
||||||
Name = s.Hostname,
|
var serverId = server.GetIdForServer().Result;
|
||||||
ID = s.EndPoint,
|
var clientHistory = _serverDataViewer.ClientHistoryAsync(_appConfig.MaxClientHistoryTime,
|
||||||
Port = s.Port,
|
CancellationToken.None).Result
|
||||||
Map = s.CurrentMap.Alias,
|
.FirstOrDefault(history => history.ServerId == serverId) ??
|
||||||
ClientCount = s.ClientNum,
|
new ClientHistoryInfo
|
||||||
MaxClients = s.MaxClients,
|
{
|
||||||
GameType = s.Gametype,
|
ServerId = serverId
|
||||||
PlayerHistory = s.ClientHistory.ToArray(),
|
};
|
||||||
Players = s.GetClientsAsList()
|
|
||||||
.Select(p => new PlayerInfo()
|
var counts = clientHistory.ClientCounts?.AsEnumerable() ?? Enumerable.Empty<ClientCountSnapshot>();
|
||||||
|
|
||||||
|
if (server.ClientHistory.Count > 0)
|
||||||
{
|
{
|
||||||
Name = p.Name,
|
counts = counts.Union(server.ClientHistory
|
||||||
ClientId = p.ClientId,
|
.Select(history => history.ToClientCountSnapshot()).Where(history =>
|
||||||
Level = p.Level.ToLocalizedLevelName(),
|
history.Time > clientHistory.ClientCounts.Last().Time));
|
||||||
LevelInt = (int)p.Level,
|
}
|
||||||
Tag = p.Tag,
|
|
||||||
ZScore = p.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers.StatManager.CLIENT_STATS_KEY)?.ZScore
|
serverInfo.Add(new ServerInfo()
|
||||||
}).ToList(),
|
{
|
||||||
ChatHistory = s.ChatHistory.ToList(),
|
Name = server.Hostname,
|
||||||
Online = !s.Throttled,
|
ID = server.EndPoint,
|
||||||
IPAddress = $"{(s.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : s.IP)}:{s.Port}",
|
Port = server.Port,
|
||||||
ConnectProtocolUrl = s.EventParser.URLProtocolFormat.FormatExt(s.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : s.IP, s.Port)
|
Map = server.CurrentMap.Alias,
|
||||||
}).ToList();
|
ClientCount = server.ClientNum,
|
||||||
|
MaxClients = server.MaxClients,
|
||||||
|
GameType = server.Gametype,
|
||||||
|
PlayerHistory = server.ClientHistory.ToArray(),
|
||||||
|
Players = server.GetClientsAsList()
|
||||||
|
.Select(p => new PlayerInfo()
|
||||||
|
{
|
||||||
|
Name = p.Name,
|
||||||
|
ClientId = p.ClientId,
|
||||||
|
Level = p.Level.ToLocalizedLevelName(),
|
||||||
|
LevelInt = (int) p.Level,
|
||||||
|
Tag = p.Tag,
|
||||||
|
ZScore = p.GetAdditionalProperty<EFClientStatistics>(IW4MAdmin.Plugins.Stats.Helpers
|
||||||
|
.StatManager
|
||||||
|
.CLIENT_STATS_KEY)?.ZScore
|
||||||
|
}).ToList(),
|
||||||
|
ChatHistory = server.ChatHistory.ToList(),
|
||||||
|
ClientCountHistory =
|
||||||
|
counts.Where(history => history.Time >= DateTime.UtcNow - _appConfig.MaxClientHistoryTime)
|
||||||
|
.ToList(),
|
||||||
|
Online = !server.Throttled,
|
||||||
|
IPAddress =
|
||||||
|
$"{(server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP)}:{server.Port}",
|
||||||
|
ConnectProtocolUrl = server.EventParser.URLProtocolFormat.FormatExt(
|
||||||
|
server.ResolvedIpEndPoint.Address.IsInternal() ? Program.Manager.ExternalIPAddress : server.IP,
|
||||||
|
server.Port)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return View("_List", serverInfo);
|
return View("_List", serverInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -50,5 +50,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row server-history mb-4">
|
<div class="row server-history mb-4">
|
||||||
<div class="server-history-row" id="server_history_@Model.ID" data-serverid="@Model.ID" data-clienthistory='@Html.Raw(Json.Serialize(Model.PlayerHistory))' data-online="@Model.Online"></div>
|
<div class="server-history-row" id="server_history_@Model.ID" data-serverid="@Model.ID"
|
||||||
|
data-clienthistory='@Html.Raw(Json.Serialize(Model.PlayerHistory))'
|
||||||
|
data-clienthistory-ex='@Html.Raw(Json.Serialize(Model.ClientCountHistory))'
|
||||||
|
data-online="@Model.Online"></div>
|
||||||
</div>
|
</div>
|
@ -2,7 +2,8 @@
|
|||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// thanks to canvasjs :(
|
// thanks to canvasjs :(
|
||||||
playerHistory.forEach(function (item, i) {
|
playerHistory.forEach(function (item, i) {
|
||||||
playerHistory[i].x = new Date(playerHistory[i].x);
|
playerHistory[i].x = new Date(playerHistory[i].timeString);
|
||||||
|
playerHistory[i].y = playerHistory[i].clientCount;
|
||||||
});
|
});
|
||||||
|
|
||||||
return new CanvasJS.Chart(`server_history_${i}`, {
|
return new CanvasJS.Chart(`server_history_${i}`, {
|
||||||
@ -84,7 +85,7 @@ $(document).ready(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('.server-history-row').each(function (index, element) {
|
$('.server-history-row').each(function (index, element) {
|
||||||
let clientHistory = $(this).data('clienthistory');
|
let clientHistory = $(this).data('clienthistory-ex');
|
||||||
let serverId = $(this).data('serverid');
|
let serverId = $(this).data('serverid');
|
||||||
let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text());
|
let maxClients = parseInt($('#server_header_' + serverId + ' .server-maxclients').text());
|
||||||
let primaryColor = $('title').css('background-color');
|
let primaryColor = $('title').css('background-color');
|
||||||
|
Loading…
Reference in New Issue
Block a user