From 41f133a337b4b650a5cfe8f2a3f81605524b04c8 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 18 May 2026 05:49:05 -0400 Subject: [PATCH] feat(admin-ui): add /virtual-tags, /scripted-alarms, and /script-log pages (tasks #25, #26, #27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gap 2 (#25): VirtualTagsTab.razor + /virtual-tags global page — list/create/toggle virtual tags per draft generation with DataType, Script, trigger, Historize, Enabled fields. Tab wired into DraftEditor. Gap 3 (#26): ScriptedAlarmsTab.razor + /scripted-alarms global page — list/create scripted alarms with AlarmType, Severity, MessageTemplate, PredicateScript, HistorizeToAveva, Retain. SeverityBand helper shows Low/Medium/High/Critical label. Tab wired into DraftEditor. Gap 4 (#27): ScriptLogHub (SignalR IAsyncEnumerable stream) tails scripts-*.log with optional ScriptName filter; ScriptLog.razor provides Start/Stop/Clear controls plus level filter dropdown. Hub registered at /hubs/script-log in Program.cs. Nav rail gains a "Scripting" eyebrow with entries for all three pages. 19 new unit tests for ScriptLogHub parse/filter/tail helpers (Category=Unit). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../Components/Layout/MainLayout.razor | 4 + .../Pages/Clusters/DraftEditor.razor | 4 + .../Pages/Clusters/ScriptedAlarmsTab.razor | 260 ++++++++++++++++++ .../Pages/Clusters/VirtualTagsTab.razor | 248 +++++++++++++++++ .../Components/Pages/ScriptLog.razor | 238 ++++++++++++++++ .../Components/Pages/ScriptedAlarms.razor | 191 +++++++++++++ .../Components/Pages/VirtualTags.razor | 182 ++++++++++++ .../Hubs/ScriptLogHub.cs | 222 +++++++++++++++ src/Server/ZB.MOM.WW.OtOpcUa.Admin/Program.cs | 1 + .../ScriptLogHubTests.cs | 198 +++++++++++++ 10 files changed, 1548 insertions(+) create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/VirtualTagsTab.razor create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/ScriptLog.razor create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/ScriptedAlarms.razor create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/VirtualTags.razor create mode 100644 src/Server/ZB.MOM.WW.OtOpcUa.Admin/Hubs/ScriptLogHub.cs create mode 100644 tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ScriptLogHubTests.cs diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor index 700e89e..7b300ad 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor @@ -30,6 +30,10 @@ Reservations Certificates Role grants +
Scripting
+ Virtual tags + Scripted alarms + Script log
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor index 266266c..444e611 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/DraftEditor.razor @@ -39,6 +39,8 @@ + +
@@ -49,6 +51,8 @@ else if (_tab == "drivers") { } else if (_tab == "acls") { } else if (_tab == "scripts") { } + else if (_tab == "virtual-tags") { } + else if (_tab == "scripted-alarms") { }
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor new file mode 100644 index 0000000..19bbe25 --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/Clusters/ScriptedAlarmsTab.razor @@ -0,0 +1,260 @@ +@using ZB.MOM.WW.OtOpcUa.Admin.Services +@using ZB.MOM.WW.OtOpcUa.Configuration.Entities +@inject ScriptedAlarmService AlarmSvc +@inject ScriptService ScriptSvc + +
+
+

Scripted Alarms

+ OPC UA Part 9 alarms raised by C# predicate scripts. Additive to driver-native alarm streams. +
+ +
+ +@if (_loading) +{ +

Loading…

+} +else if (_alarms.Count == 0 && !_showForm) +{ +
No scripted alarms yet in this draft.
+} +else +{ + @if (_alarms.Count > 0) + { +
+
+ Scripted alarms in draft gen @GenerationId + @_alarms.Count alarm@(_alarms.Count == 1 ? "" : "s") +
+
+ + + + + + + + + + + + + + + + @foreach (var a in _alarms) + { + + + + + + + + + + + + } + +
NameEquipmentTypeSeverityPredicate scriptHistorizeRetainEnabled
@a.Name@a.EquipmentId@a.AlarmType@a.Severity @SeverityBand(a.Severity)@(ScriptName(a.PredicateScriptId)) + @if (a.HistorizeToAveva) { Aveva } + else { } + + @if (a.Retain) { yes } + else { } + + @if (a.Enabled) { enabled } + else { disabled } + + +
+
+
+ } +} + +@if (_showForm) +{ +
+
+ New scripted alarm + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + @if (_scripts.Count == 0) + { +
No scripts in this draft — create one in the Scripts tab first.
+ } +
+
+ + +
+
+
+ + +
+
+
+
+ + +
+
+
+ + @if (_error is not null) + { +
@_error
+ } + +
+ + +
+
+
+} + +@code { + [Parameter] public long GenerationId { get; set; } + [Parameter] public string ClusterId { get; set; } = string.Empty; + + private static readonly string[] AlarmTypes = + ["AlarmCondition", "LimitAlarm", "OffNormalAlarm", "DiscreteAlarm"]; + + private bool _loading = true; + private bool _busy; + private bool _showForm; + private List _alarms = []; + private List