Scope alarm tracking to selected templates and surface endpoint/security state on the dashboard so operators can deploy in large galaxies without drowning clients in irrelevant alarms or guessing what the server is advertising

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-13 09:48:57 -04:00
parent c5ed5312a9
commit 517d92c76f
25 changed files with 1511 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.Json;
@@ -112,6 +113,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
Historian = historianInfo,
Alarms = alarmInfo,
Redundancy = BuildRedundancyInfo(),
Endpoints = BuildEndpointsInfo(),
Footer = new FooterInfo
{
Timestamp = DateTime.UtcNow,
@@ -143,10 +145,37 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
ActiveAlarmCount = _nodeManager?.ActiveAlarmCount ?? 0,
TransitionCount = _nodeManager?.AlarmTransitionCount ?? 0,
AckEventCount = _nodeManager?.AlarmAckEventCount ?? 0,
AckWriteFailures = _nodeManager?.AlarmAckWriteFailures ?? 0
AckWriteFailures = _nodeManager?.AlarmAckWriteFailures ?? 0,
FilterEnabled = _nodeManager?.AlarmFilterEnabled ?? false,
FilterPatternCount = _nodeManager?.AlarmFilterPatternCount ?? 0,
FilterIncludedObjectCount = _nodeManager?.AlarmFilterIncludedObjectCount ?? 0
};
}
private EndpointsInfo BuildEndpointsInfo()
{
var info = new EndpointsInfo();
if (_serverHost == null)
return info;
info.BaseAddresses = _serverHost.BaseAddresses.ToList();
info.UserTokenPolicies = _serverHost.UserTokenPolicies.Distinct().ToList();
foreach (var policy in _serverHost.SecurityPolicies)
{
var uri = policy.SecurityPolicyUri ?? "";
var hashIdx = uri.LastIndexOf('#');
var name = hashIdx >= 0 && hashIdx < uri.Length - 1 ? uri.Substring(hashIdx + 1) : uri;
info.SecurityProfiles.Add(new SecurityProfileInfo
{
PolicyUri = uri,
PolicyName = name,
SecurityMode = policy.SecurityMode.ToString()
});
}
return info;
}
private RedundancyInfo? BuildRedundancyInfo()
{
if (_redundancyConfig == null || !_redundancyConfig.Enabled)
@@ -208,6 +237,37 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
sb.AppendLine($"<p>Status: <b>{data.Health.Status}</b> — {data.Health.Message}</p>");
sb.AppendLine("</div>");
// Endpoints panel (exposed URLs + security profiles)
var endpointsColor = data.Endpoints.BaseAddresses.Count > 0 ? "green" : "gray";
sb.AppendLine($"<div class='panel {endpointsColor}'><h2>Endpoints</h2>");
if (data.Endpoints.BaseAddresses.Count == 0)
{
sb.AppendLine("<p>No endpoints — OPC UA server not started.</p>");
}
else
{
sb.AppendLine("<p><b>Base Addresses:</b></p><ul>");
foreach (var addr in data.Endpoints.BaseAddresses)
sb.AppendLine($"<li>{WebUtility.HtmlEncode(addr)}</li>");
sb.AppendLine("</ul>");
sb.AppendLine("<p><b>Security Profiles:</b></p>");
sb.AppendLine("<table><tr><th>Mode</th><th>Policy</th><th>Policy URI</th></tr>");
foreach (var profile in data.Endpoints.SecurityProfiles)
{
sb.AppendLine(
$"<tr><td>{WebUtility.HtmlEncode(profile.SecurityMode)}</td>" +
$"<td>{WebUtility.HtmlEncode(profile.PolicyName)}</td>" +
$"<td>{WebUtility.HtmlEncode(profile.PolicyUri)}</td></tr>");
}
sb.AppendLine("</table>");
if (data.Endpoints.UserTokenPolicies.Count > 0)
sb.AppendLine(
$"<p><b>User Token Policies:</b> {WebUtility.HtmlEncode(string.Join(", ", data.Endpoints.UserTokenPolicies))}</p>");
}
sb.AppendLine("</div>");
// Redundancy panel (only when enabled)
if (data.Redundancy != null)
{
@@ -258,6 +318,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.Status
$"<p>Tracking: <b>{data.Alarms.TrackingEnabled}</b> | Conditions: {data.Alarms.ConditionCount} | Active: <b>{data.Alarms.ActiveAlarmCount}</b></p>");
sb.AppendLine(
$"<p>Transitions: {data.Alarms.TransitionCount:N0} | Ack Events: {data.Alarms.AckEventCount:N0} | Ack Write Failures: {data.Alarms.AckWriteFailures}</p>");
if (data.Alarms.FilterEnabled)
sb.AppendLine(
$"<p>Filter: <b>{data.Alarms.FilterPatternCount}</b> pattern(s), <b>{data.Alarms.FilterIncludedObjectCount}</b> object(s) included</p>");
sb.AppendLine("</div>");
// Operations table