<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="1" />
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
- <BoxContainer Orientation="Horizontal" HorizontalAlignment="Right">
- <Button Margin="0 0 10 0" Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}"/>
- <Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" />
- <Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" />
+ <BoxContainer Orientation="Horizontal" HorizontalExpand="True">
+ <Button Margin="0 0 10 0" Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}"
+ HorizontalAlignment="Left"/>
+ <Button Visible="False" Name="Bans" />
+ <Button Visible="False" Name="Notes" />
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" />
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" />
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" />
using Content.Client.Administration.UI.Tabs.AdminTab;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.Bwoink;
-using Content.Client.UserInterface.Systems.Chat.Controls;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network;
using Robust.Shared.Utility;
ChannelSelector.OnSelectionChanged += sel =>
{
_currentPlayer = sel;
- if (sel is not null)
+ if (sel == null)
+ {
+ SetPlayerActionsVisibility(false);
+ }
+ else
{
SwitchToChannel(sel.SessionId);
+
+ SetPlayerActionsVisibility(true);
+ Bans.Text = Loc.GetString("admin-player-actions-bans", ("count", sel.Bans));
+ Notes.Text = Loc.GetString("admin-player-actions-notes", ("count", sel.Notes));
}
ChannelSelector.PlayerListContainer.DirtyList();
{
uiController.PopOut();
};
+
+ SetPlayerActionsVisibility(_currentPlayer != null);
}
private Dictionary<Control, (CancellationTokenSource cancellation, string? originalText)> Confirmations { get; } = new();
{
foreach (var bw in BwoinkArea.Children)
bw.Visible = false;
- var panel = AHelpHelper.EnsurePanel(ch);
- panel.Visible = true;
+ AHelpHelper.EnsurePanel(ch).Visible = true;
}
private bool TryConfirm(Button button)
button.Text = Loc.GetString("admin-player-actions-confirm");
return false;
}
+
+ private void SetPlayerActionsVisibility(bool visible)
+ {
+ Bans.Visible = visible;
+ Notes.Visible = visible;
+ Kick.Visible = visible;
+ Ban.Visible = visible;
+ Respawn.Visible = visible;
+ Teleport.Visible = visible;
+ }
}
}
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.Bwoink"
- SetSize="900 500"
+ SetSize="1000 500"
HeaderClass="windowHeaderAlert"
TitleClass="windowTitleAlert"
Title="{Loc 'bwoink-user-title'}" >
-using System.Text;
-using System.Threading;
-using Content.Client.Administration.Managers;
-using Content.Client.Administration.UI.CustomControls;
-using Content.Client.Administration.UI.Tabs.AdminTab;
-using Content.Client.Stylesheets;
-using Content.Client.UserInterface.Systems.Bwoink;
-using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
-using Robust.Client.Console;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Network;
-using Robust.Shared.Utility;
-using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Administration.UI.Bwoink
{
return await _db.GetAdminNotes(player);
}
+ public async Task<int> CountNotes(Guid player)
+ {
+ return await _db.CountAdminNotes(player);
+ }
+
public async Task<string> GetPlayerName(Guid player)
{
return (await _db.GetPlayerRecordByUserId(new NetUserId(player)))?.LastSeenUserName ?? string.Empty;
Task DeleteNote(int noteId, IPlayerSession deletedBy);
Task ModifyNote(int noteId, IPlayerSession editedBy, string message);
Task<List<AdminNote>> GetNotes(Guid player);
+ Task<int> CountNotes(Guid player);
Task<string> GetPlayerName(Guid player);
}
using System.Globalization;
using System.Linq;
+using System.Threading.Tasks;
using Content.Server.Administration.Managers;
-using Content.Server.GameTicking.Events;
+using Content.Server.Administration.Notes;
+using Content.Server.Database;
using Content.Server.IdentityManagement;
using Content.Server.Players;
using Content.Server.Roles;
using Robust.Server.Player;
using Robust.Shared.Enums;
using Robust.Shared.Network;
+using Job = Content.Server.Roles.Job;
namespace Content.Server.Administration.Systems
{
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
+ [Dependency] private readonly IServerDbManager _db = default!;
+ [Dependency] private readonly IAdminNotesManager _notes = default!;
private readonly Dictionary<NetUserId, PlayerInfo> _playerList = new();
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestartCleanup);
}
- private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
+ private async void OnRoundRestartCleanup(RoundRestartCleanupEvent ev)
{
_roundActivePlayers.Clear();
return;
_playerManager.TryGetSessionById(id, out var session);
- _playerList[id] = GetPlayerInfo(playerData, session);
+ _playerList[id] = await GetPlayerInfo(playerData, session);
}
var updateEv = new FullPlayerListEvent() { PlayersInfo = _playerList.Values.ToList() };
}
}
- public void UpdatePlayerList(IPlayerSession player)
+ public async void UpdatePlayerList(IPlayerSession player)
{
- _playerList[player.UserId] = GetPlayerInfo(player.Data, player);
+ _playerList[player.UserId] = await GetPlayerInfo(player.Data, player);
var playerInfoChangedEvent = new PlayerInfoChangedEvent
{
RaiseNetworkEvent(ev, playerSession.ConnectedClient);
}
- private PlayerInfo GetPlayerInfo(IPlayerData data, IPlayerSession? session)
+ private async Task<PlayerInfo> GetPlayerInfo(IPlayerData data, IPlayerSession? session)
{
var name = data.UserName;
var entityName = string.Empty;
var connected = session != null && session.Status is SessionStatus.Connected or SessionStatus.InGame;
+ var bans = await _db.CountServerBansAsync(null, data.UserId, null);
+ var notes = await _notes.CountNotes(data.UserId);
+
return new PlayerInfo(name, entityName, identityName, startingRole, antag, session?.AttachedEntity, data.UserId,
- connected, _roundActivePlayers.Contains(data.UserId));
+ connected, _roundActivePlayers.Contains(data.UserId), bans, notes);
}
}
}
ImmutableArray<byte>? hwId,
bool includeUnbanned);
+ /// <summary>
+ /// Counts an user's bans.
+ /// This will return pardoned bans as well.
+ /// One of <see cref="address"/> or <see cref="userId"/> need to not be null.
+ /// </summary>
+ /// <param name="address">The ip address of the user.</param>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="hwId">The HWId of the user.</param>
+ /// <param name="includeUnbanned">Include pardoned and expired bans.</param>
+ /// <returns>The user's ban history.</returns>
+ public abstract Task<int> CountServerBansAsync(
+ IPAddress? address,
+ NetUserId? userId,
+ ImmutableArray<byte>? hwId,
+ bool includeUnbanned);
+
public abstract Task AddServerBanAsync(ServerBanDef serverBan);
public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
.ToListAsync();
}
+ public async Task<int> CountAdminNotes(Guid player)
+ {
+ await using var db = await GetDb();
+ return await db.DbContext.AdminNotes
+ .Where(note => note.PlayerUserId == player)
+ .Where(note => !note.Deleted)
+ .Include(note => note.Round)
+ .Include(note => note.CreatedBy)
+ .Include(note => note.LastEditedBy)
+ .Include(note => note.Player)
+ .CountAsync();
+ }
+
public async Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt)
{
await using var db = await GetDb();
ImmutableArray<byte>? hwId,
bool includeUnbanned=true);
+ /// <summary>
+ /// Counts an user's bans.
+ /// One of <see cref="address"/> or <see cref="userId"/> need to not be null.
+ /// </summary>
+ /// <param name="address">The ip address of the user.</param>
+ /// <param name="userId">The id of the user.</param>
+ /// <param name="hwId">The HWId of the user.</param>
+ /// <param name="includeUnbanned">If true, bans that have been expired or pardoned are also included.</param>
+ /// <returns>The user's ban history.</returns>
+ Task<int> CountServerBansAsync(
+ IPAddress? address,
+ NetUserId? userId,
+ ImmutableArray<byte>? hwId,
+ bool includeUnbanned=true);
+
Task AddServerBanAsync(ServerBanDef serverBan);
Task AddServerUnbanAsync(ServerUnbanDef serverBan);
Task<int> AddAdminNote(int? roundId, Guid player, string message, Guid createdBy, DateTime createdAt);
Task<AdminNote?> GetAdminNote(int id);
Task<List<AdminNote>> GetAdminNotes(Guid player);
+ Task<int> CountAdminNotes(Guid player);
Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt);
Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt);
return _db.GetServerBansAsync(address, userId, hwId, includeUnbanned);
}
+ public Task<int> CountServerBansAsync(IPAddress? address, NetUserId? userId, ImmutableArray<byte>? hwId, bool includeUnbanned = true)
+ {
+ DbReadOpsMetric.Inc();
+ return _db.CountServerBansAsync(address, userId, hwId, includeUnbanned);
+ }
+
public Task AddServerBanAsync(ServerBanDef serverBan)
{
DbWriteOpsMetric.Inc();
return _db.GetAdminNotes(player);
}
+ public Task<int> CountAdminNotes(Guid player)
+ {
+ DbReadOpsMetric.Inc();
+ return _db.CountAdminNotes(player);
+ }
+
public Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt)
{
DbWriteOpsMetric.Inc();
return bans;
}
+ public override async Task<int> CountServerBansAsync(IPAddress? address, NetUserId? userId, ImmutableArray<byte>? hwId, bool includeUnbanned)
+ {
+ if (address == null && userId == null && hwId == null)
+ {
+ throw new ArgumentException("Address, userId, and hwId cannot all be null");
+ }
+
+ await using var db = await GetDbImpl();
+
+ var exempt = await GetBanExemptionCore(db, userId);
+ var query = MakeBanLookupQuery(address, userId, hwId, db, includeUnbanned, exempt);
+
+ return await query.CountAsync();
+ }
+
private static IQueryable<ServerBan> MakeBanLookupQuery(
IPAddress? address,
NetUserId? userId,
.ToList()!;
}
+ public override async Task<int> CountServerBansAsync(IPAddress? address, NetUserId? userId, ImmutableArray<byte>? hwId, bool includeUnbanned)
+ {
+ await using var db = await GetDbImpl();
+
+ var exempt = await GetBanExemptionCore(db, userId);
+
+ // SQLite can't do the net masking stuff we need to match IP address ranges.
+ // So just pull down the whole list into memory.
+ var queryBans = await GetAllBans(db.SqliteDbContext, includeUnbanned, exempt);
+
+ return queryBans
+ .Where(b => BanMatches(b, address, userId, hwId))
+ .Select(ConvertBan)
+ .Count();
+ }
+
private static async Task<List<ServerBan>> GetAllBans(
SqliteServerDbContext db,
bool includeUnbanned,
EntityUid? EntityUid,
NetUserId SessionId,
bool Connected,
- bool ActiveThisRound);
+ bool ActiveThisRound,
+ int Bans,
+ int Notes);
}
-admin-player-actions-bans = Ban List
-admin-player-actions-notes = Notes
+admin-player-actions-bans = Ban List ({$count})
+admin-player-actions-notes = Notes ({$count})
admin-player-actions-kick = Kick
admin-player-actions-ban = Ban
admin-player-actions-ahelp = AHelp