zio fork 1.8.2b-zio10: rank tags match CS2-Tags, log rank changes to sa_rank_log
Some checks failed
Build and Publish / build (push) Has been cancelled
Build and Publish / publish (push) Has been cancelled

This commit is contained in:
Astra 2026-09-29 16:56:38 +01:00
parent cf1090cbd2
commit f46ccf18ce
5 changed files with 100 additions and 17 deletions

View file

@ -247,7 +247,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
/// </summary>
/// <param name="playerSteamId">The SteamID of the admin to delete.</param>
/// <param name="globalDelete">Whether to delete the admin globally or only for the current server.</param>
public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false)
/// <param name="adminSteamId">zio: SteamID64 of who removed them, 0 for Console, for the rank log.</param>
/// <param name="adminName">zio: name of who removed them, for the rank log.</param>
public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false, ulong adminSteamId = 0, string adminName = "Console")
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(playerSteamId)) return;
@ -255,8 +257,14 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
// zio: note the rank and name before they're gone, for the rank log.
var before = await GetRank(connection, playerSteamId);
var playerName = await connection.ExecuteScalarAsync<string?>(
"SELECT player_name FROM sa_admins WHERE player_steamid = @playerSteamId ORDER BY server_id IS NULL DESC, id LIMIT 1",
new { playerSteamId }) ?? "";
var sql = databaseProvider.GetDeleteAdminQuery(globalDelete);
await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId });
await LogRankChange(connection, playerSteamId, playerName, before, await GetRank(connection, playerSteamId), adminSteamId, adminName);
}
catch (Exception ex)
{
@ -273,7 +281,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
/// <param name="immunity">Immunity level.</param>
/// <param name="time">Duration in minutes for admin expiration; 0 means permanent.</param>
/// <param name="globalAdmin">Whether the admin is global or server-specific.</param>
public async Task AddAdminBySteamId(string playerSteamId, string playerName, List<string> flagsList, int immunity = 0, int time = 0, bool globalAdmin = false)
/// <param name="adminSteamId">zio: SteamID64 of who added them, 0 for Console, for the rank log.</param>
/// <param name="adminName">zio: name of who added them, for the rank log.</param>
public async Task AddAdminBySteamId(string playerSteamId, string playerName, List<string> flagsList, int immunity = 0, int time = 0, bool globalAdmin = false, ulong adminSteamId = 0, string adminName = "Console")
{
if (databaseProvider == null) return;
@ -285,6 +295,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var before = await GetRank(connection, playerSteamId); // zio: for the rank log
var insertAdminSql = databaseProvider.GetAddAdminQuery();
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
@ -307,6 +318,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
});
}
await LogRankChange(connection, playerSteamId, playerName, before, await GetRank(connection, playerSteamId), adminSteamId, adminName);
await Server.NextWorldUpdateAsync(() =>
{
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
@ -318,6 +331,69 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
}
}
// zio: rank history in sa_rank_log, a table owned by our web panel (simpleadmin-web's
// sql/rank-log.sql), which logs its own changes there too. A player's rank is their
// highest-immunity group; a change is only logged when that changes.
private sealed class RankRow
{
public string Name { get; set; } = "";
public int Immunity { get; set; }
}
private static bool _rankLogMissingWarned;
// zio: the player's current rank, or null. Only unexpired admin rows and groups that apply to
// this server count, the same set SimpleAdmin loads.
private static async Task<RankRow?> GetRank(System.Data.Common.DbConnection connection, string playerSteamId)
{
return await connection.QueryFirstOrDefaultAsync<RankRow>("""
SELECT g.name AS Name, g.immunity AS Immunity
FROM sa_admins a
JOIN sa_admins_flags f ON f.admin_id = a.id
JOIN sa_groups g ON g.name = f.flag
JOIN sa_groups_servers gs ON gs.group_id = g.id AND (gs.server_id IS NULL OR gs.server_id = @serverid)
WHERE a.player_steamid = @playerSteamId AND (a.ends IS NULL OR a.ends > @now)
AND (a.server_id IS NULL OR a.server_id = @serverid)
ORDER BY g.immunity DESC, g.id LIMIT 1
""", new { playerSteamId, now = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
}
// zio: records a rank change in sa_rank_log. Never fails the admin change itself: if the table
// is missing (the panel hasn't created it), it warns once and carries on.
private static async Task LogRankChange(System.Data.Common.DbConnection connection, string playerSteamId, string playerName,
RankRow? before, RankRow? after, ulong adminSteamId, string adminName)
{
if (before?.Name == after?.Name) return;
var kind = before == null ? "added"
: after == null ? "removed"
: after.Immunity > before.Immunity ? "promoted"
: after.Immunity < before.Immunity ? "demoted"
: "changed";
try
{
await connection.ExecuteAsync("""
INSERT INTO sa_rank_log (player_steamid, player_name, old_rank, new_rank, kind, admin_steamid, admin_name, source, created)
VALUES (@playerSteamId, @playerName, @oldRank, @newRank, @kind, @adminSteamId, @adminName, 'game', @created)
""", new
{
playerSteamId = ulong.Parse(playerSteamId),
playerName,
oldRank = before?.Name,
newRank = after?.Name,
kind,
adminSteamId,
adminName,
created = Time.ActualDateTime()
});
}
catch (Exception ex)
{
if (_rankLogMissingWarned) return;
_rankLogMissingWarned = true;
CS2_SimpleAdmin._logger?.LogWarning($"Couldn't write sa_rank_log (the web panel creates it): {ex.Message}");
}
}
/// <summary>
/// Adds a new group with flags and immunity asynchronously.
/// </summary>