Better notes and bans (#14228)

Co-authored-by: Chief-Engineer <119664036+Chief-Engineer@users.noreply.github.com>
This commit is contained in:
Riggle
2023-07-21 13:38:52 +02:00
committed by GitHub
parent c6cb6ad928
commit 579913b617
84 changed files with 9820 additions and 886 deletions

View File

@@ -0,0 +1,38 @@
using Content.Client.Eui;
using Content.Shared.Administration.Notes;
using Content.Shared.Eui;
using JetBrains.Annotations;
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
namespace Content.Client.Administration.UI.AdminRemarks;
[UsedImplicitly]
public sealed class AdminMessageEui : BaseEui
{
private readonly AdminMessagePopupWindow _popup;
public AdminMessageEui()
{
_popup = new AdminMessagePopupWindow();
_popup.OnAcceptPressed += () => SendMessage(new Accept());
_popup.OnDismissPressed += () => SendMessage(new Dismiss());
_popup.OnClose += () => SendMessage(new CloseEuiMessage());
}
public override void HandleState(EuiStateBase state)
{
if (state is not AdminMessageEuiState s)
{
return;
}
_popup.SetMessage(s.Message);
_popup.SetDetails(s.AdminName, s.AddedOn);
_popup.Timer = s.Time;
}
public override void Opened()
{
_popup.OpenCentered();
}
}

View File

@@ -0,0 +1,26 @@
<ui:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
VerticalExpand="True" HorizontalExpand="True"
Title="{Loc admin-notes-message-window-title}"
MinSize="600 170">
<PanelContainer VerticalExpand="True" HorizontalExpand="True">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#25252A" />
</PanelContainer.PanelOverride>
<ScrollContainer HScrollEnabled="False" VerticalExpand="True" HorizontalExpand="True" Margin="4">
<BoxContainer Orientation="Vertical" SeparationOverride="10" VerticalAlignment="Bottom">
<Label Name="AdminLabel" Text="Loading..." />
<RichTextLabel Name="MessageLabel" />
<Label Name="WaitLabel" />
<BoxContainer Orientation="Horizontal">
<Button Name="DismissButton"
Text="{Loc 'admin-notes-message-dismiss'}" />
<Button Name="AcceptButton"
Text="{Loc 'admin-notes-message-accept'}"
Disabled="True" />
</BoxContainer>
</BoxContainer>
</ScrollContainer>
</PanelContainer>
</ui:FancyWindow>

View File

@@ -0,0 +1,75 @@
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client.Administration.UI.AdminRemarks;
[GenerateTypedNameReferences]
public sealed partial class AdminMessagePopupWindow : FancyWindow
{
private float _timer = float.MaxValue;
public float Timer
{
get => _timer;
set
{
WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
_timer = value;
}
}
public event Action? OnDismissPressed;
public event Action? OnAcceptPressed;
public AdminMessagePopupWindow()
{
RobustXamlLoader.Load(this);
AcceptButton.OnPressed += OnAcceptButtonPressed;
DismissButton.OnPressed += OnDismissButtonPressed;
}
public void SetMessage(string message)
{
MessageLabel.SetMessage(message);
}
public void SetDetails(string adminName, DateTime addedOn)
{
AdminLabel.Text = Loc.GetString("admin-notes-message-admin", ("admin", adminName), ("date", addedOn));
}
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
{
OnDismissPressed?.Invoke();
Close();
}
private void OnAcceptButtonPressed(BaseButton.ButtonEventArgs obj)
{
OnAcceptPressed?.Invoke();
Close();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (!AcceptButton.Disabled)
return;
if (Timer > 0.0)
{
if (Timer - args.DeltaSeconds < 0)
Timer = 0;
else
Timer -= args.DeltaSeconds;
}
else
{
AcceptButton.Disabled = false;
}
}
}

View File

@@ -0,0 +1,17 @@
<ui:FancyWindow xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
VerticalExpand="True" HorizontalExpand="True"
Title="{Loc admin-remarks-title}"
SetSize="600 400">
<PanelContainer>
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#25252A" />
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical" Margin="4">
<ScrollContainer VerticalExpand="True" HorizontalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Name="NotesContainer" Access="Public" VerticalExpand="True" />
</ScrollContainer>
</BoxContainer>
</PanelContainer>
</ui:FancyWindow>

View File

@@ -0,0 +1,49 @@
using System.Linq;
using Content.Client.Administration.UI.Notes;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.AdminRemarks;
[GenerateTypedNameReferences]
public sealed partial class AdminRemarksWindow : FancyWindow
{
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
private readonly SpriteSystem _sprites;
private readonly Dictionary<(int, NoteType), AdminNotesLine> _inputs = new();
public AdminRemarksWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprites = _entitySystem.GetEntitySystem<SpriteSystem>();
}
public void SetNotes(Dictionary<(int, NoteType), SharedAdminNote> notes)
{
foreach (var (id, input) in _inputs)
{
if (notes.ContainsKey(id))
continue;
NotesContainer.RemoveChild(input);
_inputs.Remove(id);
}
foreach (var note in notes.Values.OrderByDescending(note => note.CreatedAt))
{
if (_inputs.TryGetValue((note.Id, note.NoteType), out var input))
{
input.UpdateNote(note);
continue;
}
input = new AdminNotesLine(_sprites, note);
NotesContainer.AddChild(input);
_inputs[(note.Id, note.NoteType)] = input;
}
}
}

View File

@@ -0,0 +1,34 @@
using Content.Client.Administration.UI.Notes;
using Content.Client.Eui;
using Content.Shared.Administration.Notes;
using Content.Shared.Eui;
using JetBrains.Annotations;
namespace Content.Client.Administration.UI.AdminRemarks;
[UsedImplicitly]
public sealed class UserNotesEui : BaseEui
{
public UserNotesEui()
{
NoteWindow = new AdminRemarksWindow();
NoteWindow.OnClose += () => SendMessage(new CloseEuiMessage());
}
private AdminRemarksWindow NoteWindow { get; }
public override void HandleState(EuiStateBase state)
{
if (state is not UserNotesEuiState s)
{
return;
}
NoteWindow.SetNotes(s.Notes);
}
public override void Opened()
{
NoteWindow.OpenCentered();
}
}

View File

@@ -0,0 +1,49 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc ban-panel-title}" MinSize="350 500">
<BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" VerticalExpand="True">
<!-- Basic info -->
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" Margin="2">
<CheckBox Name="PlayerCheckbox" MinWidth="100" Text="{Loc ban-panel-player}" Pressed="True" />
<Control MinWidth="50" />
<LineEdit Name="PlayerNameLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc ban-panel-player}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<CheckBox Name="IpCheckbox" MinWidth="100" Text="{Loc ban-panel-ip}" Pressed="False" />
<Control MinWidth="50" />
<LineEdit Name="IpLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc ban-panel-ip}" ToolTip="{Loc ban-panel-ip-hwid-tooltip}" Editable="False" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<CheckBox Name="HwidCheckbox" MinWidth="100" Text="{Loc ban-panel-hwid}" Pressed="True" />
<Control MinWidth="50" />
<LineEdit Name="HwidLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc ban-panel-hwid}" ToolTip="{Loc ban-panel-ip-hwid-tooltip}" />
</BoxContainer>
<CheckBox Name="LastConnCheckbox" Margin="2" Text="{Loc ban-panel-last-conn}" Pressed="True" />
<BoxContainer Orientation="Horizontal" Margin="2">
<LineEdit Name="TimeLine" MaxWidth="150" MinWidth="70" PlaceHolder="0" />
<OptionButton Name="MultiplierOption" />
<Control MinWidth="50" />
<Label Name="ExpiresLabel" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="4">
<OptionButton Name="TypeOption" />
<Control MinWidth="30"></Control>
<Label Text="{Loc ban-panel-severity}" />
<OptionButton Name="SeverityOption" />
</BoxContainer>
<cc:HSeparator Margin="1"/>
<TextEdit Name="ReasonTextEdit" MinHeight="100" VerticalExpand="True" HorizontalExpand="True" />
</BoxContainer>
<!-- Player List -->
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
<!-- Role list (auto-generated) -->
<ScrollContainer>
<BoxContainer Name="RolesContainer" Orientation="Vertical" />
</ScrollContainer>
</TabContainer>
<Button Name="SubmitButton" Text="{Loc ban-panel-submit}" HorizontalExpand="True" />
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,459 @@
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using Content.Client.Administration.UI.CustomControls;
using Content.Client.Stylesheets;
using Content.Shared.Administration;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.BanPanel;
[GenerateTypedNameReferences]
public sealed partial class BanPanel : DefaultWindow
{
public event Action<string?, (IPAddress, int)?, bool, byte[]?, bool, uint, string, NoteSeverity, string[]?>? BanSubmitted;
public event Action<string>? PlayerChanged;
private string? PlayerUsername { get; set; }
private (IPAddress, int)? IpAddress { get; set; }
private byte[]? Hwid { get; set; }
private double TimeEntered { get; set; }
private uint Multiplier { get; set; }
private bool HasBanFlag { get; set; }
private TimeSpan? ButtonResetOn { get; set; }
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated.
private readonly List<CheckBox> _roleCheckboxes = new();
[Dependency] private readonly IGameTiming _gameTiming = default!;
private enum TabNumbers
{
BasicInfo,
//Text,
Players,
Roles
}
private enum Multipliers
{
Minutes,
Hours,
Days,
Weeks,
Months,
Years,
Permanent
}
private enum Types
{
None,
Server,
Role
}
public BanPanel()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
PlayerCheckbox.OnPressed += _ =>
{
PlayerNameLine.Editable = PlayerCheckbox.Pressed;
PlayerNameLine.ModulateSelfOverride = null;
};
TimeLine.OnTextChanged += OnMinutesChanged;
MultiplierOption.OnItemSelected += args =>
{
MultiplierOption.SelectId(args.Id);
OnMultiplierChanged();
};
IpLine.OnFocusExit += _ => OnIpChanged();
IpCheckbox.OnPressed += _ =>
{
IpLine.Editable = IpCheckbox.Pressed;
OnIpChanged();
};
HwidLine.OnFocusExit += _ => OnHwidChanged();
HwidCheckbox.OnPressed += _ =>
{
HwidLine.Editable = HwidCheckbox.Pressed;
OnHwidChanged();
};
TypeOption.OnItemSelected += args =>
{
TypeOption.SelectId(args.Id);
OnTypeChanged();
};
LastConnCheckbox.OnPressed += args =>
{
IpLine.ModulateSelfOverride = null;
HwidLine.ModulateSelfOverride = null;
OnIpChanged();
OnHwidChanged();
};
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-high"), (int) NoteSeverity.High);
SeverityOption.SelectId((int) NoteSeverity.Medium);
SeverityOption.OnItemSelected += args => SeverityOption.SelectId(args.Id);
MultiplierOption.AddItem(Loc.GetString("ban-panel-minutes"), (int) Multipliers.Minutes);
MultiplierOption.AddItem(Loc.GetString("ban-panel-hours"), (int) Multipliers.Hours);
MultiplierOption.AddItem(Loc.GetString("ban-panel-days"), (int) Multipliers.Days);
MultiplierOption.AddItem(Loc.GetString("ban-panel-weeks"), (int) Multipliers.Weeks);
MultiplierOption.AddItem(Loc.GetString("ban-panel-months"), (int) Multipliers.Months);
MultiplierOption.AddItem(Loc.GetString("ban-panel-years"), (int) Multipliers.Years);
MultiplierOption.AddItem(Loc.GetString("ban-panel-permanent"), (int) Multipliers.Permanent);
MultiplierOption.SelectId((int) Multipliers.Minutes);
OnMultiplierChanged();
Tabs.SetTabTitle((int) TabNumbers.BasicInfo, Loc.GetString("ban-panel-tabs-basic"));
//Tabs.SetTabTitle((int) TabNumbers.Text, Loc.GetString("ban-panel-tabs-reason"));
Tabs.SetTabTitle((int) TabNumbers.Players, Loc.GetString("ban-panel-tabs-players"));
Tabs.SetTabTitle((int) TabNumbers.Roles, Loc.GetString("ban-panel-tabs-role"));
Tabs.SetTabVisible((int) TabNumbers.Roles, false);
TypeOption.AddItem(Loc.GetString("ban-panel-select"), (int) Types.None);
TypeOption.AddItem(Loc.GetString("ban-panel-server"), (int) Types.Server);
TypeOption.AddItem(Loc.GetString("ban-panel-role"), (int) Types.Role);
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
}
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
}
private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
{
var outerContainer = new BoxContainer
{
Name = $"{roleName}GroupOuterBox",
HorizontalExpand = true,
VerticalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(4)
};
var departmentCheckbox = new CheckBox
{
Name = $"{roleName}GroupCheckbox",
Text = roleName,
Modulate = color,
HorizontalAlignment = HAlignment.Left
};
outerContainer.AddChild(departmentCheckbox);
var innerContainer = new BoxContainer
{
Name = $"{roleName}GroupInnerBox",
HorizontalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Horizontal
};
departmentCheckbox.OnToggled += args =>
{
foreach (var child in innerContainer.Children)
{
if (child is CheckBox c)
{
c.Pressed = args.Pressed;
}
}
};
outerContainer.AddChild(innerContainer);
foreach (var role in roleList)
{
AddRoleCheckbox(role, innerContainer, departmentCheckbox);
}
RolesContainer.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = color
}
});
RolesContainer.AddChild(outerContainer);
RolesContainer.AddChild(new HSeparator());
}
private void AddRoleCheckbox(string role, Control container, CheckBox header)
{
var roleCheckbox = new CheckBox
{
Name = $"{role}RoleCheckbox",
Text = role
};
roleCheckbox.OnToggled += args =>
{
if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed))
header.Pressed = args.Pressed;
else
header.Pressed = false;
};
container.AddChild(roleCheckbox);
_roleCheckboxes.Add(roleCheckbox);
}
public void UpdateBanFlag(bool newFlag)
{
HasBanFlag = newFlag;
SubmitButton.Visible = HasBanFlag;
ModulateSelfOverride = HasBanFlag ? Color.Red : null;
}
public void UpdatePlayerData(string playerName)
{
if (string.IsNullOrEmpty(playerName))
{
PlayerNameLine.ModulateSelfOverride = Color.Red;
ErrorLevel |= ErrorLevelEnum.PlayerName;
UpdateSubmitEnabled();
return;
}
PlayerNameLine.ModulateSelfOverride = null;
ErrorLevel &= ~ErrorLevelEnum.PlayerName;
UpdateSubmitEnabled();
PlayerUsername = playerName;
PlayerNameLine.Text = playerName;
}
[Flags]
private enum ErrorLevelEnum : byte
{
None = 0,
Minutes = 1 << 0,
PlayerName = 1 << 1,
IpAddress = 1 << 2,
Hwid = 1 << 3,
}
private ErrorLevelEnum ErrorLevel { get; set; }
private void OnMinutesChanged(LineEdit.LineEditEventArgs args)
{
TimeLine.Text = args.Text;
if (!double.TryParse(args.Text, out var result))
{
ExpiresLabel.Text = "err";
ErrorLevel |= ErrorLevelEnum.Minutes;
TimeLine.ModulateSelfOverride = Color.Red;
UpdateSubmitEnabled();
return;
}
ErrorLevel &= ~ErrorLevelEnum.Minutes;
TimeLine.ModulateSelfOverride = null;
TimeEntered = result;
UpdateSubmitEnabled();
UpdateExpiresLabel();
}
private void OnMultiplierChanged()
{
TimeLine.Editable = MultiplierOption.SelectedId != (int) Multipliers.Permanent;
Multiplier = MultiplierOption.SelectedId switch
{
(int) Multipliers.Minutes => 1,
(int) Multipliers.Hours => 60,
(int) Multipliers.Days => 60 * 24,
(int) Multipliers.Weeks => 60 * 24 * 7,
(int) Multipliers.Months => 60 * 24 * 30,
(int) Multipliers.Years => 60 * 24 * 365,
(int) Multipliers.Permanent => 0,
_ => throw new ArgumentOutOfRangeException(nameof(MultiplierOption.SelectedId), "Multiplier out of range")
};
UpdateExpiresLabel();
}
private void UpdateExpiresLabel()
{
var minutes = (uint) (TimeEntered * Multiplier);
ExpiresLabel.Text = minutes == 0
? $"{Loc.GetString("admin-note-editor-expiry-label")} {Loc.GetString("server-ban-string-never")}"
: $"{Loc.GetString("admin-note-editor-expiry-label")} {DateTime.Now + TimeSpan.FromMinutes(minutes):yyyy/MM/dd HH:mm:ss}";
}
private void OnIpChanged()
{
if (LastConnCheckbox.Pressed && IpAddress is null || !IpCheckbox.Pressed)
{
IpAddress = null;
ErrorLevel &= ~ErrorLevelEnum.IpAddress;
IpLine.ModulateSelfOverride = null;
UpdateSubmitEnabled();
return;
}
var ip = IpLine.Text;
var hid = "0";
if (ip.Contains('/'))
{
var split = ip.Split('/');
ip = split[0];
hid = split[1];
}
if (!IPAddress.TryParse(ip, out var parsedIp) || !byte.TryParse(hid, out var hidInt) || hidInt > 128 || hidInt > 32 && parsedIp.AddressFamily == AddressFamily.InterNetwork)
{
ErrorLevel |= ErrorLevelEnum.IpAddress;
IpLine.ModulateSelfOverride = Color.Red;
UpdateSubmitEnabled();
return;
}
if (hidInt == 0)
hidInt = (byte) (parsedIp.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32);
IpAddress = (parsedIp, hidInt);
ErrorLevel &= ~ErrorLevelEnum.IpAddress;
IpLine.ModulateSelfOverride = null;
UpdateSubmitEnabled();
}
private void OnHwidChanged()
{
var hwidString = HwidLine.Text;
var length = 3 * (hwidString.Length / 4) - hwidString.TakeLast(2).Count(c => c == '=');
Hwid = new byte[length];
if (HwidCheckbox.Pressed && !(string.IsNullOrEmpty(hwidString) && LastConnCheckbox.Pressed) && !Convert.TryFromBase64String(hwidString, Hwid, out _))
{
ErrorLevel |= ErrorLevelEnum.Hwid;
HwidLine.ModulateSelfOverride = Color.Red;
UpdateSubmitEnabled();
return;
}
ErrorLevel &= ~ErrorLevelEnum.Hwid;
HwidLine.ModulateSelfOverride = null;
UpdateSubmitEnabled();
if (LastConnCheckbox.Pressed || !HwidCheckbox.Pressed)
{
Hwid = null;
return;
}
Hwid = Convert.FromHexString(hwidString);
}
private void OnTypeChanged()
{
TypeOption.ModulateSelfOverride = null;
Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role);
}
private void UpdateSubmitEnabled()
{
SubmitButton.Disabled = ErrorLevel != ErrorLevelEnum.None;
}
private void OnPlayerNameChanged()
{
if (PlayerUsername == PlayerNameLine.Text)
return;
PlayerUsername = PlayerNameLine.Text;
if (!PlayerCheckbox.Pressed)
return;
if (string.IsNullOrWhiteSpace(PlayerUsername))
ErrorLevel |= ErrorLevelEnum.PlayerName;
else
ErrorLevel &= ~ErrorLevelEnum.PlayerName;
UpdateSubmitEnabled();
PlayerChanged?.Invoke(PlayerUsername);
}
public void OnPlayerSelectionChanged(PlayerInfo? player)
{
PlayerNameLine.Text = player?.Username ?? string.Empty;
OnPlayerNameChanged();
}
private void ResetTextEditor(GUIBoundKeyEventArgs _)
{
ReasonTextEdit.ModulateSelfOverride = null;
ReasonTextEdit.OnKeyBindDown -= ResetTextEditor;
}
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
string[]? roles = null;
if (TypeOption.SelectedId == (int) Types.Role)
{
var rolesList = new List<string>();
if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty");
rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
if (rolesList.Count == 0)
{
Tabs.CurrentTab = (int) TabNumbers.Roles;
return;
}
roles = rolesList.ToArray();
}
if (TypeOption.SelectedId == (int) Types.None)
{
TypeOption.ModulateSelfOverride = Color.Red;
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
return;
}
var reason = Rope.Collapse(ReasonTextEdit.TextRope);
if (string.IsNullOrWhiteSpace(reason))
{
//Tabs.CurrentTab = (int) TabNumbers.Text;
Tabs.CurrentTab = (int) TabNumbers.BasicInfo;
ReasonTextEdit.GrabKeyboardFocus();
ReasonTextEdit.ModulateSelfOverride = Color.Red;
ReasonTextEdit.OnKeyBindDown += ResetTextEditor;
return;
}
if (ButtonResetOn is null)
{
ButtonResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
SubmitButton.ModulateSelfOverride = Color.Red;
SubmitButton.Text = Loc.GetString("ban-panel-confirm");
return;
}
var player = PlayerCheckbox.Pressed ? PlayerUsername : null;
var useLastIp = IpCheckbox.Pressed && LastConnCheckbox.Pressed && IpAddress is null;
var useLastHwid = HwidCheckbox.Pressed && LastConnCheckbox.Pressed && Hwid is null;
var severity = (NoteSeverity) SeverityOption.SelectedId;
BanSubmitted?.Invoke(player, IpAddress, useLastIp, Hwid, useLastHwid, (uint) (TimeEntered * Multiplier), reason, severity, roles);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
// This checks for null for free, do not invert it as null always produces a false value
if (_gameTiming.CurTime > ButtonResetOn)
{
ButtonResetOn = null;
SubmitButton.ModulateSelfOverride = null;
SubmitButton.Text = Loc.GetString("ban-panel-submit");
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Client.Eui;
using Content.Shared.Administration;
using Content.Shared.Eui;
using JetBrains.Annotations;
namespace Content.Client.Administration.UI.BanPanel;
[UsedImplicitly]
public sealed class BanPanelEui : BaseEui
{
private BanPanel BanPanel { get; }
public BanPanelEui()
{
BanPanel = new BanPanel();
BanPanel.OnClose += () => SendMessage(new CloseEuiMessage());
BanPanel.BanSubmitted += (player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles)
=> SendMessage(new BanPanelEuiStateMsg.CreateBanRequest(player, ip, useLastIp, hwid, useLastHwid, minutes, reason, severity, roles));
BanPanel.PlayerChanged += player => SendMessage(new BanPanelEuiStateMsg.GetPlayerInfoRequest(player));
}
public override void HandleState(EuiStateBase state)
{
if (state is not BanPanelEuiState s)
{
return;
}
BanPanel.UpdateBanFlag(s.HasBan);
BanPanel.UpdatePlayerData(s.PlayerName);
}
public override void Opened()
{
BanPanel.OpenCentered();
}
public override void Closed()
{
BanPanel.Close();
BanPanel.Dispose();
}
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Text;
using System.Threading;
using Content.Client.Administration.Managers;
@@ -123,12 +123,10 @@ namespace Content.Client.Administration.UI.Bwoink
_console.ExecuteCommand($"adminnotes \"{_currentPlayer.SessionId}\"");
};
// ew
Ban.OnPressed += _ =>
{
var bw = new BanWindow();
bw.OnPlayerSelectionChanged(_currentPlayer);
bw.Open();
if (_currentPlayer is not null)
_console.ExecuteCommand($"banpanel \"{_currentPlayer.SessionId}\"");
};
Kick.OnPressed += _ =>

View File

@@ -1,4 +1,4 @@
<Control xmlns="https://spacestation14.io"
<Control xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer>
<PanelContainer.PanelOverride>
@@ -8,8 +8,8 @@
<ScrollContainer VerticalExpand="True" HorizontalExpand="True" HScrollEnabled="False">
<BoxContainer Orientation="Vertical" Name="Notes" Access="Public" VerticalExpand="True"/>
</ScrollContainer>
<Label Name="NewNoteLabel" Text="{Loc admin-notes-new-note}" />
<HistoryLineEdit Name="NewNote"/>
<Button Name="ShowMoreButton" Text="{Loc admin-notes-show-more}" Visible="False" HorizontalAlignment="Center" />
<Button Name="NewNoteButton" Text="{Loc admin-notes-new-note}" Disabled="True" />
</BoxContainer>
</PanelContainer>
</Control>

View File

@@ -1,119 +1,167 @@
using System.Linq;
using System.Linq;
using System.Numerics;
using Content.Shared.Administration.Notes;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using static Robust.Client.UserInterface.Controls.LineEdit;
using Robust.Shared.Configuration;
namespace Content.Client.Administration.UI.Notes;
[GenerateTypedNameReferences]
public sealed partial class AdminNotesControl : Control
{
public event Action<int, string>? OnNoteChanged;
public event Action<string>? OnNewNoteEntered;
public event Action<int>? OnNoteDeleted;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? NoteChanged;
public event Action<NoteType, string, NoteSeverity?, bool, DateTime?>? NewNoteEntered;
public event Action<int, NoteType>? NoteDeleted;
private AdminNotesLinePopup? _popup;
private readonly SpriteSystem _sprites;
private readonly double _noteFreshDays;
private readonly double _noteStaleDays;
public AdminNotesControl()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprites = _entitySystem.GetEntitySystem<SpriteSystem>();
NewNote.OnTextEntered += NewNoteEntered;
// There should be a warning somewhere if fresh > stale
// I thought about putting it here but then it would spam you every time you open notes
_noteFreshDays = _cfg.GetCVar(CCVars.NoteFreshDays);
_noteStaleDays = _cfg.GetCVar(CCVars.NoteStaleDays);
NewNoteButton.OnPressed += OnNewNoteButtonPressed;
ShowMoreButton.OnPressed += OnShowMoreButtonPressed;
}
private Dictionary<int, AdminNotesLine> Inputs { get; } = new();
private Dictionary<(int noteId, NoteType noteType), AdminNotesLine> Inputs { get; } = new();
private bool CanCreate { get; set; }
private bool CanDelete { get; set; }
private bool CanEdit { get; set; }
private string PlayerName { get; set; } = "<Error>";
private void NewNoteEntered(LineEditEventArgs args)
public void SetPlayerName(string playerName)
{
if (string.IsNullOrWhiteSpace(args.Text))
{
return;
}
NewNote.Clear();
OnNewNoteEntered?.Invoke(args.Text);
PlayerName = playerName;
}
private void NoteSubmitted(AdminNotesLine input)
private void OnNewNoteButtonPressed(BaseButton.ButtonEventArgs obj)
{
var text = input.EditText.Trim();
if (input.OriginalMessage == text)
var noteEdit = new NoteEdit(null, PlayerName, CanCreate, CanEdit);
noteEdit.SubmitPressed += OnNoteSubmitted;
noteEdit.OpenCentered();
}
private void OnNoteSubmitted(int id, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
{
if (id == 0)
{
NewNoteEntered?.Invoke(type, message, severity, secret, expiryTime);
return;
}
OnNoteChanged?.Invoke(input.Id, text);
NoteChanged?.Invoke(id, type, message, severity, secret, expiryTime);
}
private bool NoteClicked(AdminNotesLine line)
{
ClosePopup();
_popup = new AdminNotesLinePopup(line.Note, CanDelete, CanEdit);
_popup.OnEditPressed += noteId =>
_popup = new AdminNotesLinePopup(line.Note, PlayerName, CanDelete, CanEdit);
_popup.OnEditPressed += (noteId, noteType) =>
{
if (!Inputs.TryGetValue(noteId, out var input))
if (!Inputs.TryGetValue((noteId, noteType), out var input))
{
return;
}
input.SetEditable(true);
var noteEdit = new NoteEdit(input.Note, PlayerName, CanCreate, CanEdit);
noteEdit.SubmitPressed += OnNoteSubmitted;
noteEdit.OpenCentered();
};
_popup.OnDeletePressed += noteId => OnNoteDeleted?.Invoke(noteId);
_popup.OnDeletePressed += (noteId, noteType) => NoteDeleted?.Invoke(noteId, noteType);
var box = UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position, Vector2.One);
_popup.Open(box);
return true;
}
private void ClosePopup()
public void SetNotes(Dictionary<(int, NoteType), SharedAdminNote> notes)
{
_popup?.Close();
_popup = null;
}
public void SetNotes(Dictionary<int, SharedAdminNote> notes)
{
foreach (var (id, input) in Inputs)
foreach (var (key, input) in Inputs)
{
if (!notes.ContainsKey(id))
if (!notes.ContainsKey(key))
{
Notes.RemoveChild(input);
Inputs.Remove(id);
// Yes this is slower than just updating, but new notes get added at the bottom. The user won't notice.
Notes.RemoveAllChildren();
Inputs.Clear();
break;
}
Notes.RemoveChild(input);
Inputs.Remove(key);
}
foreach (var note in notes.Values.OrderBy(note => note.Id))
var showMoreButtonVisible = false;
foreach (var note in notes.Values.OrderByDescending(note => note.CreatedAt))
{
if (Inputs.TryGetValue(note.Id, out var input))
if (Inputs.TryGetValue((note.Id, note.NoteType), out var input))
{
input.UpdateNote(note);
continue;
}
input = new AdminNotesLine(note);
input.OnSubmitted += NoteSubmitted;
input = new AdminNotesLine(_sprites, note);
input.OnClicked += NoteClicked;
var timeDiff = DateTime.UtcNow - note.CreatedAt;
float alpha;
if (_noteFreshDays == 0 || timeDiff.TotalDays <= _noteFreshDays)
{
alpha = 1f;
}
else if (_noteStaleDays == 0 || timeDiff.TotalDays > _noteStaleDays)
{
alpha = 0f;
input.Visible = false;
showMoreButtonVisible = true;
}
else
{
alpha = (float) (1 - Math.Clamp((timeDiff.TotalDays - _noteFreshDays) / (_noteStaleDays - _noteFreshDays), 0, 1));
}
input.Modulate = input.Modulate.WithAlpha(alpha);
Notes.AddChild(input);
Inputs[note.Id] = input;
Inputs[(note.Id, note.NoteType)] = input;
ShowMoreButton.Visible = showMoreButtonVisible;
}
}
private void OnShowMoreButtonPressed(BaseButton.ButtonEventArgs obj)
{
foreach (var input in Inputs.Values)
{
input.Modulate = input.Modulate.WithAlpha(1f);
input.Visible = true;
}
ShowMoreButton.Visible = false;
}
public void SetPermissions(bool create, bool delete, bool edit)
{
CanCreate = create;
CanDelete = delete;
CanEdit = edit;
NewNoteLabel.Visible = create;
NewNote.Visible = create;
NewNoteButton.Visible = create;
NewNoteButton.Disabled = !create;
}
protected override void Dispose(bool disposing)
@@ -125,21 +173,14 @@ public sealed partial class AdminNotesControl : Control
return;
}
foreach (var input in Inputs.Values)
{
input.OnSubmitted -= NoteSubmitted;
}
Inputs.Clear();
NewNote.OnTextEntered -= NewNoteEntered;
NewNoteButton.OnPressed -= OnNewNoteButtonPressed;
if (_popup != null)
{
UserInterfaceManager.PopupRoot.RemoveChild(_popup);
}
OnNoteChanged = null;
OnNewNoteEntered = null;
OnNoteDeleted = null;
NoteDeleted = null;
}
}

