Add redundancy panel to status dashboard

Shows mode, role, ServiceLevel, ApplicationUri, and redundant server
set when redundancy is enabled. Primary renders with a green border,
secondary with yellow. Also included in the JSON API response.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-28 15:27:52 -04:00
parent afd6c33d9d
commit f0a076ec26
3 changed files with 86 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Text;
using System.Text.Json;
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
using ZB.MOM.WW.LmxOpcUa.Host.Domain;
using ZB.MOM.WW.LmxOpcUa.Host.GalaxyRepository;
using ZB.MOM.WW.LmxOpcUa.Host.Metrics;
@@ -21,6 +22,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
private GalaxyRepositoryStats? _galaxyStats;
private OpcUaServerHost? _serverHost;
private LmxNodeManager? _nodeManager;
private RedundancyConfiguration? _redundancyConfig;
private string? _applicationUri;
/// <summary>
/// Initializes a new status report service for the dashboard using the supplied health-check policy and refresh interval.
@@ -43,13 +46,16 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
/// <param name="nodeManager">The node manager whose queue depth and MXAccess event throughput should be surfaced on the dashboard.</param>
public void SetComponents(IMxAccessClient? mxAccessClient, PerformanceMetrics? metrics,
GalaxyRepositoryStats? galaxyStats, OpcUaServerHost? serverHost,
LmxNodeManager? nodeManager = null)
LmxNodeManager? nodeManager = null,
RedundancyConfiguration? redundancyConfig = null, string? applicationUri = null)
{
_mxAccessClient = mxAccessClient;
_metrics = metrics;
_galaxyStats = galaxyStats;
_serverHost = serverHost;
_nodeManager = nodeManager;
_redundancyConfig = redundancyConfig;
_applicationUri = applicationUri;
}
/// <summary>
@@ -90,6 +96,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
TotalEvents = _nodeManager?.TotalMxChangeEvents ?? 0
},
Operations = _metrics?.GetStatistics() ?? new(),
Redundancy = BuildRedundancyInfo(),
Footer = new FooterInfo
{
Timestamp = DateTime.UtcNow,
@@ -98,6 +105,30 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
};
}
private RedundancyInfo? BuildRedundancyInfo()
{
if (_redundancyConfig == null || !_redundancyConfig.Enabled)
return null;
var mxConnected = (_mxAccessClient?.State ?? ConnectionState.Disconnected) == ConnectionState.Connected;
var dbConnected = _galaxyStats?.DbConnected ?? false;
var isPrimary = string.Equals(_redundancyConfig.Role, "Primary", StringComparison.OrdinalIgnoreCase);
var baseLevel = isPrimary
? _redundancyConfig.ServiceLevelBase
: Math.Max(0, _redundancyConfig.ServiceLevelBase - 50);
var calculator = new ServiceLevelCalculator();
return new RedundancyInfo
{
Enabled = true,
Mode = _redundancyConfig.Mode,
Role = _redundancyConfig.Role,
ServiceLevel = calculator.Calculate(baseLevel, mxConnected, dbConnected),
ApplicationUri = _applicationUri ?? "",
ServerUris = new System.Collections.Generic.List<string>(_redundancyConfig.ServerUris)
};
}
/// <summary>
/// Generates the operator-facing HTML dashboard for the current bridge status.
/// </summary>
@@ -131,6 +162,17 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
sb.AppendLine($"<p>Status: <b>{data.Health.Status}</b> — {data.Health.Message}</p>");
sb.AppendLine("</div>");
// Redundancy panel (only when enabled)
if (data.Redundancy != null)
{
var roleColor = data.Redundancy.Role == "Primary" ? "green" : "yellow";
sb.AppendLine($"<div class='panel {roleColor}'><h2>Redundancy</h2>");
sb.AppendLine($"<p>Mode: <b>{data.Redundancy.Mode}</b> | Role: <b>{data.Redundancy.Role}</b> | Service Level: <b>{data.Redundancy.ServiceLevel}</b></p>");
sb.AppendLine($"<p>Application URI: {data.Redundancy.ApplicationUri}</p>");
sb.AppendLine($"<p>Redundant Set: {string.Join(", ", data.Redundancy.ServerUris)}</p>");
sb.AppendLine("</div>");
}
// Subscriptions panel
sb.AppendLine("<div class='panel gray'><h2>Subscriptions</h2>");
sb.AppendLine($"<p>Active: {data.Subscriptions.ActiveCount}</p>");