@page "/alerts" @* Live alarm tail via SignalR. Subscribes to /hubs/alerts and shows the most-recent AlarmTransitionEvent entries published by ScriptedAlarmActor (Runtime/ScriptedAlarms) and the AB CIP ALMD bridge. *@ @attribute [Microsoft.AspNetCore.Authorization.Authorize] @rendermode RenderMode.InteractiveServer @using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs @using ZB.MOM.WW.OtOpcUa.Commons.Messages.Alerts @inject IInProcessBroadcaster Alarms @implements IDisposable

Alerts

@(_connected ? "live" : "disconnected")
Live alarm transitions from the cluster's alerts DPS topic. Shows the most-recent @Capacity entries since the page opened; reload for a fresh window. Sources: ScriptedAlarmActor, native driver alarm bridges (AB CIP ALMD, Galaxy where wired).
@if (_rows.Count == 0) {
No alarms in the current window. The table will populate as soon as a ScriptedAlarmActor or driver alarm bridge publishes a transition.
} else {
Recent transitions (@_rows.Count)
@foreach (var e in _rows) { }
Time Alarm Equipment Kind Severity User Message
@e.TimestampUtc.ToString("HH:mm:ss.fff") @e.AlarmId
@e.AlarmName
@e.EquipmentPath @e.TransitionKind @e.Severity @e.User @e.Message
} @code { private const int Capacity = 200; private readonly List _rows = new(); private bool _connected; protected override void OnInitialized() { // Live alarm tail straight from the in-process broadcaster (fed by AlertSignalRBridge off the // 'alerts' DPS topic). A Blazor Server component can't self-connect a SignalR HubConnection // behind a reverse proxy — see IInProcessBroadcaster — so we subscribe in-process instead. Alarms.Received += OnAlarm; _connected = true; } private void OnAlarm(AlarmTransitionEvent evt) => // Marshal both the mutation and the re-render onto the circuit sync context so this can't // race ClearAsync (which runs there) over the shared _rows list. InvokeAsync(() => { _rows.Insert(0, evt); if (_rows.Count > Capacity) _rows.RemoveAt(_rows.Count - 1); StateHasChanged(); }); private async Task ClearAsync() { _rows.Clear(); await InvokeAsync(StateHasChanged); } private static string KindChipClass(string kind) => kind switch { "Activated" => "chip-alert", "Cleared" => "chip-ok", "Acknowledged" or "Confirmed" => "chip-caution", "Shelved" or "Disabled" => "chip-idle", _ => "chip-idle", }; public void Dispose() => Alarms.Received -= OnAlarm; }