View File

@@ -1,4 +1,4 @@
using Content.Client.Eui;
using Content.Client.Eui;
using Content.Shared.Administration.Notes;
using Content.Shared.Eui;
using JetBrains.Annotations;
@@ -14,15 +14,10 @@ public sealed class AdminNotesEui : BaseEui
NoteWindow = new AdminNotesWindow();
NoteControl = NoteWindow.Notes;
NoteControl.OnNoteChanged += (id, text) => SendMessage(new EditNoteRequest(id, text));
NoteControl.OnNewNoteEntered += text => SendMessage(new CreateNoteRequest(text));
NoteControl.OnNoteDeleted += id => SendMessage(new DeleteNoteRequest(id));
NoteWindow.OnClose += OnClosed;
}
private void OnClosed()
{
SendMessage(new CloseEuiMessage());
NoteControl.NoteChanged += (id, type, text, severity, secret, expiryTime) => SendMessage(new EditNoteRequest(id, type, text, severity, secret, expiryTime));
NoteControl.NewNoteEntered += (type, text, severity, secret, expiryTime) => SendMessage(new CreateNoteRequest(type, text, severity, secret, expiryTime));
NoteControl.NoteDeleted += (id, type) => SendMessage(new DeleteNoteRequest(id, type));
NoteWindow.OnClose += () => SendMessage(new CloseEuiMessage());
}
public override void Closed()
@@ -43,6 +38,7 @@ public sealed class AdminNotesEui : BaseEui
}
NoteWindow.SetTitlePlayer(s.NotedPlayerName);
NoteControl.SetPlayerName(s.NotedPlayerName);
NoteControl.SetNotes(s.Notes);
NoteControl.SetPermissions(s.CanCreate, s.CanDelete, s.CanEdit);
}

View File

@@ -1,5 +1,23 @@
<BoxContainer xmlns="https://spacestation14.io"
<BoxContainer xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Orientation="Vertical">
<cc:HSeparator Name="Separator"/>
<BoxContainer Name ="MetadataContainer" Orientation="Horizontal">
<TextureRect Name="SeverityRect" Margin="2"/>
<Label Name="TimeLabel" Margin="4 0" />
<cc:HSeparator Margin="4 0" />
<Label Name="ServerLabel" />
<cc:HSeparator Margin="4 0" />
<Label Name="RoundLabel" />
<cc:HSeparator Margin="4 0" />
<Label Name="AdminLabel" />
<cc:HSeparator Margin="4 0" />
<Label Name="PlaytimeLabel" />
<cc:HSeparator Name="SecretSeparator" Visible="False" Margin="4 0" />
<Label Name="SecretLabel" Text="{Loc admin-notes-secret} " Visible="False" />
</BoxContainer>
<RichTextLabel Name="NoteLabel" />
<Label Name="ExpiresLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
<Label Name="ExtraLabel" Visible="False" Modulate="#1AA7EC" />
<Label Name="EditedLabel" Visible="False" />
<cc:HSeparator Name="Separator" />
</BoxContainer>

View File

@@ -1,85 +1,172 @@
using Content.Shared.Administration.Notes;
using System.Text;
using Content.Client.Resources;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
using static Robust.Client.UserInterface.Controls.LineEdit;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Notes;
[GenerateTypedNameReferences]
public sealed partial class AdminNotesLine : BoxContainer
{
private RichTextLabel? _label;
private LineEdit? _edit;
private readonly SpriteSystem _sprites;
public AdminNotesLine(SharedAdminNote note)
private const string AdminNotesTextureBase = "/Textures/Interface/AdminNotes/";
private static readonly Dictionary<NoteSeverity, string> SeverityIcons = new()
{
{ NoteSeverity.None, AdminNotesTextureBase + "none_button.png" },
{ NoteSeverity.Minor, AdminNotesTextureBase + "minor_button.png" },
{ NoteSeverity.Medium, AdminNotesTextureBase + "medium_button.png" },
{ NoteSeverity.High, AdminNotesTextureBase + "high_button.png" },
};
private static readonly Dictionary<NoteType, string> NoteTypeIcons = new()
{
{ NoteType.Message, AdminNotesTextureBase + "message.png" },
{ NoteType.Watchlist, AdminNotesTextureBase + "watchlist.png" },
};
public AdminNotesLine(SpriteSystem sprites, SharedAdminNote note)
{
RobustXamlLoader.Load(this);
_sprites = sprites;
Note = note;
MouseFilter = MouseFilterMode.Pass;
AddLabel();
Separator.Visible = true;
Refresh();
}
public SharedAdminNote Note { get; private set; }
public int Id => Note.Id;
public string OriginalMessage => Note.Message;
public string EditText => _edit?.Text ?? OriginalMessage;
public event Action<AdminNotesLine>? OnSubmitted;
public event Func<AdminNotesLine, bool>? OnClicked;
private void AddLabel()
/// <summary>
/// Attempts to refresh the current note line with new data. The note it draws data on is stored in <see cref="Note"/>
/// </summary>
private void Refresh()
{
if (_edit != null)
{
_edit.OnTextEntered -= Submitted;
_edit.OnFocusExit -= Submitted;
string? iconPath;
if(Note.NoteSeverity is not null)
SeverityIcons.TryGetValue(Note.NoteSeverity.Value, out iconPath);
else
NoteTypeIcons.TryGetValue(Note.NoteType, out iconPath);
RemoveChild(_edit);
_edit = null;
if (iconPath is null)
{
SeverityRect.Visible = false;
Logger.WarningS("admin.notes", $"Could not find an icon for note ID {Note.Id}");
}
else
{
SeverityRect.Texture = _sprites.Frame0(new SpriteSpecifier.Texture(new ResPath(iconPath)));
}
_label = new RichTextLabel();
_label.SetMessage(Note.Message);
TimeLabel.Text = Note.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss");
ServerLabel.Text = Note.ServerName ?? "Unknown";
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
AdminLabel.Text = Note.CreatedByName;
PlaytimeLabel.Text = $"{Note.PlaytimeAtNote.TotalHours: 0.0}h";
AddChild(_label);
_label.SetPositionFirst();
Separator.Visible = true;
}
private void AddLineEdit()
{
if (_label != null)
if (Note.Secret)
{
RemoveChild(_label);
_label = null;
SecretSeparator.Visible = true;
SecretLabel.Visible = true;
}
_edit = new LineEdit {Text = Note.Message};
_edit.OnTextEntered += Submitted;
_edit.OnFocusExit += Submitted;
if (Note.UnbannedTime is not null)
{
ExtraLabel.Text = Loc.GetString("admin-notes-unbanned", ("admin", Note.UnbannedByName ?? "[error]"), ("date", Note.UnbannedTime));
ExtraLabel.Visible = true;
}
else if (Note.ExpiryTime is not null)
{
// Notes should never be visible when expired, bans should
if (Note.ExpiryTime.Value > DateTime.UtcNow)
{
ExpiresLabel.Text = Loc.GetString("admin-note-editor-expiry-label-params",
("date", Note.ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss")),
("expiresIn", (Note.ExpiryTime.Value - DateTime.UtcNow).ToString("d'd 'hh':'mm")));
ExpiresLabel.Modulate = Color.FromHex("#86DC3D");
}
else
{
ExpiresLabel.Text = Loc.GetString("admin-note-editor-expiry-label-expired");
}
ExpiresLabel.Visible = true;
}
AddChild(_edit);
_edit.SetPositionFirst();
_edit.GrabKeyboardFocus();
_edit.CursorPosition = _edit.Text.Length;
if (Note.LastEditedAt > Note.CreatedAt)
{
EditedLabel.Text = Loc.GetString("admin-notes-edited", ("author", Note.EditedByName), ("date", Note.LastEditedAt));
EditedLabel.Visible = true;
}
Separator.Visible = false;
switch (Note.NoteType)
{
case NoteType.ServerBan:
NoteLabel.SetMessage(FormatBanMessage());
break;
case NoteType.RoleBan:
NoteLabel.SetMessage(FormatRoleBanMessage());
break;
case NoteType.Note:
case NoteType.Watchlist:
case NoteType.Message:
default:
NoteLabel.SetMessage(Note.Message);
break;
}
if (Note.Seen == true)
{
ExtraLabel.Text = Loc.GetString("admin-notes-message-seen");
ExtraLabel.Visible = true;
}
}
private void Submitted(LineEditEventArgs args)
private string FormatBanMessage()
{
OnSubmitted?.Invoke(this);
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {Loc.GetString("admin-notes-the-server")} ");
return FormatBanMessageCommon(banMessage);
}
AddLabel();
private string FormatRoleBanMessage()
{
var banMessage = new StringBuilder($"{Loc.GetString("admin-notes-banned-from")} {string.Join(", ", Note.BannedRoles ?? new []{"unknown"})} ");
return FormatBanMessageCommon(banMessage);
}
var note = Note with {Message = args.Text};
UpdateNote(note);
private string FormatBanMessageCommon(StringBuilder sb)
{
if (Note.ExpiryTime is null)
{
sb.Append(Loc.GetString("admin-notes-permanently"));
}
else
{
sb.Append("for ");
var banLength = Note.ExpiryTime.Value - Note.CreatedAt;
if (banLength.Days > 0)
sb.Append(Loc.GetString("admin-notes-days", ("days", banLength.TotalDays.ToString(".00"))));
else if (banLength.Hours > 0)
sb.Append(Loc.GetString("admin-notes-hours", ("hours", banLength.TotalHours.ToString(".00"))));
else
sb.Append(Loc.GetString("admin-notes-minutes", ("minutes", banLength.TotalMinutes.ToString(".00"))));
}
sb.Append(" - ");
sb.Append(Note.Message);
return sb.ToString();
}
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
@@ -101,24 +188,7 @@ public sealed partial class AdminNotesLine : BoxContainer
public void UpdateNote(SharedAdminNote note)
{
Note = note;
_label?.SetMessage(note.Message);
if (_edit != null && _edit.Text != note.Message)
{
_edit.Text = note.Message;
}
}
public void SetEditable(bool editable)
{
if (editable)
{
AddLineEdit();
}
else
{
AddLabel();
}
Refresh();
}
protected override void Dispose(bool disposing)
@@ -130,13 +200,6 @@ public sealed partial class AdminNotesLine : BoxContainer
return;
}
if (_edit != null)
{
_edit.OnTextEntered -= Submitted;
_edit.OnFocusExit -= Submitted;
}
OnSubmitted = null;
OnClicked = null;
}
}

View File

@@ -1,16 +1,21 @@
<Popup xmlns="https://spacestation14.io"
<Popup xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252A"/>
<gfx:StyleBoxFlat BackgroundColor="#25252A" BorderThickness="1" BorderColor="#18181B"/>
</PanelContainer.PanelOverride>
<BoxContainer Orientation="Vertical">
<Label Name="PlayerNameLabel"/>
<Label Name="IdLabel"/>
<Label Name="TypeLabel"/>
<Label Name="SeverityLabel"/>
<Label Name="RoundIdLabel"/>
<Label Name="CreatedByLabel"/>
<Label Name="CreatedAtLabel"/>
<Label Name="EditedByLabel"/>
<Label Name="EditedAtLabel"/>
<Label Name="ExpiryTimeLabel"/>
<TextEdit Name="NoteTextEdit" Editable="False" MinHeight="24" />
<BoxContainer Orientation="Horizontal">
<Button Name="EditButton" Text="{Loc admin-notes-edit}"/>
<Control HorizontalExpand="True"/>

View File

@@ -1,7 +1,9 @@
using Content.Shared.Administration.Notes;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using static Robust.Client.UserInterface.Controls.BaseButton;
namespace Content.Client.Administration.UI.Notes;
@@ -9,57 +11,89 @@ namespace Content.Client.Administration.UI.Notes;
[GenerateTypedNameReferences]
public sealed partial class AdminNotesLinePopup : Popup
{
public event Action<int>? OnEditPressed;
public event Action<int>? OnDeletePressed;
public event Action<int, NoteType>? OnEditPressed;
public event Action<int, NoteType>? OnDeletePressed;
public AdminNotesLinePopup(SharedAdminNote note, bool showDelete, bool showEdit)
[Dependency] private readonly IGameTiming _gameTiming = default!;
public AdminNotesLinePopup(SharedAdminNote note, string playerName, bool showDelete, bool showEdit)
{
RobustXamlLoader.Load(this);
NoteId = note.Id;
NoteType = note.NoteType;
DeleteButton.Visible = showDelete;
EditButton.Visible = showEdit;
UserInterfaceManager.ModalRoot.AddChild(this);
PlayerNameLabel.Text = Loc.GetString("admin-notes-for", ("player", playerName));
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
TypeLabel.Text = Loc.GetString("admin-notes-type", ("type", note.NoteType));
SeverityLabel.Text = Loc.GetString("admin-notes-severity", ("severity", note.NoteSeverity ?? NoteSeverity.None));
RoundIdLabel.Text = note.Round == null
? Loc.GetString("admin-notes-round-id-unknown")
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToString("dd MMM yyyy HH:mm:ss")));
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")));
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
EditedAtLabel.Text = Loc.GetString("admin-notes-last-edited-at", ("date", note.LastEditedAt.ToString("dd MMM yyyy HH:mm:ss")));
EditedAtLabel.Text = Loc.GetString("admin-notes-last-edited-at", ("date", note.LastEditedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? Loc.GetString("admin-notes-edited-never")));
ExpiryTimeLabel.Text = note.ExpiryTime == null
? Loc.GetString("admin-notes-expires-never")
: Loc.GetString("admin-notes-expires", ("expires", note.ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss")));
NoteTextEdit.InsertAtCursor(note.Message);
if (note.NoteType is NoteType.ServerBan or NoteType.RoleBan)
{
DeleteButton.Text = Loc.GetString("admin-notes-hide");
}
EditButton.OnPressed += EditPressed;
DeleteButton.OnPressed += DeletePressed;
}
private int NoteId { get; }
private bool ConfirmingDelete { get; set; }
private NoteType NoteType { get; }
private TimeSpan? DeleteResetOn { get; set; }
private void EditPressed(ButtonEventArgs args)
{
OnEditPressed?.Invoke(NoteId);
OnEditPressed?.Invoke(NoteId, NoteType);
Close();
}
private void DeletePressed(ButtonEventArgs args)
{
if (!ConfirmingDelete)
if (DeleteResetOn is null)
{
ConfirmingDelete = true;
DeleteResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
DeleteButton.Text = Loc.GetString("admin-notes-delete-confirm");
DeleteButton.ModulateSelfOverride = Color.Red;
return;
}
ConfirmingDelete = false;
DeleteButton.ModulateSelfOverride = null;
OnDeletePressed?.Invoke(NoteId);
ResetDeleteButton();
OnDeletePressed?.Invoke(NoteId, NoteType);
Close();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
// This checks for null for free, do not invert it as null always produces a false value
if (DeleteResetOn < _gameTiming.CurTime)
{
ResetDeleteButton();
DeleteResetOn = null;
}
}
private void ResetDeleteButton()
{
DeleteButton.Text = Loc.GetString("admin-notes-delete");
DeleteButton.ModulateSelfOverride = null;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

View File

@@ -1,5 +1,7 @@
<DefaultWindow xmlns="https://spacestation14.io"
<ui:FancyWindow xmlns="https://spacestation14.io"
xmlns:notes="clr-namespace:Content.Client.Administration.UI.Notes"
SetSize="400 400">
<notes:AdminNotesControl Name="Notes" Access="Public"/>
</DefaultWindow>
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="600 400"
Title="Loading...">
<notes:AdminNotesControl Name="Notes" Access="Public" Margin="4"/>
</ui:FancyWindow>

View File

@@ -1,11 +1,11 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Notes;
[GenerateTypedNameReferences]
public sealed partial class AdminNotesWindow : DefaultWindow
public sealed partial class AdminNotesWindow : FancyWindow
{
public AdminNotesWindow()
{

View File

@@ -0,0 +1,22 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="Loading..."
MinSize="400 200">
<BoxContainer Orientation="Vertical" Margin="4">
<TextEdit Name="NoteTextEdit" HorizontalExpand="True" VerticalExpand="True" />
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Label Name="ExpiryLabel" Text="{Loc admin-note-editor-expiry-label}" Visible="False" />
<HistoryLineEdit Name="ExpiryLineEdit" PlaceHolder="{Loc admin-note-editor-expiry-placeholder}"
Visible="False" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<OptionButton Name="TypeOption" HorizontalAlignment="Center" />
<OptionButton Name="SeverityOption" HorizontalAlignment="Center" />
<CheckBox Name="SecretCheckBox" Text="{Loc admin-note-editor-secret}"
ToolTip="{Loc admin-note-editor-secret-tooltip}" />
<CheckBox Name="PermanentCheckBox" Pressed="True" Text="{Loc admin-note-editor-expiry-checkbox}"
ToolTip="{Loc admin-note-editor-expiry-checkbox-tooltip}" />
<Button Name="SubmitButton" Text="{Loc admin-note-editor-submit}" HorizontalAlignment="Right" />
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,242 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Administration.UI.Notes;
[GenerateTypedNameReferences]
public sealed partial class NoteEdit : FancyWindow
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
public event Action<int, NoteType, string, NoteSeverity?, bool, DateTime?>? SubmitPressed;
public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool canEdit)
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
PlayerName = playerName;
Title = Loc.GetString("admin-note-editor-title-new", ("player", PlayerName));
ResetSubmitButton();
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-note"), (int) NoteType.Note);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-message"), (int) NoteType.Message);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-watchlist"), (int) NoteType.Watchlist);
TypeOption.OnItemSelected += OnTypeChanged;
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) Shared.Database.NoteSeverity.None);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) Shared.Database.NoteSeverity.Minor);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) Shared.Database.NoteSeverity.Medium);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-high"), (int) Shared.Database.NoteSeverity.High);
SeverityOption.OnItemSelected += OnSeverityChanged;
PermanentCheckBox.OnPressed += OnPermanentPressed;
SecretCheckBox.OnPressed += OnSecretPressed;
SubmitButton.OnPressed += OnSubmitButtonPressed;
if (note is null && !canCreate)
{
SubmitButton.Disabled = true;
TypeOption.Disabled = true;
SeverityOption.Disabled = true;
}
if (note is not null)
{
Title = Loc.GetString("admin-note-editor-title-existing", ("id", note.Id), ("player", PlayerName), ("author", note.CreatedByName));
NoteId = note.Id;
NoteType = note.NoteType;
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-server-ban"), (int) NoteType.ServerBan);
TypeOption.AddItem(Loc.GetString("admin-note-editor-type-role-ban"), (int) NoteType.RoleBan);
TypeOption.SelectId((int)NoteType);
TypeOption.Disabled = true;
NoteTextEdit.InsertAtCursor(note.Message);
NoteSeverity = note.NoteSeverity ?? Shared.Database.NoteSeverity.Minor;
SeverityOption.SelectId((int)NoteSeverity);
SeverityOption.Disabled = note.NoteType is not (NoteType.Note or NoteType.ServerBan or NoteType.RoleBan);
IsSecret = note.Secret;
SecretCheckBox.Pressed = note.Secret;
SecretCheckBox.Disabled = note.NoteType is not NoteType.Note;
ExpiryTime = note.ExpiryTime;
if (ExpiryTime is not null)
{
PermanentCheckBox.Pressed = false;
UpdatePermanentCheckboxFields();
ExpiryLineEdit.Text = ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
}
if (!canEdit)
{
SubmitButton.Disabled = true;
}
}
}
private string PlayerName { get; }
private int NoteId { get; }
private bool IsSecret { get; set; }
private NoteType NoteType { get; set; }
private NoteSeverity? NoteSeverity { get; set; } = Shared.Database.NoteSeverity.None;
private DateTime? ExpiryTime { get; set; }
private TimeSpan? DeleteResetOn { get; set; }
private void OnTypeChanged(OptionButton.ItemSelectedEventArgs args)
{
// We should be resetting the underlying values too but the server handles that anyway
switch (args.Id)
{
case (int) NoteType.Note: // Note: your standard note, does nothing special
NoteType = NoteType.Note;
SecretCheckBox.Disabled = false;
SecretCheckBox.Pressed = false;
SeverityOption.Disabled = false;
PermanentCheckBox.Pressed = true;
UpdatePermanentCheckboxFields();
break;
case (int) NoteType.Message: // Message: these are shown to the player when they log on
NoteType = NoteType.Message;
SecretCheckBox.Disabled = true;
SecretCheckBox.Pressed = false;
SeverityOption.Disabled = true;
SeverityOption.SelectId((int) Shared.Database.NoteSeverity.None);
NoteSeverity = null;
PermanentCheckBox.Pressed = false;
UpdatePermanentCheckboxFields();
break;
case (int) NoteType.Watchlist: // Watchlist: these are always secret and only shown to admins when the player logs on
NoteType = NoteType.Watchlist;
SecretCheckBox.Disabled = true;
SecretCheckBox.Pressed = true;
SeverityOption.Disabled = true;
SeverityOption.SelectId((int) Shared.Database.NoteSeverity.None);
NoteSeverity = null;
PermanentCheckBox.Pressed = false;
UpdatePermanentCheckboxFields();
break;
default: // Wuh oh
throw new ArgumentOutOfRangeException(nameof(args.Id), args.Id, "Unknown note type");
}
TypeOption.SelectId(args.Id);
}
private void OnPermanentPressed(BaseButton.ButtonEventArgs _)
{
UpdatePermanentCheckboxFields();
}
private void UpdatePermanentCheckboxFields()
{
ExpiryLabel.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty;
}
private void OnSecretPressed(BaseButton.ButtonEventArgs _)
{
IsSecret = SecretCheckBox.Pressed;
}
private void OnSeverityChanged(OptionButton.ItemSelectedEventArgs args)
{
NoteSeverity = (NoteSeverity) args.Id;
SeverityOption.SelectId(args.Id);
}
private void OnSubmitButtonPressed(BaseButton.ButtonEventArgs args)
{
if (!ParseExpiryTime())
return;
if (DeleteResetOn is null)
{
DeleteResetOn = _gameTiming.CurTime.Add(TimeSpan.FromSeconds(3));
SubmitButton.Text = Loc.GetString("admin-note-editor-submit-confirm");
SubmitButton.ModulateSelfOverride = Color.Red;
// Task.Delay(3000).ContinueWith(_ => ResetSubmitButton()); // TODO: fix
return;
}
ResetSubmitButton();
SubmitPressed?.Invoke(NoteId, NoteType, Rope.Collapse(NoteTextEdit.TextRope), NoteSeverity, IsSecret, ExpiryTime);
if (Parent is null)
{
_console.ExecuteCommand($"adminnotes \"{PlayerName}\"");
}
Close();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
// This checks for null for free, do not invert it as null always produces a false value
if (DeleteResetOn > _gameTiming.CurTime)
{
ResetSubmitButton();
DeleteResetOn = null;
}
}
private void ResetSubmitButton()
{
SubmitButton.Text = Loc.GetString("admin-note-editor-submit");
SubmitButton.ModulateSelfOverride = null;
UpdateDraw();
}
/// <summary>
/// Tries to parse the currently entered expiry time. As a side effect this function
/// will colour its respective line edit to indicate an error
/// </summary>
/// <returns>True if parsing was successful, false if not</returns>
private bool ParseExpiryTime()
{
// If the checkbox is pressed the note is permanent, so expiry is null
if (PermanentCheckBox.Pressed)
{
ExpiryTime = null;
return true;
}
if (string.IsNullOrWhiteSpace(ExpiryLineEdit.Text) || !DateTime.TryParse(ExpiryLineEdit.Text, out var result) || DateTime.UtcNow > result)
{
ExpiryLineEdit.ModulateSelfOverride = Color.Red;
return false;
}
ExpiryTime = result;
ExpiryLineEdit.ModulateSelfOverride = null;
return true;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
{
return;
}
PermanentCheckBox.OnPressed -= OnPermanentPressed;
SecretCheckBox.OnPressed -= OnSecretPressed;
SubmitButton.OnPressed -= OnSubmitButtonPressed;
SubmitPressed = null;
}
}

View File

@@ -1,4 +1,4 @@
<Control
<Control
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
@@ -8,7 +8,7 @@
<BoxContainer Orientation="Vertical">
<GridContainer Columns="3">
<cc:UICommandButton Command="kick" Text="{Loc admin-player-actions-window-title}" WindowType="{x:Type at:PlayerActionsWindow}" />
<cc:UICommandButton Command="ban" Text="{Loc admin-player-actions-window-ban}" WindowType="{x:Type at:BanWindow}" />
<cc:CommandButton Command="banpanel" Text="{Loc admin-player-actions-window-ban}" />
<cc:CommandButton Command="aghost" Text="{Loc admin-player-actions-window-admin-ghost}" />
<cc:UICommandButton Command="tpto" Text="{Loc admin-player-actions-window-teleport}" WindowType="{x:Type at:TeleportWindow}" />
<cc:CommandButton Command="permissions" Text="{Loc admin-player-actions-window-permissions}" />

