diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor
index 2a706b5f..c4272905 100644
--- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor
+++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/DebugView.razor
@@ -191,9 +191,10 @@
| Alarm |
+ Kind |
State |
+ Sev |
Level |
- Priority |
Timestamp |
@@ -201,18 +202,44 @@
@foreach (var alarm in FilteredAlarmStates)
{
+ title="@BuildAlarmTooltip(alarm)">
|
@alarm.AlarmName
@if (!string.IsNullOrEmpty(alarm.Message))
{
๐ฌ
}
+ @if (!string.IsNullOrEmpty(alarm.SourceReference))
+ {
+ @alarm.SourceReference
+ }
+ |
+
+ @FormatKind(alarm.Kind)
|
@alarm.State
+ @if (alarm.Kind != AlarmKind.Computed)
+ {
+ @if (alarm.Condition.Active && !alarm.Condition.Acknowledged)
+ {
+ Unacked
+ }
+ @if (alarm.Condition.Shelve != AlarmShelveState.Unshelved)
+ {
+ Shelved
+ }
+ @if (alarm.Condition.Suppressed)
+ {
+ Suppressed
+ }
+ }
|
+ @alarm.Condition.Severity |
@if (alarm.Level != AlarmLevel.None)
{
@@ -224,7 +251,6 @@
โ
}
|
- @alarm.Priority |
@alarm.Timestamp.LocalDateTime.ToString("HH:mm:ss")
@@ -284,7 +310,8 @@
string.IsNullOrWhiteSpace(_alarmFilter)
? _alarmStates.Values.OrderBy(a => a.AlarmName).ToList()
: _alarmStates.Values
- .Where(a => a.AlarmName.Contains(_alarmFilter, StringComparison.OrdinalIgnoreCase))
+ .Where(a => a.AlarmName.Contains(_alarmFilter, StringComparison.OrdinalIgnoreCase)
+ || a.SourceReference.Contains(_alarmFilter, StringComparison.OrdinalIgnoreCase))
.OrderBy(a => a.AlarmName)
.ToList();
@@ -575,6 +602,40 @@
_ => "bg-secondary"
};
+ /// Badge class distinguishing computed (neutral) from native (info) alarms.
+ private static string GetKindBadge(AlarmKind kind) => kind switch
+ {
+ AlarmKind.Computed => "bg-secondary",
+ _ => "bg-info text-dark"
+ };
+
+ /// Short display label for the alarm kind.
+ private static string FormatKind(AlarmKind kind) => kind switch
+ {
+ AlarmKind.NativeOpcUa => "OPC UA",
+ AlarmKind.NativeMxAccess => "MxAccess",
+ _ => "Computed"
+ };
+
+ ///
+ /// Builds the row tooltip from the alarm's operator message plus native
+ /// metadata (type, category, operator, raise time, current/limit value).
+ /// Returns null when there is nothing extra to show.
+ ///
+ private static string? BuildAlarmTooltip(AlarmStateChanged a)
+ {
+ var parts = new List();
+ if (!string.IsNullOrEmpty(a.Message)) parts.Add(a.Message);
+ if (!string.IsNullOrEmpty(a.AlarmTypeName)) parts.Add($"Type: {a.AlarmTypeName}");
+ if (!string.IsNullOrEmpty(a.Category)) parts.Add($"Category: {a.Category}");
+ if (!string.IsNullOrEmpty(a.OperatorUser)) parts.Add($"By: {a.OperatorUser}");
+ if (!string.IsNullOrEmpty(a.OperatorComment)) parts.Add($"Comment: {a.OperatorComment}");
+ if (a.OriginalRaiseTime.HasValue) parts.Add($"Raised: {a.OriginalRaiseTime.Value.LocalDateTime:HH:mm:ss}");
+ if (!string.IsNullOrEmpty(a.CurrentValue)) parts.Add($"Value: {a.CurrentValue}");
+ if (!string.IsNullOrEmpty(a.LimitValue)) parts.Add($"Limit: {a.LimitValue}");
+ return parts.Count == 0 ? null : string.Join(" ยท ", parts);
+ }
+
private static string FormatLevel(AlarmLevel level) => level switch
{
AlarmLevel.HighHigh => "HiHi",
diff --git a/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugViewAlarmTableTests.cs b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugViewAlarmTableTests.cs
new file mode 100644
index 00000000..09be8afe
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/DebugViewAlarmTableTests.cs
@@ -0,0 +1,103 @@
+using System.Reflection;
+using System.Security.Claims;
+using Bunit;
+using Microsoft.AspNetCore.Components.Authorization;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
+using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
+using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
+using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms;
+using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
+using ZB.MOM.WW.ScadaBridge.Communication;
+using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
+using DebugViewPage = ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Deployment.DebugView;
+
+namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Deployment;
+
+///
+/// Task 23: the DebugView alarm table surfaces enriched native alarm state โ
+/// kind, severity, source reference, and a composite condition badge set
+/// (Unacked / Shelved / Suppressed).
+///
+public class DebugViewAlarmTableTests : BunitContext
+{
+ private IRenderedComponent RenderPage()
+ {
+ JSInterop.Mode = JSRuntimeMode.Loose;
+
+ var repo = Substitute.For();
+ var siteRepo = Substitute.For();
+ siteRepo.GetAllSitesAsync().Returns(new List());
+ Services.AddSingleton(repo);
+ Services.AddSingleton(siteRepo);
+
+ var comms = new CommunicationService(
+ Options.Create(new CommunicationOptions()),
+ NullLogger.Instance);
+ Services.AddSingleton(comms);
+
+ var grpcFactory = new SiteStreamGrpcClientFactory(NullLoggerFactory.Instance);
+ var debugStream = new DebugStreamService(
+ comms, new ServiceCollection().BuildServiceProvider(), grpcFactory,
+ NullLogger.Instance);
+ Services.AddSingleton(debugStream);
+
+ var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "deployer") }, "TestCookie");
+ var stubAuth = new StubAuthStateProvider(new AuthenticationState(new ClaimsPrincipal(identity)));
+ Services.AddSingleton(stubAuth);
+ Services.AddScoped(_ => new SiteScopeService(stubAuth));
+
+ return Render();
+ }
+
+ private sealed class StubAuthStateProvider : AuthenticationStateProvider
+ {
+ private readonly AuthenticationState _state;
+ public StubAuthStateProvider(AuthenticationState state) => _state = state;
+ public override Task GetAuthenticationStateAsync() => Task.FromResult(_state);
+ }
+
+ private static void SetField(DebugViewPage c, string name, object? value) =>
+ typeof(DebugViewPage).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!.SetValue(c, value);
+
+ private static Dictionary AlarmStates(DebugViewPage c) =>
+ (Dictionary)typeof(DebugViewPage)
+ .GetField("_alarmStates", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(c)!;
+
+ [Fact]
+ public void AlarmTable_RendersNativeAlarm_WithSeverityKindAndShelvedBadge()
+ {
+ var cut = RenderPage();
+
+ var native = new AlarmStateChanged("inst", "T01.Hi", AlarmState.Active, 800, DateTimeOffset.UtcNow)
+ {
+ Kind = AlarmKind.NativeOpcUa,
+ SourceReference = "ns=2;s=Tank01.Level.HiHi",
+ Condition = new AlarmConditionState(
+ Active: true, Acknowledged: false, Confirmed: null,
+ Shelve: AlarmShelveState.OneShotShelved, Suppressed: false, Severity: 800)
+ };
+
+ cut.InvokeAsync(() =>
+ {
+ SetField(cut.Instance, "_connected", true);
+ SetField(cut.Instance, "_snapshot",
+ new DebugViewSnapshot("inst", new List(),
+ new List { native }, DateTimeOffset.UtcNow));
+ AlarmStates(cut.Instance)["T01.Hi"] = native;
+ });
+ cut.Render();
+
+ var markup = cut.Markup;
+ Assert.Contains("800", markup); // severity surfaced
+ Assert.Contains("Shelved", markup); // composite condition badge
+ Assert.Contains("OPC UA", markup); // native kind badge
+ Assert.Contains("ns=2;s=Tank01.Level.HiHi", markup); // source reference
+ Assert.Contains("Unacked", markup); // active + unacknowledged
+ }
+}
|