* Add admin logging, models, migrations * Add logging damage changes * Add Log admin flag, LogFilter, Logs admin menu tab, message Refactor admin logging API * Change admin log get method names * Fix the name again * Minute amount of reorganization * Reset Postgres db snapshot * Reset Sqlite db snapshot * Make AdminLog have a composite primary key of round, id * Minute cleanup * Change admin system to do a type check instead of index check * Make admin logs use C# 10 interpolated string handlers * Implement UI on its own window Custom controls Searching Add admin log converters * Implement limits into the query * Change logs to be put into an OutputPanel instead for text wrapping * Add log <-> player m2m relationship back * UI improvements, make text wrap, add separators * Remove entity prefix from damaged log * Add explicit m2m model, fix any players filter * Add debug command to test bulk adding logs * Admin logs now just kinda go * Add histogram for database update time * Make admin log system update run every 5 seconds * Add a cap to the log queue and a metric for how many times it has been reached * Add metric for logs sent in a round * Make cvars out of admin logs queue send delay and cap * Merge fixes * Reset some changes * Add test for adding and getting a single log * Add tests for bulk adding logs * Add test for querying logs * Add CallerArgumentExpression to LogStringHandler methods and test * Improve UI, fix SQLite, add searching by round * Add entities to admin logs * Move distinct after orderby * Add migrations * ef core eat my ass * Add cvar for client logs batch size * Sort logs from newest to oldest by default * Merge fixes * Reorganize tests and add one for date ordering * Add note to log types to not change their numeric values * Add impacts to logs, better UI filtering * Make log add callable from shared for convenience * Get current round id directly from game ticker * Revert namespace change for DamageableSystem
205 lines
5.9 KiB
C#
205 lines
5.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Content.Server.Administration.Managers;
|
|
using Content.Server.EUI;
|
|
using Content.Server.GameTicking;
|
|
using Content.Shared.Administration;
|
|
using Content.Shared.Administration.Logs;
|
|
using Content.Shared.CCVar;
|
|
using Content.Shared.Eui;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Log;
|
|
using static Content.Shared.Administration.AdminLogsEuiMsg;
|
|
|
|
namespace Content.Server.Administration.Logs;
|
|
|
|
public sealed class AdminLogsEui : BaseEui
|
|
{
|
|
[Dependency] private readonly IAdminManager _adminManager = default!;
|
|
[Dependency] private readonly ILogManager _logManager = default!;
|
|
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
|
|
|
private readonly ISawmill _sawmill;
|
|
private readonly AdminLogSystem _logSystem;
|
|
|
|
private int _clientBatchSize;
|
|
private bool _isLoading = true;
|
|
private readonly Dictionary<Guid, string> _players = new();
|
|
private CancellationTokenSource _logSendCancellation = new();
|
|
private LogFilter _filter;
|
|
|
|
public AdminLogsEui()
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
_sawmill = _logManager.GetSawmill(AdminLogSystem.SawmillId);
|
|
|
|
_configuration.OnValueChanged(CCVars.AdminLogsClientBatchSize, ClientBatchSizeChanged, true);
|
|
|
|
_logSystem = EntitySystem.Get<AdminLogSystem>();
|
|
_filter = new LogFilter
|
|
{
|
|
CancellationToken = _logSendCancellation.Token,
|
|
Limit = _clientBatchSize
|
|
};
|
|
}
|
|
|
|
public int CurrentRoundId => EntitySystem.Get<GameTicker>().RoundId;
|
|
|
|
public override async void Opened()
|
|
{
|
|
base.Opened();
|
|
|
|
_adminManager.OnPermsChanged += OnPermsChanged;
|
|
|
|
var roundId = _filter.Round ?? EntitySystem.Get<GameTicker>().RoundId;
|
|
LoadFromDb(roundId);
|
|
}
|
|
|
|
private void ClientBatchSizeChanged(int value)
|
|
{
|
|
_clientBatchSize = value;
|
|
}
|
|
|
|
private void OnPermsChanged(AdminPermsChangedEventArgs args)
|
|
{
|
|
if (args.Player == Player && !_adminManager.HasAdminFlag(Player, AdminFlags.Logs))
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
|
|
public override EuiStateBase GetNewState()
|
|
{
|
|
if (_isLoading)
|
|
{
|
|
return new AdminLogsEuiState(CurrentRoundId, new Dictionary<Guid, string>())
|
|
{
|
|
IsLoading = true
|
|
};
|
|
}
|
|
|
|
var state = new AdminLogsEuiState(CurrentRoundId, _players);
|
|
|
|
return state;
|
|
}
|
|
|
|
public override async void HandleMessage(EuiMessageBase msg)
|
|
{
|
|
if (!_adminManager.HasAdminFlag(Player, AdminFlags.Logs))
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (msg)
|
|
{
|
|
case Close _:
|
|
{
|
|
Close();
|
|
break;
|
|
}
|
|
case LogsRequest request:
|
|
{
|
|
_sawmill.Info($"Admin log request from admin with id {Player.UserId.UserId} and name {Player.Name}");
|
|
|
|
_logSendCancellation.Cancel();
|
|
_logSendCancellation = new CancellationTokenSource();
|
|
_filter = new LogFilter
|
|
{
|
|
CancellationToken = _logSendCancellation.Token,
|
|
Round = request.RoundId,
|
|
Types = request.Types,
|
|
Impacts = request.Impacts,
|
|
Before = request.Before,
|
|
After = request.After,
|
|
AnyPlayers = request.AnyPlayers,
|
|
AllPlayers = request.AllPlayers,
|
|
LastLogId = 0,
|
|
Limit = _clientBatchSize
|
|
};
|
|
|
|
var roundId = _filter.Round ??= EntitySystem.Get<GameTicker>().RoundId;
|
|
LoadFromDb(roundId);
|
|
|
|
SendLogs(true);
|
|
break;
|
|
}
|
|
case NextLogsRequest:
|
|
{
|
|
_sawmill.Info($"Admin log next batch request from admin with id {Player.UserId.UserId} and name {Player.Name}");
|
|
|
|
SendLogs(false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private async void SendLogs(bool replace)
|
|
{
|
|
var logs = new List<SharedAdminLog>(_clientBatchSize);
|
|
|
|
await Task.Run(async () =>
|
|
{
|
|
var results = await Task.Run(() => _logSystem.All(_filter));
|
|
|
|
await foreach (var record in results.WithCancellation(_logSendCancellation.Token))
|
|
{
|
|
var log = new SharedAdminLog(record.Id, record.Type, record.Impact, record.Date, record.Message, record.Players);
|
|
logs.Add(log);
|
|
}
|
|
}, _filter.CancellationToken);
|
|
|
|
if (logs.Count > 0)
|
|
{
|
|
var largestId = _filter.DateOrder switch
|
|
{
|
|
DateOrder.Ascending => ^1,
|
|
DateOrder.Descending => 0,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(_filter.DateOrder), _filter.DateOrder, null)
|
|
};
|
|
|
|
_filter.LastLogId = logs[largestId].Id;
|
|
}
|
|
|
|
var message = new NewLogs(logs.ToArray(), replace);
|
|
|
|
SendMessage(message);
|
|
}
|
|
|
|
public override void Closed()
|
|
{
|
|
base.Closed();
|
|
|
|
_configuration.UnsubValueChanged(CCVars.AdminLogsClientBatchSize, ClientBatchSizeChanged);
|
|
_adminManager.OnPermsChanged -= OnPermsChanged;
|
|
|
|
_logSendCancellation.Cancel();
|
|
_logSendCancellation.Dispose();
|
|
}
|
|
|
|
private async void LoadFromDb(int roundId)
|
|
{
|
|
_isLoading = true;
|
|
StateDirty();
|
|
|
|
var round = await Task.Run(() => _logSystem.Round(roundId));
|
|
var players = round.Players
|
|
.ToDictionary(player => player.UserId, player => player.LastSeenUserName);
|
|
|
|
_players.Clear();
|
|
|
|
foreach (var (id, name) in players)
|
|
{
|
|
_players.Add(id, name);
|
|
}
|
|
|
|
_isLoading = false;
|
|
StateDirty();
|
|
}
|
|
}
|