View File

@@ -1,29 +0,0 @@
<DefaultWindow
xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Title="{Loc Ban}" MinSize="425 325">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc Player}" MinWidth="100" />
<Control MinWidth="50" />
<LineEdit Name="PlayerNameLine" MinWidth="100" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc Reason}" MinSize="100 0" />
<Control MinSize="50 0" />
<LineEdit Name="ReasonLine" MinSize="100 0" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc Minutes}" MinWidth="100" />
<Control MinWidth="50" />
<LineEdit Name="MinutesLine" MinWidth="100" HorizontalExpand="True" PlaceHolder="{Loc 0 minutes for a permanent ban}" />
<Button Name="HourButton" Text="+1h (0)"/>
<Button Name="DayButton" Text="+1d (0)"/>
<Button Name="WeekButton" Text="+1w (0)"/>
<Button Name="MonthButton" Text="+1M (0)"/>
</BoxContainer>
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
<Control MinWidth="50" />
<Button Name="SubmitButton" Text="{Loc Ban}" />
</BoxContainer>
</DefaultWindow>

View File

@@ -1,84 +0,0 @@
using Content.Shared.Administration;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.LineEdit;
namespace Content.Client.Administration.UI.Tabs.AdminTab
{
[GenerateTypedNameReferences]
[UsedImplicitly]
public sealed partial class BanWindow : DefaultWindow
{
public BanWindow()
{
RobustXamlLoader.Load(this);
PlayerNameLine.OnTextChanged += _ => OnPlayerNameChanged();
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
MinutesLine.OnTextChanged += UpdateButtonsText;
HourButton.OnPressed += _ => AddMinutes(60);
DayButton.OnPressed += _ => AddMinutes(1440);
WeekButton.OnPressed += _ => AddMinutes(10080);
MonthButton.OnPressed += _ => AddMinutes(43200);
}
private bool TryGetMinutes(string str, out uint minutes)
{
if(string.IsNullOrWhiteSpace(str))
{
minutes = 0;
return true;
}
return uint.TryParse(str, out minutes);
}
private void AddMinutes(uint add)
{
if (!TryGetMinutes(MinutesLine.Text, out var minutes))
return;
MinutesLine.Text = $"{minutes + add}";
UpdateButtons(minutes+add);
}
private void UpdateButtonsText(LineEditEventArgs obj)
{
if (!TryGetMinutes(obj.Text, out var minutes))
return;
UpdateButtons(minutes);
}
private void UpdateButtons(uint minutes)
{
HourButton.Text = $"+1h ({minutes / 60})";
DayButton.Text = $"+1d ({minutes / 1440})";
WeekButton.Text = $"+1w ({minutes / 10080})";
MonthButton.Text = $"+1M ({minutes / 43200})";
}
private void OnPlayerNameChanged()
{
SubmitButton.Disabled = string.IsNullOrEmpty(PlayerNameLine.Text);
}
public void OnPlayerSelectionChanged(PlayerInfo? player)
{
PlayerNameLine.Text = player?.Username ?? string.Empty;
OnPlayerNameChanged();
}
private void SubmitButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
// Small verification if Player Name exists
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
$"ban \"{PlayerNameLine.Text}\" \"{CommandParsing.Escape(ReasonLine.Text)}\" {MinutesLine.Text}");
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,714 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class AdminNotesImprovement : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban");
migrationBuilder.RenameColumn(
name: "user_id",
table: "server_role_ban",
newName: "player_user_id");
migrationBuilder.RenameIndex(
name: "IX_server_role_ban_user_id",
table: "server_role_ban",
newName: "IX_server_role_ban_player_user_id");
migrationBuilder.RenameColumn(
name: "user_id",
table: "server_ban",
newName: "player_user_id");
migrationBuilder.RenameIndex(
name: "IX_server_ban_user_id",
table: "server_ban",
newName: "IX_server_ban_player_user_id");
migrationBuilder.RenameColumn(
name: "shown_to_player",
table: "admin_notes",
newName: "secret");
migrationBuilder.UpdateData(
table: "admin_notes",
keyColumn: "secret",
keyValue: false,
column: "secret",
value: true);
migrationBuilder.AddColumn<bool>(
name: "hidden",
table: "server_role_ban",
type: "boolean",
nullable: false,
defaultValue: true);
migrationBuilder.AddColumn<DateTime>(
name: "last_edited_at",
table: "server_role_ban",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "last_edited_by_id",
table: "server_role_ban",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<TimeSpan>(
name: "playtime_at_note",
table: "server_role_ban",
type: "interval",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0));
migrationBuilder.AddColumn<int>(
name: "round_id",
table: "server_role_ban",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "severity",
table: "server_role_ban",
type: "integer",
nullable: false,
defaultValue: 2);
migrationBuilder.AddColumn<bool>(
name: "hidden",
table: "server_ban",
type: "boolean",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<DateTime>(
name: "last_edited_at",
table: "server_ban",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "last_edited_by_id",
table: "server_ban",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<TimeSpan>(
name: "playtime_at_note",
table: "server_ban",
type: "interval",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0));
migrationBuilder.AddColumn<int>(
name: "round_id",
table: "server_ban",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "severity",
table: "server_ban",
type: "integer",
nullable: false,
defaultValue: 3);
migrationBuilder.AlterColumn<Guid>(
name: "player_user_id",
table: "admin_notes",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<Guid>(
name: "last_edited_by_id",
table: "admin_notes",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AlterColumn<Guid>(
name: "created_by_id",
table: "admin_notes",
type: "uuid",
nullable: true,
oldClrType: typeof(Guid),
oldType: "uuid");
migrationBuilder.AddColumn<DateTime>(
name: "expiration_time",
table: "admin_notes",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "severity",
table: "admin_notes",
type: "integer",
nullable: false,
defaultValue: 1);
migrationBuilder.AddColumn<TimeSpan>(
name: "playtime_at_note",
table: "admin_notes",
type: "interval",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0));
migrationBuilder.CreateTable(
name: "admin_messages",
columns: table => new
{
admin_messages_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
round_id = table.Column<int>(type: "integer", nullable: true),
player_user_id = table.Column<Guid>(type: "uuid", nullable: true),
playtime_at_note = table.Column<TimeSpan>(type: "interval", nullable: false),
message = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
created_by_id = table.Column<Guid>(type: "uuid", nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
last_edited_by_id = table.Column<Guid>(type: "uuid", nullable: true),
last_edited_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
expiration_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
deleted = table.Column<bool>(type: "boolean", nullable: false),
deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true),
deleted_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
seen = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_messages", x => x.admin_messages_id);
table.ForeignKey(
name: "FK_admin_messages_player_created_by_id",
column: x => x.created_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_player_deleted_by_id",
column: x => x.deleted_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_player_last_edited_by_id",
column: x => x.last_edited_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_player_player_user_id",
column: x => x.player_user_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_round_round_id",
column: x => x.round_id,
principalTable: "round",
principalColumn: "round_id");
});
migrationBuilder.CreateTable(
name: "admin_watchlists",
columns: table => new
{
admin_watchlists_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
round_id = table.Column<int>(type: "integer", nullable: true),
player_user_id = table.Column<Guid>(type: "uuid", nullable: true),
playtime_at_note = table.Column<TimeSpan>(type: "interval", nullable: false),
message = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
created_by_id = table.Column<Guid>(type: "uuid", nullable: true),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
last_edited_by_id = table.Column<Guid>(type: "uuid", nullable: true),
last_edited_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
expiration_time = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
deleted = table.Column<bool>(type: "boolean", nullable: false),
deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true),
deleted_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_watchlists", x => x.admin_watchlists_id);
table.ForeignKey(
name: "FK_admin_watchlists_player_created_by_id",
column: x => x.created_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_player_deleted_by_id",
column: x => x.deleted_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_player_last_edited_by_id",
column: x => x.last_edited_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_player_player_user_id",
column: x => x.player_user_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_round_round_id",
column: x => x.round_id,
principalTable: "round",
principalColumn: "round_id");
});
migrationBuilder.CreateIndex(
name: "IX_server_role_ban_banning_admin",
table: "server_role_ban",
column: "banning_admin");
migrationBuilder.CreateIndex(
name: "IX_server_role_ban_last_edited_by_id",
table: "server_role_ban",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_server_role_ban_round_id",
table: "server_role_ban",
column: "round_id");
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban",
sql: "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_server_ban_banning_admin",
table: "server_ban",
column: "banning_admin");
migrationBuilder.CreateIndex(
name: "IX_server_ban_last_edited_by_id",
table: "server_ban",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_server_ban_round_id",
table: "server_ban",
column: "round_id");
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban",
sql: "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_created_by_id",
table: "admin_messages",
column: "created_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_deleted_by_id",
table: "admin_messages",
column: "deleted_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_last_edited_by_id",
table: "admin_messages",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_player_user_id",
table: "admin_messages",
column: "player_user_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_round_id",
table: "admin_messages",
column: "round_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_created_by_id",
table: "admin_watchlists",
column: "created_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_deleted_by_id",
table: "admin_watchlists",
column: "deleted_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_last_edited_by_id",
table: "admin_watchlists",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_player_user_id",
table: "admin_watchlists",
column: "player_user_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_round_id",
table: "admin_watchlists",
column: "round_id");
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes",
column: "created_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes",
column: "deleted_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes",
column: "player_user_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_ban_player_banning_admin",
table: "server_ban",
column: "banning_admin",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_ban_player_last_edited_by_id",
table: "server_ban",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_ban_round_round_id",
table: "server_ban",
column: "round_id",
principalTable: "round",
principalColumn: "round_id");
migrationBuilder.AddForeignKey(
name: "FK_server_role_ban_player_banning_admin",
table: "server_role_ban",
column: "banning_admin",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_role_ban_player_last_edited_by_id",
table: "server_role_ban",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_role_ban_round_round_id",
table: "server_role_ban",
column: "round_id",
principalTable: "round",
principalColumn: "round_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_server_ban_player_banning_admin",
table: "server_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_ban_player_last_edited_by_id",
table: "server_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_ban_round_round_id",
table: "server_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_role_ban_player_banning_admin",
table: "server_role_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_role_ban_player_last_edited_by_id",
table: "server_role_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_role_ban_round_round_id",
table: "server_role_ban");
migrationBuilder.DropTable(
name: "admin_messages");
migrationBuilder.DropTable(
name: "admin_watchlists");
migrationBuilder.DropIndex(
name: "IX_server_role_ban_banning_admin",
table: "server_role_ban");
migrationBuilder.DropIndex(
name: "IX_server_role_ban_last_edited_by_id",
table: "server_role_ban");
migrationBuilder.DropIndex(
name: "IX_server_role_ban_round_id",
table: "server_role_ban");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban");
migrationBuilder.DropIndex(
name: "IX_server_ban_banning_admin",
table: "server_ban");
migrationBuilder.DropIndex(
name: "IX_server_ban_last_edited_by_id",
table: "server_ban");
migrationBuilder.DropIndex(
name: "IX_server_ban_round_id",
table: "server_ban");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban");
migrationBuilder.DropColumn(
name: "hidden",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "last_edited_at",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "last_edited_by_id",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "playtime_at_note",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "round_id",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "severity",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "hidden",
table: "server_ban");
migrationBuilder.DropColumn(
name: "last_edited_at",
table: "server_ban");
migrationBuilder.DropColumn(
name: "last_edited_by_id",
table: "server_ban");
migrationBuilder.DropColumn(
name: "playtime_at_note",
table: "server_ban");
migrationBuilder.DropColumn(
name: "round_id",
table: "server_ban");
migrationBuilder.DropColumn(
name: "severity",
table: "server_ban");
migrationBuilder.DropColumn(
name: "expiration_time",
table: "admin_notes");
migrationBuilder.DropColumn(
name: "severity",
table: "admin_notes");
migrationBuilder.DropColumn(
name: "playtime_at_note",
table: "admin_notes");
migrationBuilder.RenameColumn(
name: "player_user_id",
table: "server_role_ban",
newName: "user_id");
migrationBuilder.RenameIndex(
name: "IX_server_role_ban_player_user_id",
table: "server_role_ban",
newName: "IX_server_role_ban_user_id");
migrationBuilder.RenameColumn(
name: "player_user_id",
table: "server_ban",
newName: "user_id");
migrationBuilder.RenameIndex(
name: "IX_server_ban_player_user_id",
table: "server_ban",
newName: "IX_server_ban_user_id");
migrationBuilder.RenameColumn(
name: "secret",
table: "admin_notes",
newName: "shown_to_player");
migrationBuilder.UpdateData(
table: "admin_notes",
keyColumn: "shown_to_player",
keyValue: true,
column: "shown_to_player",
value: false);
migrationBuilder.AlterColumn<Guid>(
name: "player_user_id",
table: "admin_notes",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "last_edited_by_id",
table: "admin_notes",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "created_by_id",
table: "admin_notes",
type: "uuid",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "uuid",
oldNullable: true);
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban",
sql: "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban",
sql: "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes",
column: "created_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes",
column: "deleted_by_id",
principalTable: "player",
principalColumn: "user_id");
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes",
column: "player_user_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using System.Net;
using System.Text.Json;
@@ -188,12 +188,12 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("admin_log_player", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_notes_id");
.HasColumnName("admin_messages_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
@@ -201,7 +201,7 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Guid>("CreatedById")
b.Property<Guid?>("CreatedById")
.HasColumnType("uuid")
.HasColumnName("created_by_id");
@@ -217,11 +217,15 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("uuid")
.HasColumnName("deleted_by_id");
b.Property<DateTime>("LastEditedAt")
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time");
b.Property<DateTime?>("LastEditedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_edited_at");
b.Property<Guid>("LastEditedById")
b.Property<Guid?>("LastEditedById")
.HasColumnType("uuid")
.HasColumnName("last_edited_by_id");
@@ -231,17 +235,107 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("character varying(4096)")
.HasColumnName("message");
b.Property<Guid>("PlayerUserId")
b.Property<Guid?>("PlayerUserId")
.HasColumnType("uuid")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("interval")
.HasColumnName("playtime_at_note");
b.Property<int?>("RoundId")
.HasColumnType("integer")
.HasColumnName("round_id");
b.Property<bool>("ShownToPlayer")
b.Property<bool>("Seen")
.HasColumnType("boolean")
.HasColumnName("shown_to_player");
.HasColumnName("seen");
b.HasKey("Id")
.HasName("PK_admin_messages");
b.HasIndex("CreatedById");
b.HasIndex("DeletedById");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_admin_messages_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_admin_messages_round_id");
b.ToTable("admin_messages", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_notes_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Guid?>("CreatedById")
.HasColumnType("uuid")
.HasColumnName("created_by_id");
b.Property<bool>("Deleted")
.HasColumnType("boolean")
.HasColumnName("deleted");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Guid?>("DeletedById")
.HasColumnType("uuid")
.HasColumnName("deleted_by_id");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time");
b.Property<DateTime?>("LastEditedAt")
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("uuid")
.HasColumnName("last_edited_by_id");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("message");
b.Property<int>("Severity")
.HasColumnType("integer")
.HasColumnName("severity");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("uuid")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("interval")
.HasColumnName("playtime_at_note");
b.Property<int?>("RoundId")
.HasColumnType("integer")
.HasColumnName("round_id");
b.Property<bool>("Secret")
.HasColumnType("boolean")
.HasColumnName("secret");
b.HasKey("Id")
.HasName("PK_admin_notes");
@@ -310,6 +404,84 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("admin_rank_flag", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("admin_watchlists_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Guid?>("CreatedById")
.HasColumnType("uuid")
.HasColumnName("created_by_id");
b.Property<bool>("Deleted")
.HasColumnType("boolean")
.HasColumnName("deleted");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<Guid?>("DeletedById")
.HasColumnType("uuid")
.HasColumnName("deleted_by_id");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("timestamp with time zone")
.HasColumnName("expiration_time");
b.Property<DateTime?>("LastEditedAt")
.IsRequired()
.HasColumnType("timestamp with time zone")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("uuid")
.HasColumnName("last_edited_by_id");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("message");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("uuid")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("interval")
.HasColumnName("playtime_at_note");
b.Property<int?>("RoundId")
.HasColumnType("integer")
.HasColumnName("round_id");
b.HasKey("Id")
.HasName("PK_admin_watchlists");
b.HasIndex("CreatedById");
b.HasIndex("DeletedById");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_admin_watchlists_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_admin_watchlists_round_id");
b.ToTable("admin_watchlists", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.Property<int>("Id")
@@ -753,27 +925,59 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<bool>("Hidden")
.HasColumnType("boolean")
.HasColumnName("hidden");
b.Property<DateTime?>("LastEditedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("uuid")
.HasColumnName("last_edited_by_id");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("uuid")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("interval")
.HasColumnName("playtime_at_note");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text")
.HasColumnName("reason");
b.Property<Guid?>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<int?>("RoundId")
.HasColumnType("integer")
.HasColumnName("round_id");
b.Property<int>("Severity")
.HasColumnType("integer")
.HasColumnName("severity");
b.HasKey("Id")
.HasName("PK_server_ban");
b.HasIndex("Address");
b.HasIndex("UserId");
b.HasIndex("BanningAdmin");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_server_ban_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_server_ban_round_id");
b.ToTable("server_ban", null, t =>
{
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
});
});
@@ -855,6 +1059,26 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("bytea")
.HasColumnName("hwid");
b.Property<bool>("Hidden")
.HasColumnType("boolean")
.HasColumnName("hidden");
b.Property<DateTime?>("LastEditedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("uuid")
.HasColumnName("last_edited_by_id");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("uuid")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("interval")
.HasColumnName("playtime_at_note");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text")
@@ -865,22 +1089,34 @@ namespace Content.Server.Database.Migrations.Postgres
.HasColumnType("text")
.HasColumnName("role_id");
b.Property<Guid?>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.Property<int?>("RoundId")
.HasColumnType("integer")
.HasColumnName("round_id");
b.Property<int>("Severity")
.HasColumnType("integer")
.HasColumnName("severity");
b.HasKey("Id")
.HasName("PK_server_role_ban");
b.HasIndex("Address");
b.HasIndex("UserId");
b.HasIndex("BanningAdmin");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_server_role_ban_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_server_role_ban_round_id");
b.ToTable("server_role_ban", null, t =>
{
t.HasCheckConstraint("AddressNotIPv6MappedIPv4", "NOT inet '::ffff:0.0.0.0/96' >>= address");
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
});
});
@@ -1101,36 +1337,80 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Player");
});
modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminMessagesCreated")
.HasForeignKey("CreatedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_created_by_id");
b.HasOne("Content.Server.Database.Player", "DeletedBy")
.WithMany("AdminMessagesDeleted")
.HasForeignKey("DeletedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_deleted_by_id");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminMessagesLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminMessagesReceived")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_player_user_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_admin_messages_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("DeletedBy");
b.Navigation("LastEditedBy");
b.Navigation("Player");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminNotesCreated")
.HasForeignKey("CreatedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_created_by_id");
b.HasOne("Content.Server.Database.Player", "DeletedBy")
.WithMany("AdminNotesDeleted")
.HasForeignKey("DeletedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_deleted_by_id");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminNotesLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminNotesReceived")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_player_user_id");
b.HasOne("Content.Server.Database.Round", "Round")
@@ -1161,6 +1441,52 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Rank");
});
modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminWatchlistsCreated")
.HasForeignKey("CreatedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_created_by_id");
b.HasOne("Content.Server.Database.Player", "DeletedBy")
.WithMany("AdminWatchlistsDeleted")
.HasForeignKey("DeletedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminWatchlistsLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminWatchlistsReceived")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_player_user_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_admin_watchlists_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("DeletedBy");
b.Navigation("LastEditedBy");
b.Navigation("Player");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
@@ -1209,6 +1535,34 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Server");
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminServerBansCreated")
.HasForeignKey("BanningAdmin")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_ban_player_banning_admin");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminServerBansLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_ban_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_server_ban_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("LastEditedBy");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
@@ -1230,6 +1584,34 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Connection");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminServerRoleBansCreated")
.HasForeignKey("BanningAdmin")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_role_ban_player_banning_admin");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminServerRoleBansLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_server_role_ban_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("LastEditedBy");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
@@ -1311,6 +1693,14 @@ namespace Content.Server.Database.Migrations.Postgres
{
b.Navigation("AdminLogs");
b.Navigation("AdminMessagesCreated");
b.Navigation("AdminMessagesDeleted");
b.Navigation("AdminMessagesLastEdited");
b.Navigation("AdminMessagesReceived");
b.Navigation("AdminNotesCreated");
b.Navigation("AdminNotesDeleted");
@@ -1318,6 +1708,22 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("AdminNotesLastEdited");
b.Navigation("AdminNotesReceived");
b.Navigation("AdminServerBansCreated");
b.Navigation("AdminServerBansLastEdited");
b.Navigation("AdminServerRoleBansCreated");
b.Navigation("AdminServerRoleBansLastEdited");
b.Navigation("AdminWatchlistsCreated");
b.Navigation("AdminWatchlistsDeleted");
b.Navigation("AdminWatchlistsLastEdited");
b.Navigation("AdminWatchlistsReceived");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,713 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class AdminNotesImprovement : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban");
migrationBuilder.RenameColumn(
name: "user_id",
table: "server_role_ban",
newName: "player_user_id");
migrationBuilder.RenameIndex(
name: "IX_server_role_ban_user_id",
table: "server_role_ban",
newName: "IX_server_role_ban_player_user_id");
migrationBuilder.RenameColumn(
name: "user_id",
table: "server_ban",
newName: "player_user_id");
migrationBuilder.RenameIndex(
name: "IX_server_ban_user_id",
table: "server_ban",
newName: "IX_server_ban_player_user_id");
migrationBuilder.RenameColumn(
name: "shown_to_player",
table: "admin_notes",
newName: "secret");
migrationBuilder.UpdateData(
table: "admin_notes",
keyColumn: "secret",
keyValue: false,
column: "secret",
value: true);
migrationBuilder.AddColumn<bool>(
name: "hidden",
table: "server_role_ban",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<DateTime>(
name: "last_edited_at",
table: "server_role_ban",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "last_edited_by_id",
table: "server_role_ban",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<TimeSpan>(
name: "playtime_at_note",
table: "server_role_ban",
type: "TEXT",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0));
migrationBuilder.AddColumn<int>(
name: "round_id",
table: "server_role_ban",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "severity",
table: "server_role_ban",
type: "INTEGER",
nullable: false,
defaultValue: 2);
migrationBuilder.AddColumn<bool>(
name: "hidden",
table: "server_ban",
type: "INTEGER",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<DateTime>(
name: "last_edited_at",
table: "server_ban",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "last_edited_by_id",
table: "server_ban",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<TimeSpan>(
name: "playtime_at_note",
table: "server_ban",
type: "TEXT",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0));
migrationBuilder.AddColumn<int>(
name: "round_id",
table: "server_ban",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "severity",
table: "server_ban",
type: "INTEGER",
nullable: false,
defaultValue: 3);
migrationBuilder.AlterColumn<Guid>(
name: "player_user_id",
table: "admin_notes",
type: "TEXT",
nullable: true,
oldClrType: typeof(Guid),
oldType: "TEXT");
migrationBuilder.AlterColumn<Guid>(
name: "last_edited_by_id",
table: "admin_notes",
type: "TEXT",
nullable: true,
oldClrType: typeof(Guid),
oldType: "TEXT");
migrationBuilder.AlterColumn<Guid>(
name: "created_by_id",
table: "admin_notes",
type: "TEXT",
nullable: true,
oldClrType: typeof(Guid),
oldType: "TEXT");
migrationBuilder.AddColumn<DateTime>(
name: "expiration_time",
table: "admin_notes",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "severity",
table: "admin_notes",
type: "INTEGER",
nullable: false,
defaultValue: 1);
migrationBuilder.AddColumn<TimeSpan>(
name: "playtime_at_note",
table: "admin_notes",
type: "TEXT",
nullable: false,
defaultValue: new TimeSpan(0, 0, 0, 0, 0));
migrationBuilder.CreateTable(
name: "admin_messages",
columns: table => new
{
admin_messages_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
round_id = table.Column<int>(type: "INTEGER", nullable: true),
player_user_id = table.Column<Guid>(type: "TEXT", nullable: true),
playtime_at_note = table.Column<TimeSpan>(type: "TEXT", nullable: false),
message = table.Column<string>(type: "TEXT", maxLength: 4096, nullable: false),
created_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
created_at = table.Column<DateTime>(type: "TEXT", nullable: false),
last_edited_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
last_edited_at = table.Column<DateTime>(type: "TEXT", nullable: true),
expiration_time = table.Column<DateTime>(type: "TEXT", nullable: true),
deleted = table.Column<bool>(type: "INTEGER", nullable: false),
deleted_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
deleted_at = table.Column<DateTime>(type: "TEXT", nullable: true),
seen = table.Column<bool>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_messages", x => x.admin_messages_id);
table.ForeignKey(
name: "FK_admin_messages_player_created_by_id",
column: x => x.created_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_player_deleted_by_id",
column: x => x.deleted_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_player_last_edited_by_id",
column: x => x.last_edited_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_player_player_user_id",
column: x => x.player_user_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_messages_round_round_id",
column: x => x.round_id,
principalTable: "round",
principalColumn: "round_id");
});
migrationBuilder.CreateTable(
name: "admin_watchlists",
columns: table => new
{
admin_watchlists_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
round_id = table.Column<int>(type: "INTEGER", nullable: true),
player_user_id = table.Column<Guid>(type: "TEXT", nullable: true),
playtime_at_note = table.Column<TimeSpan>(type: "TEXT", nullable: false),
message = table.Column<string>(type: "TEXT", maxLength: 4096, nullable: false),
created_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
created_at = table.Column<DateTime>(type: "TEXT", nullable: false),
last_edited_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
last_edited_at = table.Column<DateTime>(type: "TEXT", nullable: false),
expiration_time = table.Column<DateTime>(type: "TEXT", nullable: true),
deleted = table.Column<bool>(type: "INTEGER", nullable: false),
deleted_by_id = table.Column<Guid>(type: "TEXT", nullable: true),
deleted_at = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_admin_watchlists", x => x.admin_watchlists_id);
table.ForeignKey(
name: "FK_admin_watchlists_player_created_by_id",
column: x => x.created_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_player_deleted_by_id",
column: x => x.deleted_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_player_last_edited_by_id",
column: x => x.last_edited_by_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_player_player_user_id",
column: x => x.player_user_id,
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
table.ForeignKey(
name: "FK_admin_watchlists_round_round_id",
column: x => x.round_id,
principalTable: "round",
principalColumn: "round_id");
});
migrationBuilder.CreateIndex(
name: "IX_server_role_ban_banning_admin",
table: "server_role_ban",
column: "banning_admin");
migrationBuilder.CreateIndex(
name: "IX_server_role_ban_last_edited_by_id",
table: "server_role_ban",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_server_role_ban_round_id",
table: "server_role_ban",
column: "round_id");
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban",
sql: "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_server_ban_banning_admin",
table: "server_ban",
column: "banning_admin");
migrationBuilder.CreateIndex(
name: "IX_server_ban_last_edited_by_id",
table: "server_ban",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_server_ban_round_id",
table: "server_ban",
column: "round_id");
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban",
sql: "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_created_by_id",
table: "admin_messages",
column: "created_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_deleted_by_id",
table: "admin_messages",
column: "deleted_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_last_edited_by_id",
table: "admin_messages",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_player_user_id",
table: "admin_messages",
column: "player_user_id");
migrationBuilder.CreateIndex(
name: "IX_admin_messages_round_id",
table: "admin_messages",
column: "round_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_created_by_id",
table: "admin_watchlists",
column: "created_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_deleted_by_id",
table: "admin_watchlists",
column: "deleted_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_last_edited_by_id",
table: "admin_watchlists",
column: "last_edited_by_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_player_user_id",
table: "admin_watchlists",
column: "player_user_id");
migrationBuilder.CreateIndex(
name: "IX_admin_watchlists_round_id",
table: "admin_watchlists",
column: "round_id");
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes",
column: "created_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes",
column: "deleted_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes",
column: "player_user_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_ban_player_banning_admin",
table: "server_ban",
column: "banning_admin",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_ban_player_last_edited_by_id",
table: "server_ban",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_ban_round_round_id",
table: "server_ban",
column: "round_id",
principalTable: "round",
principalColumn: "round_id");
migrationBuilder.AddForeignKey(
name: "FK_server_role_ban_player_banning_admin",
table: "server_role_ban",
column: "banning_admin",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_role_ban_player_last_edited_by_id",
table: "server_role_ban",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.SetNull);
migrationBuilder.AddForeignKey(
name: "FK_server_role_ban_round_round_id",
table: "server_role_ban",
column: "round_id",
principalTable: "round",
principalColumn: "round_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes");
migrationBuilder.DropForeignKey(
name: "FK_server_ban_player_banning_admin",
table: "server_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_ban_player_last_edited_by_id",
table: "server_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_ban_round_round_id",
table: "server_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_role_ban_player_banning_admin",
table: "server_role_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_role_ban_player_last_edited_by_id",
table: "server_role_ban");
migrationBuilder.DropForeignKey(
name: "FK_server_role_ban_round_round_id",
table: "server_role_ban");
migrationBuilder.DropTable(
name: "admin_messages");
migrationBuilder.DropTable(
name: "admin_watchlists");
migrationBuilder.DropIndex(
name: "IX_server_role_ban_banning_admin",
table: "server_role_ban");
migrationBuilder.DropIndex(
name: "IX_server_role_ban_last_edited_by_id",
table: "server_role_ban");
migrationBuilder.DropIndex(
name: "IX_server_role_ban_round_id",
table: "server_role_ban");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban");
migrationBuilder.DropIndex(
name: "IX_server_ban_banning_admin",
table: "server_ban");
migrationBuilder.DropIndex(
name: "IX_server_ban_last_edited_by_id",
table: "server_ban");
migrationBuilder.DropIndex(
name: "IX_server_ban_round_id",
table: "server_ban");
migrationBuilder.DropCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban");
migrationBuilder.DropColumn(
name: "hidden",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "last_edited_at",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "last_edited_by_id",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "playtime_at_note",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "round_id",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "severity",
table: "server_role_ban");
migrationBuilder.DropColumn(
name: "hidden",
table: "server_ban");
migrationBuilder.DropColumn(
name: "last_edited_at",
table: "server_ban");
migrationBuilder.DropColumn(
name: "last_edited_by_id",
table: "server_ban");
migrationBuilder.DropColumn(
name: "playtime_at_note",
table: "server_ban");
migrationBuilder.DropColumn(
name: "round_id",
table: "server_ban");
migrationBuilder.DropColumn(
name: "severity",
table: "server_ban");
migrationBuilder.DropColumn(
name: "expiration_time",
table: "admin_notes");
migrationBuilder.DropColumn(
name: "severity",
table: "admin_notes");
migrationBuilder.DropColumn(
name: "playtime_at_note",
table: "admin_notes");
migrationBuilder.RenameColumn(
name: "player_user_id",
table: "server_role_ban",
newName: "user_id");
migrationBuilder.RenameIndex(
name: "IX_server_role_ban_player_user_id",
table: "server_role_ban",
newName: "IX_server_role_ban_user_id");
migrationBuilder.RenameColumn(
name: "player_user_id",
table: "server_ban",
newName: "user_id");
migrationBuilder.RenameIndex(
name: "IX_server_ban_player_user_id",
table: "server_ban",
newName: "IX_server_ban_user_id");
migrationBuilder.RenameColumn(
name: "secret",
table: "admin_notes",
newName: "shown_to_player");
migrationBuilder.UpdateData(
table: "admin_notes",
keyColumn: "shown_to_player",
keyValue: true,
column: "shown_to_player",
value: false);
migrationBuilder.AlterColumn<Guid>(
name: "player_user_id",
table: "admin_notes",
type: "TEXT",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "last_edited_by_id",
table: "admin_notes",
type: "TEXT",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AlterColumn<Guid>(
name: "created_by_id",
table: "admin_notes",
type: "TEXT",
nullable: false,
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
oldClrType: typeof(Guid),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_role_ban",
sql: "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.AddCheckConstraint(
name: "HaveEitherAddressOrUserIdOrHWId",
table: "server_ban",
sql: "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_created_by_id",
table: "admin_notes",
column: "created_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_deleted_by_id",
table: "admin_notes",
column: "deleted_by_id",
principalTable: "player",
principalColumn: "user_id");
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_last_edited_by_id",
table: "admin_notes",
column: "last_edited_by_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_admin_notes_player_player_user_id",
table: "admin_notes",
column: "player_user_id",
principalTable: "player",
principalColumn: "user_id",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@@ -1,4 +1,4 @@
// <auto-generated />
// <auto-generated />
using System;
using Content.Server.Database;
using Microsoft.EntityFrameworkCore;
@@ -170,18 +170,18 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("admin_log_player", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_notes_id");
.HasColumnName("admin_messages_id");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT")
.HasColumnName("created_at");
b.Property<Guid>("CreatedById")
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT")
.HasColumnName("created_by_id");
@@ -197,11 +197,15 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("deleted_by_id");
b.Property<DateTime>("LastEditedAt")
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<DateTime?>("LastEditedAt")
.HasColumnType("TEXT")
.HasColumnName("last_edited_at");
b.Property<Guid>("LastEditedById")
b.Property<Guid?>("LastEditedById")
.HasColumnType("TEXT")
.HasColumnName("last_edited_by_id");
@@ -211,17 +215,105 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("message");
b.Property<Guid>("PlayerUserId")
b.Property<Guid?>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("TEXT")
.HasColumnName("playtime_at_note");
b.Property<int?>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.Property<bool>("ShownToPlayer")
b.Property<bool>("Seen")
.HasColumnType("INTEGER")
.HasColumnName("shown_to_player");
.HasColumnName("seen");
b.HasKey("Id")
.HasName("PK_admin_messages");
b.HasIndex("CreatedById");
b.HasIndex("DeletedById");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_admin_messages_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_admin_messages_round_id");
b.ToTable("admin_messages", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_notes_id");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT")
.HasColumnName("created_at");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT")
.HasColumnName("created_by_id");
b.Property<bool>("Deleted")
.HasColumnType("INTEGER")
.HasColumnName("deleted");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("TEXT")
.HasColumnName("deleted_at");
b.Property<Guid?>("DeletedById")
.HasColumnType("TEXT")
.HasColumnName("deleted_by_id");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<DateTime?>("LastEditedAt")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("TEXT")
.HasColumnName("last_edited_by_id");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("TEXT")
.HasColumnName("message");
b.Property<int>("Severity")
.HasColumnType("INTEGER")
.HasColumnName("severity");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("TEXT")
.HasColumnName("playtime_at_note");
b.Property<int?>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.Property<bool>("Secret")
.HasColumnType("INTEGER")
.HasColumnName("secret");
b.HasKey("Id")
.HasName("PK_admin_notes");
@@ -286,6 +378,82 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("admin_rank_flag", (string)null);
});
modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("admin_watchlists_id");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT")
.HasColumnName("created_at");
b.Property<Guid?>("CreatedById")
.HasColumnType("TEXT")
.HasColumnName("created_by_id");
b.Property<bool>("Deleted")
.HasColumnType("INTEGER")
.HasColumnName("deleted");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("TEXT")
.HasColumnName("deleted_at");
b.Property<Guid?>("DeletedById")
.HasColumnType("TEXT")
.HasColumnName("deleted_by_id");
b.Property<DateTime?>("ExpirationTime")
.HasColumnType("TEXT")
.HasColumnName("expiration_time");
b.Property<DateTime?>("LastEditedAt")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("TEXT")
.HasColumnName("last_edited_by_id");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(4096)
.HasColumnType("TEXT")
.HasColumnName("message");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("TEXT")
.HasColumnName("playtime_at_note");
b.Property<int?>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.HasKey("Id")
.HasName("PK_admin_watchlists");
b.HasIndex("CreatedById");
b.HasIndex("DeletedById");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_admin_watchlists_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_admin_watchlists_round_id");
b.ToTable("admin_watchlists", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.Property<int>("Id")
@@ -701,25 +869,57 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<bool>("Hidden")
.HasColumnType("INTEGER")
.HasColumnName("hidden");
b.Property<DateTime?>("LastEditedAt")
.HasColumnType("TEXT")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("TEXT")
.HasColumnName("last_edited_by_id");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("TEXT")
.HasColumnName("playtime_at_note");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<int?>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.Property<int>("Severity")
.HasColumnType("INTEGER")
.HasColumnName("severity");
b.HasKey("Id")
.HasName("PK_server_ban");
b.HasIndex("Address");
b.HasIndex("UserId");
b.HasIndex("BanningAdmin");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_server_ban_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_server_ban_round_id");
b.ToTable("server_ban", null, t =>
{
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
});
});
@@ -797,6 +997,26 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("BLOB")
.HasColumnName("hwid");
b.Property<bool>("Hidden")
.HasColumnType("INTEGER")
.HasColumnName("hidden");
b.Property<DateTime?>("LastEditedAt")
.HasColumnType("TEXT")
.HasColumnName("last_edited_at");
b.Property<Guid?>("LastEditedById")
.HasColumnType("TEXT")
.HasColumnName("last_edited_by_id");
b.Property<Guid?>("PlayerUserId")
.HasColumnType("TEXT")
.HasColumnName("player_user_id");
b.Property<TimeSpan>("PlaytimeAtNote")
.HasColumnType("TEXT")
.HasColumnName("playtime_at_note");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
@@ -807,20 +1027,32 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("TEXT")
.HasColumnName("role_id");
b.Property<Guid?>("UserId")
.HasColumnType("TEXT")
.HasColumnName("user_id");
b.Property<int?>("RoundId")
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.Property<int>("Severity")
.HasColumnType("INTEGER")
.HasColumnName("severity");
b.HasKey("Id")
.HasName("PK_server_role_ban");
b.HasIndex("Address");
b.HasIndex("UserId");
b.HasIndex("BanningAdmin");
b.HasIndex("LastEditedById");
b.HasIndex("PlayerUserId")
.HasDatabaseName("IX_server_role_ban_player_user_id");
b.HasIndex("RoundId")
.HasDatabaseName("IX_server_role_ban_round_id");
b.ToTable("server_role_ban", null, t =>
{
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL");
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL");
});
});
@@ -1033,36 +1265,80 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Player");
});
modelBuilder.Entity("Content.Server.Database.AdminMessage", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminMessagesCreated")
.HasForeignKey("CreatedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_created_by_id");
b.HasOne("Content.Server.Database.Player", "DeletedBy")
.WithMany("AdminMessagesDeleted")
.HasForeignKey("DeletedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_deleted_by_id");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminMessagesLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminMessagesReceived")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_messages_player_player_user_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_admin_messages_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("DeletedBy");
b.Navigation("LastEditedBy");
b.Navigation("Player");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminNotesCreated")
.HasForeignKey("CreatedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_created_by_id");
b.HasOne("Content.Server.Database.Player", "DeletedBy")
.WithMany("AdminNotesDeleted")
.HasForeignKey("DeletedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_deleted_by_id");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminNotesLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminNotesReceived")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_notes_player_player_user_id");
b.HasOne("Content.Server.Database.Round", "Round")
@@ -1093,6 +1369,52 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Rank");
});
modelBuilder.Entity("Content.Server.Database.AdminWatchlist", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminWatchlistsCreated")
.HasForeignKey("CreatedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_created_by_id");
b.HasOne("Content.Server.Database.Player", "DeletedBy")
.WithMany("AdminWatchlistsDeleted")
.HasForeignKey("DeletedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_deleted_by_id");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminWatchlistsLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Player", "Player")
.WithMany("AdminWatchlistsReceived")
.HasForeignKey("PlayerUserId")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_admin_watchlists_player_player_user_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_admin_watchlists_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("DeletedBy");
b.Navigation("LastEditedBy");
b.Navigation("Player");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.Antag", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
@@ -1141,6 +1463,34 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Server");
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminServerBansCreated")
.HasForeignKey("BanningAdmin")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_ban_player_banning_admin");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminServerBansLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_ban_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_server_ban_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("LastEditedBy");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
@@ -1162,6 +1512,34 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Connection");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleBan", b =>
{
b.HasOne("Content.Server.Database.Player", "CreatedBy")
.WithMany("AdminServerRoleBansCreated")
.HasForeignKey("BanningAdmin")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_role_ban_player_banning_admin");
b.HasOne("Content.Server.Database.Player", "LastEditedBy")
.WithMany("AdminServerRoleBansLastEdited")
.HasForeignKey("LastEditedById")
.HasPrincipalKey("UserId")
.OnDelete(DeleteBehavior.SetNull)
.HasConstraintName("FK_server_role_ban_player_last_edited_by_id");
b.HasOne("Content.Server.Database.Round", "Round")
.WithMany()
.HasForeignKey("RoundId")
.HasConstraintName("FK_server_role_ban_round_round_id");
b.Navigation("CreatedBy");
b.Navigation("LastEditedBy");
b.Navigation("Round");
});
modelBuilder.Entity("Content.Server.Database.ServerRoleUnban", b =>
{
b.HasOne("Content.Server.Database.ServerRoleBan", "Ban")
@@ -1243,6 +1621,14 @@ namespace Content.Server.Database.Migrations.Sqlite
{
b.Navigation("AdminLogs");
b.Navigation("AdminMessagesCreated");
b.Navigation("AdminMessagesDeleted");
b.Navigation("AdminMessagesLastEdited");
b.Navigation("AdminMessagesReceived");
b.Navigation("AdminNotesCreated");
b.Navigation("AdminNotesDeleted");
@@ -1250,6 +1636,22 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("AdminNotesLastEdited");
b.Navigation("AdminNotesReceived");
b.Navigation("AdminServerBansCreated");
b.Navigation("AdminServerBansLastEdited");
b.Navigation("AdminServerRoleBansCreated");
b.Navigation("AdminServerRoleBansLastEdited");
b.Navigation("AdminWatchlistsCreated");
b.Navigation("AdminWatchlistsDeleted");
b.Navigation("AdminWatchlistsLastEdited");
b.Navigation("AdminWatchlistsReceived");
});
modelBuilder.Entity("Content.Server.Database.Preference", b =>

View File

@@ -37,6 +37,8 @@ namespace Content.Server.Database
public DbSet<PlayTime> PlayTime { get; set; } = default!;
public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -114,20 +116,20 @@ namespace Content.Server.Database
.HasKey(logPlayer => new {logPlayer.PlayerUserId, logPlayer.LogId, logPlayer.RoundId});
modelBuilder.Entity<ServerBan>()
.HasIndex(p => p.UserId);
.HasIndex(p => p.PlayerUserId);
modelBuilder.Entity<ServerBan>()
.HasIndex(p => p.Address);
modelBuilder.Entity<ServerBan>()
.HasIndex(p => p.UserId);
.HasIndex(p => p.PlayerUserId);
modelBuilder.Entity<ServerUnban>()
.HasIndex(p => p.BanId)
.IsUnique();
modelBuilder.Entity<ServerBan>().ToTable(t =>
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"));
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
// Ban exemption can't have flags 0 since that wouldn't exempt anything.
// The row should be removed if setting to 0.
@@ -135,20 +137,20 @@ namespace Content.Server.Database
t.HasCheckConstraint("FlagsNotZero", "flags != 0"));
modelBuilder.Entity<ServerRoleBan>()
.HasIndex(p => p.UserId);
.HasIndex(p => p.PlayerUserId);
modelBuilder.Entity<ServerRoleBan>()
.HasIndex(p => p.Address);
modelBuilder.Entity<ServerRoleBan>()
.HasIndex(p => p.UserId);
.HasIndex(p => p.PlayerUserId);
modelBuilder.Entity<ServerRoleUnban>()
.HasIndex(p => p.BanId)
.IsUnique();
modelBuilder.Entity<ServerRoleBan>().ToTable(t =>
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR user_id IS NOT NULL OR hwid IS NOT NULL"));
t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
modelBuilder.Entity<Player>()
.HasIndex(p => p.UserId)
@@ -160,29 +162,118 @@ namespace Content.Server.Database
modelBuilder.Entity<ConnectionLog>()
.HasIndex(p => p.UserId);
// SetNull is necessary here so you can safely delete admins (GDPR right to erasure) while keeping the notes intact
modelBuilder.Entity<AdminNote>()
.HasOne(note => note.Player)
.WithMany(player => player.AdminNotesReceived)
.HasForeignKey(note => note.PlayerUserId)
.HasPrincipalKey(player => player.UserId);
.HasPrincipalKey(player => player.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminNote>()
.HasOne(version => version.CreatedBy)
.WithMany(author => author.AdminNotesCreated)
.HasForeignKey(note => note.CreatedById)
.HasPrincipalKey(author => author.UserId);
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminNote>()
.HasOne(version => version.LastEditedBy)
.WithMany(author => author.AdminNotesLastEdited)
.HasForeignKey(note => note.LastEditedById)
.HasPrincipalKey(author => author.UserId);
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminNote>()
.HasOne(version => version.DeletedBy)
.WithMany(author => author.AdminNotesDeleted)
.HasForeignKey(note => note.DeletedById)
.HasPrincipalKey(author => author.UserId);
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminWatchlist>()
.HasOne(note => note.Player)
.WithMany(player => player.AdminWatchlistsReceived)
.HasForeignKey(note => note.PlayerUserId)
.HasPrincipalKey(player => player.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminWatchlist>()
.HasOne(version => version.CreatedBy)
.WithMany(author => author.AdminWatchlistsCreated)
.HasForeignKey(note => note.CreatedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminWatchlist>()
.HasOne(version => version.LastEditedBy)
.WithMany(author => author.AdminWatchlistsLastEdited)
.HasForeignKey(note => note.LastEditedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminWatchlist>()
.HasOne(version => version.DeletedBy)
.WithMany(author => author.AdminWatchlistsDeleted)
.HasForeignKey(note => note.DeletedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminMessage>()
.HasOne(note => note.Player)
.WithMany(player => player.AdminMessagesReceived)
.HasForeignKey(note => note.PlayerUserId)
.HasPrincipalKey(player => player.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminMessage>()
.HasOne(version => version.CreatedBy)
.WithMany(author => author.AdminMessagesCreated)
.HasForeignKey(note => note.CreatedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminMessage>()
.HasOne(version => version.LastEditedBy)
.WithMany(author => author.AdminMessagesLastEdited)
.HasForeignKey(note => note.LastEditedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<AdminMessage>()
.HasOne(version => version.DeletedBy)
.WithMany(author => author.AdminMessagesDeleted)
.HasForeignKey(note => note.DeletedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<ServerBan>()
.HasOne(ban => ban.CreatedBy)
.WithMany(author => author.AdminServerBansCreated)
.HasForeignKey(ban => ban.BanningAdmin)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<ServerBan>()
.HasOne(ban => ban.LastEditedBy)
.WithMany(author => author.AdminServerBansLastEdited)
.HasForeignKey(ban => ban.LastEditedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<ServerRoleBan>()
.HasOne(ban => ban.CreatedBy)
.WithMany(author => author.AdminServerRoleBansCreated)
.HasForeignKey(ban => ban.BanningAdmin)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<ServerRoleBan>()
.HasOne(ban => ban.LastEditedBy)
.WithMany(author => author.AdminServerRoleBansLastEdited)
.HasForeignKey(ban => ban.LastEditedById)
.HasPrincipalKey(author => author.UserId)
.OnDelete(DeleteBehavior.SetNull);
}
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
@@ -313,6 +404,18 @@ namespace Content.Server.Database
public List<AdminNote> AdminNotesCreated { get; set; } = null!;
public List<AdminNote> AdminNotesLastEdited { get; set; } = null!;
public List<AdminNote> AdminNotesDeleted { get; set; } = null!;
public List<AdminWatchlist> AdminWatchlistsReceived { get; set; } = null!;
public List<AdminWatchlist> AdminWatchlistsCreated { get; set; } = null!;
public List<AdminWatchlist> AdminWatchlistsLastEdited { get; set; } = null!;
public List<AdminWatchlist> AdminWatchlistsDeleted { get; set; } = null!;
public List<AdminMessage> AdminMessagesReceived { get; set; } = null!;
public List<AdminMessage> AdminMessagesCreated { get; set; } = null!;
public List<AdminMessage> AdminMessagesLastEdited { get; set; } = null!;
public List<AdminMessage> AdminMessagesDeleted { get; set; } = null!;
public List<ServerBan> AdminServerBansCreated { get; set; } = null!;
public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
}
[Table("whitelist")]
@@ -427,12 +530,13 @@ namespace Content.Server.Database
public interface IBanCommon<TUnban> where TUnban : IUnbanCommon
{
int Id { get; set; }
Guid? UserId { get; set; }
Guid? PlayerUserId { get; set; }
(IPAddress, int)? Address { get; set; }
byte[]? HWId { get; set; }
DateTime BanTime { get; set; }
DateTime? ExpirationTime { get; set; }
string Reason { get; set; }
NoteSeverity Severity { get; set; }
Guid? BanningAdmin { get; set; }
TUnban? Unban { get; set; }
}
@@ -477,20 +581,26 @@ namespace Content.Server.Database
/// <remarks>
/// At least one of UserID, IP, or HWID must be given (otherwise the ban would match nothing).
/// </remarks>
[Table("server_ban")]
[Table("server_ban"), Index(nameof(PlayerUserId))]
public class ServerBan : IBanCommon<ServerUnban>
{
public int Id { get; set; }
[ForeignKey("Round")]
public int? RoundId { get; set; }
public Round? Round { get; set; }
/// <summary>
/// The user ID of the banned player.
/// </summary>
public Guid? UserId { get; set; }
public Guid? PlayerUserId { get; set; }
[Required] public TimeSpan PlaytimeAtNote { get; set; }
/// <summary>
/// CIDR IP address range of the ban. The whole range can match the ban.
/// </summary>
[Column(TypeName = "inet")] public (IPAddress, int)? Address { get; set; }
[Column(TypeName = "inet")]
public (IPAddress, int)? Address { get; set; }
/// <summary>
/// Hardware ID of the banned player.
@@ -512,11 +622,32 @@ namespace Content.Server.Database
/// </summary>
public string Reason { get; set; } = null!;
/// <summary>
/// The severity of the incident
/// </summary>
public NoteSeverity Severity { get; set; }
/// <summary>
/// User ID of the admin that applied the ban.
/// </summary>
[ForeignKey("CreatedBy")]
public Guid? BanningAdmin { get; set; }
public Player? CreatedBy { get; set; }
/// <summary>
/// User ID of the admin that last edited the note
/// </summary>
[ForeignKey("LastEditedBy")]
public Guid? LastEditedById { get; set; }
public Player? LastEditedBy { get; set; }
/// <summary>
/// When the ban was last edited
/// </summary>
public DateTime? LastEditedAt { get; set; }
/// <summary>
/// Optional flags that allow adding exemptions to the ban via <see cref="ServerBanExemption"/>.
/// </summary>
@@ -538,6 +669,11 @@ namespace Content.Server.Database
/// </remarks>
public bool AutoDelete { get; set; }
/// <summary>
/// Whether to display this ban in the admin remarks (notes) panel
/// </summary>
public bool Hidden { get; set; }
public List<ServerBanHit> BanHits { get; set; } = null!;
}
@@ -633,11 +769,14 @@ namespace Content.Server.Database
public ConnectionLog Connection { get; set; } = null!;
}
[Table("server_role_ban")]
[Table("server_role_ban"), Index(nameof(PlayerUserId))]
public sealed class ServerRoleBan : IBanCommon<ServerRoleUnban>
{
public int Id { get; set; }
public Guid? UserId { get; set; }
public int? RoundId { get; set; }
public Round? Round { get; set; }
public Guid? PlayerUserId { get; set; }
[Required] public TimeSpan PlaytimeAtNote { get; set; }
[Column(TypeName = "inet")] public (IPAddress, int)? Address { get; set; }
public byte[]? HWId { get; set; }
@@ -646,9 +785,17 @@ namespace Content.Server.Database
public DateTime? ExpirationTime { get; set; }
public string Reason { get; set; } = null!;
public Guid? BanningAdmin { get; set; }
public NoteSeverity Severity { get; set; }
[ForeignKey("CreatedBy")] public Guid? BanningAdmin { get; set; }
public Player? CreatedBy { get; set; }
[ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
public Player? LastEditedBy { get; set; }
public DateTime? LastEditedAt { get; set; }
public ServerRoleUnban? Unban { get; set; }
public bool Hidden { get; set; }
public string RoleId { get; set; } = null!;
}
@@ -695,34 +842,126 @@ namespace Content.Server.Database
public byte[] Data { get; set; } = default!;
}
public interface IAdminRemarksCommon
{
public int Id { get; }
public int? RoundId { get; }
public Round? Round { get; }
public Guid? PlayerUserId { get; }
public Player? Player { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Message { get; }
public Player? CreatedBy { get; }
public DateTime CreatedAt { get; }
public Player? LastEditedBy { get; }
public DateTime? LastEditedAt { get; }
public DateTime? ExpirationTime { get; }
public bool Deleted { get; }
}
[Index(nameof(PlayerUserId))]
public class AdminNote
public class AdminNote : IAdminRemarksCommon
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
[ForeignKey("Round")] public int? RoundId { get; set; }
public Round? Round { get; set; }
[Required, ForeignKey("Player")] public Guid PlayerUserId { get; set; }
public Player Player { get; set; } = default!;
[ForeignKey("Player")] public Guid? PlayerUserId { get; set; }
public Player? Player { get; set; }
[Required] public TimeSpan PlaytimeAtNote { get; set; }
[Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
[Required] public NoteSeverity Severity { get; set; }
[Required, ForeignKey("CreatedBy")] public Guid CreatedById { get; set; }
[Required] public Player CreatedBy { get; set; } = default!;
[ForeignKey("CreatedBy")] public Guid? CreatedById { get; set; }
public Player? CreatedBy { get; set; }
[Required] public DateTime CreatedAt { get; set; }
[Required, ForeignKey("LastEditedBy")] public Guid LastEditedById { get; set; }
[Required] public Player LastEditedBy { get; set; } = default!;
[ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
public Player? LastEditedBy { get; set; }
[Required] public DateTime LastEditedAt { get; set; }
[Required] public DateTime? LastEditedAt { get; set; }
public DateTime? ExpirationTime { get; set; }
public bool Deleted { get; set; }
[ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
public Player? DeletedBy { get; set; }
public DateTime? DeletedAt { get; set; }
public bool ShownToPlayer { get; set; }
public bool Secret { get; set; }
}
[Index(nameof(PlayerUserId))]
public class AdminWatchlist : IAdminRemarksCommon
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
[ForeignKey("Round")] public int? RoundId { get; set; }
public Round? Round { get; set; }
[ForeignKey("Player")] public Guid? PlayerUserId { get; set; }
public Player? Player { get; set; }
[Required] public TimeSpan PlaytimeAtNote { get; set; }
[Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
[ForeignKey("CreatedBy")] public Guid? CreatedById { get; set; }
public Player? CreatedBy { get; set; }
[Required] public DateTime CreatedAt { get; set; }
[ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
public Player? LastEditedBy { get; set; }
[Required] public DateTime? LastEditedAt { get; set; }
public DateTime? ExpirationTime { get; set; }
public bool Deleted { get; set; }
[ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
public Player? DeletedBy { get; set; }
public DateTime? DeletedAt { get; set; }
}
[Index(nameof(PlayerUserId))]
public class AdminMessage : IAdminRemarksCommon
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
[ForeignKey("Round")] public int? RoundId { get; set; }
public Round? Round { get; set; }
[ForeignKey("Player")]
public Guid? PlayerUserId { get; set; }
public Player? Player { get; set; }
[Required] public TimeSpan PlaytimeAtNote { get; set; }
[Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
[ForeignKey("CreatedBy")] public Guid? CreatedById { get; set; }
public Player? CreatedBy { get; set; }
[Required] public DateTime CreatedAt { get; set; }
[ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
public Player? LastEditedBy { get; set; }
public DateTime? LastEditedAt { get; set; }
public DateTime? ExpirationTime { get; set; }
public bool Deleted { get; set; }
[ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
public Player? DeletedBy { get; set; }
public DateTime? DeletedAt { get; set; }
public bool Seen { get; set; }
}
}

View File

@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Chat.Managers;
using Content.Server.Construction.Conditions;
using Content.Server.EUI;
using Content.Shared.Administration;
using Content.Shared.Database;
using Content.Shared.Eui;
using Microsoft.CodeAnalysis;
using Robust.Server.Player;
using Robust.Shared.Network;
namespace Content.Server.Administration;
public sealed class BanPanelEui : BaseEui
{
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly IPlayerLocator _playerLocator = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IAdminManager _admins = default!;
private NetUserId? PlayerId { get; set; }
private string PlayerName { get; set; } = string.Empty;
private IPAddress? LastAddress { get; set; }
private ImmutableArray<byte>? LastHwid { get; set; }
public BanPanelEui()
{
IoCManager.InjectDependencies(this);
}
public override EuiStateBase GetNewState()
{
var hasBan = _admins.HasAdminFlag(Player, AdminFlags.Ban);
return new BanPanelEuiState(PlayerName, hasBan);
}
public override void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case BanPanelEuiStateMsg.CreateBanRequest r:
BanPlayer(r.Player, r.IpAddress, r.UseLastIp, r.Hwid?.ToImmutableArray(), r.UseLastHwid, r.Minutes, r.Severity, r.Reason, r.Roles);
break;
case BanPanelEuiStateMsg.GetPlayerInfoRequest r:
ChangePlayer(r.PlayerUsername);
break;
}
}
private async void BanPlayer(string? target, string? ipAddressString, bool useLastIp, ImmutableArray<byte>? hwid, bool useLastHwid, uint minutes, NoteSeverity severity, string reason, IReadOnlyCollection<string>? roles)
{
if (!_admins.HasAdminFlag(Player, AdminFlags.Ban))
{
Logger.WarningS("admin.bans_eui", $"{Player.Name} ({Player.UserId}) tried to create a ban with no ban flag");
return;
}
if (target == null && string.IsNullOrWhiteSpace(ipAddressString) && hwid == null)
{
_chat.DispatchServerMessage(Player, Loc.GetString("ban-panel-no-data"));
return;
}
(IPAddress, int)? addressRange = null;
if (ipAddressString is not null)
{
var hid = "0";
var split = ipAddressString.Split('/', 2);
ipAddressString = split[0];
if (split.Length > 1)
hid = split[1];
if (!IPAddress.TryParse(ipAddressString, out var ipAddress) || !uint.TryParse(hid, out var hidInt) || hidInt > 128 || hidInt > 32 && ipAddress.AddressFamily == AddressFamily.InterNetwork)
{
_chat.DispatchServerMessage(Player, Loc.GetString("ban-panel-invalid-ip"));
return;
}
if (hidInt == 0)
hidInt = (uint) (ipAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32);
addressRange = (ipAddress, (int) hidInt);
}
var targetUid = target is not null ? PlayerId : null;
addressRange = useLastIp && LastAddress is not null ? (LastAddress, LastAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32) : addressRange;
var targetHWid = useLastHwid ? LastHwid : hwid;
if (target != null && target != PlayerName || Guid.TryParse(target, out var parsed) && parsed != PlayerId)
{
var located = await _playerLocator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
_chat.DispatchServerMessage(Player, Loc.GetString("cmd-ban-player"));
return;
}
targetUid = located.UserId;
var targetAddress = located.LastAddress;
if (useLastIp && targetAddress != null)
{
if (targetAddress.IsIPv4MappedToIPv6)
targetAddress = targetAddress.MapToIPv4();
// Ban /128 for IPv6, /32 for IPv4.
var hid = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 128 : 32;
addressRange = (targetAddress, hid);
}
targetHWid = useLastHwid ? located.LastHWId : hwid;
}
if (roles?.Count > 0)
{
var now = DateTimeOffset.UtcNow;
foreach (var role in roles)
{
_banManager.CreateRoleBan(targetUid, target, Player.UserId, addressRange, targetHWid, role, minutes, severity, reason, now);
}
return;
}
_banManager.CreateServerBan(targetUid, target, Player.UserId, addressRange, targetHWid, minutes, severity, reason);
Close();
}
public async void ChangePlayer(string playerNameOrId)
{
var located = await _playerLocator.LookupIdByNameOrIdAsync(playerNameOrId);
ChangePlayer(located?.UserId, located?.Username ?? string.Empty, located?.LastAddress, located?.LastHWId);
}
public void ChangePlayer(NetUserId? playerId, string playerName, IPAddress? lastAddress, ImmutableArray<byte>? lastHwid)
{
PlayerId = playerId;
PlayerName = playerName;
LastAddress = lastAddress;
LastHwid = lastHwid;
StateDirty();
}
public override async void Opened()
{
base.Opened();
_admins.OnPermsChanged += OnPermsChanged;
}
public override void Closed()
{
base.Closed();
_admins.OnPermsChanged -= OnPermsChanged;
}
private void OnPermsChanged(AdminPermsChangedEventArgs args)
{
if (args.Player != Player)
{
return;
}
StateDirty();
}
}

View File

@@ -2,144 +2,141 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using Content.Server.Administration.Managers;
using Content.Server.Administration.Notes;
using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Players.PlayTimeTracking;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Ban)]
public sealed class BanCommand : LocalizedCommands
{
[AdminCommand(AdminFlags.Ban)]
public sealed class BanCommand : LocalizedCommands
[Dependency] private readonly IPlayerLocator _locator = default!;
[Dependency] private readonly IBanManager _bans = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override string Command => "ban";
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
public override string Command => "ban";
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
string target;
string reason;
uint minutes;
if (!Enum.TryParse(_cfg.GetCVar(CCVars.ServerBanDefaultSeverity), out NoteSeverity severity))
{
var player = shell.Player as IPlayerSession;
var plyMgr = IoCManager.Resolve<IPlayerManager>();
var locator = IoCManager.Resolve<IPlayerLocator>();
var dbMan = IoCManager.Resolve<IServerDbManager>();
string target;
string reason;
uint minutes;
switch (args.Length)
{
case 2:
target = args[0];
reason = args[1];
minutes = 0;
break;
case 3:
target = args[0];
reason = args[1];
if (!uint.TryParse(args[2], out minutes))
{
shell.WriteLine($"{args[2]} is not a valid amount of minutes.\n{Help}");
return;
}
break;
default:
shell.WriteLine($"Invalid amount of arguments.{Help}");
return;
}
var located = await locator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
shell.WriteError(LocalizationManager.GetString("cmd-ban-player"));
return;
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
var targetAddr = located.LastAddress;
if (player != null && player.UserId == targetUid)
{
shell.WriteLine(LocalizationManager.GetString("cmd-ban-self"));
return;
}
DateTimeOffset? expires = null;
if (minutes > 0)
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes);
}
(IPAddress, int)? addrRange = null;
if (targetAddr != null)
{
if (targetAddr.IsIPv4MappedToIPv6)
targetAddr = targetAddr.MapToIPv4();
// Ban /64 for IPv4, /32 for IPv4.
var cidr = targetAddr.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32;
addrRange = (targetAddr, cidr);
}
var banDef = new ServerBanDef(
null,
targetUid,
addrRange,
targetHWid,
DateTimeOffset.Now,
expires,
reason,
player?.UserId,
null);
await dbMan.AddServerBanAsync(banDef);
var response = new StringBuilder($"Banned {target} with reason \"{reason}\"");
response.Append(expires == null ? " permanently." : $" until {expires}");
shell.WriteLine(response.ToString());
if (plyMgr.TryGetSessionById(targetUid, out var targetPlayer))
{
var message = banDef.FormatBanMessage(_cfg, LocalizationManager);
targetPlayer.ConnectedClient.Disconnect(message);
}
Logger.WarningS("admin.server_ban", "Server ban severity could not be parsed from config! Defaulting to high.");
severity = NoteSeverity.High;
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
switch (args.Length)
{
if (args.Length == 1)
{
var playerMgr = IoCManager.Resolve<IPlayerManager>();
var options = playerMgr.ServerSessions.Select(c => c.Name).OrderBy(c => c).ToArray();
return CompletionResult.FromHintOptions(options, LocalizationManager.GetString("cmd-ban-hint"));
}
case 2:
target = args[0];
reason = args[1];
minutes = 0;
break;
case 3:
target = args[0];
reason = args[1];
if (args.Length == 2)
return CompletionResult.FromHint(LocalizationManager.GetString("cmd-ban-hint-reason"));
if (args.Length == 3)
{
var durations = new CompletionOption[]
if (!uint.TryParse(args[2], out minutes))
{
new("0", LocalizationManager.GetString("cmd-ban-hint-duration-1")),
new("1440", LocalizationManager.GetString("cmd-ban-hint-duration-2")),
new("4320", LocalizationManager.GetString("cmd-ban-hint-duration-3")),
new("10080", LocalizationManager.GetString("cmd-ban-hint-duration-4")),
new("20160", LocalizationManager.GetString("cmd-ban-hint-duration-5")),
new("43800", LocalizationManager.GetString("cmd-ban-hint-duration-6")),
};
shell.WriteLine(Loc.GetString("cmd-ban-invalid-minutes", ("minutes", args[2])));
shell.WriteLine(Help);
return;
}
return CompletionResult.FromHintOptions(durations, LocalizationManager.GetString("cmd-ban-hint-duration"));
}
break;
case 4:
target = args[0];
reason = args[1];
return CompletionResult.Empty;
if (!uint.TryParse(args[2], out minutes))
{
shell.WriteLine(Loc.GetString("cmd-ban-invalid-minutes", ("minutes", args[2])));
shell.WriteLine(Help);
return;
}
if (!Enum.TryParse(args[3], ignoreCase: true, out severity))
{
shell.WriteLine(Loc.GetString("cmd-ban-invalid-severity", ("severity", args[3])));
shell.WriteLine(Help);
return;
}
break;
default:
shell.WriteLine(Loc.GetString("cmd-ban-invalid-arguments"));
shell.WriteLine(Help);
return;
}
var located = await _locator.LookupIdByNameOrIdAsync(target);
var player = shell.Player as IPlayerSession;
if (located == null)
{
shell.WriteError(Loc.GetString("cmd-ban-player"));
return;
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
_bans.CreateServerBan(targetUid, target, player?.UserId, null, targetHWid, minutes, severity, reason);
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
var options = _playerManager.ServerSessions.Select(c => c.Name).OrderBy(c => c).ToArray();
return CompletionResult.FromHintOptions(options, LocalizationManager.GetString("cmd-ban-hint"));
}
if (args.Length == 2)
return CompletionResult.FromHint(LocalizationManager.GetString("cmd-ban-hint-reason"));
if (args.Length == 3)
{
var durations = new CompletionOption[]
{
new("0", LocalizationManager.GetString("cmd-ban-hint-duration-1")),
new("1440", LocalizationManager.GetString("cmd-ban-hint-duration-2")),
new("4320", LocalizationManager.GetString("cmd-ban-hint-duration-3")),
new("10080", LocalizationManager.GetString("cmd-ban-hint-duration-4")),
new("20160", LocalizationManager.GetString("cmd-ban-hint-duration-5")),
new("43800", LocalizationManager.GetString("cmd-ban-hint-duration-6")),
};
return CompletionResult.FromHintOptions(durations, LocalizationManager.GetString("cmd-ban-hint-duration"));
}
if (args.Length == 4)
{
var severities = new CompletionOption[]
{
new("none", Loc.GetString("admin-note-editor-severity-none")),
new("minor", Loc.GetString("admin-note-editor-severity-low")),
new("medium", Loc.GetString("admin-note-editor-severity-medium")),
new("high", Loc.GetString("admin-note-editor-severity-high")),
};
return CompletionResult.FromHintOptions(severities, Loc.GetString("cmd-ban-hint-severity"));
}
return CompletionResult.Empty;
}
}

View File

@@ -0,0 +1,52 @@
using Content.Shared.Administration;
using Robust.Shared.Console;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Server.EUI;
using Robust.Server.Player;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Ban)]
public sealed class BanPanelCommand : LocalizedCommands
{
[Dependency] private readonly IPlayerLocator _locator = default!;
[Dependency] private readonly EuiManager _euis = default!;
public override string Command => "banpanel";
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (shell.Player is not IPlayerSession player)
{
shell.WriteError(Loc.GetString("cmd-banpanel-server"));
return;
}
switch (args.Length)
{
case 0:
_euis.OpenEui(new BanPanelEui(), player);
break;
case 1:
var located = await _locator.LookupIdByNameOrIdAsync(args[0]);
if (located is null)
{
shell.WriteError(Loc.GetString("cmd-banpanel-player-err"));
return;
}
var ui = new BanPanelEui();
_euis.OpenEui(ui, player);
ui.ChangePlayer(located.UserId, located.Username, located.LastAddress, located.LastHWId);
break;
default:
shell.WriteLine(Loc.GetString("cmd-ban-invalid-arguments"));
shell.WriteLine(Help);
return;
}
}
}

View File

@@ -1,6 +1,9 @@
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
@@ -9,9 +12,10 @@ namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Ban)]
public sealed class DepartmentBanCommand : IConsoleCommand
{
[Dependency] private readonly IPlayerLocator _locater = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly RoleBanManager _bans = default!;
[Dependency] private readonly IPlayerLocator _locator = default!;
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public string Command => "departmentban";
public string Description => Loc.GetString("cmd-departmentban-desc");
@@ -23,6 +27,11 @@ public sealed class DepartmentBanCommand : IConsoleCommand
string department;
string reason;
uint minutes;
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), out NoteSeverity severity))
{
Logger.WarningS("admin.department_ban", "Department ban severity could not be parsed from config! Defaulting to medium.");
severity = NoteSeverity.Medium;
}
switch (args.Length)
{
@@ -43,6 +52,24 @@ public sealed class DepartmentBanCommand : IConsoleCommand
return;
}
break;
case 5:
target = args[0];
department = args[1];
reason = args[2];
if (!uint.TryParse(args[3], out minutes))
{
shell.WriteError(Loc.GetString("cmd-roleban-minutes-parse", ("time", args[3]), ("help", Help)));
return;
}
if (!Enum.TryParse(args[4], ignoreCase: true, out severity))
{
shell.WriteLine(Loc.GetString("cmd-roleban-severity-parse", ("severity", args[4]), ("help", Help)));
return;
}
break;
default:
shell.WriteError(Loc.GetString("cmd-roleban-arg-count"));
@@ -55,20 +82,25 @@ public sealed class DepartmentBanCommand : IConsoleCommand
return;
}
var located = await _locater.LookupIdByNameOrIdAsync(target);
var located = await _locator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
shell.WriteError(Loc.GetString("cmd-roleban-name-parse"));
return;
}
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
// If you are trying to remove the following variable, please don't. It's there because the note system groups role bans by time, reason and banning admin.
// Without it the note list will get needlessly cluttered.
var now = DateTimeOffset.UtcNow;
foreach (var job in departmentProto.Roles)
{
_bans.CreateJobBan(shell, located, job, reason, minutes);
_banManager.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, now);
}
_bans.SendRoleBans(located);
_banManager.SendRoleBans(located.UserId);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
@@ -83,6 +115,14 @@ public sealed class DepartmentBanCommand : IConsoleCommand
new("43800", Loc.GetString("cmd-roleban-hint-duration-6")),
};
var severities = new CompletionOption[]
{
new("none", Loc.GetString("admin-note-editor-severity-none")),
new("minor", Loc.GetString("admin-note-editor-severity-low")),
new("medium", Loc.GetString("admin-note-editor-severity-medium")),
new("high", Loc.GetString("admin-note-editor-severity-high")),
};
return args.Length switch
{
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(),
@@ -91,6 +131,7 @@ public sealed class DepartmentBanCommand : IConsoleCommand
Loc.GetString("cmd-roleban-hint-2")),
3 => CompletionResult.FromHint(Loc.GetString("cmd-roleban-hint-3")),
4 => CompletionResult.FromHintOptions(durOpts, Loc.GetString("cmd-roleban-hint-4")),
5 => CompletionResult.FromHintOptions(severities, Loc.GetString("cmd-roleban-hint-5")),
_ => CompletionResult.Empty
};
}

