From 001d61c6d5999e6d66ad4824ef547acdc6be02ce Mon Sep 17 00:00:00 2001 From: Palladinium Date: Thu, 6 Feb 2025 16:10:21 +1100 Subject: [PATCH] Add air alarm admin logs (#33426) --- .../Atmos/Monitor/Systems/AirAlarmSystem.cs | 20 +++++ .../Monitor/Systems/AtmosMonitoringSystem.cs | 56 ++++++++++++++ .../Unary/EntitySystems/GasVentPumpSystem.cs | 41 ++++++++++ .../EntitySystems/GasVentScrubberSystem.cs | 40 ++++++++++ Content.Shared.Database/LogType.cs | 5 ++ .../Atmos/Monitor/AtmosAlarmThreshold.cs | 75 ++++++++++++++++++- 6 files changed, 236 insertions(+), 1 deletion(-) diff --git a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs index 63dbb19a57..0ed91a9bc8 100644 --- a/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AirAlarmSystem.cs @@ -9,10 +9,12 @@ using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Shared.Access.Components; using Content.Shared.Access.Systems; +using Content.Shared.Administration.Logs; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Monitor.Components; using Content.Shared.Atmos.Piping.Unary.Components; +using Content.Shared.Database; using Content.Shared.DeviceLinking; using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork.Systems; @@ -37,6 +39,7 @@ namespace Content.Server.Atmos.Monitor.Systems; public sealed class AirAlarmSystem : EntitySystem { [Dependency] private readonly AccessReaderSystem _access = default!; + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly AtmosAlarmableSystem _atmosAlarmable = default!; [Dependency] private readonly AtmosDeviceNetworkSystem _atmosDevNet = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNet = default!; @@ -296,6 +299,7 @@ public sealed class AirAlarmSystem : EntitySystem addr = netConn.Address; } + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} mode to {args.Mode}"); SetMode(uid, addr, args.Mode, false); } else @@ -307,15 +311,26 @@ public sealed class AirAlarmSystem : EntitySystem private void OnUpdateAutoMode(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAutoModeMessage args) { component.AutoMode = args.Enabled; + + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {ToPrettyString(uid)} auto mode to {args.Enabled}"); UpdateUI(uid, component); } private void OnUpdateThreshold(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateAlarmThresholdMessage args) { if (AccessCheck(uid, args.Actor, component)) + { + if (args.Gas != null) + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Gas} {args.Type} threshold using {ToPrettyString(uid)}"); + else + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} {args.Type} threshold using {ToPrettyString(uid)}"); + SetThreshold(uid, args.Address, args.Type, args.Threshold, args.Gas); + } else + { UpdateUI(uid, component); + } } private void OnUpdateDeviceData(EntityUid uid, AirAlarmComponent component, AirAlarmUpdateDeviceDataMessage args) @@ -323,6 +338,8 @@ public sealed class AirAlarmSystem : EntitySystem if (AccessCheck(uid, args.Actor, component) && _deviceList.ExistsInDeviceList(uid, args.Address)) { + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} changed {args.Address} settings using {ToPrettyString(uid)}"); + SetDeviceData(uid, args.Address, args.Data); } else @@ -344,6 +361,7 @@ public sealed class AirAlarmSystem : EntitySystem case GasVentPumpData ventData: foreach (string addr in component.VentData.Keys) { + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to vent {addr}"); SetData(uid, addr, args.Data); } break; @@ -351,6 +369,7 @@ public sealed class AirAlarmSystem : EntitySystem case GasVentScrubberData scrubberData: foreach (string addr in component.ScrubberData.Keys) { + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(args.Actor)} copied settings to scrubber {addr}"); SetData(uid, addr, args.Data); } break; @@ -379,6 +398,7 @@ public sealed class AirAlarmSystem : EntitySystem if (!_access.IsAllowed(user.Value, uid, reader)) { _popup.PopupEntity(Loc.GetString("air-alarm-ui-access-denied"), user.Value, user.Value); + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Low, $"{ToPrettyString(user)} attempted to access {ToPrettyString(uid)} without access"); return false; } diff --git a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs index 3805c012b7..99cf0109bb 100644 --- a/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs +++ b/Content.Server/Atmos/Monitor/Systems/AtmosMonitoringSystem.cs @@ -9,9 +9,11 @@ using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Shared.Administration.Logs; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Piping.Components; +using Content.Shared.Database; using Content.Shared.DeviceNetwork; using Content.Shared.Power; using Content.Shared.Tag; @@ -25,6 +27,7 @@ namespace Content.Server.Atmos.Monitor.Systems; // a danger), and atmos (which triggers based on set thresholds). public sealed class AtmosMonitorSystem : EntitySystem { + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly AtmosDeviceSystem _atmosDeviceSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!; @@ -393,21 +396,74 @@ public sealed class AtmosMonitorSystem : EntitySystem if (!Resolve(uid, ref monitor)) return; + // Used for logging after the switch statement + string logPrefix = ""; + string logValueSuffix = ""; + AtmosAlarmThreshold? logPreviousThreshold = null; + switch (type) { case AtmosMonitorThresholdType.Pressure: + logPrefix = "pressure"; + logValueSuffix = "kPa"; + logPreviousThreshold = monitor.PressureThreshold; + monitor.PressureThreshold = threshold; break; case AtmosMonitorThresholdType.Temperature: + logPrefix = "temperature"; + logValueSuffix = "K"; + logPreviousThreshold = monitor.TemperatureThreshold; + monitor.TemperatureThreshold = threshold; break; case AtmosMonitorThresholdType.Gas: if (gas == null || monitor.GasThresholds == null) return; + + logPrefix = ((Gas) gas).ToString(); + logValueSuffix = "kPa"; + monitor.GasThresholds.TryGetValue((Gas) gas, out logPreviousThreshold); + monitor.GasThresholds[(Gas) gas] = threshold; break; } + // Admin log each change separately rather than logging the whole state + if (logPreviousThreshold != null) + { + if (threshold.Ignore != logPreviousThreshold.Ignore) + { + string enabled = threshold.Ignore ? "disabled" : "enabled"; + _adminLogger.Add( + LogType.AtmosDeviceSetting, + LogImpact.Medium, + $"{ToPrettyString(uid)} {logPrefix} thresholds {enabled}" + ); + } + + foreach (var change in threshold.GetChanges(logPreviousThreshold)) + { + if (change.Current.Enabled != change.Previous?.Enabled) + { + string enabled = change.Current.Enabled ? "enabled" : "disabled"; + _adminLogger.Add( + LogType.AtmosDeviceSetting, + LogImpact.Medium, + $"{ToPrettyString(uid)} {logPrefix} {change.Type} {enabled}" + ); + } + + if (change.Current.Value != change.Previous?.Value) + { + _adminLogger.Add( + LogType.AtmosDeviceSetting, + LogImpact.Medium, + $"{ToPrettyString(uid)} {logPrefix} {change.Type} changed from {change.Previous?.Value} {logValueSuffix} to {change.Current.Value} {logValueSuffix}" + ); + } + } + } } /// diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs index 23016debeb..c58d6eb14b 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentPumpSystem.cs @@ -9,6 +9,7 @@ using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; +using Content.Shared.Administration.Logs; using Content.Shared.Atmos; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Piping.Components; @@ -16,6 +17,7 @@ using Content.Shared.Atmos.Piping.Unary; using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Atmos.Visuals; using Content.Shared.Audio; +using Content.Shared.Database; using Content.Shared.DeviceNetwork; using Content.Shared.DoAfter; using Content.Shared.Examine; @@ -30,6 +32,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems [UsedImplicitly] public sealed class GasVentPumpSystem : EntitySystem { + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!; [Dependency] private readonly DeviceLinkSystem _signalSystem = default!; @@ -232,6 +235,44 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentPumpData? setData)) break; + var previous = component.ToAirAlarmData(); + + if (previous.Enabled != setData.Enabled) + { + string enabled = setData.Enabled ? "enabled" : "disabled" ; + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}"); + } + + if (previous.PumpDirection != setData.PumpDirection) + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}"); + + if (previous.PressureChecks != setData.PressureChecks) + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure check changed to {setData.PressureChecks}"); + + if (previous.ExternalPressureBound != setData.ExternalPressureBound) + { + _adminLogger.Add( + LogType.AtmosDeviceSetting, + LogImpact.Medium, + $"{ToPrettyString(uid)} external pressure bound changed from {previous.ExternalPressureBound} kPa to {setData.ExternalPressureBound} kPa" + ); + } + + if (previous.InternalPressureBound != setData.InternalPressureBound) + { + _adminLogger.Add( + LogType.AtmosDeviceSetting, + LogImpact.Medium, + $"{ToPrettyString(uid)} internal pressure bound changed from {previous.InternalPressureBound} kPa to {setData.InternalPressureBound} kPa" + ); + } + + if (previous.PressureLockoutOverride != setData.PressureLockoutOverride) + { + string enabled = setData.PressureLockoutOverride ? "enabled" : "disabled" ; + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} pressure lockout override {enabled}"); + } + component.FromAirAlarmData(setData); UpdateState(uid, component); diff --git a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs index a633d29e41..0207535398 100644 --- a/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs +++ b/Content.Server/Atmos/Piping/Unary/EntitySystems/GasVentScrubberSystem.cs @@ -10,12 +10,14 @@ using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; +using Content.Shared.Administration.Logs; using Content.Shared.Atmos; using Content.Shared.Atmos.Piping.Unary.Visuals; using Content.Shared.Atmos.Monitor; using Content.Shared.Atmos.Piping.Components; using Content.Shared.Atmos.Piping.Unary.Components; using Content.Shared.Audio; +using Content.Shared.Database; using Content.Shared.DeviceNetwork; using Content.Shared.Power; using Content.Shared.Tools.Systems; @@ -27,6 +29,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems [UsedImplicitly] public sealed class GasVentScrubberSystem : EntitySystem { + [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!; [Dependency] private readonly DeviceNetworkSystem _deviceNetSystem = default!; [Dependency] private readonly NodeContainerSystem _nodeContainer = default!; @@ -163,6 +166,43 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems if (!args.Data.TryGetValue(DeviceNetworkConstants.CmdSetState, out GasVentScrubberData? setData)) break; + var previous = component.ToAirAlarmData(); + + if (previous.Enabled != setData.Enabled) + { + string enabled = setData.Enabled ? "enabled" : "disabled" ; + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {enabled}"); + } + + // TODO: IgnoreAlarms? + + if (previous.PumpDirection != setData.PumpDirection) + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} direction changed to {setData.PumpDirection}"); + + // TODO: This is iterating through both sets, it could probably be faster but they're both really small sets anyways + foreach (Gas gas in previous.FilterGases) + if (!setData.FilterGases.Contains(gas)) + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering disabled"); + + foreach (Gas gas in setData.FilterGases) + if (!previous.FilterGases.Contains(gas)) + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} {gas} filtering enabled"); + + if (previous.VolumeRate != setData.VolumeRate) + { + _adminLogger.Add( + LogType.AtmosDeviceSetting, + LogImpact.Medium, + $"{ToPrettyString(uid)} volume rate changed from {previous.VolumeRate} L to {setData.VolumeRate} L" + ); + } + + if (previous.WideNet != setData.WideNet) + { + string enabled = setData.WideNet ? "enabled" : "disabled" ; + _adminLogger.Add(LogType.AtmosDeviceSetting, LogImpact.Medium, $"{ToPrettyString(uid)} WideNet {enabled}"); + } + component.FromAirAlarmData(setData); UpdateState(uid, component); diff --git a/Content.Shared.Database/LogType.cs b/Content.Shared.Database/LogType.cs index 1bbaa96223..c2896b33be 100644 --- a/Content.Shared.Database/LogType.cs +++ b/Content.Shared.Database/LogType.cs @@ -444,4 +444,9 @@ public enum LogType /// A player interacted with a PDA or its cartridge component /// PdaInteract = 96, + + /// + /// An atmos networked device (such as a vent or pump) has had its settings changed, usually through an air alarm + /// + AtmosDeviceSetting = 97, } diff --git a/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs b/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs index c37c70a3ab..f80a887b4f 100644 --- a/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs +++ b/Content.Shared/Atmos/Monitor/AtmosAlarmThreshold.cs @@ -253,10 +253,57 @@ public sealed partial class AtmosAlarmThreshold break; } } + + /// + /// Iterates through the changes that these threshold settings would make from a + /// previous instance. Basically, diffs the two settings. + /// + public IEnumerable GetChanges(AtmosAlarmThreshold previous) + { + if (LowerBound != previous.LowerBound) + yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.LowerDanger, previous.LowerBound, LowerBound); + + if (LowerWarningBound != previous.LowerWarningBound) + yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.LowerWarning, previous.LowerWarningBound, LowerWarningBound); + + if (UpperBound != previous.UpperBound) + yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.UpperDanger, previous.UpperBound, UpperBound); + + if (UpperWarningBound != previous.UpperWarningBound) + yield return new AtmosAlarmThresholdChange(AtmosMonitorLimitType.UpperWarning, previous.UpperWarningBound, UpperWarningBound); + } +} + +/// +/// A change of a single value between two AtmosAlarmThreshold, for a given AtmosMonitorLimitType +/// +public readonly struct AtmosAlarmThresholdChange +{ + /// + /// The type of change between the two threshold sets + /// + public readonly AtmosMonitorLimitType Type; + + /// + /// The value in the old threshold set + /// + public readonly AlarmThresholdSetting? Previous; + + /// + /// The value in the new threshold set + /// + public readonly AlarmThresholdSetting Current; + + public AtmosAlarmThresholdChange(AtmosMonitorLimitType type, AlarmThresholdSetting? previous, AlarmThresholdSetting current) + { + Type = type; + Previous = previous; + Current = current; + } } [DataDefinition, Serializable] -public readonly partial struct AlarmThresholdSetting +public readonly partial struct AlarmThresholdSetting: IEquatable { [DataField("enabled")] public bool Enabled { get; init; } = true; @@ -289,6 +336,32 @@ public readonly partial struct AlarmThresholdSetting { return this with {Enabled = enabled}; } + + public bool Equals(AlarmThresholdSetting other) + { + if (Enabled != other.Enabled) + return false; + + if (Value != other.Value) + return false; + + return true; + } + + public static bool operator ==(AlarmThresholdSetting lhs, AlarmThresholdSetting rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(AlarmThresholdSetting lhs, AlarmThresholdSetting rhs) + { + return !lhs.Equals(rhs); + } + + public override int GetHashCode() + { + return HashCode.Combine(Enabled, Value); + } } public enum AtmosMonitorThresholdBound -- 2.51.2