feat(dashboard): distinct 'forced' subtag provider badge
Render Fallback:Mode=ForceSubtag as a cyan 'Subtag monitoring (forced)' badge, distinct from the amber failover 'degraded' badge, so an intentional configuration isn't shown as a fault. Distinguished by the shared AlarmProviderReasons.ForcedSubtag reason carried on the provider-status feed.
This commit is contained in:
+6
-2
@@ -188,8 +188,12 @@ used as before.
|
|||||||
`OnAlarmTransitionEvent` and `ActiveAlarmSnapshot` proto fields. The
|
`OnAlarmTransitionEvent` and `ActiveAlarmSnapshot` proto fields. The
|
||||||
`AlarmFeedMessage` feed emits an `AlarmProviderStatus` message (the
|
`AlarmFeedMessage` feed emits an `AlarmProviderStatus` message (the
|
||||||
`provider_status` oneof case) on stream open and on every switch. The
|
`provider_status` oneof case) on stream open and on every switch. The
|
||||||
dashboard shows a Bootstrap badge (green for alarm manager, amber when
|
dashboard shows a Bootstrap badge: green ("Alarm Manager") when healthy, amber
|
||||||
degraded). Metrics: `mxgateway.alarms.provider_mode` gauge (1 = alarmmgr,
|
("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.
|
2 = subtag) and `mxgateway.alarms.provider_switches` counter.
|
||||||
|
|
||||||
Forced modes are available via `MxGateway:Alarms:Fallback:Mode`:
|
Forced modes are available via `MxGateway:Alarms:Fallback:Mode`:
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace ZB.MOM.WW.MxGateway.Server.Alarms;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Well-known <c>reason</c> strings carried on the alarm feed's
|
||||||
|
/// <c>AlarmProviderStatus</c> message. Shared between the producer
|
||||||
|
/// (<see cref="GatewayAlarmMonitor" />) and consumers (e.g. the dashboard
|
||||||
|
/// provider badge) so the two cannot drift on a magic string.
|
||||||
|
/// </summary>
|
||||||
|
public static class AlarmProviderReasons
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reason set when the monitor starts in subtag mode because
|
||||||
|
/// <c>MxGateway:Alarms:Fallback:Mode</c> is <c>ForceSubtag</c> — a
|
||||||
|
/// deliberate configuration, not a runtime failover. Lets the dashboard
|
||||||
|
/// distinguish a forced subtag mode from an unexpected degraded failover.
|
||||||
|
/// </summary>
|
||||||
|
public const string ForcedSubtag = "Forced subtag mode (configuration)";
|
||||||
|
}
|
||||||
@@ -170,7 +170,7 @@ public sealed class GatewayAlarmMonitor : BackgroundService, IGatewayAlarmServic
|
|||||||
case AlarmProviderMode.Subtag:
|
case AlarmProviderMode.Subtag:
|
||||||
initialMode = AlarmProviderMode.Subtag;
|
initialMode = AlarmProviderMode.Subtag;
|
||||||
initialDegraded = true;
|
initialDegraded = true;
|
||||||
initialReason = "Forced subtag mode (configuration)";
|
initialReason = AlarmProviderReasons.ForcedSubtag;
|
||||||
break;
|
break;
|
||||||
case AlarmProviderMode.Alarmmgr:
|
case AlarmProviderMode.Alarmmgr:
|
||||||
initialMode = AlarmProviderMode.Alarmmgr;
|
initialMode = AlarmProviderMode.Alarmmgr;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
using ZB.MOM.WW.MxGateway.Contracts.Proto;
|
||||||
|
using ZB.MOM.WW.MxGateway.Server.Alarms;
|
||||||
|
|
||||||
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
|
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
|
||||||
|
|
||||||
@@ -22,9 +23,20 @@ public sealed record DashboardAlarmProviderStatus(
|
|||||||
/// <summary>Badge label shown when the feed has fallen back to subtag monitoring.</summary>
|
/// <summary>Badge label shown when the feed has fallen back to subtag monitoring.</summary>
|
||||||
public const string DegradedLabel = "Subtag monitoring (degraded)";
|
public const string DegradedLabel = "Subtag monitoring (degraded)";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Badge label shown when the feed is in subtag monitoring because it was
|
||||||
|
/// deliberately configured (<c>Fallback:Mode=ForceSubtag</c>), as opposed
|
||||||
|
/// to an unexpected failover. A stable, intended state rather than a fault.
|
||||||
|
/// </summary>
|
||||||
|
public const string ForcedSubtagLabel = "Subtag monitoring (forced)";
|
||||||
|
|
||||||
private const string HealthyBadge = "bg-success";
|
private const string HealthyBadge = "bg-success";
|
||||||
private const string DegradedBadge = "bg-warning text-dark";
|
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";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default status assumed before the first provider-status message
|
/// The default status assumed before the first provider-status message
|
||||||
/// arrives: healthy alarm-manager mode.
|
/// arrives: healthy alarm-manager mode.
|
||||||
@@ -48,13 +60,24 @@ public sealed record DashboardAlarmProviderStatus(
|
|||||||
// the contract sets degraded=true whenever mode == SUBTAG, but guard
|
// the contract sets degraded=true whenever mode == SUBTAG, but guard
|
||||||
// against either being set independently.
|
// against either being set independently.
|
||||||
bool degraded = status.Degraded || status.Mode == AlarmProviderMode.Subtag;
|
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(
|
return new DashboardAlarmProviderStatus(
|
||||||
Mode: status.Mode,
|
Mode: status.Mode,
|
||||||
IsDegraded: degraded,
|
IsDegraded: degraded,
|
||||||
Label: degraded ? DegradedLabel : AlarmManagerLabel,
|
Label: label,
|
||||||
BadgeCssClass: degraded ? DegradedBadge : HealthyBadge,
|
BadgeCssClass: badge,
|
||||||
Reason: status.Reason ?? string.Empty,
|
Reason: reason,
|
||||||
SinceUtc: status.Since?.ToDateTimeOffset());
|
SinceUtc: status.Since?.ToDateTimeOffset());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,6 +172,28 @@ public sealed class DashboardBrowseAndAlarmModelTests
|
|||||||
Assert.Equal("x", model.Reason);
|
Assert.Equal("x", model.Reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifies that a configured forced-subtag provider status renders the
|
||||||
|
/// distinct "forced" badge (cyan/info), not the amber failover-degraded one.
|
||||||
|
/// </summary>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Verifies that the formatter renders array elements and element type correctly.</summary>
|
/// <summary>Verifies that the formatter renders array elements and element type correctly.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public void FormatValue_AndDataType_RenderArrayElementsAndElementType()
|
public void FormatValue_AndDataType_RenderArrayElementsAndElementType()
|
||||||
|
|||||||
Reference in New Issue
Block a user