View File

@@ -0,0 +1,38 @@
using Content.Server.Administration.Notes;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
[AnyCommand]
public sealed class OpenUserVisibleNotesCommand : IConsoleCommand
{
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly IAdminNotesManager _notes = default!;
public const string CommandName = "adminremarks";
public string Command => CommandName;
public string Description => Loc.GetString("admin-remarks-command-description");
public string Help => $"Usage: {Command}";
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (!_configuration.GetCVar(CCVars.SeeOwnNotes))
{
shell.WriteError(Loc.GetString("admin-remarks-command-error"));
return;
}
if (shell.Player is not IPlayerSession player)
{
shell.WriteError("This does not work from the server console.");
return;
}
await _notes.OpenUserNotesEui(player);
}
}

View File

@@ -1,19 +1,20 @@
using System.Linq;
using System.Linq;
using System.Text;
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
using Serilog;
namespace Content.Server.Administration.Commands;
[AdminCommand(AdminFlags.Ban)]
public sealed class RoleBanCommand : IConsoleCommand
{
[Dependency] private readonly IPlayerLocator _locator = default!;
[Dependency] private readonly RoleBanManager _bans = default!;
[Dependency] private readonly IBanManager _bans = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public string Command => "roleban";
public string Description => Loc.GetString("cmd-roleban-desc");
@@ -25,6 +26,11 @@ public sealed class RoleBanCommand : IConsoleCommand
string job;
string reason;
uint minutes;
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), out NoteSeverity severity))
{
Logger.WarningS("admin.role_ban", "Role ban severity could not be parsed from config! Defaulting to medium.");
severity = NoteSeverity.Medium;
}
switch (args.Length)
{
@@ -45,6 +51,24 @@ public sealed class RoleBanCommand : IConsoleCommand
return;
}
break;
case 5:
target = args[0];
job = args[1];
reason = args[2];
if (!uint.TryParse(args[3], out minutes))
{
shell.WriteError(Loc.GetString("cmd-roleban-minutes-parse", ("time", args[3]), ("help", Help)));
return;
}
if (!Enum.TryParse(args[4], ignoreCase: true, out severity))
{
shell.WriteLine(Loc.GetString("cmd-roleban-severity-parse", ("severity", args[4]), ("help", Help)));
return;
}
break;
default:
shell.WriteError(Loc.GetString("cmd-roleban-arg-count"));
@@ -53,15 +77,17 @@ public sealed class RoleBanCommand : IConsoleCommand
}
var located = await _locator.LookupIdByNameOrIdAsync(target);
if (located == null)
{
shell.WriteError(Loc.GetString("cmd-roleban-name-parse"));
return;
}
_bans.CreateJobBan(shell, located, job, reason, minutes);
_bans.SendRoleBans(located);
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
_bans.CreateRoleBan(targetUid, located.Username, shell.Player?.UserId, null, targetHWid, job, minutes, severity, reason, DateTimeOffset.UtcNow);
_bans.SendRoleBans(located.UserId);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
@@ -76,6 +102,14 @@ public sealed class RoleBanCommand : IConsoleCommand
new("43800", Loc.GetString("cmd-roleban-hint-duration-6")),
};
var severities = new CompletionOption[]
{
new("none", Loc.GetString("admin-note-editor-severity-none")),
new("minor", Loc.GetString("admin-note-editor-severity-low")),
new("medium", Loc.GetString("admin-note-editor-severity-medium")),
new("high", Loc.GetString("admin-note-editor-severity-high")),
};
return args.Length switch
{
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(),
@@ -84,6 +118,7 @@ public sealed class RoleBanCommand : IConsoleCommand
Loc.GetString("cmd-roleban-hint-2")),
3 => CompletionResult.FromHint(Loc.GetString("cmd-roleban-hint-3")),
4 => CompletionResult.FromHintOptions(durOpts, Loc.GetString("cmd-roleban-hint-4")),
5 => CompletionResult.FromHintOptions(severities, Loc.GetString("cmd-roleban-hint-5")),
_ => CompletionResult.Empty
};
}

