Admin notes (#7259)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Right">
|
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
|
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" />
|
||||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" />
|
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" />
|
||||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" />
|
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" />
|
||||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" />
|
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" />
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ namespace Content.Client.Administration.UI
|
|||||||
return bch!.LastMessage.CompareTo(ach!.LastMessage);
|
return bch!.LastMessage.CompareTo(ach!.LastMessage);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Notes.OnPressed += _ =>
|
||||||
|
{
|
||||||
|
if (_currentPlayer is not null)
|
||||||
|
_console.ExecuteCommand($"adminnotes \"{_currentPlayer.SessionId}\"");
|
||||||
|
};
|
||||||
|
|
||||||
// ew
|
// ew
|
||||||
Ban.OnPressed += _ =>
|
Ban.OnPressed += _ =>
|
||||||
{
|
{
|
||||||
@@ -138,6 +144,9 @@ namespace Content.Client.Administration.UI
|
|||||||
|
|
||||||
private void FixButtons()
|
private void FixButtons()
|
||||||
{
|
{
|
||||||
|
Notes.Visible = _adminManager.HasFlag(AdminFlags.ViewNotes);
|
||||||
|
Notes.Disabled = !Notes.Visible;
|
||||||
|
|
||||||
Ban.Visible = _adminManager.HasFlag(AdminFlags.Ban);
|
Ban.Visible = _adminManager.HasFlag(AdminFlags.Ban);
|
||||||
Ban.Disabled = !Ban.Visible;
|
Ban.Disabled = !Ban.Visible;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
MinHeight="400">
|
MinHeight="400">
|
||||||
<PanelContainer>
|
<PanelContainer>
|
||||||
<PanelContainer.PanelOverride>
|
<PanelContainer.PanelOverride>
|
||||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"/>
|
<gfx:StyleBoxFlat BackgroundColor="#25252A"/>
|
||||||
</PanelContainer.PanelOverride>
|
</PanelContainer.PanelOverride>
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
using Content.Shared.Administration;
|
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.IoC;
|
using static Content.Shared.Administration.Logs.AdminLogsEuiMsg;
|
||||||
using static Content.Shared.Administration.AdminLogsEuiMsg;
|
|
||||||
|
|
||||||
namespace Content.Client.Administration.UI.Logs;
|
namespace Content.Client.Administration.UI.Logs;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Control xmlns="https://spacestation14.io"
|
||||||
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
|
MinWidth="400"
|
||||||
|
MinHeight="400">
|
||||||
|
<PanelContainer>
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BackgroundColor="#25252A"/>
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<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"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</Control>
|
||||||
146
Content.Client/Administration/UI/Notes/AdminNotesControl.xaml.cs
Normal file
146
Content.Client/Administration/UI/Notes/AdminNotesControl.xaml.cs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using static Robust.Client.UserInterface.Controls.LineEdit;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.Notes;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AdminNotesControl : Control
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||||
|
|
||||||
|
public event Action<int, string>? OnNoteChanged;
|
||||||
|
public event Action<string>? OnNewNoteEntered;
|
||||||
|
public event Action<int>? OnNoteDeleted;
|
||||||
|
|
||||||
|
private AdminNotesLinePopup? _popup;
|
||||||
|
|
||||||
|
public AdminNotesControl()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
NewNote.OnTextEntered += NewNoteEntered;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<int, AdminNotesLine> Inputs { get; } = new();
|
||||||
|
private bool CanCreate { get; set; }
|
||||||
|
private bool CanDelete { get; set; }
|
||||||
|
private bool CanEdit { get; set; }
|
||||||
|
|
||||||
|
private void NewNoteEntered(LineEditEventArgs args)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(args.Text))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NewNote.Clear();
|
||||||
|
OnNewNoteEntered?.Invoke(args.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NoteSubmitted(AdminNotesLine input)
|
||||||
|
{
|
||||||
|
var text = input.EditText.Trim();
|
||||||
|
if (input.OriginalMessage == text)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnNoteChanged?.Invoke(input.Id, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NoteRightClicked(AdminNotesLine line)
|
||||||
|
{
|
||||||
|
ClosePopup();
|
||||||
|
|
||||||
|
_popup = new AdminNotesLinePopup(line.Note, CanDelete, CanEdit);
|
||||||
|
_popup.OnEditPressed += noteId =>
|
||||||
|
{
|
||||||
|
if (!Inputs.TryGetValue(noteId, out var input))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.SetEditable(true);
|
||||||
|
};
|
||||||
|
_popup.OnDeletePressed += noteId => OnNoteDeleted?.Invoke(noteId);
|
||||||
|
|
||||||
|
var box = UIBox2.FromDimensions(_ui.MousePositionScaled.Position, (1, 1));
|
||||||
|
_popup.Open(box);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClosePopup()
|
||||||
|
{
|
||||||
|
_popup?.Close();
|
||||||
|
_popup = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetNotes(Dictionary<int, SharedAdminNote> notes)
|
||||||
|
{
|
||||||
|
foreach (var (id, input) in Inputs)
|
||||||
|
{
|
||||||
|
if (!notes.ContainsKey(id))
|
||||||
|
{
|
||||||
|
Notes.RemoveChild(input);
|
||||||
|
Inputs.Remove(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var note in notes.Values.OrderBy(note => note.Id))
|
||||||
|
{
|
||||||
|
if (Inputs.TryGetValue(note.Id, out var input))
|
||||||
|
{
|
||||||
|
input.UpdateNote(note);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
input = new AdminNotesLine(note);
|
||||||
|
input.OnSubmitted += NoteSubmitted;
|
||||||
|
input.OnRightClicked += NoteRightClicked;
|
||||||
|
Notes.AddChild(input);
|
||||||
|
Inputs[note.Id] = input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPermissions(bool create, bool delete, bool edit)
|
||||||
|
{
|
||||||
|
CanCreate = create;
|
||||||
|
CanDelete = delete;
|
||||||
|
CanEdit = edit;
|
||||||
|
NewNoteLabel.Visible = create;
|
||||||
|
NewNote.Visible = create;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var input in Inputs.Values)
|
||||||
|
{
|
||||||
|
input.OnSubmitted -= NoteSubmitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
Inputs.Clear();
|
||||||
|
NewNote.OnTextEntered -= NewNoteEntered;
|
||||||
|
|
||||||
|
if (_popup != null)
|
||||||
|
{
|
||||||
|
_ui.PopupRoot.RemoveChild(_popup);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnNoteChanged = null;
|
||||||
|
OnNewNoteEntered = null;
|
||||||
|
OnNoteDeleted = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Content.Client/Administration/UI/Notes/AdminNotesEui.cs
Normal file
42
Content.Client/Administration/UI/Notes/AdminNotesEui.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Client.Eui;
|
||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Content.Shared.Eui;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using static Content.Shared.Administration.Notes.AdminNoteEuiMsg;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.Notes;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class AdminNotesEui : BaseEui
|
||||||
|
{
|
||||||
|
public AdminNotesEui()
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminNotesWindow NoteWindow { get; }
|
||||||
|
|
||||||
|
private AdminNotesControl NoteControl { get; }
|
||||||
|
|
||||||
|
public override void HandleState(EuiStateBase state)
|
||||||
|
{
|
||||||
|
if (state is not AdminNotesEuiState s)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteWindow.SetTitlePlayer(s.NotedPlayerName);
|
||||||
|
NoteControl.SetNotes(s.Notes);
|
||||||
|
NoteControl.SetPermissions(s.CanCreate, s.CanDelete, s.CanEdit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Opened()
|
||||||
|
{
|
||||||
|
NoteWindow.OpenCentered();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<BoxContainer xmlns="https://spacestation14.io"
|
||||||
|
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||||
|
Orientation="Vertical">
|
||||||
|
<cc:HSeparator Name="Separator"/>
|
||||||
|
</BoxContainer>
|
||||||
142
Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs
Normal file
142
Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
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;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.Notes;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AdminNotesLine : BoxContainer
|
||||||
|
{
|
||||||
|
private RichTextLabel? _label;
|
||||||
|
private LineEdit? _edit;
|
||||||
|
|
||||||
|
public AdminNotesLine(SharedAdminNote note)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
Note = note;
|
||||||
|
MouseFilter = MouseFilterMode.Pass;
|
||||||
|
|
||||||
|
AddLabel();
|
||||||
|
}
|
||||||
|
|
||||||
|
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>? OnRightClicked;
|
||||||
|
|
||||||
|
private void AddLabel()
|
||||||
|
{
|
||||||
|
if (_edit != null)
|
||||||
|
{
|
||||||
|
_edit.OnTextEntered -= Submitted;
|
||||||
|
_edit.OnFocusExit -= Submitted;
|
||||||
|
|
||||||
|
RemoveChild(_edit);
|
||||||
|
_edit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_label = new RichTextLabel();
|
||||||
|
_label.SetMessage(Note.Message);
|
||||||
|
|
||||||
|
AddChild(_label);
|
||||||
|
_label.SetPositionFirst();
|
||||||
|
|
||||||
|
Separator.Visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddLineEdit()
|
||||||
|
{
|
||||||
|
if (_label != null)
|
||||||
|
{
|
||||||
|
RemoveChild(_label);
|
||||||
|
_label = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_edit = new LineEdit {Text = Note.Message};
|
||||||
|
_edit.OnTextEntered += Submitted;
|
||||||
|
_edit.OnFocusExit += Submitted;
|
||||||
|
|
||||||
|
AddChild(_edit);
|
||||||
|
_edit.SetPositionFirst();
|
||||||
|
_edit.GrabKeyboardFocus();
|
||||||
|
_edit.CursorPosition = _edit.Text.Length;
|
||||||
|
|
||||||
|
Separator.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Submitted(LineEditEventArgs args)
|
||||||
|
{
|
||||||
|
OnSubmitted?.Invoke(this);
|
||||||
|
|
||||||
|
AddLabel();
|
||||||
|
|
||||||
|
var note = Note with {Message = args.Text};
|
||||||
|
UpdateNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
base.KeyBindDown(args);
|
||||||
|
|
||||||
|
if (args.Function != EngineKeyFunctions.UIRightClick &&
|
||||||
|
args.Function != EngineKeyFunctions.UIClick)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OnRightClicked?.Invoke(this) == true)
|
||||||
|
{
|
||||||
|
args.Handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_edit != null)
|
||||||
|
{
|
||||||
|
_edit.OnTextEntered -= Submitted;
|
||||||
|
_edit.OnFocusExit -= Submitted;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnSubmitted = null;
|
||||||
|
OnRightClicked = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<Popup xmlns="https://spacestation14.io"
|
||||||
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||||
|
<PanelContainer>
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BackgroundColor="#25252A"/>
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<Label Name="IdLabel"/>
|
||||||
|
<Label Name="RoundIdLabel"/>
|
||||||
|
<Label Name="CreatedByLabel"/>
|
||||||
|
<Label Name="CreatedAtLabel"/>
|
||||||
|
<Label Name="EditedByLabel"/>
|
||||||
|
<Label Name="EditedAtLabel"/>
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Button Name="EditButton" Text="{Loc admin-notes-edit}"/>
|
||||||
|
<Control HorizontalExpand="True"/>
|
||||||
|
<Button Name="DeleteButton" Text="{Loc admin-notes-delete}" HorizontalAlignment="Right"/>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</Popup>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.Notes;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AdminNotesLinePopup : Popup
|
||||||
|
{
|
||||||
|
public event Action<int>? OnEditPressed;
|
||||||
|
public event Action<int>? OnDeletePressed;
|
||||||
|
|
||||||
|
public AdminNotesLinePopup(SharedAdminNote note, bool showDelete, bool showEdit)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
NoteId = note.Id;
|
||||||
|
DeleteButton.Visible = showDelete;
|
||||||
|
EditButton.Visible = showEdit;
|
||||||
|
|
||||||
|
UserInterfaceManager.ModalRoot.AddChild(this);
|
||||||
|
|
||||||
|
IdLabel.Text = Loc.GetString("admin-notes-id", ("id", note.Id));
|
||||||
|
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")));
|
||||||
|
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")));
|
||||||
|
|
||||||
|
EditButton.OnPressed += EditPressed;
|
||||||
|
DeleteButton.OnPressed += DeletePressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NoteId { get; }
|
||||||
|
private bool ConfirmingDelete { get; set; }
|
||||||
|
|
||||||
|
private void EditPressed(ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
OnEditPressed?.Invoke(NoteId);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeletePressed(ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
if (!ConfirmingDelete)
|
||||||
|
{
|
||||||
|
ConfirmingDelete = true;
|
||||||
|
DeleteButton.Text = Loc.GetString("admin-notes-delete-confirm");
|
||||||
|
DeleteButton.ModulateSelfOverride = Color.Red;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfirmingDelete = false;
|
||||||
|
DeleteButton.ModulateSelfOverride = null;
|
||||||
|
OnDeletePressed?.Invoke(NoteId);
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (!disposing)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditButton.OnPressed -= EditPressed;
|
||||||
|
DeleteButton.OnPressed -= DeletePressed;
|
||||||
|
|
||||||
|
OnEditPressed = null;
|
||||||
|
OnDeletePressed = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<DefaultWindow xmlns="https://spacestation14.io"
|
||||||
|
xmlns:notes="clr-namespace:Content.Client.Administration.UI.Notes"
|
||||||
|
MinWidth="400"
|
||||||
|
MinHeight="400">
|
||||||
|
<notes:AdminNotesControl Name="Notes" Access="Public"/>
|
||||||
|
</DefaultWindow>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.Administration.UI.Notes;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class AdminNotesWindow : DefaultWindow
|
||||||
|
{
|
||||||
|
public AdminNotesWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTitlePlayer(string playerName)
|
||||||
|
{
|
||||||
|
Title = Loc.GetString("admin-notes-title", ("player", playerName));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
<cc:CommandButton Command="announceui" Text="{Loc Announce}"/>
|
<cc:CommandButton Command="announceui" Text="{Loc Announce}"/>
|
||||||
<cc:UICommandButton Command="callshuttle" Text="{Loc (Re)call Shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
|
<cc:UICommandButton Command="callshuttle" Text="{Loc (Re)call Shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
|
||||||
<cc:CommandButton Command="adminlogs" Text="{Loc Admin Logs}"/>
|
<cc:CommandButton Command="adminlogs" Text="{Loc Admin Logs}"/>
|
||||||
|
<cc:CommandButton Command="adminnotes" Text="{Loc Admin Notes}"/>
|
||||||
</GridContainer>
|
</GridContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</Control>
|
</Control>
|
||||||
|
|||||||
1211
Content.Server.Database/Migrations/Postgres/20220324144654_AdminNotes.Designer.cs
generated
Normal file
1211
Content.Server.Database/Migrations/Postgres/20220324144654_AdminNotes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Postgres
|
||||||
|
{
|
||||||
|
public partial class AdminNotes : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "admin_notes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
admin_notes_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: false),
|
||||||
|
message = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||||
|
created_by_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
last_edited_by_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
last_edited_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
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),
|
||||||
|
shown_to_player = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_admin_notes", x => x.admin_notes_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_created_by_id",
|
||||||
|
column: x => x.created_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_deleted_by_id",
|
||||||
|
column: x => x.deleted_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_last_edited_by_id",
|
||||||
|
column: x => x.last_edited_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_player_user_id",
|
||||||
|
column: x => x.player_user_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_round_round_id",
|
||||||
|
column: x => x.round_id,
|
||||||
|
principalTable: "round",
|
||||||
|
principalColumn: "round_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_created_by_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "created_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_deleted_by_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "deleted_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_last_edited_by_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "last_edited_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_player_user_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "player_user_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_round_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "round_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "admin_notes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -184,6 +184,79 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.ToTable("admin_log_player", (string)null);
|
b.ToTable("admin_log_player", (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>("LastEditedAt")
|
||||||
|
.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<int?>("RoundId")
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("round_id");
|
||||||
|
|
||||||
|
b.Property<bool>("ShownToPlayer")
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasColumnName("shown_to_player");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_admin_notes");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedById");
|
||||||
|
|
||||||
|
b.HasIndex("DeletedById");
|
||||||
|
|
||||||
|
b.HasIndex("LastEditedById");
|
||||||
|
|
||||||
|
b.HasIndex("PlayerUserId")
|
||||||
|
.HasDatabaseName("IX_admin_notes_player_user_id");
|
||||||
|
|
||||||
|
b.HasIndex("RoundId")
|
||||||
|
.HasDatabaseName("IX_admin_notes_round_id");
|
||||||
|
|
||||||
|
b.ToTable("admin_notes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
|
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -927,6 +1000,54 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
b.Navigation("Player");
|
b.Navigation("Player");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
||||||
|
.WithMany("AdminNotesCreated")
|
||||||
|
.HasForeignKey("CreatedById")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_admin_notes_player_created_by_id");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Player", "DeletedBy")
|
||||||
|
.WithMany("AdminNotesDeleted")
|
||||||
|
.HasForeignKey("DeletedById")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.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()
|
||||||
|
.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()
|
||||||
|
.HasConstraintName("FK_admin_notes_player_player_user_id");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Round", "Round")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoundId")
|
||||||
|
.HasConstraintName("FK_admin_notes_round_round_id");
|
||||||
|
|
||||||
|
b.Navigation("CreatedBy");
|
||||||
|
|
||||||
|
b.Navigation("DeletedBy");
|
||||||
|
|
||||||
|
b.Navigation("LastEditedBy");
|
||||||
|
|
||||||
|
b.Navigation("Player");
|
||||||
|
|
||||||
|
b.Navigation("Round");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
|
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.AdminRank", "Rank")
|
b.HasOne("Content.Server.Database.AdminRank", "Rank")
|
||||||
@@ -1076,6 +1197,14 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AdminLogs");
|
b.Navigation("AdminLogs");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesCreated");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesDeleted");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesLastEdited");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesReceived");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||||
|
|||||||
1153
Content.Server.Database/Migrations/Sqlite/20220324144649_AdminNotes.Designer.cs
generated
Normal file
1153
Content.Server.Database/Migrations/Sqlite/20220324144649_AdminNotes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,95 @@
|
|||||||
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Content.Server.Database.Migrations.Sqlite
|
||||||
|
{
|
||||||
|
public partial class AdminNotes : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "admin_notes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
admin_notes_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: false),
|
||||||
|
message = table.Column<string>(type: "TEXT", maxLength: 4096, nullable: false),
|
||||||
|
created_by_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
created_at = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
last_edited_by_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
|
last_edited_at = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
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),
|
||||||
|
shown_to_player = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_admin_notes", x => x.admin_notes_id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_created_by_id",
|
||||||
|
column: x => x.created_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_deleted_by_id",
|
||||||
|
column: x => x.deleted_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id");
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_last_edited_by_id",
|
||||||
|
column: x => x.last_edited_by_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_player_player_user_id",
|
||||||
|
column: x => x.player_user_id,
|
||||||
|
principalTable: "player",
|
||||||
|
principalColumn: "user_id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_admin_notes_round_round_id",
|
||||||
|
column: x => x.round_id,
|
||||||
|
principalTable: "round",
|
||||||
|
principalColumn: "round_id");
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_created_by_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "created_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_deleted_by_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "deleted_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_last_edited_by_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "last_edited_by_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_player_user_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "player_user_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_admin_notes_round_id",
|
||||||
|
table: "admin_notes",
|
||||||
|
column: "round_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "admin_notes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -168,6 +168,77 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.ToTable("admin_log_player", (string)null);
|
b.ToTable("admin_log_player", (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>("LastEditedAt")
|
||||||
|
.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<int?>("RoundId")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("round_id");
|
||||||
|
|
||||||
|
b.Property<bool>("ShownToPlayer")
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasColumnName("shown_to_player");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("PK_admin_notes");
|
||||||
|
|
||||||
|
b.HasIndex("CreatedById");
|
||||||
|
|
||||||
|
b.HasIndex("DeletedById");
|
||||||
|
|
||||||
|
b.HasIndex("LastEditedById");
|
||||||
|
|
||||||
|
b.HasIndex("PlayerUserId")
|
||||||
|
.HasDatabaseName("IX_admin_notes_player_user_id");
|
||||||
|
|
||||||
|
b.HasIndex("RoundId")
|
||||||
|
.HasDatabaseName("IX_admin_notes_round_id");
|
||||||
|
|
||||||
|
b.ToTable("admin_notes", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
|
modelBuilder.Entity("Content.Server.Database.AdminRank", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -869,6 +940,54 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
b.Navigation("Player");
|
b.Navigation("Player");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Content.Server.Database.Player", "CreatedBy")
|
||||||
|
.WithMany("AdminNotesCreated")
|
||||||
|
.HasForeignKey("CreatedById")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired()
|
||||||
|
.HasConstraintName("FK_admin_notes_player_created_by_id");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Player", "DeletedBy")
|
||||||
|
.WithMany("AdminNotesDeleted")
|
||||||
|
.HasForeignKey("DeletedById")
|
||||||
|
.HasPrincipalKey("UserId")
|
||||||
|
.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()
|
||||||
|
.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()
|
||||||
|
.HasConstraintName("FK_admin_notes_player_player_user_id");
|
||||||
|
|
||||||
|
b.HasOne("Content.Server.Database.Round", "Round")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoundId")
|
||||||
|
.HasConstraintName("FK_admin_notes_round_round_id");
|
||||||
|
|
||||||
|
b.Navigation("CreatedBy");
|
||||||
|
|
||||||
|
b.Navigation("DeletedBy");
|
||||||
|
|
||||||
|
b.Navigation("LastEditedBy");
|
||||||
|
|
||||||
|
b.Navigation("Player");
|
||||||
|
|
||||||
|
b.Navigation("Round");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
|
modelBuilder.Entity("Content.Server.Database.AdminRankFlag", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Content.Server.Database.AdminRank", "Rank")
|
b.HasOne("Content.Server.Database.AdminRank", "Rank")
|
||||||
@@ -1018,6 +1137,14 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||||||
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
modelBuilder.Entity("Content.Server.Database.Player", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("AdminLogs");
|
b.Navigation("AdminLogs");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesCreated");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesDeleted");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesLastEdited");
|
||||||
|
|
||||||
|
b.Navigation("AdminNotesReceived");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
modelBuilder.Entity("Content.Server.Database.Preference", b =>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ namespace Content.Server.Database
|
|||||||
public DbSet<ServerRoleBan> RoleBan { get; set; } = default!;
|
public DbSet<ServerRoleBan> RoleBan { get; set; } = default!;
|
||||||
public DbSet<ServerRoleUnban> RoleUnban { get; set; } = default!;
|
public DbSet<ServerRoleUnban> RoleUnban { get; set; } = default!;
|
||||||
public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
|
public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
|
||||||
|
public DbSet<AdminNote> AdminNotes { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@@ -140,6 +141,30 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
modelBuilder.Entity<ConnectionLog>()
|
modelBuilder.Entity<ConnectionLog>()
|
||||||
.HasIndex(p => p.UserId);
|
.HasIndex(p => p.UserId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<AdminNote>()
|
||||||
|
.HasOne(note => note.Player)
|
||||||
|
.WithMany(player => player.AdminNotesReceived)
|
||||||
|
.HasForeignKey(note => note.PlayerUserId)
|
||||||
|
.HasPrincipalKey(player => player.UserId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<AdminNote>()
|
||||||
|
.HasOne(version => version.CreatedBy)
|
||||||
|
.WithMany(author => author.AdminNotesCreated)
|
||||||
|
.HasForeignKey(note => note.CreatedById)
|
||||||
|
.HasPrincipalKey(author => author.UserId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<AdminNote>()
|
||||||
|
.HasOne(version => version.LastEditedBy)
|
||||||
|
.WithMany(author => author.AdminNotesLastEdited)
|
||||||
|
.HasForeignKey(note => note.LastEditedById)
|
||||||
|
.HasPrincipalKey(author => author.UserId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<AdminNote>()
|
||||||
|
.HasOne(version => version.DeletedBy)
|
||||||
|
.WithMany(author => author.AdminNotesDeleted)
|
||||||
|
.HasForeignKey(note => note.DeletedById)
|
||||||
|
.HasPrincipalKey(author => author.UserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
|
||||||
@@ -251,6 +276,11 @@ namespace Content.Server.Database
|
|||||||
public List<AdminLogPlayer> AdminLogs { get; set; } = null!;
|
public List<AdminLogPlayer> AdminLogs { get; set; } = null!;
|
||||||
|
|
||||||
public DateTime? LastReadRules { get; set; }
|
public DateTime? LastReadRules { get; set; }
|
||||||
|
|
||||||
|
public List<AdminNote> AdminNotesReceived { get; set; } = null!;
|
||||||
|
public List<AdminNote> AdminNotesCreated { get; set; } = null!;
|
||||||
|
public List<AdminNote> AdminNotesLastEdited { get; set; } = null!;
|
||||||
|
public List<AdminNote> AdminNotesDeleted { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Table("whitelist")]
|
[Table("whitelist")]
|
||||||
@@ -477,4 +507,35 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
public byte[] Data { get; set; } = default!;
|
public byte[] Data { get; set; } = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Index(nameof(PlayerUserId))]
|
||||||
|
public class AdminNote
|
||||||
|
{
|
||||||
|
[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!;
|
||||||
|
|
||||||
|
[Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required, ForeignKey("CreatedBy")] public Guid CreatedById { get; set; }
|
||||||
|
[Required] public Player CreatedBy { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required] public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
[Required, ForeignKey("LastEditedBy")] public Guid LastEditedById { get; set; }
|
||||||
|
[Required] public Player LastEditedBy { get; set; } = default!;
|
||||||
|
|
||||||
|
[Required] public DateTime LastEditedAt { 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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Content.Server.Administration.Notes;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Commands;
|
||||||
|
|
||||||
|
[AdminCommand(AdminFlags.ViewNotes)]
|
||||||
|
public sealed class OpenAdminNotesCommand : IConsoleCommand
|
||||||
|
{
|
||||||
|
public const string CommandName = "adminnotes";
|
||||||
|
|
||||||
|
public string Command => CommandName;
|
||||||
|
public string Description => "Opens the admin notes panel.";
|
||||||
|
public string Help => $"Usage: {Command} <notedPlayerUserId>";
|
||||||
|
|
||||||
|
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (shell.Player is not IPlayerSession player)
|
||||||
|
{
|
||||||
|
shell.WriteError("This does not work from the server console.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Guid notedPlayer;
|
||||||
|
|
||||||
|
switch (args.Length)
|
||||||
|
{
|
||||||
|
case 1 when Guid.TryParse(args[0], out notedPlayer):
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
shell.WriteError($"Invalid arguments.\n{Help}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await IoCManager.Resolve<IAdminNotesManager>().OpenEui(player, notedPlayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
@@ -11,11 +9,8 @@ using Content.Shared.Administration.Logs;
|
|||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using static Content.Shared.Administration.AdminLogsEuiMsg;
|
using static Content.Shared.Administration.Logs.AdminLogsEuiMsg;
|
||||||
|
|
||||||
namespace Content.Server.Administration.Logs;
|
namespace Content.Server.Administration.Logs;
|
||||||
|
|
||||||
|
|||||||
156
Content.Server/Administration/Notes/AdminNotesEui.cs
Normal file
156
Content.Server/Administration/Notes/AdminNotesEui.cs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Administration.Managers;
|
||||||
|
using Content.Server.EUI;
|
||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Content.Shared.Eui;
|
||||||
|
using static Content.Shared.Administration.Notes.AdminNoteEuiMsg;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Notes;
|
||||||
|
|
||||||
|
public sealed class AdminNotesEui : BaseEui
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IAdminManager _admins = default!;
|
||||||
|
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
||||||
|
|
||||||
|
public AdminNotesEui()
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Guid NotedPlayer { get; set; }
|
||||||
|
private string NotedPlayerName { get; set; } = string.Empty;
|
||||||
|
private Dictionary<int, SharedAdminNote> Notes { get; set; } = new();
|
||||||
|
|
||||||
|
public override async void Opened()
|
||||||
|
{
|
||||||
|
base.Opened();
|
||||||
|
|
||||||
|
_admins.OnPermsChanged += OnPermsChanged;
|
||||||
|
_notesMan.NoteAdded += NoteModified;
|
||||||
|
_notesMan.NoteModified += NoteModified;
|
||||||
|
_notesMan.NoteDeleted += NoteDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Closed()
|
||||||
|
{
|
||||||
|
base.Closed();
|
||||||
|
|
||||||
|
_admins.OnPermsChanged -= OnPermsChanged;
|
||||||
|
_notesMan.NoteAdded -= NoteModified;
|
||||||
|
_notesMan.NoteModified -= NoteModified;
|
||||||
|
_notesMan.NoteDeleted -= NoteDeleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override EuiStateBase GetNewState()
|
||||||
|
{
|
||||||
|
return new AdminNotesEuiState(
|
||||||
|
NotedPlayerName,
|
||||||
|
Notes,
|
||||||
|
_notesMan.CanCreate(Player),
|
||||||
|
_notesMan.CanDelete(Player),
|
||||||
|
_notesMan.CanEdit(Player)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async void HandleMessage(EuiMessageBase msg)
|
||||||
|
{
|
||||||
|
base.HandleMessage(msg);
|
||||||
|
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case Close _:
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CreateNoteRequest {Message: var message}:
|
||||||
|
{
|
||||||
|
if (!_notesMan.CanCreate(Player))
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(message))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _notesMan.AddNote(Player, NotedPlayer, message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DeleteNoteRequest request:
|
||||||
|
{
|
||||||
|
if (!_notesMan.CanDelete(Player))
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _notesMan.DeleteNote(request.Id, Player);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EditNoteRequest request:
|
||||||
|
{
|
||||||
|
if (!_notesMan.CanEdit(Player))
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.Message))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _notesMan.ModifyNote(request.Id, Player, request.Message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ChangeNotedPlayer(Guid notedPlayer)
|
||||||
|
{
|
||||||
|
NotedPlayer = notedPlayer;
|
||||||
|
await LoadFromDb();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NoteModified(SharedAdminNote note)
|
||||||
|
{
|
||||||
|
Notes[note.Id] = note;
|
||||||
|
StateDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NoteDeleted(int id)
|
||||||
|
{
|
||||||
|
Notes.Remove(id);
|
||||||
|
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;
|
||||||
|
|
||||||
|
StateDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPermsChanged(AdminPermsChangedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Player == Player && !_notesMan.CanView(Player))
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StateDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Content.Server/Administration/Notes/AdminNotesExtensions.cs
Normal file
20
Content.Server/Administration/Notes/AdminNotesExtensions.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Notes;
|
||||||
|
|
||||||
|
public static class AdminNotesExtensions
|
||||||
|
{
|
||||||
|
public static SharedAdminNote ToShared(this AdminNote note)
|
||||||
|
{
|
||||||
|
return new SharedAdminNote(
|
||||||
|
note.Id,
|
||||||
|
note.RoundId,
|
||||||
|
note.Message,
|
||||||
|
note.CreatedBy.LastSeenUserName,
|
||||||
|
note.LastEditedBy.LastSeenUserName,
|
||||||
|
note.CreatedAt,
|
||||||
|
note.LastEditedAt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
136
Content.Server/Administration/Notes/AdminNotesManager.cs
Normal file
136
Content.Server/Administration/Notes/AdminNotesManager.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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 Robust.Server.Player;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Notes;
|
||||||
|
|
||||||
|
public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IAdminManager _admins = default!;
|
||||||
|
[Dependency] private readonly IServerDbManager _db = default!;
|
||||||
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
[Dependency] private readonly EuiManager _euis = default!;
|
||||||
|
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||||
|
|
||||||
|
public const string SawmillId = "admin.notes";
|
||||||
|
|
||||||
|
public event Action<SharedAdminNote>? NoteAdded;
|
||||||
|
public event Action<SharedAdminNote>? NoteModified;
|
||||||
|
public event Action<int>? NoteDeleted;
|
||||||
|
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
|
public bool CanCreate(IPlayerSession admin)
|
||||||
|
{
|
||||||
|
return CanEdit(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanDelete(IPlayerSession admin)
|
||||||
|
{
|
||||||
|
return CanEdit(admin);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanEdit(IPlayerSession admin)
|
||||||
|
{
|
||||||
|
return _admins.HasAdminFlag(admin, AdminFlags.EditNotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanView(IPlayerSession admin)
|
||||||
|
{
|
||||||
|
return _admins.HasAdminFlag(admin, AdminFlags.ViewNotes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OpenEui(IPlayerSession admin, Guid notedPlayer)
|
||||||
|
{
|
||||||
|
var ui = new AdminNotesEui();
|
||||||
|
_euis.OpenEui(ui, admin);
|
||||||
|
|
||||||
|
await ui.ChangeNotedPlayer(notedPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddNote(IPlayerSession createdBy, Guid player, string message)
|
||||||
|
{
|
||||||
|
_sawmill.Info($"Player {createdBy.Name} added note with message {message}");
|
||||||
|
|
||||||
|
_systems.TryGetEntitySystem(out GameTicker? ticker);
|
||||||
|
int? round = ticker == null || ticker.RoundId == 0 ? null : ticker.RoundId;
|
||||||
|
var createdAt = DateTime.UtcNow;
|
||||||
|
var noteId = await _db.AddAdminNote(round, player, message, createdBy.UserId, createdAt);
|
||||||
|
|
||||||
|
var note = new SharedAdminNote(
|
||||||
|
noteId,
|
||||||
|
round,
|
||||||
|
message,
|
||||||
|
createdBy.Name,
|
||||||
|
createdBy.Name,
|
||||||
|
createdAt,
|
||||||
|
createdAt
|
||||||
|
);
|
||||||
|
NoteAdded?.Invoke(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteNote(int noteId, IPlayerSession deletedBy)
|
||||||
|
{
|
||||||
|
var note = await _db.GetAdminNote(noteId);
|
||||||
|
if (note == null)
|
||||||
|
{
|
||||||
|
_sawmill.Info($"Player {deletedBy.Name} tried to delete non-existent note {noteId}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sawmill.Info($"Player {deletedBy.Name} deleted note {noteId}");
|
||||||
|
|
||||||
|
var deletedAt = DateTime.UtcNow;
|
||||||
|
await _db.DeleteAdminNote(noteId, deletedBy.UserId, deletedAt);
|
||||||
|
|
||||||
|
NoteDeleted?.Invoke(noteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ModifyNote(int noteId, IPlayerSession editedBy, string message)
|
||||||
|
{
|
||||||
|
message = message.Trim();
|
||||||
|
|
||||||
|
var note = await _db.GetAdminNote(noteId);
|
||||||
|
if (note == null || note.Message == message)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sawmill.Info($"Player {editedBy.Name} modified note {noteId} with message {message}");
|
||||||
|
|
||||||
|
var editedAt = DateTime.UtcNow;
|
||||||
|
await _db.EditAdminNote(noteId, message, editedBy.UserId, editedAt);
|
||||||
|
|
||||||
|
var sharedNote = new SharedAdminNote(
|
||||||
|
noteId,
|
||||||
|
note.RoundId,
|
||||||
|
message,
|
||||||
|
note.CreatedBy.LastSeenUserName,
|
||||||
|
editedBy.Name,
|
||||||
|
note.CreatedAt,
|
||||||
|
note.LastEditedAt
|
||||||
|
);
|
||||||
|
NoteModified?.Invoke(sharedNote);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<AdminNote>> GetNotes(Guid player)
|
||||||
|
{
|
||||||
|
return await _db.GetAdminNotes(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetPlayerName(Guid player)
|
||||||
|
{
|
||||||
|
return (await _db.GetPlayerRecordByUserId(new NetUserId(player)))?.LastSeenUserName ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PostInject()
|
||||||
|
{
|
||||||
|
_sawmill = _logManager.GetSawmill(SawmillId);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
Content.Server/Administration/Notes/AdminNotesSystem.cs
Normal file
43
Content.Server/Administration/Notes/AdminNotesSystem.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Content.Server.Administration.Commands;
|
||||||
|
using Content.Shared.Database;
|
||||||
|
using Content.Shared.Verbs;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Notes;
|
||||||
|
|
||||||
|
public sealed class AdminNotesSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConsoleHost _console = default!;
|
||||||
|
[Dependency] private readonly IAdminNotesManager _notes = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddVerbs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddVerbs(GetVerbsEvent<Verb> ev)
|
||||||
|
{
|
||||||
|
if (EntityManager.GetComponentOrNull<ActorComponent>(ev.User) is not {PlayerSession: var user} ||
|
||||||
|
EntityManager.GetComponentOrNull<ActorComponent>(ev.Target) is not {PlayerSession: var target})
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_notes.CanView(user))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var verb = new Verb
|
||||||
|
{
|
||||||
|
Text = Loc.GetString("admin-notes-verb-text"),
|
||||||
|
Category = VerbCategory.Admin,
|
||||||
|
IconTexture = "/Textures/Interface/VerbIcons/examine.svg.192dpi.png",
|
||||||
|
Act = () => _console.RemoteExecuteCommand(user, $"{OpenAdminNotesCommand.CommandName} \"{target.UserId}\""),
|
||||||
|
Impact = LogImpact.Low
|
||||||
|
};
|
||||||
|
|
||||||
|
ev.Verbs.Add(verb);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Content.Server/Administration/Notes/IAdminNotesManager.cs
Normal file
24
Content.Server/Administration/Notes/IAdminNotesManager.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Database;
|
||||||
|
using Content.Shared.Administration.Notes;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration.Notes;
|
||||||
|
|
||||||
|
public interface IAdminNotesManager
|
||||||
|
{
|
||||||
|
event Action<SharedAdminNote>? NoteAdded;
|
||||||
|
event Action<SharedAdminNote>? NoteModified;
|
||||||
|
event Action<int>? NoteDeleted;
|
||||||
|
|
||||||
|
bool CanCreate(IPlayerSession admin);
|
||||||
|
bool CanDelete(IPlayerSession admin);
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -822,6 +822,69 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Admin Notes
|
||||||
|
|
||||||
|
public virtual async Task<int> AddAdminNote(AdminNote note)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
db.DbContext.AdminNotes.Add(note);
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
return note.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)
|
||||||
|
.Include(note => note.CreatedBy)
|
||||||
|
.Include(note => note.LastEditedBy)
|
||||||
|
.Include(note => note.DeletedBy)
|
||||||
|
.Include(note => note.Player)
|
||||||
|
.SingleOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<AdminNote>> GetAdminNotes(Guid player)
|
||||||
|
{
|
||||||
|
await using var db = await GetDb();
|
||||||
|
return await db.DbContext.AdminNotes
|
||||||
|
.Where(note => note.PlayerUserId == player)
|
||||||
|
.Where(note => !note.Deleted)
|
||||||
|
.Include(note => note.Round)
|
||||||
|
.Include(note => note.CreatedBy)
|
||||||
|
.Include(note => note.LastEditedBy)
|
||||||
|
.Include(note => note.Player)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAdminNote(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.Deleted = true;
|
||||||
|
note.DeletedById = deletedBy;
|
||||||
|
note.DeletedAt = deletedAt;
|
||||||
|
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
await db.DbContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
protected abstract Task<DbGuard> GetDb();
|
protected abstract Task<DbGuard> GetDb();
|
||||||
|
|
||||||
protected abstract class DbGuard : IAsyncDisposable
|
protected abstract class DbGuard : IAsyncDisposable
|
||||||
|
|||||||
@@ -195,6 +195,16 @@ namespace Content.Server.Database
|
|||||||
Task SetLastReadRules(NetUserId player, DateTime time);
|
Task SetLastReadRules(NetUserId player, DateTime time);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Admin Notes
|
||||||
|
|
||||||
|
Task<int> AddAdminNote(int? roundId, Guid player, string message, Guid createdBy, DateTime createdAt);
|
||||||
|
Task<AdminNote?> GetAdminNote(int id);
|
||||||
|
Task<List<AdminNote>> GetAdminNotes(Guid player);
|
||||||
|
Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt);
|
||||||
|
Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt);
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ServerDbManager : IServerDbManager
|
public sealed class ServerDbManager : IServerDbManager
|
||||||
@@ -485,6 +495,42 @@ namespace Content.Server.Database
|
|||||||
return _db.SetLastReadRules(player, time);
|
return _db.SetLastReadRules(player, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<int> AddAdminNote(int? roundId, Guid player, string message, Guid createdBy, DateTime createdAt)
|
||||||
|
{
|
||||||
|
var note = new AdminNote
|
||||||
|
{
|
||||||
|
RoundId = roundId,
|
||||||
|
CreatedById = createdBy,
|
||||||
|
LastEditedById = createdBy,
|
||||||
|
PlayerUserId = player,
|
||||||
|
Message = message,
|
||||||
|
CreatedAt = createdAt,
|
||||||
|
LastEditedAt = createdAt
|
||||||
|
};
|
||||||
|
|
||||||
|
return _db.AddAdminNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<AdminNote?> GetAdminNote(int id)
|
||||||
|
{
|
||||||
|
return _db.GetAdminNote(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<AdminNote>> GetAdminNotes(Guid player)
|
||||||
|
{
|
||||||
|
return _db.GetAdminNotes(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt)
|
||||||
|
{
|
||||||
|
return _db.DeleteAdminNote(id, deletedBy, deletedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task EditAdminNote(int id, string message, Guid editedBy, DateTime editedAt)
|
||||||
|
{
|
||||||
|
return _db.EditAdminNote(id, message, editedBy, editedAt);
|
||||||
|
}
|
||||||
|
|
||||||
private DbContextOptions<PostgresServerDbContext> CreatePostgresOptions()
|
private DbContextOptions<PostgresServerDbContext> CreatePostgresOptions()
|
||||||
{
|
{
|
||||||
var host = _cfg.GetCVar(CCVars.DatabasePgHost);
|
var host = _cfg.GetCVar(CCVars.DatabasePgHost);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
@@ -11,7 +9,6 @@ using Content.Server.Preferences.Managers;
|
|||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -491,6 +488,22 @@ namespace Content.Server.Database
|
|||||||
await db.DbContext.SaveChangesAsync();
|
await db.DbContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<int> AddAdminNote(AdminNote note)
|
||||||
|
{
|
||||||
|
await using (var db = await GetDb())
|
||||||
|
{
|
||||||
|
var nextId = 1;
|
||||||
|
if (await db.DbContext.AdminNotes.AnyAsync())
|
||||||
|
{
|
||||||
|
nextId = await db.DbContext.AdminNotes.MaxAsync(dbVersion => dbVersion.Id) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
note.Id = nextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await base.AddAdminNote(note);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<DbGuardImpl> GetDbImpl()
|
private async Task<DbGuardImpl> GetDbImpl()
|
||||||
{
|
{
|
||||||
await _dbReadyTask;
|
await _dbReadyTask;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
|
using Content.Server.Administration.Notes;
|
||||||
using Content.Server.Afk;
|
using Content.Server.Afk;
|
||||||
using Content.Server.AI.Utility;
|
using Content.Server.AI.Utility;
|
||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
@@ -17,7 +18,6 @@ using Content.Server.Objectives;
|
|||||||
using Content.Server.Objectives.Interfaces;
|
using Content.Server.Objectives.Interfaces;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Server.Voting.Managers;
|
using Content.Server.Voting.Managers;
|
||||||
using Content.Shared.Actions;
|
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Kitchen;
|
using Content.Shared.Kitchen;
|
||||||
using Content.Shared.Module;
|
using Content.Shared.Module;
|
||||||
@@ -51,6 +51,7 @@ namespace Content.Server.IoC
|
|||||||
IoCManager.Register<RulesManager, RulesManager>();
|
IoCManager.Register<RulesManager, RulesManager>();
|
||||||
IoCManager.Register<RoleBanManager, RoleBanManager>();
|
IoCManager.Register<RoleBanManager, RoleBanManager>();
|
||||||
IoCManager.Register<NetworkResourceManager>();
|
IoCManager.Register<NetworkResourceManager>();
|
||||||
|
IoCManager.Register<IAdminNotesManager, AdminNotesManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System;
|
namespace Content.Shared.Administration
|
||||||
|
|
||||||
namespace Content.Shared.Administration
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Permissions that admins can have.
|
/// Permissions that admins can have.
|
||||||
@@ -80,6 +78,16 @@ namespace Content.Shared.Administration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Adminhelp = 1 << 12,
|
Adminhelp = 1 << 12,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lets you view admin notes.
|
||||||
|
/// </summary>
|
||||||
|
ViewNotes = 1 << 13,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lets you create, edit and delete admin notes.
|
||||||
|
/// </summary>
|
||||||
|
EditNotes = 1 << 14,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dangerous host permissions like scsi.
|
/// Dangerous host permissions like scsi.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
using System;
|
using Content.Shared.Database;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.Administration.Logs;
|
|
||||||
using Content.Shared.Database;
|
|
||||||
using Content.Shared.Eui;
|
using Content.Shared.Eui;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
namespace Content.Shared.Administration;
|
namespace Content.Shared.Administration.Logs;
|
||||||
|
|
||||||
[Serializable, NetSerializable]
|
[Serializable, NetSerializable]
|
||||||
public sealed class AdminLogsEuiState : EuiStateBase
|
public sealed class AdminLogsEuiState : EuiStateBase
|
||||||
66
Content.Shared/Administration/Notes/AdminNotesEuiState.cs
Normal file
66
Content.Shared/Administration/Notes/AdminNotesEuiState.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
using Content.Shared.Eui;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
NotedPlayerName = notedPlayerName;
|
||||||
|
Notes = notes;
|
||||||
|
CanCreate = canCreate;
|
||||||
|
CanDelete = canDelete;
|
||||||
|
CanEdit = canEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string NotedPlayerName { get; }
|
||||||
|
public Dictionary<int, SharedAdminNote> Notes { get; }
|
||||||
|
public bool CanCreate { get; }
|
||||||
|
public bool CanDelete { get; }
|
||||||
|
public bool CanEdit { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AdminNoteEuiMsg
|
||||||
|
{
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class Close : EuiMessageBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class CreateNoteRequest : EuiMessageBase
|
||||||
|
{
|
||||||
|
public CreateNoteRequest(string message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class DeleteNoteRequest : EuiMessageBase
|
||||||
|
{
|
||||||
|
public DeleteNoteRequest(int id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class EditNoteRequest : EuiMessageBase
|
||||||
|
{
|
||||||
|
public EditNoteRequest(int id, string message)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Content.Shared/Administration/Notes/SharedAdminNote.cs
Normal file
6
Content.Shared/Administration/Notes/SharedAdminNote.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Administration.Notes;
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed record SharedAdminNote(int Id, int? Round, string Message, string CreatedByName, string EditedByName, DateTime CreatedAt, DateTime LastEditedAt);
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
admin-player-actions-notes = Notes
|
||||||
admin-player-actions-kick = Kick
|
admin-player-actions-kick = Kick
|
||||||
admin-player-actions-ban = Ban
|
admin-player-actions-ban = Ban
|
||||||
admin-player-actions-ahelp = AHelp
|
admin-player-actions-ahelp = AHelp
|
||||||
|
|||||||
16
Resources/Locale/en-US/administration/ui/admin-notes.ftl
Normal file
16
Resources/Locale/en-US/administration/ui/admin-notes.ftl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# UI
|
||||||
|
admin-notes-title = Notes for {$player}
|
||||||
|
admin-notes-new-note = New note:
|
||||||
|
admin-notes-id = Id: {$id}
|
||||||
|
admin-notes-round-id = Round Id: {$id}
|
||||||
|
admin-notes-round-id-unknown = Round Id: Unknown
|
||||||
|
admin-notes-created-by = Created by: {$author}
|
||||||
|
admin-notes-created-at = Created At: {$date}
|
||||||
|
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?
|
||||||
|
|
||||||
|
# Verb
|
||||||
|
admin-notes-verb-text = Open Admin Notes
|
||||||
Reference in New Issue
Block a user