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:
@@ -215,7 +215,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host
|
||||
// Step 13: Dashboard
|
||||
_healthCheck = new HealthCheckService();
|
||||
_statusReport = new StatusReportService(_healthCheck, _config.Dashboard.RefreshIntervalSeconds);
|
||||
_statusReport.SetComponents(effectiveMxClient, _metrics, _galaxyStats, _serverHost, _nodeManager);
|
||||
_statusReport.SetComponents(effectiveMxClient, _metrics, _galaxyStats, _serverHost, _nodeManager,
|
||||
_config.Redundancy, _config.OpcUa.ApplicationUri);
|
||||
|
||||
if (_config.Dashboard.Enabled)
|
||||
{
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
/// </summary>
|
||||
public Dictionary<string, MetricsStatistics> Operations { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the redundancy state when redundancy is enabled.
|
||||
/// </summary>
|
||||
public RedundancyInfo? Redundancy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets footer details such as the snapshot timestamp and service version.
|
||||
/// </summary>
|
||||
@@ -160,6 +165,42 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
|
||||
public long TotalEvents { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dashboard model for redundancy state. Only populated when redundancy is enabled.
|
||||
/// </summary>
|
||||
public class RedundancyInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets whether redundancy is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the redundancy mode (e.g., "Warm", "Hot").
|
||||
/// </summary>
|
||||
public string Mode { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets this instance's role ("Primary" or "Secondary").
|
||||
/// </summary>
|
||||
public string Role { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current ServiceLevel byte.
|
||||
/// </summary>
|
||||
public byte ServiceLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets this instance's ApplicationUri.
|
||||
/// </summary>
|
||||
public string ApplicationUri { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of all server URIs in the redundant set.
|
||||
/// </summary>
|
||||
public List<string> ServerUris { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dashboard model for the status page footer.
|
||||
/// </summary>
|
||||
|
||||
@@ -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>");
|
||||
|
||||
Reference in New Issue
Block a user