View File

@@ -0,0 +1,259 @@
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Content.Server.Chat.Managers;
using Content.Server.Database;
using Content.Server.GameTicking;
using Content.Shared.Database;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles;
using Microsoft.CodeAnalysis;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Managers;
public sealed class BanManager : IBanManager, IPostInjectInit
{
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
public const string SawmillId = "admin.bans";
public const string JobPrefix = "Job:";
private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
public void Initialize()
{
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Connected
|| _cachedRoleBans.ContainsKey(e.Session.UserId))
return;
var netChannel = e.Session.ConnectedClient;
await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId);
}
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
{
if (banDef.UserId != null)
{
if (!_cachedRoleBans.TryGetValue(banDef.UserId.Value, out var roleBans))
{
roleBans = new HashSet<ServerRoleBanDef>();
_cachedRoleBans.Add(banDef.UserId.Value, roleBans);
}
if (!roleBans.Contains(banDef))
roleBans.Add(banDef);
}
await _db.AddServerRoleBanAsync(banDef);
return true;
}
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
{
return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null;
}
private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray<byte>? hwId = null)
{
var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false);
var userRoleBans = new HashSet<ServerRoleBanDef>();
foreach (var ban in roleBans)
{
userRoleBans.Add(ban);
}
_cachedRoleBans[userId] = userRoleBans;
}
public void Restart()
{
// Clear out players that have disconnected.
var toRemove = new List<NetUserId>();
foreach (var player in _cachedRoleBans.Keys)
{
if (!_playerManager.TryGetSessionById(player, out _))
toRemove.Add(player);
}
foreach (var player in toRemove)
{
_cachedRoleBans.Remove(player);
}
// Check for expired bans
foreach (var roleBans in _cachedRoleBans.Values)
{
roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime);
}
}
#region Server Bans
public async void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason)
{
DateTimeOffset? expires = null;
if (minutes > 0)
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
}
_systems.TryGetEntitySystem<GameTicker>(out var ticker);
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
var banDef = new ServerBanDef(
null,
target,
addressRange,
hwid,
DateTimeOffset.Now,
expires,
roundId,
playtime,
reason,
severity,
banningAdmin,
null);
await _db.AddServerBanAsync(banDef);
var adminName = banningAdmin == null
? Loc.GetString("system-user")
: (await _db.GetPlayerRecordByUserId(banningAdmin.Value))?.LastSeenUserName ?? Loc.GetString("system-user");
var targetName = target is null ? "null" : $"{targetUsername} ({target})";
var addressRangeString = addressRange != null
? $"{addressRange.Value.Item1}/{addressRange.Value.Item2}"
: "null";
var hwidString = hwid != null
? string.Concat(hwid.Value.Select(x => x.ToString("x2")))
: "null";
var expiresString = expires == null ? Loc.GetString("server-ban-string-never") : $"{expires}";
var logMessage = Loc.GetString("server-ban-string", ("admin", adminName), ("severity", severity),
("expires", expiresString), ("name", targetName), ("ip", addressRangeString),
("hwid", hwidString), ("reason", reason));
_sawmill.Info(logMessage);
_chat.SendAdminAlert(logMessage);
// If we're not banning a player we don't care about disconnecting people
if (target == null)
return;
// Is the player connected?
if (!_playerManager.TryGetSessionById(target.Value, out var targetPlayer))
return;
// If they are, kick them
var message = banDef.FormatBanMessage(_cfg, _localizationManager);
targetPlayer.ConnectedClient.Disconnect(message);
}
#endregion
#region Job Bans
// If you are trying to remove timeOfBan, please don't. It's there because the note system groups role bans by time, reason and banning admin.
// Removing it will clutter the note list. Please also make sure that department bans are applied to roles with the same DateTimeOffset.
public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan)
{
if (!_prototypeManager.TryIndex(role, out JobPrototype? _))
{
throw new ArgumentException($"Invalid role '{role}'", nameof(role));
}
role = string.Concat(JobPrefix, role);
DateTimeOffset? expires = null;
if (minutes > 0)
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes.Value);
}
_systems.TryGetEntitySystem(out GameTicker? ticker);
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
var playtime = target == null ? TimeSpan.Zero : (await _db.GetPlayTimes(target.Value)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
var banDef = new ServerRoleBanDef(
null,
target,
addressRange,
hwid,
timeOfBan,
expires,
roundId,
playtime,
reason,
severity,
banningAdmin,
null,
role);
if (!await AddRoleBan(banDef))
{
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-existing", ("target", targetUsername ?? "null"), ("role", role)));
return;
}
var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
}
public HashSet<string>? GetJobBans(NetUserId playerUserId)
{
if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
return null;
return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
.Select(ban => ban.Role[JobPrefix.Length..])
.ToHashSet();
}
#endregion
public void SendRoleBans(NetUserId userId)
{
if (!_playerManager.TryGetSessionById(userId, out var player))
{
return;
}
SendRoleBans(player);
}
public void SendRoleBans(IPlayerSession pSession)
{
if (!_cachedRoleBans.TryGetValue(pSession.UserId, out var roleBans))
{
_sawmill.Error($"Tried to send rolebans for {pSession.Name} but none cached?");
return;
}
var bans = new MsgRoleBans()
{
Bans = roleBans.Select(o => o.Role).ToList()
};
_sawmill.Debug($"Sent rolebans to {pSession.Name}");
_netManager.ServerSendMessage(bans, pSession.ConnectedClient);
}
public void PostInject()
{
_sawmill = _logManager.GetSawmill(SawmillId);
}
}

View File

@@ -0,0 +1,50 @@
using System.Collections.Immutable;
using Content.Shared.Database;
using Robust.Server.Player;
using Robust.Shared.Network;
using System.Net;
namespace Content.Server.Administration.Managers;
public interface IBanManager
{
public void Initialize();
public void Restart();
/// <summary>
/// Bans the specified target, address range and / or HWID. One of them must be non-null
/// </summary>
/// <param name="target">Target user, username or GUID, null for none</param>
/// <param name="banningAdmin">The person who banned our target</param>
/// <param name="addressRange">Address range, null for none</param>
/// <param name="hwid">H</param>
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
/// <param name="severity">Severity of the resulting ban note</param>
/// <param name="reason">Reason for the ban</param>
public void CreateServerBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, uint? minutes, NoteSeverity severity, string reason);
public HashSet<string>? GetRoleBans(NetUserId playerUserId);
public HashSet<string>? GetJobBans(NetUserId playerUserId);
/// <summary>
/// Creates a job ban for the specified target, username or GUID
/// </summary>
/// <param name="shell">Shell reference so we can write messages</param>
/// <param name="target">Target user, username or GUID, null for none</param>
/// <param name="job">Job to be banned from</param>
/// <param name="severity">Severity of the resulting ban note</param>
/// <param name="reason">Reason for the ban</param>
/// <param name="minutes">Number of minutes to ban for. 0 and null mean permanent</param>
/// <param name="timeOfBan">Time when the ban was applied, used for grouping role bans</param>
public void CreateRoleBan(NetUserId? target, string? targetUsername, NetUserId? banningAdmin, (IPAddress, int)? addressRange, ImmutableArray<byte>? hwid, string role, uint? minutes, NoteSeverity severity, string reason, DateTimeOffset timeOfBan);
/// <summary>
/// Sends role bans to the target
/// </summary>
/// <param name="pSession">Player's user ID</param>
public void SendRoleBans(NetUserId userId);
/// <summary>
/// Sends role bans to the target
/// </summary>
/// <param name="pSession">Player's session</param>
public void SendRoleBans(IPlayerSession pSession);
}

