diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Uns/EquipmentPage.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Uns/EquipmentPage.razor index d25cd9c1..64e34f5d 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Uns/EquipmentPage.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Uns/EquipmentPage.razor @@ -34,7 +34,7 @@ else - + @if (_activeTab == "details") @@ -226,7 +226,52 @@ else Existing="_vtagModalExisting" Scripts="_vtagScriptOptions" OnSaved="OnVirtualTagSavedAsync" OnCancel="@(() => { _vtagModalVisible = false; })" /> } - else if (_activeTab == "alarms") {

Alarms tab — wired in a later task.

} + else if (_activeTab == "alarms") + { +
+ +
+ @if (!string.IsNullOrWhiteSpace(_alarmError)) + { +
@_alarmError
+ } + @if (_alarms is null) + { +

Loading…

+ } + else if (_alarms.Count == 0) + { +

No alarms yet.

+ } + else + { + + + + + + @foreach (var a in _alarms) + { + + + + + + + + + } + +
NameTypeSeverityPredicateEnabledActions
@a.Name@a.AlarmType@a.Severity@a.PredicateScriptId@(a.Enabled ? "Yes" : "No") + + +
+ } + + + } } @code { @@ -263,6 +308,14 @@ else private VirtualTagEditDto? _vtagModalExisting; private IReadOnlyList<(string Id, string Display)> _vtagScriptOptions = Array.Empty<(string, string)>(); + // --- Alarms tab state. _alarms is null until the tab is first activated. --- + private IReadOnlyList? _alarms; + private string? _alarmError; + private bool _alarmModalVisible; + private bool _alarmModalIsNew; + private ScriptedAlarmEditDto? _alarmModalExisting; + private IReadOnlyList<(string Id, string Display)> _alarmScriptOptions = Array.Empty<(string, string)>(); + private string TabClass(string tab) => _activeTab == tab ? "active" : ""; /// @@ -278,6 +331,7 @@ else if (IsNew) { return; } if (tab == "tags" && _tags is null) { await ReloadTagsAsync(); } else if (tab == "vtags" && _vtags is null) { await ReloadVirtualTagsAsync(); } + else if (tab == "alarms" && _alarms is null) { await ReloadAlarmsAsync(); } } // --- Tags tab handlers (mirror GlobalUns; the owning equipment is fixed = EquipmentId) --- @@ -370,6 +424,51 @@ else else { _vtagError = r.Error; } } + // --- Alarms tab handlers (mirror the Tags/Virtual Tags tabs; the owning equipment is fixed = EquipmentId) --- + + private async Task ReloadAlarmsAsync() + { + _alarms = await Svc.LoadAlarmsForEquipmentAsync(EquipmentId!); + } + + private async Task OpenAddAlarm() + { + _alarmError = null; + _alarmModalIsNew = true; + _alarmModalExisting = null; + _alarmScriptOptions = await Svc.LoadScriptsAsync(); + _alarmModalVisible = true; + } + + private async Task OpenEditAlarm(string scriptedAlarmId) + { + _alarmError = null; + var dto = await Svc.LoadScriptedAlarmAsync(scriptedAlarmId); + if (dto is null) { _alarmError = "That alarm no longer exists; the list was refreshed."; await ReloadAlarmsAsync(); return; } + _alarmModalIsNew = false; + _alarmModalExisting = dto; + _alarmScriptOptions = await Svc.LoadScriptsAsync(); + _alarmModalVisible = true; + } + + private async Task OnAlarmSavedAsync() + { + _alarmModalVisible = false; + await ReloadAlarmsAsync(); + } + + private async Task DeleteAlarm(string scriptedAlarmId) + { + _alarmModalVisible = false; + _alarmError = null; + // Load the alarm fresh to capture its current RowVersion for the concurrency-guarded delete. + var dto = await Svc.LoadScriptedAlarmAsync(scriptedAlarmId); + if (dto is null) { await ReloadAlarmsAsync(); return; } + var r = await Svc.DeleteScriptedAlarmAsync(scriptedAlarmId, dto.RowVersion); + if (r.Ok) { await ReloadAlarmsAsync(); } + else { _alarmError = r.Error; } + } + protected override async Task OnParametersSetAsync() { _loading = true; @@ -381,6 +480,7 @@ else // lazy loaders re-fetch for the (possibly different) equipment this parameter set targets. _tags = null; _vtags = null; + _alarms = null; if (!IsNew) { _equipment = await Svc.LoadEquipmentAsync(EquipmentId!); diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/ScriptedAlarmModal.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/ScriptedAlarmModal.razor new file mode 100644 index 00000000..799ed463 --- /dev/null +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Uns/ScriptedAlarmModal.razor @@ -0,0 +1,207 @@ +@* Create/edit modal for an equipment-bound scripted alarm, wired straight into IUnsTreeService. The host + page owns visibility and supplies the owning equipment id (create) or the loaded ScriptedAlarmEditDto + (edit), plus the candidate predicate-script list. The owning equipment is fixed by the tab, so there is + no equipment selector. On a successful save it raises OnSaved so the host can refresh the equipment's + alarms in place. Lifted from the standalone ScriptedAlarmEdit page (retired in a later task). *@ +@using System.ComponentModel.DataAnnotations +@using Microsoft.AspNetCore.Components.Forms +@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns +@inject IUnsTreeService Svc + +@if (Visible) +{ + + +} + +@code { + /// Whether the modal is shown. The host owns this flag. + [Parameter] public bool Visible { get; set; } + + /// true to create a new scripted alarm; false to edit . + [Parameter] public bool IsNew { get; set; } + + /// The owning equipment id the created alarm binds to (used only on create). + [Parameter] public string? EquipmentId { get; set; } + + /// The scripted alarm being edited, when is false. + [Parameter] public ScriptedAlarmEditDto? Existing { get; set; } + + /// The candidate predicate scripts as (Id, Display) pairs. + [Parameter] public IReadOnlyList<(string Id, string Display)> Scripts { get; set; } = Array.Empty<(string, string)>(); + + /// Raised after a successful create/save so the host can refresh the equipment's alarms and close. + [Parameter] public EventCallback OnSaved { get; set; } + + /// Raised when the user cancels so the host can close. + [Parameter] public EventCallback OnCancel { get; set; } + + private FormModel _form = new(); + private bool _busy; + private string? _error; + + protected override void OnParametersSet() + { + // Rebuild the working form whenever the host (re)opens the modal for a fresh target. + if (IsNew) + { + _form = new FormModel(); + } + else if (Existing is not null) + { + _form = new FormModel + { + ScriptedAlarmId = Existing.ScriptedAlarmId, + Name = Existing.Name, + AlarmType = Existing.AlarmType, + Severity = Existing.Severity, + MessageTemplate = Existing.MessageTemplate, + PredicateScriptId = Existing.PredicateScriptId, + HistorizeToAveva = Existing.HistorizeToAveva, + Retain = Existing.Retain, + Enabled = Existing.Enabled, + }; + } + _error = null; + } + + private async Task SaveAsync() + { + _busy = true; + _error = null; + try + { + var input = new ScriptedAlarmInput(_form.ScriptedAlarmId, _form.Name, _form.AlarmType, _form.Severity, + _form.MessageTemplate, _form.PredicateScriptId, _form.HistorizeToAveva, _form.Retain, _form.Enabled); + + var result = IsNew + ? await Svc.CreateScriptedAlarmAsync(EquipmentId!, input) + : await Svc.UpdateScriptedAlarmAsync(Existing!.ScriptedAlarmId, input, Existing.RowVersion); + + if (result.Ok) + { + await OnSaved.InvokeAsync(); + } + else + { + _error = result.Error; + } + } + finally + { + _busy = false; + } + } + + private Task CancelAsync() => OnCancel.InvokeAsync(); + + private sealed class FormModel + { + [Required, RegularExpression("^[A-Za-z0-9_-]+$")] public string ScriptedAlarmId { get; set; } = ""; + [Required] public string Name { get; set; } = ""; + [Required] public string AlarmType { get; set; } = "LimitAlarm"; + [Range(1, 1000)] public int Severity { get; set; } = 500; + [Required] public string PredicateScriptId { get; set; } = ""; + [Required] public string MessageTemplate { get; set; } = ""; + public bool HistorizeToAveva { get; set; } = true; + public bool Retain { get; set; } = true; + public bool Enabled { get; set; } = true; + } +}