diff --git a/Content.Client/Administration/Systems/AdminWhoSystem.cs b/Content.Client/Administration/Systems/AdminWhoSystem.cs
new file mode 100644
index 0000000000..81e801ece5
--- /dev/null
+++ b/Content.Client/Administration/Systems/AdminWhoSystem.cs
@@ -0,0 +1,31 @@
+using Content.Shared.Administration;
+
+namespace Content.Client.Administration.Systems;
+
+///
+/// Client system for handling admin who requests
+///
+public sealed class AdminWhoSystem : EntitySystem
+{
+ public event Action>? OnAdminWhoUpdate;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnAdminWhoResponse);
+ }
+
+ ///
+ /// Request the list of online administrators from the server
+ ///
+ public void RequestAdminWho()
+ {
+ RaiseNetworkEvent(new RequestAdminWhoEvent());
+ }
+
+ private void OnAdminWhoResponse(AdminWhoResponseEvent args, EntitySessionEventArgs session)
+ {
+ OnAdminWhoUpdate?.Invoke(args.Admins);
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/Administration/UI/Bwoink/AdminWhoUIController.cs b/Content.Client/Administration/UI/Bwoink/AdminWhoUIController.cs
new file mode 100644
index 0000000000..23cc08298a
--- /dev/null
+++ b/Content.Client/Administration/UI/Bwoink/AdminWhoUIController.cs
@@ -0,0 +1,65 @@
+
+using Content.Client.Administration.Systems;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface.Controllers;
+
+namespace Content.Client.Administration.UI.Bwoink
+{
+ [UsedImplicitly]
+ public sealed class AdminWhoUIController : UIController, IOnSystemChanged
+ {
+ private AdminWhoWindow? _dialog;
+ private AdminWhoSystem? _adminWhoSystem;
+
+ protected override string SawmillName => "c.c.admin.adminwho";
+
+ public void OnSystemLoaded(AdminWhoSystem system)
+ {
+ _adminWhoSystem = system;
+
+ _dialog = new AdminWhoWindow();
+ _dialog.Initialize(system);
+ }
+
+ public void OnSystemUnloaded(AdminWhoSystem system)
+ {
+ if (_dialog != null)
+ {
+ _dialog.Close();
+ _dialog.Uninitialize();
+ _dialog.Dispose();
+ _dialog = null;
+ }
+
+ _adminWhoSystem = null;
+ }
+
+ public void Open()
+ {
+ if (_dialog == null)
+ {
+ if (_adminWhoSystem == null)
+ return;
+
+ _dialog = new AdminWhoWindow();
+ _dialog.Initialize(_adminWhoSystem);
+ }
+
+ _dialog.OpenCentered();
+ _dialog.RefreshAdminList();
+ }
+
+ public void Toggle()
+ {
+ if (_dialog?.IsOpen == true)
+ Close();
+ else
+ Open();
+ }
+
+ public void Close()
+ {
+ _dialog?.Close();
+ }
+ }
+}
diff --git a/Content.Client/Administration/UI/Bwoink/AdminWhoWindow.xaml b/Content.Client/Administration/UI/Bwoink/AdminWhoWindow.xaml
new file mode 100644
index 0000000000..811dbd9b97
--- /dev/null
+++ b/Content.Client/Administration/UI/Bwoink/AdminWhoWindow.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Bwoink/AdminWhoWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/AdminWhoWindow.xaml.cs
new file mode 100644
index 0000000000..e1f9717f0e
--- /dev/null
+++ b/Content.Client/Administration/UI/Bwoink/AdminWhoWindow.xaml.cs
@@ -0,0 +1,102 @@
+using System.Text;
+using Content.Client.Administration.Managers;
+using Content.Client.Administration.Systems;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Administration;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Administration.UI.Bwoink
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class AdminWhoWindow : FancyWindow
+ {
+ private AdminWhoSystem? _adminWhoSystem;
+
+ public AdminWhoWindow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ RefreshButton.OnPressed += _ => RefreshAdminList();
+ CloseButton.OnPressed += _ => Close();
+ }
+
+ public void Initialize(AdminWhoSystem system)
+ {
+ _adminWhoSystem = system;
+ _adminWhoSystem.OnAdminWhoUpdate += OnAdminListReceived;
+
+ // Request the list when the window is initialized
+ RefreshAdminList();
+ }
+
+ public void Uninitialize()
+ {
+ if (_adminWhoSystem != null)
+ {
+ _adminWhoSystem.OnAdminWhoUpdate -= OnAdminListReceived;
+ _adminWhoSystem = null;
+ }
+ }
+
+ public void RefreshAdminList()
+ {
+ AdminsContainer.RemoveAllChildren();
+
+ NoAdminsLabel.Visible = false;
+ LoadingLabel.Visible = true;
+
+ _adminWhoSystem?.RequestAdminWho();
+ }
+
+ private void OnAdminListReceived(List admins)
+ {
+ LoadingLabel.Visible = false;
+
+ if (admins.Count == 0)
+ {
+ NoAdminsLabel.Visible = true;
+ return;
+ }
+
+ NoAdminsLabel.Visible = false;
+
+ // Add each admin
+ foreach (var admin in admins)
+ {
+ var adminText = new StringBuilder();
+ adminText.Append(admin.Name);
+
+ if (!string.IsNullOrEmpty(admin.Title))
+ adminText.Append($": [{admin.Title}]");
+
+ if (admin.IsStealth)
+ adminText.Append(" (S)");
+
+ if (admin.IsAfk)
+ adminText.Append(" [AFK]");
+
+ var adminLabel = new Label
+ {
+ Text = adminText.ToString(),
+ StyleClasses = { "LabelText" },
+ HorizontalAlignment = Control.HAlignment.Left,
+ Margin = new Thickness(16, 2, 8, 2)
+ };
+ AdminsContainer.AddChild(adminLabel);
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Uninitialize();
+ }
+ base.Dispose(disposing);
+ }
+ }
+}
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml
index 2c27fdd2ce..4450bd4f16 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml
@@ -16,6 +16,8 @@
+
+
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
index 54c2edb8b7..6d8ae297f7 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkControl.xaml.cs
@@ -203,6 +203,14 @@ namespace Content.Client.Administration.UI.Bwoink
{
uiController.PopOut();
};
+
+ // Sunrise-Start
+ AdminWho.OnPressed += _ =>
+ {
+ var ctrl = _ui.GetUIController();
+ ctrl.Toggle();
+ };
+ // Sunrise-End
}
public void OnBwoink(NetUserId channel)
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml b/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml
index b1bd283ab8..ea36af016f 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml
@@ -8,4 +8,9 @@
+
+
+
+
+
diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs
index 0c76510fd3..248ae6da4e 100644
--- a/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs
+++ b/Content.Client/Administration/UI/Bwoink/BwoinkPanel.xaml.cs
@@ -1,5 +1,6 @@
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
@@ -10,6 +11,8 @@ namespace Content.Client.Administration.UI.Bwoink
[GenerateTypedNameReferences]
public sealed partial class BwoinkPanel : BoxContainer
{
+ [Dependency] private readonly IUserInterfaceManager _ui = default!;
+
private readonly Action _messageSender;
public int Unread { get; private set; } = 0;
@@ -25,6 +28,7 @@ namespace Content.Client.Administration.UI.Bwoink
public BwoinkPanel(Action messageSender)
{
RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this); // Sunrise-Edit
var msg = new FormattedMessage();
msg.PushColor(Color.LightGray);
@@ -41,6 +45,15 @@ namespace Content.Client.Administration.UI.Bwoink
};
SenderLineEdit.OnTextEntered += Input_OnTextEntered;
SenderLineEdit.OnTextChanged += Input_OnTextChanged;
+
+ // Sunrise-Start
+ AdminWhoButton.OnPressed += _ =>
+ {
+ var ctrl = _ui.GetUIController();
+ ctrl.Toggle();
+ };
+ // Sunrise-End
+
UpdateTypingIndicator();
}
diff --git a/Content.Client/UserInterface/Systems/Bwoink/AHelpUIController.cs b/Content.Client/UserInterface/Systems/Bwoink/AHelpUIController.cs
index 4aecedd160..d03ac3c1f6 100644
--- a/Content.Client/UserInterface/Systems/Bwoink/AHelpUIController.cs
+++ b/Content.Client/UserInterface/Systems/Bwoink/AHelpUIController.cs
@@ -637,7 +637,10 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
if (_window is { Disposed: false })
return;
_chatPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(_ownerId, text, true, false));
- _chatPanel.AHelpDescLabel.Visible = true; // Sunrise-Edit
+ // Sunrise-Start
+ _chatPanel.AHelpDescLabel.Visible = true;
+ _chatPanel.AdminWhoButton.Visible = true;
+ // Sunrise-End
_chatPanel.InputTextChanged += text => InputTextChanged?.Invoke(_ownerId, text);
_chatPanel.RelayedToDiscordLabel.Visible = relayActive;
_window = new DefaultWindow()
diff --git a/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml b/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml
index 5c5e0a7289..fe8d576b6b 100644
--- a/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml
+++ b/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml
@@ -5,13 +5,13 @@
-
+ StyleClasses="ButtonBig" Margin="0 0 10 0"/>
+
+ StyleClasses="ButtonBig" Visible="False" Margin="0 0 10 0" />
+ StyleClasses="ButtonBig" HorizontalAlignment="Right" Visible="False" />
diff --git a/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml.cs b/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml.cs
index 9e0f321f8c..ab58d20b99 100644
--- a/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml.cs
+++ b/Content.Client/_Sunrise/MentorHelp/MentorHelpControl.xaml.cs
@@ -1,9 +1,11 @@
using System.Linq;
using Content.Client.Administration.Systems;
+using Content.Client.Administration.UI.Bwoink;
using Content.Shared._Sunrise.MentorHelp;
using Content.Shared.Administration;
using Content.Shared.Database;
using Robust.Client.AutoGenerated;
+using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -65,6 +67,12 @@ namespace Content.Client._Sunrise.MentorHelp
// Handle tab changes to load closed tickets when needed
TicketsTabContainer.OnTabChanged += OnTabChanged;
+ AdminWhoButton.OnPressed += _ =>
+ {
+ var ctrl = _ui.GetUIController();
+ ctrl.Toggle();
+ };
+
// Set initial state
SwitchState(ViewState.TicketsList);
}
diff --git a/Content.Server/Administration/Systems/AdminWhoSystem.cs b/Content.Server/Administration/Systems/AdminWhoSystem.cs
new file mode 100644
index 0000000000..a2f32c5993
--- /dev/null
+++ b/Content.Server/Administration/Systems/AdminWhoSystem.cs
@@ -0,0 +1,64 @@
+using Content.Server.Administration.Managers;
+using Content.Server.Afk;
+using Content.Shared.Administration;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Administration.Systems;
+
+///
+/// Server system for handling admin who requests
+///
+public sealed class AdminWhoSystem : EntitySystem
+{
+ [Dependency] private readonly IAfkManager _afkManager = default!;
+ [Dependency] private readonly IAdminManager _adminManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnRequestAdminWho);
+ }
+
+ private void OnRequestAdminWho(RequestAdminWhoEvent args, EntitySessionEventArgs session)
+ {
+ if (session.SenderSession == null)
+ return;
+
+ var seeStealth = false;
+ var seeAfk = false;
+
+ // Check if the requesting player can see stealth admins and AFK status
+ if (session.SenderSession.AttachedEntity != null)
+ {
+ var playerData = _adminManager.GetAdminData(session.SenderSession);
+ if (playerData != null)
+ {
+ seeStealth = playerData.CanStealth();
+ seeAfk = _adminManager.HasAdminFlag(session.SenderSession, AdminFlags.Admin);
+ }
+ }
+
+ var adminList = new List();
+
+ foreach (var admin in _adminManager.ActiveAdmins)
+ {
+ var adminData = _adminManager.GetAdminData(admin);
+ DebugTools.AssertNotNull(adminData);
+
+ if (adminData!.Stealth && !seeStealth)
+ continue;
+
+ var isAfk = seeAfk && _afkManager.IsAfk(admin);
+
+ adminList.Add(new AdminWhoEntry(
+ admin.Name,
+ adminData.Title,
+ adminData.Stealth,
+ isAfk
+ ));
+ }
+
+ RaiseNetworkEvent(new AdminWhoResponseEvent(adminList), session.SenderSession);
+ }
+}
\ No newline at end of file
diff --git a/Content.Shared/Administration/AdminWhoSystem.cs b/Content.Shared/Administration/AdminWhoSystem.cs
new file mode 100644
index 0000000000..9f1775f399
--- /dev/null
+++ b/Content.Shared/Administration/AdminWhoSystem.cs
@@ -0,0 +1,45 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Administration;
+
+///
+/// Request event to get the list of online administrators
+///
+[Serializable, NetSerializable]
+public sealed class RequestAdminWhoEvent : EntityEventArgs
+{
+}
+
+///
+/// Response event containing the list of online administrators
+///
+[Serializable, NetSerializable]
+public sealed class AdminWhoResponseEvent : EntityEventArgs
+{
+ public readonly List Admins;
+
+ public AdminWhoResponseEvent(List admins)
+ {
+ Admins = admins;
+ }
+}
+
+///
+/// Information about a single administrator
+///
+[Serializable, NetSerializable]
+public sealed class AdminWhoEntry
+{
+ public readonly string Name;
+ public readonly string? Title;
+ public readonly bool IsStealth;
+ public readonly bool IsAfk;
+
+ public AdminWhoEntry(string name, string? title, bool isStealth, bool isAfk)
+ {
+ Name = name;
+ Title = title;
+ IsStealth = isStealth;
+ IsAfk = isAfk;
+ }
+}
\ No newline at end of file
diff --git a/Resources/Locale/en-US/_strings/_sunrise/admin-who/admin-who.ftl b/Resources/Locale/en-US/_strings/_sunrise/admin-who/admin-who.ftl
new file mode 100644
index 0000000000..7d34358e91
--- /dev/null
+++ b/Resources/Locale/en-US/_strings/_sunrise/admin-who/admin-who.ftl
@@ -0,0 +1,7 @@
+admin-who-button = Admins Online
+admin-who-title = Online Administrators
+admin-who-refresh = Refresh
+admin-who-close = Close
+admin-who-no-admins = No administrators are currently online.
+admin-who-loading = Loading administrators...
+admin-who-info = This feature displays which administrators are currently online and available to help with any issues you may have.
diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/admin-who/admin-who.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/admin-who/admin-who.ftl
new file mode 100644
index 0000000000..2986361f0b
--- /dev/null
+++ b/Resources/Locale/ru-RU/_strings/_sunrise/admin-who/admin-who.ftl
@@ -0,0 +1,7 @@
+admin-who-button = Админы онлайн
+admin-who-title = Администраторы онлайн
+admin-who-refresh = Обновить
+admin-who-close = Закрыть
+admin-who-no-admins = В данный момент нет администраторов онлайн.
+admin-who-loading = Загрузка администраторов...
+admin-who-info = Эта функция показывает, какие администраторы в данный момент онлайн и доступны для помощи с любыми вопросами.