View File

@@ -1,206 +0,0 @@
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared.Players;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Server.Administration.Managers;
public sealed class RoleBanManager
{
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IServerDbManager _db = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private const string JobPrefix = "Job:";
private ISawmill _sawmill = default!;
private readonly Dictionary<NetUserId, HashSet<ServerRoleBanDef>> _cachedRoleBans = new();
public void Initialize()
{
_sawmill = Logger.GetSawmill("rolebans");
_netManager.RegisterNetMessage<MsgRoleBans>();
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Connected
|| _cachedRoleBans.ContainsKey(e.Session.UserId))
{
return;
}
var netChannel = e.Session.ConnectedClient;
await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId);
SendRoleBans(e.Session);
}
private async Task<bool> AddRoleBan(ServerRoleBanDef banDef)
{
if (banDef.UserId != null)
{
if (!_cachedRoleBans.TryGetValue(banDef.UserId.Value, out var roleBans))
{
roleBans = new HashSet<ServerRoleBanDef>();
_cachedRoleBans.Add(banDef.UserId.Value, roleBans);
}
roleBans.Add(banDef);
}
await _db.AddServerRoleBanAsync(banDef);
return true;
}
public void SendRoleBans(LocatedPlayerData located)
{
if (!_playerManager.TryGetSessionById(located.UserId, out var player))
{
return;
}
SendRoleBans(player);
}
public void SendRoleBans(IPlayerSession pSession)
{
if (!_cachedRoleBans.TryGetValue(pSession.UserId, out var roleBans))
{
_sawmill.Error($"Tried to send rolebans for {pSession.Name} but none cached?");
return;
}
var bans = new MsgRoleBans()
{
Bans = roleBans.Select(o => o.Role).ToList()
};
_sawmill.Debug($"Sent rolebans to {pSession.Name}");
_netManager.ServerSendMessage(bans, pSession.ConnectedClient);
}
public HashSet<string>? GetRoleBans(NetUserId playerUserId)
{
return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans) ? roleBans.Select(banDef => banDef.Role).ToHashSet() : null;
}
private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray<byte>? hwId = null)
{
var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false);
var userRoleBans = new HashSet<ServerRoleBanDef>();
foreach (var ban in roleBans)
{
userRoleBans.Add(ban);
}
_cachedRoleBans[userId] = userRoleBans;
}
public void Restart()
{
// Clear out players that have disconnected.
var toRemove = new List<NetUserId>();
foreach (var player in _cachedRoleBans.Keys)
{
if (!_playerManager.TryGetSessionById(player, out _))
toRemove.Add(player);
}
foreach (var player in toRemove)
{
_cachedRoleBans.Remove(player);
}
// Check for expired bans
foreach (var (_, roleBans) in _cachedRoleBans)
{
roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime);
}
}
#region Job Bans
public async void CreateJobBan(IConsoleShell shell, LocatedPlayerData located, string job, string reason, uint minutes)
{
if (!_prototypeManager.TryIndex(job, out JobPrototype? _))
{
shell.WriteError(Loc.GetString("cmd-roleban-job-parse", ("job", job)));
return;
}
job = string.Concat(JobPrefix, job);
CreateRoleBan(shell, located, job, reason, minutes);
}
public HashSet<string>? GetJobBans(NetUserId playerUserId)
{
if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
return null;
return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
.Select(ban => ban.Role[JobPrefix.Length..])
.ToHashSet();
}
#endregion
#region Commands
private async void CreateRoleBan(IConsoleShell shell, LocatedPlayerData located, string role, string reason, uint minutes)
{
var targetUid = located.UserId;
var targetHWid = located.LastHWId;
var targetAddress = located.LastAddress;
DateTimeOffset? expires = null;
if (minutes > 0)
{
expires = DateTimeOffset.Now + TimeSpan.FromMinutes(minutes);
}
(IPAddress, int)? addressRange = null;
if (targetAddress != null)
{
if (targetAddress.IsIPv4MappedToIPv6)
targetAddress = targetAddress.MapToIPv4();
// Ban /64 for IPv4, /32 for IPv4.
var cidr = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 64 : 32;
addressRange = (targetAddress, cidr);
}
var player = shell.Player as IPlayerSession;
var banDef = new ServerRoleBanDef(
null,
targetUid,
addressRange,
targetHWid,
DateTimeOffset.Now,
expires,
reason,
player?.UserId,
null,
role);
if (!await AddRoleBan(banDef))
{
shell.WriteLine(Loc.GetString("cmd-roleban-existing", ("target", located.Username), ("role", role)));
return;
}
var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
shell.WriteLine(Loc.GetString("cmd-roleban-success", ("target", located.Username), ("role", role), ("reason", reason), ("length", length)));
}
#endregion
}

View File

@@ -0,0 +1,63 @@
using Content.Server.Database;
using Content.Server.EUI;
using Content.Shared.Administration.Notes;
using Content.Shared.CCVar;
using Content.Shared.Eui;
using Robust.Shared.Configuration;
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
namespace Content.Server.Administration.Notes;
public sealed class AdminMessageEui : BaseEui
{
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly float _closeWait;
private AdminMessage? _message;
private DateTime _startTime;
public AdminMessageEui()
{
IoCManager.InjectDependencies(this);
_closeWait = _cfg.GetCVar(CCVars.MessageWaitTime);
}
public void SetMessage(AdminMessage message)
{
_message = message;
_startTime = DateTime.UtcNow;
StateDirty();
}
public override EuiStateBase GetNewState()
{
if (_message == null)
return new AdminMessageEuiState(float.MaxValue, "An error has occurred.", string.Empty, DateTime.MinValue);
return new AdminMessageEuiState(
_closeWait,
_message.Message,
_message.CreatedBy?.LastSeenUserName ?? "[System]",
_message.CreatedAt
);
}
public override async void HandleMessage(EuiMessageBase msg)
{
base.HandleMessage(msg);
switch (msg)
{
case Accept:
if (_message == null)
break;
// No escape
if (DateTime.UtcNow - _startTime >= TimeSpan.FromSeconds(_closeWait))
await _notesMan.MarkMessageAsSeen(_message.Id);
Close();
break;
case Dismiss:
Close();
break;
}
}
}

View File

@@ -1,8 +1,12 @@
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.EUI;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
using Content.Shared.Eui;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.Database;
using Robust.Shared.Network;
using static Content.Shared.Administration.Notes.AdminNoteEuiMsg;
namespace Content.Server.Administration.Notes;
@@ -11,6 +15,8 @@ public sealed class AdminNotesEui : BaseEui
{
[Dependency] private readonly IAdminManager _admins = default!;
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
[Dependency] private readonly IPlayerLocator _locator = default!;
[Dependency] private readonly IServerDbManager _db = default!;
public AdminNotesEui()
{
@@ -19,7 +25,8 @@ public sealed class AdminNotesEui : BaseEui
private Guid NotedPlayer { get; set; }
private string NotedPlayerName { get; set; } = string.Empty;
private Dictionary<int, SharedAdminNote> Notes { get; set; } = new();
private bool HasConnectedBefore { get; set; }
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
public override async void Opened()
{
@@ -46,7 +53,7 @@ public sealed class AdminNotesEui : BaseEui
return new AdminNotesEuiState(
NotedPlayerName,
Notes,
_notesMan.CanCreate(Player),
_notesMan.CanCreate(Player) && HasConnectedBefore,
_notesMan.CanDelete(Player),
_notesMan.CanEdit(Player)
);
@@ -58,49 +65,51 @@ public sealed class AdminNotesEui : BaseEui
switch (msg)
{
case CreateNoteRequest {Message: var message}:
{
if (!_notesMan.CanCreate(Player))
case CreateNoteRequest request:
{
Close();
if (!_notesMan.CanCreate(Player))
{
break;
}
if (string.IsNullOrWhiteSpace(request.Message))
{
break;
}
if (request.ExpiryTime is not null && request.ExpiryTime <= DateTime.UtcNow)
{
break;
}
await _notesMan.AddAdminRemark(Player, NotedPlayer, request.NoteType, request.Message, request.NoteSeverity, request.Secret, request.ExpiryTime);
break;
}
if (string.IsNullOrWhiteSpace(message))
{
break;
}
await _notesMan.AddNote(Player, NotedPlayer, message);
break;
}
case DeleteNoteRequest request:
{
if (!_notesMan.CanDelete(Player))
{
Close();
if (!_notesMan.CanDelete(Player))
{
break;
}
await _notesMan.DeleteAdminRemark(request.Id, request.Type, Player);
break;
}
await _notesMan.DeleteNote(request.Id, Player);
break;
}
case EditNoteRequest request:
{
if (!_notesMan.CanEdit(Player))
{
Close();
if (!_notesMan.CanEdit(Player))
{
break;
}
if (string.IsNullOrWhiteSpace(request.Message))
{
break;
}
await _notesMan.ModifyAdminRemark(request.Id, request.Type, Player, request.Message, request.NoteSeverity, request.Secret, request.ExpiryTime);
break;
}
if (string.IsNullOrWhiteSpace(request.Message))
{
break;
}
await _notesMan.ModifyNote(request.Id, Player, request.Message);
break;
}
}
}
@@ -115,7 +124,7 @@ public sealed class AdminNotesEui : BaseEui
if (note.Player != NotedPlayer)
return;
Notes[note.Id] = note;
Notes[(note.Id, note.NoteType)] = note;
StateDirty();
}
@@ -124,28 +133,29 @@ public sealed class AdminNotesEui : BaseEui
if (note.Player != NotedPlayer)
return;
Notes.Remove(note.Id);
Notes.Remove((note.Id, note.NoteType));
StateDirty();
}
private async Task LoadFromDb()
{
NotedPlayerName = await _notesMan.GetPlayerName(NotedPlayer);
var notes = new Dictionary<int, SharedAdminNote>();
foreach (var note in await _notesMan.GetNotes(NotedPlayer))
{
notes.Add(note.Id, note.ToShared());
}
Notes = notes;
var locatedPlayer = await _locator.LookupIdAsync((NetUserId) NotedPlayer);
NotedPlayerName = locatedPlayer?.Username ?? string.Empty;
HasConnectedBefore = locatedPlayer?.LastAddress is not null;
Notes = (from note in await _notesMan.GetAllAdminRemarks(NotedPlayer)
select note.ToShared())
.ToDictionary(sharedNote => (sharedNote.Id, sharedNote.NoteType));
StateDirty();
}
private void OnPermsChanged(AdminPermsChangedEventArgs args)
{
if (args.Player == Player && !_notesMan.CanView(Player))
if (args.Player != Player)
{
return;
}
if (!_notesMan.CanView(Player))
{
Close();
}

View File

@@ -1,21 +1,75 @@
using Content.Server.Database;
using System.Diagnostics;
using Content.Server.Database;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
namespace Content.Server.Administration.Notes;
public static class AdminNotesExtensions
{
public static SharedAdminNote ToShared(this AdminNote note)
public static SharedAdminNote ToShared(this IAdminRemarksCommon note)
{
NoteSeverity? severity = null;
var secret = false;
NoteType type;
string[]? bannedRoles = null;
string? unbannedByName = null;
DateTime? unbannedTime = null;
bool? seen = null;
switch (note)
{
case AdminNote adminNote:
type = NoteType.Note;
severity = adminNote.Severity;
secret = adminNote.Secret;
break;
case AdminWatchlist:
type = NoteType.Watchlist;
secret = true;
break;
case AdminMessage adminMessage:
type = NoteType.Message;
seen = adminMessage.Seen;
break;
case ServerBanNote ban:
type = NoteType.ServerBan;
severity = ban.Severity;
unbannedTime = ban.UnbanTime;
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
break;
case ServerRoleBanNote roleBan:
type = NoteType.RoleBan;
severity = roleBan.Severity;
bannedRoles = roleBan.Roles;
unbannedTime = roleBan.UnbanTime;
unbannedByName = roleBan.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), note.GetType(), "Unknown note type");
}
// There may be bans without a user, but why would we ever be converting them to shared notes?
if (note.PlayerUserId is null)
throw new ArgumentNullException(nameof(note.PlayerUserId), "Player user ID cannot be null for a note");
return new SharedAdminNote(
note.Id,
note.PlayerUserId.Value,
note.RoundId,
note.PlayerUserId,
note.Round?.Server.Name,
note.PlaytimeAtNote,
type,
note.Message,
note.CreatedBy.LastSeenUserName,
note.LastEditedBy.LastSeenUserName,
severity,
secret,
note.CreatedBy?.LastSeenUserName ?? Loc.GetString("system-user"),
note.LastEditedBy?.LastSeenUserName ?? string.Empty,
note.CreatedAt,
note.LastEditedAt
note.LastEditedAt,
note.ExpirationTime,
bannedRoles,
unbannedTime,
unbannedByName,
seen
);
}
}

View File

@@ -1,11 +1,17 @@
using System.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
using Content.Server.Administration.Managers;
using Content.Server.Database;
using Content.Server.EUI;
using Content.Server.GameTicking;
using Content.Shared.Administration;
using Content.Shared.Administration.Notes;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Players.PlayTimeTracking;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
namespace Content.Server.Administration.Notes;
@@ -17,6 +23,7 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly EuiManager _euis = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
[Dependency] private readonly IConfigurationManager _config = default!;
public const string SawmillId = "admin.notes";
@@ -54,91 +61,280 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
await ui.ChangeNotedPlayer(notedPlayer);
}
public async Task AddNote(IPlayerSession createdBy, Guid player, string message)
public async Task OpenUserNotesEui(IPlayerSession player)
{
_sawmill.Info($"Player {createdBy.Name} added note with message {message}");
var ui = new UserNotesEui();
_euis.OpenEui(ui, player);
await ui.UpdateNotes();
}
public async Task AddAdminRemark(IPlayerSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
{
message = message.Trim();
// There's a foreign key constraint in place here. If there's no player record, it will fail.
// Not like there's much use in adding notes on accounts that have never connected.
// You can still ban them just fine, which is why we should allow admins to view their bans with the notes panel
if (await _db.GetPlayerRecordByUserId((NetUserId) player) is null)
return;
var sb = new StringBuilder($"{createdBy.Name} added a");
if (secret && type == NoteType.Note)
{
sb.Append(" secret");
}
sb.Append($" {type} with message {message}");
switch (type)
{
case NoteType.Note:
sb.Append($" with {severity} severity");
break;
case NoteType.Message:
severity = null;
secret = false;
break;
case NoteType.Watchlist:
severity = null;
secret = true;
break;
case NoteType.ServerBan:
case NoteType.RoleBan:
default:
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
}
if (expiryTime is not null)
{
sb.Append($" which expires on {expiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
}
_sawmill.Info(sb.ToString());
_systems.TryGetEntitySystem(out GameTicker? ticker);
int? round = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
int? roundId = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
var serverName = _config.GetCVar(CCVars.AdminLogsServerName); // This could probably be done another way, but this is fine. For displaying only.
var createdAt = DateTime.UtcNow;
var noteId = await _db.AddAdminNote(round, player, message, createdBy.UserId, createdAt);
var playtime = (await _db.GetPlayTimes(player)).Find(p => p.Tracker == PlayTimeTrackingShared.TrackerOverall)?.TimeSpent ?? TimeSpan.Zero;
int noteId;
bool? seen = null;
switch (type)
{
case NoteType.Note:
if (severity is null)
throw new ArgumentException("Severity cannot be null for a note", nameof(severity));
noteId = await _db.AddAdminNote(roundId, player, playtime, message, severity.Value, secret, createdBy.UserId, createdAt, expiryTime);
break;
case NoteType.Watchlist:
secret = true;
noteId = await _db.AddAdminWatchlist(roundId, player, playtime, message, createdBy.UserId, createdAt, expiryTime);
break;
case NoteType.Message:
noteId = await _db.AddAdminMessage(roundId, player, playtime, message, createdBy.UserId, createdAt, expiryTime);
seen = false;
break;
case NoteType.ServerBan: // Add bans using the ban panel, not note edit
case NoteType.RoleBan:
default:
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
}
var note = new SharedAdminNote(
noteId,
round,
player,
roundId,
serverName,
playtime,
type,
message,
severity,
secret,
createdBy.Name,
createdBy.Name,
createdAt,
createdAt
createdAt,
expiryTime,
null,
null,
null,
seen
);
NoteAdded?.Invoke(note);
}
public async Task DeleteNote(int noteId, IPlayerSession deletedBy)
private async Task<SharedAdminNote?> GetAdminRemark(int id, NoteType type)
{
var note = await _db.GetAdminNote(noteId);
return type switch
{
NoteType.Note => (await _db.GetAdminNote(id))?.ToShared(),
NoteType.Watchlist => (await _db.GetAdminWatchlist(id))?.ToShared(),
NoteType.Message => (await _db.GetAdminMessage(id))?.ToShared(),
NoteType.ServerBan => (await _db.GetServerBanAsNoteAsync(id))?.ToShared(),
NoteType.RoleBan => (await _db.GetServerRoleBanAsNoteAsync(id))?.ToShared(),
_ => throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type")
};
}
public async Task DeleteAdminRemark(int noteId, NoteType type, IPlayerSession deletedBy)
{
var note = await GetAdminRemark(noteId, type);
if (note == null)
{
_sawmill.Info($"Player {deletedBy.Name} tried to delete non-existent note {noteId}");
_sawmill.Warning($"Player {deletedBy.Name} has tried to delete non-existent {type} {noteId}");
return;
}
_sawmill.Info($"Player {deletedBy.Name} deleted note {noteId}");
var deletedAt = DateTime.UtcNow;
await _db.DeleteAdminNote(noteId, deletedBy.UserId, deletedAt);
var sharedNote = new SharedAdminNote(
noteId,
note.RoundId,
note.PlayerUserId,
note.Message,
note.CreatedBy.LastSeenUserName,
note.LastEditedBy.LastSeenUserName,
note.CreatedAt,
note.LastEditedAt
);
NoteDeleted?.Invoke(sharedNote);
switch (type)
{
case NoteType.Note:
await _db.DeleteAdminNote(noteId, deletedBy.UserId, deletedAt);
break;
case NoteType.Watchlist:
await _db.DeleteAdminWatchlist(noteId, deletedBy.UserId, deletedAt);
break;
case NoteType.Message:
await _db.DeleteAdminMessage(noteId, deletedBy.UserId, deletedAt);
break;
case NoteType.ServerBan:
await _db.HideServerBanFromNotes(noteId, deletedBy.UserId, deletedAt);
break;
case NoteType.RoleBan:
await _db.HideServerRoleBanFromNotes(noteId, deletedBy.UserId, deletedAt);
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
}
_sawmill.Info($"{deletedBy.Name} has deleted {type} {noteId}");
NoteDeleted?.Invoke(note);
}
public async Task ModifyNote(int noteId, IPlayerSession editedBy, string message)
public async Task ModifyAdminRemark(int noteId, NoteType type, IPlayerSession editedBy, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
{
message = message.Trim();
var note = await _db.GetAdminNote(noteId);
if (note == null || note.Message == message)
var note = await GetAdminRemark(noteId, type);
// If the note doesn't exist or is the same, we skip updating it
if (note == null ||
note.Message == message &&
note.NoteSeverity == severity &&
note.Secret == secret &&
note.ExpiryTime == expiryTime)
{
return;
}
_sawmill.Info($"Player {editedBy.Name} modified note {noteId} with message {message}");
var sb = new StringBuilder($"{editedBy.Name} has modified {type} {noteId}");
if (note.Message != message)
{
sb.Append($", modified message from {note.Message} to {message}");
}
if (note.Secret != secret)
{
sb.Append($", made it {(secret ? "secret" : "visible")}");
}
if (note.NoteSeverity != severity)
{
sb.Append($", updated the severity from {note.NoteSeverity} to {severity}");
}
if (note.ExpiryTime != expiryTime)
{
sb.Append(", updated the expiry time from ");
if (note.ExpiryTime is null)
sb.Append("never");
else
sb.Append($"{note.ExpiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
sb.Append(" to ");
if (expiryTime is null)
sb.Append("never");
else
sb.Append($"{expiryTime.Value.ToUniversalTime(): yyyy-MM-dd HH:mm:ss} UTC");
}
_sawmill.Info(sb.ToString());
var editedAt = DateTime.UtcNow;
await _db.EditAdminNote(noteId, message, editedBy.UserId, editedAt);
var sharedNote = new SharedAdminNote(
noteId,
note.RoundId,
note.PlayerUserId,
message,
note.CreatedBy.LastSeenUserName,
editedBy.Name,
note.CreatedAt,
note.LastEditedAt
);
NoteModified?.Invoke(sharedNote);
switch (type)
{
case NoteType.Note:
if (severity is null)
throw new ArgumentException("Severity cannot be null for a note", nameof(severity));
await _db.EditAdminNote(noteId, message, severity.Value, secret, editedBy.UserId, editedAt, expiryTime);
break;
case NoteType.Watchlist:
await _db.EditAdminWatchlist(noteId, message, editedBy.UserId, editedAt, expiryTime);
break;
case NoteType.Message:
await _db.EditAdminMessage(noteId, message, editedBy.UserId, editedAt, expiryTime);
break;
case NoteType.ServerBan:
if (severity is null)
throw new ArgumentException("Severity cannot be null for a ban", nameof(severity));
await _db.EditServerBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
break;
case NoteType.RoleBan:
if (severity is null)
throw new ArgumentException("Severity cannot be null for a role ban", nameof(severity));
await _db.EditServerRoleBan(noteId, message, severity.Value, expiryTime, editedBy.UserId, editedAt);
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown note type");
}
var newNote = note with
{
Message = message,
NoteSeverity = severity,
Secret = secret,
LastEditedAt = editedAt,
EditedByName = editedBy.Name,
ExpiryTime = expiryTime
};
NoteModified?.Invoke(newNote);
}
public async Task<List<AdminNote>> GetNotes(Guid player)
public async Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player)
{
return await _db.GetAdminNotes(player);
return await _db.GetAllAdminRemarks(player);
}
public async Task<string> GetPlayerName(Guid player)
public async Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player)
{
return (await _db.GetPlayerRecordByUserId(new NetUserId(player)))?.LastSeenUserName ?? string.Empty;
if (_config.GetCVar(CCVars.SeeOwnNotes))
{
return await _db.GetVisibleAdminNotes(player);
}
_sawmill.Warning($"Someone tried to call GetVisibleNotes for {player} when see_own_notes was false");
return new List<IAdminRemarksCommon>();
}
public async Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player)
{
return await _db.GetActiveWatchlists(player);
}
public async Task<List<AdminMessage>> GetNewMessages(Guid player)
{
return await _db.GetMessages(player);
}
public async Task MarkMessageAsSeen(int id)
{
await _db.MarkMessageAsSeen(id);
}
public void PostInject()

View File

@@ -1,20 +1,33 @@
using Content.Server.Administration.Commands;
using Content.Server.Administration.Commands;
using Content.Server.Chat.Managers;
using Content.Server.EUI;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Verbs;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.Utility;
namespace Content.Server.Administration.Notes;
public sealed class AdminNotesSystem : EntitySystem
public sealed class AdminNotesSystem : EntitySystem, IPostInjectInit
{
[Dependency] private readonly IConsoleHost _console = default!;
[Dependency] private readonly IAdminNotesManager _notes = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly EuiManager _euis = default!;
public const string SawmillId = "admin.notes_system";
private ISawmill _sawmill = default!;
public override void Initialize()
{
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddVerbs);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
}
private void AddVerbs(GetVerbsEvent<Verb> ev)
@@ -41,4 +54,43 @@ public sealed class AdminNotesSystem : EntitySystem
ev.Verbs.Add(verb);
}
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.NewStatus != SessionStatus.Connected)
return;
var messages = await _notes.GetNewMessages(e.Session.UserId);
var watchlists = await _notes.GetActiveWatchlists(e.Session.UserId);
if (!_playerManager.TryGetPlayerData(e.Session.UserId, out var playerData))
{
_sawmill.Error($"Could not get player data for ID {e.Session.UserId}");
}
var username = playerData?.UserName ?? e.Session.UserId.ToString();
foreach (var watchlist in watchlists)
{
_chat.SendAdminAlert(Loc.GetString("admin-notes-watchlist", ("player", username), ("message", watchlist.Message)));
}
foreach (var message in messages)
{
var messageString = Loc.GetString("admin-notes-new-message", ("admin", message.CreatedBy?.LastSeenUserName ?? "[System]"), ("message", message.Message));
// Only open the popup if the user hasn't seen it yet
if (!message.Seen)
{
var ui = new AdminMessageEui();
_euis.OpenEui(ui, e.Session);
ui.SetMessage(message);
}
// Send the message anyway
_chat.DispatchServerMessage(e.Session, messageString);
}
}
public void PostInject()
{
_sawmill = _logManager.GetSawmill(SawmillId);
}
}

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using System.Threading.Tasks;
using Content.Server.Database;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
using Robust.Server.Player;
namespace Content.Server.Administration.Notes;
@@ -16,9 +17,33 @@ public interface IAdminNotesManager
bool CanEdit(IPlayerSession admin);
bool CanView(IPlayerSession admin);
Task OpenEui(IPlayerSession admin, Guid notedPlayer);
Task AddNote(IPlayerSession createdBy, Guid player, string message);
Task DeleteNote(int noteId, IPlayerSession deletedBy);
Task ModifyNote(int noteId, IPlayerSession editedBy, string message);
Task<List<AdminNote>> GetNotes(Guid player);
Task<string> GetPlayerName(Guid player);
Task OpenUserNotesEui(IPlayerSession player);
Task AddAdminRemark(IPlayerSession createdBy, Guid player, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
Task DeleteAdminRemark(int noteId, NoteType type, IPlayerSession deletedBy);
Task ModifyAdminRemark(int noteId, NoteType type, IPlayerSession editedBy, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime);
/// <summary>
/// Queries the database and retrieves all notes, secret and visible
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>ALL non-deleted notes, secret or not</returns>
Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player);
/// <summary>
/// Queries the database and retrieves the notes a player should see
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>All player-visible notes</returns>
Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player);
/// <summary>
/// Queries the database and retrieves watchlists that may have been placed on the player
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>Active watchlists</returns>
Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player);
/// <summary>
/// Queries the database and retrieves new messages a player has gotten
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>All unread messages</returns>
Task<List<AdminMessage>> GetNewMessages(Guid player);
Task MarkMessageAsSeen(int id);
}

View File

