diff --git a/gateway.md b/gateway.md index 0665864..087b77f 100644 --- a/gateway.md +++ b/gateway.md @@ -188,8 +188,12 @@ used as before. `OnAlarmTransitionEvent` and `ActiveAlarmSnapshot` proto fields. The `AlarmFeedMessage` feed emits an `AlarmProviderStatus` message (the `provider_status` oneof case) on stream open and on every switch. The -dashboard shows a Bootstrap badge (green for alarm manager, amber when -degraded). Metrics: `mxgateway.alarms.provider_mode` gauge (1 = alarmmgr, +dashboard shows a Bootstrap badge: green ("Alarm Manager") when healthy, amber +("Subtag monitoring (degraded)") on an unexpected failover, and cyan ("Subtag +monitoring (forced)") when subtag mode is the configured `Fallback:Mode=ForceSubtag` +— the latter distinguished by the well-known `AlarmProviderStatus.reason` +(`AlarmProviderReasons.ForcedSubtag`) so an intentional configuration is not shown +as a fault. Metrics: `mxgateway.alarms.provider_mode` gauge (1 = alarmmgr, 2 = subtag) and `mxgateway.alarms.provider_switches` counter. Forced modes are available via `MxGateway:Alarms:Fallback:Mode`: diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs new file mode 100644 index 0000000..045f3aa --- /dev/null +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/AlarmProviderReasons.cs @@ -0,0 +1,18 @@ +namespace ZB.MOM.WW.MxGateway.Server.Alarms; + +/// +/// Well-known reason strings carried on the alarm feed's +/// AlarmProviderStatus message. Shared between the producer +/// () and consumers (e.g. the dashboard +/// provider badge) so the two cannot drift on a magic string. +/// +public static class AlarmProviderReasons +{ + /// + /// Reason set when the monitor starts in subtag mode because + /// MxGateway:Alarms:Fallback:Mode is ForceSubtag — a + /// deliberate configuration, not a runtime failover. Lets the dashboard + /// distinguish a forced subtag mode from an unexpected degraded failover. + /// + public const string ForcedSubtag = "Forced subtag mode (configuration)"; +} diff --git a/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs b/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs index d4e6253..ef07c9d 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Alarms/GatewayAlarmMonitor.cs @@ -170,7 +170,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic case AlarmProviderMode.Subtag: initialMode = AlarmProviderMode.Subtag; initialDegraded = true; - initialReason = "Forced subtag mode (configuration)"; + initialReason = AlarmProviderReasons.ForcedSubtag; break; case AlarmProviderMode.Alarmmgr: initialMode = AlarmProviderMode.Alarmmgr; diff --git a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs index 37251b0..de035b1 100644 --- a/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs +++ b/src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAlarmProviderStatus.cs @@ -1,4 +1,5 @@ using ZB.MOM.WW.MxGateway.Contracts.Proto; +using ZB.MOM.WW.MxGateway.Server.Alarms; namespace ZB.MOM.WW.MxGateway.Server.Dashboard; @@ -22,9 +23,20 @@ public sealed record DashboardAlarmProviderStatus( /// Badge label shown when the feed has fallen back to subtag monitoring. public const string DegradedLabel = "Subtag monitoring (degraded)"; + /// + /// Badge label shown when the feed is in subtag monitoring because it was + /// deliberately configured (Fallback:Mode=ForceSubtag), as opposed + /// to an unexpected failover. A stable, intended state rather than a fault. + /// + public const string ForcedSubtagLabel = "Subtag monitoring (forced)"; + private const string HealthyBadge = "bg-success"; private const string DegradedBadge = "bg-warning text-dark"; + // Cyan/info badge: visually distinct from the amber failover-degraded badge — + // forced subtag is an intentional configuration, not an alarm-manager fault. + private const string ForcedSubtagBadge = "bg-info text-dark"; + /// /// The default status assumed before the first provider-status message /// arrives: healthy alarm-manager mode. @@ -48,13 +60,24 @@ public sealed record DashboardAlarmProviderStatus( // the contract sets degraded=true whenever mode == SUBTAG, but guard // against either being set independently. bool degraded = status.Degraded || status.Mode == AlarmProviderMode.Subtag; + string reason = status.Reason ?? string.Empty; + + // A configured ForceSubtag start carries the well-known forced reason and + // is a deliberate mode, not a failover — render it distinctly so an + // operator isn't alarmed by a "(degraded)" badge for an intended config. + bool forced = degraded + && status.Mode == AlarmProviderMode.Subtag + && string.Equals(reason, AlarmProviderReasons.ForcedSubtag, StringComparison.Ordinal); + + string label = !degraded ? AlarmManagerLabel : forced ? ForcedSubtagLabel : DegradedLabel; + string badge = !degraded ? HealthyBadge : forced ? ForcedSubtagBadge : DegradedBadge; return new DashboardAlarmProviderStatus( Mode: status.Mode, IsDegraded: degraded, - Label: degraded ? DegradedLabel : AlarmManagerLabel, - BadgeCssClass: degraded ? DegradedBadge : HealthyBadge, - Reason: status.Reason ?? string.Empty, + Label: label, + BadgeCssClass: badge, + Reason: reason, SinceUtc: status.Since?.ToDateTimeOffset()); } diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs index 46a7d29..1709cc8 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardBrowseAndAlarmModelTests.cs @@ -172,6 +172,28 @@ public sealed class DashboardBrowseAndAlarmModelTests Assert.Equal("x", model.Reason); } + /// + /// Verifies that a configured forced-subtag provider status renders the + /// distinct "forced" badge (cyan/info), not the amber failover-degraded one. + /// + [Fact] + public void FromProviderStatus_Subtag_ForcedReason_ForcedBadge() + { + AlarmProviderStatus status = new() + { + Mode = AlarmProviderMode.Subtag, + Degraded = true, + Reason = ZB.MOM.WW.MxGateway.Server.Alarms.AlarmProviderReasons.ForcedSubtag, + }; + + DashboardAlarmProviderStatus model = DashboardAlarmProviderStatus.FromProviderStatus(status); + + Assert.True(model.IsDegraded); + Assert.Equal(DashboardAlarmProviderStatus.ForcedSubtagLabel, model.Label); + Assert.Contains("bg-info", model.BadgeCssClass, StringComparison.Ordinal); + Assert.DoesNotContain("bg-warning", model.BadgeCssClass, StringComparison.Ordinal); + } + /// Verifies that the formatter renders array elements and element type correctly. [Fact] public void FormatValue_AndDataType_RenderArrayElementsAndElementType()