@@ -0,0 +1,49 @@
using System.Linq;
using System.Threading.Tasks;
using Content.Server.EUI;
using Content.Shared.Administration.Notes;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Eui;
using Robust.Shared.Configuration;
namespace Content.Server.Administration.Notes;
public sealed class UserNotesEui : BaseEui
{
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly bool _seeOwnNotes;
public UserNotesEui()
{
IoCManager.InjectDependencies(this);
_seeOwnNotes = _cfg.GetCVar(CCVars.SeeOwnNotes);
if (!_seeOwnNotes)
{
Logger.WarningS("admin.notes", "User notes initialized when see_own_notes set to false");
}
}
private Dictionary<(int, NoteType), SharedAdminNote> Notes { get; set; } = new();
public override EuiStateBase GetNewState()
{
return new UserNotesEuiState(
Notes
);
}
public async Task UpdateNotes()
{
if (!_seeOwnNotes)
{
Logger.WarningS("admin.notes", $"User {Player.Name} with ID {Player.UserId} tried to update their own user notes when see_own_notes was set to false");
return;
}
Notes = (await _notesMan.GetVisibleRemarks(Player.UserId)).Select(note => note.ToShared()).ToDictionary(note => (note.Id, note.NoteType));
StateDirty();
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Work around https://github.com/dotnet/project-system/issues/4314 -->
<TargetFramework>$(TargetFramework)</TargetFramework>
@@ -15,6 +15,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2022.1.0" PrivateAssets="All" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Content.Packaging\Content.Packaging.csproj" />

View File

@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Net;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
@@ -16,7 +17,10 @@ namespace Content.Server.Database
public DateTimeOffset BanTime { get; }
public DateTimeOffset? ExpirationTime { get; }
public int? RoundId { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Reason { get; }
public NoteSeverity Severity { get; set; }
public NetUserId? BanningAdmin { get; }
public ServerUnbanDef? Unban { get; }
@@ -27,7 +31,10 @@ namespace Content.Server.Database
ImmutableArray<byte>? hwId,
DateTimeOffset banTime,
DateTimeOffset? expirationTime,
int? roundId,
TimeSpan playtimeAtNote,
string reason,
NoteSeverity severity,
NetUserId? banningAdmin,
ServerUnbanDef? unban)
{
@@ -49,7 +56,10 @@ namespace Content.Server.Database
HWId = hwId;
BanTime = banTime;
ExpirationTime = expirationTime;
RoundId = roundId;
PlaytimeAtNote = playtimeAtNote;
Reason = reason;
Severity = severity;
BanningAdmin = banningAdmin;
Unban = unban;
}
@@ -66,10 +76,9 @@ namespace Content.Server.Database
else
{
var appeal = cfg.GetCVar(CCVars.InfoLinksAppeal);
if (!string.IsNullOrWhiteSpace(appeal))
expires = loc.GetString("ban-banned-permanent-appeal", ("link", appeal));
else
expires = loc.GetString("ban-banned-permanent");
expires = !string.IsNullOrWhiteSpace(appeal)
? loc.GetString("ban-banned-permanent-appeal", ("link", appeal))
: loc.GetString("ban-banned-permanent");
}
return $"""

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Database;
namespace Content.Server.Database
{
public record ServerBanNote(int Id, int? RoundId, Round? Round, Guid? PlayerUserId, Player? Player,
TimeSpan PlaytimeAtNote, string Message, NoteSeverity Severity, Player? CreatedBy, DateTime CreatedAt,
Player? LastEditedBy, DateTime? LastEditedAt, DateTime? ExpirationTime, bool Deleted, Player? UnbanningAdmin,
DateTime? UnbanTime) : IAdminRemarksCommon;
}

View File

@@ -5,7 +5,9 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Shared.Administration.Logs;
using Content.Shared.Database;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Preferences;
@@ -30,7 +32,8 @@ namespace Content.Server.Database
.AsSingleQuery()
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
if (prefs is null) return null;
if (prefs is null)
return null;
var maxSlot = prefs.Profiles.Max(p => p.Slot) + 1;
var profiles = new Dictionary<int, ICharacterProfile>(maxSlot);
@@ -339,6 +342,21 @@ namespace Content.Server.Database
public abstract Task AddServerBanAsync(ServerBanDef serverBan);
public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
public async Task EditServerBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt)
{
await using var db = await GetDb();
var ban = await db.DbContext.Ban.SingleOrDefaultAsync(b => b.Id == id);
if (ban is null)
return;
ban.Severity = severity;
ban.Reason = reason;
ban.ExpirationTime = expiration;
ban.LastEditedById = editedBy;
ban.LastEditedAt = editedAt;
await db.DbContext.SaveChangesAsync();
}
protected static async Task<ServerBanExemptFlags?> GetBanExemptionCore(DbGuard db, NetUserId? userId)
{
if (userId == null)
@@ -415,6 +433,21 @@ namespace Content.Server.Database
public abstract Task AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban);
public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt)
{
await using var db = await GetDb();
var ban = await db.DbContext.RoleBan.SingleOrDefaultAsync(b => b.Id == id);
if (ban is null)
return;
ban.Severity = severity;
ban.Reason = reason;
ban.ExpirationTime = expiration;
ban.LastEditedById = editedBy;
ban.LastEditedAt = editedAt;
await db.DbContext.SaveChangesAsync();
}
#endregion
#region Playtime
@@ -959,12 +992,29 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return note.Id;
}
public virtual async Task<int> AddAdminWatchlist(AdminWatchlist watchlist)
{
await using var db = await GetDb();
db.DbContext.AdminWatchlists.Add(watchlist);
await db.DbContext.SaveChangesAsync();
return watchlist.Id;
}
public virtual async Task<int> AddAdminMessage(AdminMessage message)
{
await using var db = await GetDb();
db.DbContext.AdminMessages.Add(message);
await db.DbContext.SaveChangesAsync();
return message.Id;
}
public async Task<AdminNote?> GetAdminNote(int id)
{
await using var db = await GetDb();
return await db.DbContext.AdminNotes
.Where(note => note.Id == id)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy)
.Include(note => note.DeletedBy)
@@ -972,17 +1022,145 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.SingleOrDefaultAsync();
}
public async Task<List<AdminNote>> GetAdminNotes(Guid player)
public async Task<AdminWatchlist?> GetAdminWatchlist(int id)
{
await using var db = await GetDb();
return await db.DbContext.AdminNotes
.Where(note => note.PlayerUserId == player)
.Where(note => !note.Deleted)
return await db.DbContext.AdminWatchlists
.Where(note => note.Id == id)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy)
.Include(note => note.DeletedBy)
.Include(note => note.Player)
.SingleOrDefaultAsync();
}
public async Task<AdminMessage?> GetAdminMessage(int id)
{
await using var db = await GetDb();
return await db.DbContext.AdminMessages
.Where(note => note.Id == id)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy)
.Include(note => note.DeletedBy)
.Include(note => note.Player)
.SingleOrDefaultAsync();
}
public async Task<ServerBanNote?> GetServerBanAsNoteAsync(int id)
{
await using var db = await GetDb();
var ban = await db.DbContext.Ban
.Include(ban => ban.Unban)
.Include(ban => ban.Round)
.ThenInclude(r => r!.Server)
.Include(ban => ban.CreatedBy)
.Include(ban => ban.LastEditedBy)
.Include(ban => ban.Unban)
.SingleOrDefaultAsync(b => b.Id == id);
if (ban is null)
return null;
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
return new ServerBanNote(ban.Id, ban.RoundId, ban.Round, ban.PlayerUserId, player,
ban.PlaytimeAtNote, ban.Reason, ban.Severity, ban.CreatedBy, ban.BanTime,
ban.LastEditedBy, ban.LastEditedAt, ban.ExpirationTime, ban.Hidden,
ban.Unban?.UnbanningAdmin == null
? null
: await db.DbContext.Player.SingleOrDefaultAsync(p =>
p.UserId == ban.Unban.UnbanningAdmin.Value),
ban.Unban?.UnbanTime);
}
public async Task<ServerRoleBanNote?> GetServerRoleBanAsNoteAsync(int id)
{
await using var db = await GetDb();
var ban = await db.DbContext.RoleBan
.Include(b => b.Unban)
.SingleOrDefaultAsync(b => b.Id == id);
if (ban is null)
return null;
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
var unbanningAdmin =
ban.Unban is null
? null
: await db.DbContext.Player.SingleOrDefaultAsync(b => b.UserId == ban.Unban.UnbanningAdmin);
return new ServerRoleBanNote(ban.Id, ban.RoundId, ban.Round, ban.PlayerUserId,
player, ban.PlaytimeAtNote, ban.Reason, ban.Severity, ban.CreatedBy,
ban.BanTime, ban.LastEditedBy, ban.LastEditedAt, ban.ExpirationTime,
ban.Hidden, new [] { ban.RoleId.Replace(BanManager.JobPrefix, null) },
unbanningAdmin, ban.Unban?.UnbanTime);
}
public async Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player)
{
await using var db = await GetDb();
List<IAdminRemarksCommon> notes = new();
notes.AddRange(
await (from note in db.DbContext.AdminNotes
where note.PlayerUserId == player &&
!note.Deleted &&
(note.ExpirationTime == null || DateTime.UtcNow < note.ExpirationTime)
select note)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy)
.Include(note => note.Player)
.ToListAsync();
.ToListAsync());
notes.AddRange(await GetActiveWatchlistsImpl(db, player));
notes.AddRange(await GetMessagesImpl(db, player));
notes.AddRange(await GetServerBansAsNotesForUser(db, player));
notes.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player));
return notes;
}
public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTime editedAt, DateTime? expiryTime)
{
await using var db = await GetDb();
var note = await db.DbContext.AdminNotes.Where(note => note.Id == id).SingleAsync();
note.Message = message;
note.Severity = severity;
note.Secret = secret;
note.LastEditedById = editedBy;
note.LastEditedAt = editedAt;
note.ExpirationTime = expiryTime;
await db.DbContext.SaveChangesAsync();
}
public async Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime)
{
await using var db = await GetDb();
var note = await db.DbContext.AdminWatchlists.Where(note => note.Id == id).SingleAsync();
note.Message = message;
note.LastEditedById = editedBy;
note.LastEditedAt = editedAt;
note.ExpirationTime = expiryTime;
await db.DbContext.SaveChangesAsync();
}
public async Task EditAdminMessage(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime)
{
await using var db = await GetDb();
var note = await db.DbContext.AdminMessages.Where(note => note.Id == id).SingleAsync();
note.Message = message;
note.LastEditedById = editedBy;
note.LastEditedAt = editedAt;
note.ExpirationTime = expiryTime;
await db.DbContext.SaveChangesAsync();
}
public async Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt)
@@ -998,18 +1176,204 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
await db.DbContext.SaveChangesAsync();
}
public async Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt)
public async Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTime deletedAt)
{
await using var db = await GetDb();
var note = await db.DbContext.AdminNotes.Where(note => note.Id == id).SingleAsync();
note.Message = message;
note.LastEditedById = editedBy;
note.LastEditedAt = editedAt;
var watchlist = await db.DbContext.AdminWatchlists.Where(note => note.Id == id).SingleAsync();
watchlist.Deleted = true;
watchlist.DeletedById = deletedBy;
watchlist.DeletedAt = deletedAt;
await db.DbContext.SaveChangesAsync();
}
public async Task DeleteAdminMessage(int id, Guid deletedBy, DateTime deletedAt)
{
await using var db = await GetDb();
var message = await db.DbContext.AdminMessages.Where(note => note.Id == id).SingleAsync();
message.Deleted = true;
message.DeletedById = deletedBy;
message.DeletedAt = deletedAt;
await db.DbContext.SaveChangesAsync();
}
public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTime deletedAt)
{
await using var db = await GetDb();
var ban = await db.DbContext.Ban.Where(ban => ban.Id == id).SingleAsync();
ban.Hidden = true;
ban.LastEditedById = deletedBy;
ban.LastEditedAt = deletedAt;
await db.DbContext.SaveChangesAsync();
}
public async Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTime deletedAt)
{
await using var db = await GetDb();
var roleBan = await db.DbContext.RoleBan.Where(roleBan => roleBan.Id == id).SingleAsync();
roleBan.Hidden = true;
roleBan.LastEditedById = deletedBy;
roleBan.LastEditedAt = deletedAt;
await db.DbContext.SaveChangesAsync();
}
public async Task<List<IAdminRemarksCommon>> GetVisibleAdminRemarks(Guid player)
{
await using var db = await GetDb();
List<IAdminRemarksCommon> notesCol = new();
notesCol.AddRange(
await (from note in db.DbContext.AdminNotes
where note.PlayerUserId == player &&
!note.Secret &&
!note.Deleted &&
(note.ExpirationTime == null || DateTime.UtcNow < note.ExpirationTime)
select note)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.Player)
.ToListAsync());
notesCol.AddRange(await GetMessagesImpl(db, player));
return notesCol;
}
public async Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player)
{
await using var db = await GetDb();
return await GetActiveWatchlistsImpl(db, player);
}
protected async Task<List<AdminWatchlist>> GetActiveWatchlistsImpl(DbGuard db, Guid player)
{
return await (from watchlist in db.DbContext.AdminWatchlists
where watchlist.PlayerUserId == player &&
!watchlist.Deleted &&
(watchlist.ExpirationTime == null || DateTime.UtcNow < watchlist.ExpirationTime)
select watchlist)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy)
.Include(note => note.Player)
.ToListAsync();
}
public async Task<List<AdminMessage>> GetMessages(Guid player)
{
await using var db = await GetDb();
return await GetMessagesImpl(db, player);
}
protected async Task<List<AdminMessage>> GetMessagesImpl(DbGuard db, Guid player)
{
return await (from message in db.DbContext.AdminMessages
where message.PlayerUserId == player &&
!message.Deleted &&
(message.ExpirationTime == null || DateTime.UtcNow < message.ExpirationTime)
select message)
.Include(note => note.Round)
.ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy)
.Include(note => note.Player)
.ToListAsync();
}
public async Task MarkMessageAsSeen(int id)
{
await using var db = await GetDb();
var message = await db.DbContext.AdminMessages.SingleAsync(m => m.Id == id);
message.Seen = true;
await db.DbContext.SaveChangesAsync();
}
// These two are here because they get converted into notes later
protected async Task<List<ServerBanNote>> GetServerBansAsNotesForUser(DbGuard db, Guid user)
{
// You can't group queries, as player will not always exist. When it doesn't, the
// whole query returns nothing
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
return await (from ban in db.DbContext.Ban
where ban.PlayerUserId == user &&
!ban.Hidden
select ban)
.Include(ban => ban.Unban)
.Include(ban => ban.Round)
.ThenInclude(r => r!.Server)
.Include(ban => ban.CreatedBy)
.Include(ban => ban.LastEditedBy)
.Include(ban => ban.Unban)
.ToAsyncEnumerable()
.SelectAwait(async ban =>
new ServerBanNote(ban.Id, ban.RoundId, ban.Round, ban.PlayerUserId, player,
ban.PlaytimeAtNote, ban.Reason, ban.Severity, ban.CreatedBy, ban.BanTime,
ban.LastEditedBy, ban.LastEditedAt, ban.ExpirationTime, ban.Hidden,
ban.Unban?.UnbanningAdmin == null
? null
: await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.Unban.UnbanningAdmin.Value),
ban.Unban?.UnbanTime)
).ToListAsync();
}
protected async Task<List<ServerRoleBanNote>> GetGroupedServerRoleBansAsNotesForUser(DbGuard db, Guid user)
{
// Server side query
var bansQuery =
(from ban in db.DbContext.RoleBan
where ban.PlayerUserId == user &&
!ban.Hidden
select ban)
.Include(ban => ban.Unban)
.Include(ban => ban.Round)
.ThenInclude(r => r!.Server)
.Include(ban => ban.CreatedBy)
.Include(ban => ban.LastEditedBy)
.Include(ban => ban.Unban)
.ToAsyncEnumerable();
// Client side query, as EF can't do groups yet
var bansEnumerable =
(from ban in bansQuery
group ban by new
{
ban.BanTime,
ban.CreatedBy,
ban.Reason,
Unbanned = ban.Unban == null
}
into banGroup
select banGroup)
.AsAsyncEnumerable();
List<ServerRoleBanNote> bans = new();
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
await foreach (var banGroup in bansEnumerable)
{
var firstBan = await banGroup.FirstAsync();
Player? unbanningAdmin = null;
if (firstBan.Unban?.UnbanningAdmin is not null)
unbanningAdmin = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == firstBan.Unban.UnbanningAdmin.Value);
bans.Add(new ServerRoleBanNote(firstBan.Id, firstBan.RoundId, firstBan.Round, firstBan.PlayerUserId,
player, firstBan.PlaytimeAtNote, firstBan.Reason, firstBan.Severity, firstBan.CreatedBy,
firstBan.BanTime, firstBan.LastEditedBy, firstBan.LastEditedAt, firstBan.ExpirationTime,
firstBan.Hidden, await banGroup.Select(ban => ban.RoleId.Replace(BanManager.JobPrefix, null)).ToArrayAsync(),
unbanningAdmin, firstBan.Unban?.UnbanTime));
}
return bans;
}
#endregion
protected abstract Task<DbGuard> GetDb();

View File

@@ -1,4 +1,4 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.IO;
using System.Net;
using System.Text.Json;
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Content.Server.Administration.Logs;
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Preferences;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
@@ -87,6 +88,14 @@ namespace Content.Server.Database
Task AddServerBanAsync(ServerBanDef serverBan);
Task AddServerUnbanAsync(ServerUnbanDef serverBan);
public Task EditServerBan(
int id,
string reason,
NoteSeverity severity,
DateTime? expiration,
Guid editedBy,
DateTime editedAt);
/// <summary>
/// Update ban exemption information for a player.
/// </summary>
@@ -132,6 +141,14 @@ namespace Content.Server.Database
Task AddServerRoleBanAsync(ServerRoleBanDef serverBan);
Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverBan);
public Task EditServerRoleBan(
int id,
string reason,
NoteSeverity severity,
DateTime? expiration,
Guid editedBy,
DateTime editedAt);
#endregion
#region Playtime
@@ -236,11 +253,27 @@ namespace Content.Server.Database
#region Admin Notes
Task<int> AddAdminNote(int? roundId, Guid player, string message, Guid createdBy, DateTime createdAt);
Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTime createdAt, DateTime? expiryTime);
Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime);
Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime);
Task<AdminNote?> GetAdminNote(int id);
Task<List<AdminNote>> GetAdminNotes(Guid player);
Task<AdminWatchlist?> GetAdminWatchlist(int id);
Task<AdminMessage?> GetAdminMessage(int id);
Task<ServerBanNote?> GetServerBanAsNoteAsync(int id);
Task<ServerRoleBanNote?> GetServerRoleBanAsNoteAsync(int id);
Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player);
Task<List<IAdminRemarksCommon>> GetVisibleAdminNotes(Guid player);
Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player);
Task<List<AdminMessage>> GetMessages(Guid player);
Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTime editedAt, DateTime? expiryTime);
Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime);
Task EditAdminMessage(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime);
Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt);
Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt);
Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTime deletedAt);
Task DeleteAdminMessage(int id, Guid deletedBy, DateTime deletedAt);
Task HideServerBanFromNotes(int id, Guid deletedBy, DateTime deletedAt);
Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTime deletedAt);
Task MarkMessageAsSeen(int id);
#endregion
}
@@ -384,16 +417,22 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban));
}
public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt));
}
public Task UpdateBanExemption(NetUserId userId, ServerBanExemptFlags flags)
{
DbWriteOpsMetric.Inc();
return _db.UpdateBanExemption(userId, flags);
return RunDbCommand(() => _db.UpdateBanExemption(userId, flags));
}
public Task<ServerBanExemptFlags> GetBanExemption(NetUserId userId)
{
DbReadOpsMetric.Inc();
return _db.GetBanExemption(userId);
return RunDbCommand(() => _db.GetBanExemption(userId));
}
#region Role Ban
@@ -424,6 +463,12 @@ namespace Content.Server.Database
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban));
}
public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt));
}
#endregion
#region Playtime
@@ -637,7 +682,7 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.SetLastReadRules(player, time));
}
public Task<int> AddAdminNote(int? roundId, Guid player, string message, Guid createdBy, DateTime createdAt)
public Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTime createdAt, DateTime? expiryTime)
{
DbWriteOpsMetric.Inc();
var note = new AdminNote
@@ -646,24 +691,123 @@ namespace Content.Server.Database
CreatedById = createdBy,
LastEditedById = createdBy,
PlayerUserId = player,
PlaytimeAtNote = playtimeAtNote,
Message = message,
Severity = severity,
Secret = secret,
CreatedAt = createdAt,
LastEditedAt = createdAt
LastEditedAt = createdAt,
ExpirationTime = expiryTime
};
return RunDbCommand(() => _db.AddAdminNote(note));
}
public Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime)
{
DbWriteOpsMetric.Inc();
var note = new AdminWatchlist
{
RoundId = roundId,
CreatedById = createdBy,
LastEditedById = createdBy,
PlayerUserId = player,
PlaytimeAtNote = playtimeAtNote,
Message = message,
CreatedAt = createdAt,
LastEditedAt = createdAt,
ExpirationTime = expiryTime
};
return RunDbCommand(() => _db.AddAdminWatchlist(note));
}
public Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime)
{
DbWriteOpsMetric.Inc();
var note = new AdminMessage
{
RoundId = roundId,
CreatedById = createdBy,
LastEditedById = createdBy,
PlayerUserId = player,
PlaytimeAtNote = playtimeAtNote,
Message = message,
CreatedAt = createdAt,
LastEditedAt = createdAt,
ExpirationTime = expiryTime
};
return RunDbCommand(() => _db.AddAdminMessage(note));
}
public Task<AdminNote?> GetAdminNote(int id)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAdminNote(id));
}
public Task<List<AdminNote>> GetAdminNotes(Guid player)
public Task<AdminWatchlist?> GetAdminWatchlist(int id)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAdminNotes(player));
return RunDbCommand(() => _db.GetAdminWatchlist(id));
}
public Task<AdminMessage?> GetAdminMessage(int id)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAdminMessage(id));
}
public Task<ServerBanNote?> GetServerBanAsNoteAsync(int id)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id));
}
public Task<ServerRoleBanNote?> GetServerRoleBanAsNoteAsync(int id)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id));
}
public Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAllAdminRemarks(player));
}
public Task<List<IAdminRemarksCommon>> GetVisibleAdminNotes(Guid player)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetVisibleAdminRemarks(player));
}
public Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetActiveWatchlists(player));
}
public Task<List<AdminMessage>> GetMessages(Guid player)
{
DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetMessages(player));
}
public Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTime editedAt, DateTime? expiryTime)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminNote(id, message, severity, secret, editedBy, editedAt, expiryTime));
}
public Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminWatchlist(id, message, editedBy, editedAt, expiryTime));
}
public Task EditAdminMessage(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminMessage(id, message, editedBy, editedAt, expiryTime));
}
public Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt)
@@ -672,10 +816,34 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.DeleteAdminNote(id, deletedBy, deletedAt));
}
public Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt)
public Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTime deletedAt)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminNote(id, message, editedBy, editedAt));
return RunDbCommand(() => _db.DeleteAdminWatchlist(id, deletedBy, deletedAt));
}
public Task DeleteAdminMessage(int id, Guid deletedBy, DateTime deletedAt)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt));
}
public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTime deletedAt)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt));
}
public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTime deletedAt)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));
}
public Task MarkMessageAsSeen(int id)
{
DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.MarkMessageAsSeen(id));
}
// Wrapper functions to run DB commands from the thread pool.

View File

@@ -1,4 +1,4 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Data;
using System.Linq;
using System.Net;
@@ -120,7 +120,7 @@ namespace Content.Server.Database
{
var newQ = db.PgDbContext.Ban
.Include(p => p.Unban)
.Where(b => b.UserId == uid.UserId);
.Where(b => b.PlayerUserId == uid.UserId);
query = query == null ? newQ : query.Union(newQ);
}
@@ -169,7 +169,7 @@ namespace Content.Server.Database
}
NetUserId? uid = null;
if (ban.UserId is {} guid)
if (ban.PlayerUserId is {} guid)
{
uid = new NetUserId(guid);
}
@@ -189,7 +189,10 @@ namespace Content.Server.Database
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
ban.BanTime,
ban.ExpirationTime,
ban.RoundId,
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
aUid,
unbanDef);
}
@@ -222,10 +225,13 @@ namespace Content.Server.Database
Address = serverBan.Address,
HWId = serverBan.HWId?.ToArray(),
Reason = serverBan.Reason,
Severity = serverBan.Severity,
BanningAdmin = serverBan.BanningAdmin?.UserId,
BanTime = serverBan.BanTime.UtcDateTime,
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
UserId = serverBan.UserId?.UserId
RoundId = serverBan.RoundId,
PlaytimeAtNote = serverBan.PlaytimeAtNote,
PlayerUserId = serverBan.UserId?.UserId
});
await db.PgDbContext.SaveChangesAsync();
@@ -310,7 +316,7 @@ namespace Content.Server.Database
{
var newQ = db.PgDbContext.RoleBan
.Include(p => p.Unban)
.Where(b => b.UserId == uid.UserId);
.Where(b => b.PlayerUserId == uid.UserId);
query = query == null ? newQ : query.Union(newQ);
}
@@ -351,7 +357,7 @@ namespace Content.Server.Database
}
NetUserId? uid = null;
if (ban.UserId is {} guid)
if (ban.PlayerUserId is {} guid)
{
uid = new NetUserId(guid);
}
@@ -371,7 +377,10 @@ namespace Content.Server.Database
ban.HWId == null ? null : ImmutableArray.Create(ban.HWId),
ban.BanTime,
ban.ExpirationTime,
ban.RoundId,
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
aUid,
unbanDef,
ban.RoleId);
@@ -405,10 +414,13 @@ namespace Content.Server.Database
Address = serverRoleBan.Address,
HWId = serverRoleBan.HWId?.ToArray(),
Reason = serverRoleBan.Reason,
Severity = serverRoleBan.Severity,
BanningAdmin = serverRoleBan.BanningAdmin?.UserId,
BanTime = serverRoleBan.BanTime.UtcDateTime,
ExpirationTime = serverRoleBan.ExpirationTime?.UtcDateTime,
UserId = serverRoleBan.UserId?.UserId,
RoundId = serverRoleBan.RoundId,
PlaytimeAtNote = serverRoleBan.PlaytimeAtNote,
PlayerUserId = serverRoleBan.UserId?.UserId,
RoleId = serverRoleBan.Role,
});

View File

@@ -133,22 +133,17 @@ namespace Content.Server.Database
ServerBanExemptFlags? exemptFlags)
{
if (!exemptFlags.GetValueOrDefault(ServerBanExemptFlags.None).HasFlag(ServerBanExemptFlags.IP)
&& address != null && ban.Address is not null && IPAddressExt.IsInSubnet(address, ban.Address.Value))
&& address != null && ban.Address is not null && address.IsInSubnet(ban.Address.Value))
{
return true;
}
if (userId is { } id && ban.UserId == id.UserId)
if (userId is { } id && ban.PlayerUserId == id.UserId)
{
return true;
}
if (hwId is { } hwIdVar && hwIdVar.Length > 0 && hwIdVar.AsSpan().SequenceEqual(ban.HWId))
{
return true;
}
return false;
return hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId);
}
public override async Task AddServerBanAsync(ServerBanDef serverBan)
@@ -159,11 +154,14 @@ namespace Content.Server.Database
{
Address = serverBan.Address,
Reason = serverBan.Reason,
Severity = serverBan.Severity,
BanningAdmin = serverBan.BanningAdmin?.UserId,
HWId = serverBan.HWId?.ToArray(),
BanTime = serverBan.BanTime.UtcDateTime,
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
UserId = serverBan.UserId?.UserId
RoundId = serverBan.RoundId,
PlaytimeAtNote = serverBan.PlaytimeAtNote,
PlayerUserId = serverBan.UserId?.UserId
});
await db.SqliteDbContext.SaveChangesAsync();
@@ -197,7 +195,8 @@ namespace Content.Server.Database
return ConvertRoleBan(ban);
}
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(IPAddress? address,
public override async Task<List<ServerRoleBanDef>> GetServerRoleBansAsync(
IPAddress? address,
NetUserId? userId,
ImmutableArray<byte>? hwId,
bool includeUnbanned)
@@ -234,22 +233,17 @@ namespace Content.Server.Database
NetUserId? userId,
ImmutableArray<byte>? hwId)
{
if (address != null && ban.Address is not null && IPAddressExt.IsInSubnet(address, ban.Address.Value))
if (address != null && ban.Address is not null && address.IsInSubnet(ban.Address.Value))
{
return true;
}
if (userId is { } id && ban.UserId == id.UserId)
if (userId is { } id && ban.PlayerUserId == id.UserId)
{
return true;
}
if (hwId is { } hwIdVar && hwIdVar.Length > 0 && hwIdVar.AsSpan().SequenceEqual(ban.HWId))
{
return true;
}
return false;
return hwId is { Length: > 0 } hwIdVar && hwIdVar.AsSpan().SequenceEqual(ban.HWId);
}
public override async Task AddServerRoleBanAsync(ServerRoleBanDef serverBan)
@@ -260,11 +254,14 @@ namespace Content.Server.Database
{
Address = serverBan.Address,
Reason = serverBan.Reason,
Severity = serverBan.Severity,
BanningAdmin = serverBan.BanningAdmin?.UserId,
HWId = serverBan.HWId?.ToArray(),
BanTime = serverBan.BanTime.UtcDateTime,
ExpirationTime = serverBan.ExpirationTime?.UtcDateTime,
UserId = serverBan.UserId?.UserId,
RoundId = serverBan.RoundId,
PlaytimeAtNote = serverBan.PlaytimeAtNote,
PlayerUserId = serverBan.UserId?.UserId,
RoleId = serverBan.Role,
});
@@ -293,7 +290,7 @@ namespace Content.Server.Database
}
NetUserId? uid = null;
if (ban.UserId is { } guid)
if (ban.PlayerUserId is { } guid)
{
uid = new NetUserId(guid);
}
@@ -314,7 +311,10 @@ namespace Content.Server.Database
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
ban.RoundId,
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
aUid,
unban,
ban.RoleId);
@@ -360,7 +360,7 @@ namespace Content.Server.Database
}
NetUserId? uid = null;
if (ban.UserId is { } guid)
if (ban.PlayerUserId is { } guid)
{
uid = new NetUserId(guid);
}
@@ -381,7 +381,10 @@ namespace Content.Server.Database
// SQLite apparently always reads DateTime as unspecified, but we always write as UTC.
DateTime.SpecifyKind(ban.BanTime, DateTimeKind.Utc),
ban.ExpirationTime == null ? null : DateTime.SpecifyKind(ban.ExpirationTime.Value, DateTimeKind.Utc),
ban.RoundId,
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
aUid,
unban);
}
@@ -483,7 +486,7 @@ namespace Content.Server.Database
var nextId = 1;
if (await db.DbContext.AdminNotes.AnyAsync())
{
nextId = await db.DbContext.AdminNotes.MaxAsync(dbVersion => dbVersion.Id) + 1;
nextId = await db.DbContext.AdminNotes.MaxAsync(adminNote => adminNote.Id) + 1;
}
note.Id = nextId;
@@ -491,6 +494,37 @@ namespace Content.Server.Database
return await base.AddAdminNote(note);
}
public override async Task<int> AddAdminWatchlist(AdminWatchlist watchlist)
{
await using (var db = await GetDb())
{
var nextId = 1;
if (await db.DbContext.AdminWatchlists.AnyAsync())
{
nextId = await db.DbContext.AdminWatchlists.MaxAsync(adminWatchlist => adminWatchlist.Id) + 1;
}
watchlist.Id = nextId;
}
return await base.AddAdminWatchlist(watchlist);
}
public override async Task<int> AddAdminMessage(AdminMessage message)
{
await using (var db = await GetDb())
{
var nextId = 1;
if (await db.DbContext.AdminMessages.AnyAsync())
{
nextId = await db.DbContext.AdminMessages.MaxAsync(adminMessage => adminMessage.Id) + 1;
}
message.Id = nextId;
}
return await base.AddAdminMessage(message);
}
private async Task<DbGuardImpl> GetDbImpl()
{

View File

@@ -1,5 +1,6 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Net;
using Content.Shared.Database;
using Robust.Shared.Network;
namespace Content.Server.Database;
@@ -13,7 +14,10 @@ public sealed class ServerRoleBanDef
public DateTimeOffset BanTime { get; }
public DateTimeOffset? ExpirationTime { get; }
public int? RoundId { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Reason { get; }
public NoteSeverity Severity { get; set; }
public NetUserId? BanningAdmin { get; }
public ServerRoleUnbanDef? Unban { get; }
public string Role { get; }
@@ -25,7 +29,10 @@ public sealed class ServerRoleBanDef
ImmutableArray<byte>? hwId,
DateTimeOffset banTime,
DateTimeOffset? expirationTime,
int? roundId,
TimeSpan playtimeAtNote,
string reason,
NoteSeverity severity,
NetUserId? banningAdmin,
ServerRoleUnbanDef? unban,
string role)
@@ -48,7 +55,10 @@ public sealed class ServerRoleBanDef
HWId = hwId;
BanTime = banTime;
ExpirationTime = expirationTime;
RoundId = roundId;
PlaytimeAtNote = playtimeAtNote;
Reason = reason;
Severity = severity;
BanningAdmin = banningAdmin;
Unban = unban;
Role = role;

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Database;
namespace Content.Server.Database
{
public record ServerRoleBanNote(int Id, int? RoundId, Round? Round, Guid? PlayerUserId, Player? Player,
TimeSpan PlaytimeAtNote, string Message, NoteSeverity Severity, Player? CreatedBy, DateTime CreatedAt,
Player? LastEditedBy, DateTime? LastEditedAt, DateTime? ExpirationTime, bool Deleted, string[] Roles,
Player? UnbanningAdmin, DateTime? UnbanTime) : IAdminRemarksCommon;
}

View File

@@ -146,7 +146,7 @@ namespace Content.Server.Entry
IoCManager.Resolve<IGameMapManager>().Initialize();
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>().PostInitialize();
IoCManager.Resolve<IBqlQueryManager>().DoAutoRegistrations();
IoCManager.Resolve<RoleBanManager>().Initialize();
IoCManager.Resolve<IBanManager>().Initialize();
}
}

View File

@@ -198,7 +198,7 @@ namespace Content.Server.GameTicking
#if DEBUG
DebugTools.Assert(_userDb.IsLoadComplete(session), $"Player was readied up but didn't have user DB data loaded yet??");
#endif
if (_roleBanManager.GetRoleBans(userId) == null)
if (_banManager.GetRoleBans(userId) == null)
{
Logger.ErrorS("RoleBans", $"Role bans for player {session} {userId} have not been loaded yet.");
continue;
@@ -437,7 +437,7 @@ namespace Content.Server.GameTicking
_mapManager.Restart();
_roleBanManager.Restart();
_banManager.Restart();
_gameMapManager.ClearSelectedMap();

View File

@@ -102,7 +102,7 @@ namespace Content.Server.GameTicking
{
var character = GetPlayerProfile(player);
var jobBans = _roleBanManager.GetJobBans(player.UserId);
var jobBans = _banManager.GetJobBans(player.UserId);
if (jobBans == null || jobId != null && jobBans.Contains(jobId))
return;
@@ -150,7 +150,7 @@ namespace Content.Server.GameTicking
var getDisallowed = _playTimeTrackings.GetDisallowedJobs(player);
restrictedRoles.UnionWith(getDisallowed);
var jobBans = _roleBanManager.GetJobBans(player.UserId);
var jobBans = _banManager.GetJobBans(player.UserId);
if(jobBans != null) restrictedRoles.UnionWith(jobBans);
// Pick best job best on prefs.

View File

@@ -119,7 +119,7 @@ namespace Content.Server.GameTicking
[Dependency] private readonly StationJobsSystem _stationJobs = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly GhostSystem _ghosts = default!;
[Dependency] private readonly RoleBanManager _roleBanManager = default!;
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly ServerUpdateManager _serverUpdates = default!;
[Dependency] private readonly PlayTimeTrackingSystem _playTimeTrackings = default!;

View File

@@ -48,7 +48,7 @@ namespace Content.Server.IoC
IoCManager.Register<IAfkManager, AfkManager>();
IoCManager.Register<IGameMapManager, GameMapManager>();
IoCManager.Register<RulesManager, RulesManager>();
IoCManager.Register<RoleBanManager, RoleBanManager>();
IoCManager.Register<IBanManager, BanManager>();
IoCManager.Register<ContentNetworkResourceManager>();
IoCManager.Register<IAdminNotesManager, AdminNotesManager>();
IoCManager.Register<GhostKickManager>();

View File

@@ -15,7 +15,7 @@ namespace Content.Server.Station.Systems;
public sealed partial class StationJobsSystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly RoleBanManager _roleBanManager = default!;
[Dependency] private readonly IBanManager _banManager = default!;
[Dependency] private readonly PlayTimeTrackingSystem _playTime = default!;
private Dictionary<int, HashSet<string>> _jobsByWeight = default!;
@@ -342,7 +342,7 @@ public sealed partial class StationJobsSystem
foreach (var (player, profile) in profiles)
{
var roleBans = _roleBanManager.GetJobBans(player);
var roleBans = _banManager.GetJobBans(player);
var profileJobs = profile.JobPriorities.Keys.ToList();
_playTime.RemoveDisallowedJobs(player, ref profileJobs);

View File

@@ -0,0 +1,28 @@
namespace Content.Shared.Database
{
/// <summary>
/// Kinds of severity that a note can have
/// </summary>
public enum NoteSeverity
{
/// <summary>
/// No severity, displays a checkmark
/// </summary>
None = 0,
/// <summary>
/// Minor severity, displays a minus
/// </summary>
Minor = 1,
/// <summary>
/// Medium severity, displays one exclamation mark
/// </summary>
Medium = 2,
/// <summary>
/// High severity, displays three exclamation marks
/// </summary>
High = 3,
}
}

View File

@@ -0,0 +1,38 @@
namespace Content.Shared.Database;
/*
* Editing the numbers here may obliterate DB records, you have been warned.
* If you do have to edit the numbers for some reason, please create migrations.
* Adding new types is fine (or even renaming), but do not remove or change them.
*/
/// <summary>
/// Different types of notes
/// </summary>
public enum NoteType
{
/// <summary>
/// Normal note
/// </summary>
Note = 0,
/// <summary>
/// Watchlist, a secret note that gets shown to online admins every time a player connects
/// </summary>
Watchlist = 1,
/// <summary>
/// A message, type of note that gets explicitly shown to the player
/// </summary>
Message = 2,
/// <summary>
/// A server ban, converted to a shared note
/// </summary>
ServerBan = 3,
/// <summary>
/// A role ban, converted to a shared note
/// </summary>
RoleBan = 4,
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Immutable;
using Content.Shared.Database;
using Content.Shared.Eui;
using Robust.Shared.Serialization;
using System.Net;
namespace Content.Shared.Administration;
[Serializable, NetSerializable]
public sealed class BanPanelEuiState : EuiStateBase
{
public string PlayerName { get; set; }
public bool HasBan { get; set; }
public BanPanelEuiState(string playerName, bool hasBan)
{
PlayerName = playerName;
HasBan = hasBan;
}
}
public static class BanPanelEuiStateMsg
{
[Serializable, NetSerializable]
public sealed class CreateBanRequest : EuiMessageBase
{
public string? Player { get; set; }
public string? IpAddress { get; set; }
public byte[]? Hwid { get; set; }
public uint Minutes { get; set; }
public string Reason { get; set; }
public NoteSeverity Severity { get; set; }
public string[]? Roles { get; set; }
public bool UseLastIp { get; set; }
public bool UseLastHwid { get; set; }
public CreateBanRequest(string? player, (IPAddress, int)? ipAddress, bool useLastIp, byte[]? hwid, bool useLastHwid, uint minutes, string reason, NoteSeverity severity, string[]? roles)
{
Player = player;
IpAddress = ipAddress == null ? null : $"{ipAddress.Value.Item1}/{ipAddress.Value.Item2}";
UseLastIp = useLastIp;
Hwid = hwid;
UseLastHwid = useLastHwid;
Minutes = minutes;
Reason = reason;
Severity = severity;
Roles = roles;
}
}
[Serializable, NetSerializable]
public sealed class GetPlayerInfoRequest : EuiMessageBase
{
public string PlayerUsername { get; set; }
public GetPlayerInfoRequest(string username)
{
PlayerUsername = username;
}
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Eui;
using Robust.Shared.Serialization;
namespace Content.Shared.Administration.Notes;
[Serializable, NetSerializable]
public sealed class AdminMessageEuiState : EuiStateBase
{
public float Time { get; set; }
public string Message { get; set; }
public string AdminName { get; set; }
public DateTime AddedOn { get; set; }
public AdminMessageEuiState(float time, string message, string adminName, DateTime addedOn)
{
Message = message;
Time = time;
AdminName = adminName;
AddedOn = addedOn;
}
}
public static class AdminMessageEuiMsg
{
[Serializable, NetSerializable]
public sealed class Accept : EuiMessageBase
{
}
[Serializable, NetSerializable]
public sealed class Dismiss : EuiMessageBase
{
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Eui;
using Content.Shared.Database;
using Content.Shared.Eui;
using Robust.Shared.Serialization;
namespace Content.Shared.Administration.Notes;
@@ -6,7 +7,7 @@ namespace Content.Shared.Administration.Notes;
[Serializable, NetSerializable]
public sealed class AdminNotesEuiState : EuiStateBase
{
public AdminNotesEuiState(string notedPlayerName, Dictionary<int, SharedAdminNote> notes, bool canCreate, bool canDelete, bool canEdit)
public AdminNotesEuiState(string notedPlayerName, Dictionary<(int, NoteType), SharedAdminNote> notes, bool canCreate, bool canDelete, bool canEdit)
{
NotedPlayerName = notedPlayerName;
Notes = notes;
@@ -16,7 +17,7 @@ public sealed class AdminNotesEuiState : EuiStateBase
}
public string NotedPlayerName { get; }
public Dictionary<int, SharedAdminNote> Notes { get; }
public Dictionary<(int noteId, NoteType noteType), SharedAdminNote> Notes { get; }
public bool CanCreate { get; }
public bool CanDelete { get; }
public bool CanEdit { get; }
@@ -27,35 +28,53 @@ public static class AdminNoteEuiMsg
[Serializable, NetSerializable]
public sealed class CreateNoteRequest : EuiMessageBase
{
public CreateNoteRequest(string message)
public CreateNoteRequest(NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
{
NoteType = type;
Message = message;
NoteSeverity = severity;
Secret = secret;
ExpiryTime = expiryTime;
}
public NoteType NoteType { get; set; }
public string Message { get; set; }
public NoteSeverity? NoteSeverity { get; set; }
public bool Secret { get; set; }
public DateTime? ExpiryTime { get; set; }
}
[Serializable, NetSerializable]
public sealed class DeleteNoteRequest : EuiMessageBase
{
public DeleteNoteRequest(int id)
public DeleteNoteRequest(int id, NoteType type)
{
Id = id;
Type = type;
}
public int Id { get; set; }
public NoteType Type { get; set; }
}
[Serializable, NetSerializable]
public sealed class EditNoteRequest : EuiMessageBase
{
public EditNoteRequest(int id, string message)
public EditNoteRequest(int id, NoteType type, string message, NoteSeverity? severity, bool secret, DateTime? expiryTime)
{
Id = id;
Type = type;
Message = message;
NoteSeverity = severity;
Secret = secret;
ExpiryTime = expiryTime;
}
public int Id { get; set; }
public NoteType Type { get; set; }
public string Message { get; set; }
public NoteSeverity? NoteSeverity { get; set; }
public bool Secret { get; set; }
public DateTime? ExpiryTime { get; set; }
}
}

View File

@@ -1,6 +1,26 @@
using Robust.Shared.Serialization;
using Content.Shared.Database;
using Robust.Shared.Serialization;
namespace Content.Shared.Administration.Notes;
[Serializable, NetSerializable]
public sealed record SharedAdminNote(int Id, int? Round, Guid Player, string Message, string CreatedByName, string EditedByName, DateTime CreatedAt, DateTime LastEditedAt);
public sealed record SharedAdminNote(
int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note.
Guid Player, // Notes player
int? Round, // Which round was it added in?
string? ServerName, // Which server was this added on?
TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note
NoteType NoteType, // Type of note
string Message, // Attached message
NoteSeverity? NoteSeverity, // Severity of the note, ban or role ban. Otherwise null.
bool Secret, // Is it visible to the player (only relevant if players can see their own notes)
string CreatedByName, // Who created it?
string EditedByName, // Who edited it last?
DateTime CreatedAt, // When was it created?
DateTime? LastEditedAt, // When was it last edited?
DateTime? ExpiryTime, // Does it expire?
string[]? BannedRoles, // Only valid for role bans. List of banned roles
DateTime? UnbannedTime, // Only valid for bans. Set if unbanned
string? UnbannedByName, // Only valid for bans. Set if unbanned
bool? Seen // Only valid for messages, otherwise should be null. Has the user seen this message?
);

View File

@@ -0,0 +1,15 @@
using Content.Shared.Database;
using Content.Shared.Eui;
using Robust.Shared.Serialization;
namespace Content.Shared.Administration.Notes;
[Serializable, NetSerializable]
public sealed class UserNotesEuiState : EuiStateBase
{
public UserNotesEuiState(Dictionary<(int, NoteType), SharedAdminNote> notes)
{
Notes = notes;
}
public Dictionary<(int, NoteType), SharedAdminNote> Notes { get; }
}

View File

@@ -647,7 +647,7 @@ namespace Content.Shared.CCVar
CVarDef.Create("net.gasoverlaythresholds", 20);
/*
* Admin stuff
* Admin
*/
public static readonly CVarDef<bool> AdminAnnounceLogin =
@@ -656,6 +656,49 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> AdminAnnounceLogout =
CVarDef.Create("admin.announce_logout", true, CVar.SERVERONLY);
/// <summary>
/// Should users be able to see their own notes? Admins will be able to see and set notes regardless
/// </summary>
public static readonly CVarDef<bool> SeeOwnNotes =
CVarDef.Create("admin.see_own_notes", false, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// The amount of days before the note starts fading. It will slowly lose opacity until it reaches stale. Set to 0 to disable.
/// </summary>
public static readonly CVarDef<double> NoteFreshDays =
CVarDef.Create("admin.note_fresh_days", 91.31055, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// The amount of days before the note completely fades, and can only be seen by admins if they press "see more notes". Set to 0
/// if you want the note to immediately disappear without fading.
/// </summary>
public static readonly CVarDef<double> NoteStaleDays =
CVarDef.Create("admin.note_stale_days", 365.2422, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// How much time does the user have to wait in seconds before confirming that they saw an admin message?
/// </summary>
public static readonly CVarDef<float> MessageWaitTime =
CVarDef.Create("admin.message_wait_time", 3f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Default severity for role bans
/// </summary>
public static readonly CVarDef<string> RoleBanDefaultSeverity =
CVarDef.Create("admin.role_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER);
/// <summary>
/// Default severity for department bans
/// </summary>
public static readonly CVarDef<string> DepartmentBanDefaultSeverity =
CVarDef.Create("admin.department_ban_default_severity", "medium", CVar.ARCHIVE | CVar.SERVER);
/// <summary>
/// Default severity for server bans
/// </summary>
public static readonly CVarDef<string> ServerBanDefaultSeverity =
CVarDef.Create("admin.server_ban_default_severity", "high", CVar.ARCHIVE | CVar.SERVER);
/// <summary>
/// Minimum explosion intensity to create an admin alert message. -1 to disable the alert.
/// </summary>

View File

@@ -21,3 +21,6 @@ grid_fill = false
auto_call_time = 0
emergency = false
arrivals = false
[admin]
see_own_notes = true

View File

@@ -1,7 +1,16 @@
# UI
admin-notes-title = Notes for {$player}
admin-notes-new-note = New note:
admin-notes-new-note = New note
admin-notes-show-more = Show more
admin-notes-for = Note for: {$player}
admin-notes-id = Id: {$id}
admin-notes-type = Type: {$type}
admin-notes-severity = Severity: {$severity}
admin-notes-secret = Secret
admin-notes-notsecret = Not secret
admin-notes-expires = Expires on: {$expires}
admin-notes-expires-never = Does not expire
admin-notes-edited-never = Never
admin-notes-round-id = Round Id: {$id}
admin-notes-round-id-unknown = Round Id: Unknown
admin-notes-created-by = Created by: {$author}
@@ -10,7 +19,59 @@ admin-notes-last-edited-by = Last edited by: {$author}
admin-notes-last-edited-at = Last edited at: {$date}
admin-notes-edit = Edit
admin-notes-delete = Delete
admin-notes-delete-confirm = Are you sure?
admin-notes-hide = Hide
admin-notes-delete-confirm = Confirm delete
admin-notes-edited = Last edit by {$author} on {$date}
admin-notes-unbanned = Unbanned by {$admin} on {$date}
admin-notes-message-window-title = Alert!
admin-notes-message-admin = New message from {$admin}, added on {$date}
admin-notes-message-wait = The accept button will be enabled after {$time} seconds.
admin-notes-message-accept = Dismiss permanently
admin-notes-message-dismiss = Dismiss for now
admin-notes-message-seen = Seen
admin-notes-banned-from = Banned from
admin-notes-the-server = the server
admin-notes-permanently = permanently
admin-notes-for = for {$player}
admin-notes-days = {$days} days
admin-notes-hours = {$hours} hours
admin-notes-minutes = {$minutes} minutes
# Note editor UI
admin-note-editor-title-new = Creating a new note for {$player}
admin-note-editor-title-existing = Editing note {$id} on {$player} by {$author}
admin-note-editor-pop-out = Pop out
admin-note-editor-secret = Secret?
admin-note-editor-secret-tooltip = Checking this will make the note not be visible by the player
admin-note-editor-type-note = Note
admin-note-editor-type-message = Message
admin-note-editor-type-watchlist = Watchlist
admin-note-editor-type-server-ban = Server Ban
admin-note-editor-type-role-ban = Role Ban
admin-note-editor-severity-none = None
admin-note-editor-severity-low = Low
admin-note-editor-severity-medium = Medium
admin-note-editor-severity-high = High
admin-note-editor-expiry-checkbox = Permanent?
admin-note-editor-expiry-checkbox-tooltip = Check this to make it expire
admin-note-editor-expiry-label = Expires on:
admin-note-editor-expiry-label-params = Expires on: {$date} (in {$expiresIn})
admin-note-editor-expiry-label-expired = Expired
admin-note-editor-expiry-placeholder = Enter expiration date (yyyy-MM-dd HH:mm:ss)
admin-note-editor-submit = Submit
admin-note-editor-submit-confirm = Are you sure?
# Verb
admin-notes-verb-text = Open Admin Notes
# Watchlist and message login
admin-notes-watchlist = Watchlist for {$player}: {$message}
admin-notes-new-message = You've received an admin message from {$admin}: {$message}
# Admin remarks
admin-remarks-command-description = Opens the admin remarks page
admin-remarks-command-error = Admin remarks have been disabled
admin-remarks-title = Admin remarks
# Misc
system-user = [System]

View File

@@ -1,5 +1,5 @@
admin-player-actions-window-title = Player Actions Panel
admin-player-actions-window-ban = Ban
admin-player-actions-window-ban = Banning panel
admin-player-actions-window-admin-ghost = Admin Ghost
admin-player-actions-window-teleport = Teleport
admin-player-actions-window-permissions = Permissions Panel

View File

@@ -2,10 +2,13 @@
cmd-ban-desc = Bans somebody
cmd-ban-help = Usage: ban <name or user ID> <reason> [duration in minutes, leave out or 0 for permanent ban]
cmd-ban-player = Unable to find a player with that name.
cmd-ban-self = You can't ban yourself!
cmd-ban-invalid-minutes = ${minutes} is not a valid amount of minutes!
cmd-ban-invalid-severity = ${severity} is not a valid severity!
cmd-ban-invalid-arguments = Invalid amount of arguments
cmd-ban-hint = <name/user ID>
cmd-ban-hint-reason = <reason>
cmd-ban-hint-duration = [duration]
cmd-ban-hint-severity = [severity]
cmd-ban-hint-duration-1 = Permanent
cmd-ban-hint-duration-2 = 1 day
@@ -14,6 +17,12 @@ cmd-ban-hint-duration-4 = 1 week
cmd-ban-hint-duration-5 = 2 week
cmd-ban-hint-duration-6 = 1 month
# ban panel
cmd-banpanel-desc = Opens the ban panel
cmd-banpanel-help = Usage: banpanel [name or user guid]
cmd-banpanel-server = This can not be used from the server console
cmd-banpanel-player-err = The specified player could not be found
# listbans
cmd-banlist-desc = Lists a user's active bans.
cmd-banlist-help = Usage: banlist <name or user ID>
@@ -39,3 +48,35 @@ cmd-ban_exemption_get-nargs = Expected exactly 1 argument
cmd-ban_exemption_get-none = User is not exempt from any bans.
cmd-ban_exemption_get-show = User is exempt from the following ban flags: {$flags}.
cmd-ban_exemption_get-arg-player = <player>
# Ban panel
ban-panel-title = Banning panel
ban-panel-player = Player
ban-panel-ip = IP
ban-panel-hwid = HWID
ban-panel-reason = Reason
ban-panel-last-conn = Use IP and HWID from last connection?
ban-panel-submit = Ban
ban-panel-confirm = Are you sure?
ban-panel-tabs-basic = Basic info
ban-panel-tabs-reason = Reason
ban-panel-tabs-players = Player List
ban-panel-tabs-role = Role ban info
ban-panel-no-data = You must provide either a user, IP or HWID to ban
ban-panel-invalid-ip = The IP address could not be parsed. Please try again
ban-panel-select = Select type
ban-panel-server = Server ban
ban-panel-role = Role ban
ban-panel-minutes = Minutes
ban-panel-hours = Hours
ban-panel-days = Days
ban-panel-weeks = Weeks
ban-panel-months = Months
ban-panel-years = Years
ban-panel-permanent = Permanent
ban-panel-ip-hwid-tooltip = Leave empty and check the checkbox below to use last connection's details
ban-panel-severity = Severity:
# Ban string
server-ban-string = {$admin} created a {$severity} severity server ban that expires {$expires} for [{$name}, {$ip}, {$hwid}], with reason: {$reason}
server-ban-string-never = never

View File

@@ -8,6 +8,7 @@ cmd-roleban-hint-1 = <name or user ID>
cmd-roleban-hint-2 = <job>
cmd-roleban-hint-3 = <reason>
cmd-roleban-hint-4 = [duration in minutes, leave out or 0 for permanent ban]
cmd-roleban-hint-5 = [severity]
cmd-roleban-hint-duration-1 = Permanent
cmd-roleban-hint-duration-2 = 1 day
@@ -37,6 +38,7 @@ cmd-rolebanlist-hint-2 = [include unbanned]
cmd-roleban-minutes-parse = {$time} is not a valid amount of minutes.\n{$help}
cmd-roleban-severity-parse = ${severity} is not a valid severity\n{$help}.
cmd-roleban-arg-count = Invalid amount of arguments.
cmd-roleban-job-parse = Job {$job} does not exist.
cmd-roleban-name-parse = Unable to find a player with that name.

View File

@@ -0,0 +1,9 @@
- files: ["none_button.png", "minor_button.png", "medium_button.png", "high_button.png"]
license: "CC-BY-SA-3.0"
copyright: "CitrusGender"
source: "https://github.com/tgstation/tgstation/pull/39808"
- files: ["watchlist.png", "message.png"]
license: "CC-BY-SA-3.0"
copyright: "Riggle"
source: "https://github.com/RigglePrime/"

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

View File

@@ -150,7 +150,7 @@ def clear_server_ban(cur: "psycopg2.cursor", user_id: str):
DELETE FROM
server_ban
WHERE
user_id = %s
player_user_id = %s
""", (user_id,))
@@ -172,7 +172,7 @@ def clear_server_role_ban(cur: "psycopg2.cursor", user_id: str):
DELETE FROM
server_role_ban
WHERE
user_id = %s
player_user_id = %s
""", (user_id,))