9e243493fb
Applies the family-wide admin-UI cleanup playbook to the Central UI so the Blazor surfaces stop diverging from the shared kit: buttons are grouped rather than individually sized, long cell values are contained instead of widening tables, and hard-coded colours give way to theme tokens. The headline fix is that MainLayout passed Accent="#2f5fd0" to ThemeShell, which the kit emits as an inline style on the shell root. Being a descendant of <html>, it beat the [data-bs-theme="dark"] override for the entire app, so the dark accent had never rendered. Declaring --accent in site.css :root instead lets both schemes resolve; light is unchanged because the value already matched the kit's light default. Theme pins to 0.4.1, which upstreams the local .btn sizing block verbatim, so that block is deleted here rather than duplicated. Verified byte-identical before removal; the repo now declares no --bs-btn-* anywhere. NOT purely cosmetic, contrary to the sweep's stated scope: four detail-modal surfaces (NotificationReport, ConfigurationAuditLog, ParkedMessages, SiteCallsReport) were additionally refactored from holding the selected record to holding its id and re-resolving from the current page each render, with the resolve doubling as the visibility gate. A background refresh that drops the row now closes the modal instead of showing a stale snapshot. This is a behaviour change and is called out rather than buried: a full-suite run turned up one intermittent CentralUI failure, CloseButton_DismissesModal, whose stack (GetRequiredEventBindingEntry during DispatchEventAsync) indicates the handler was disposed between render and click — a window the previous field-held record made structurally impossible. Treat the modal lifecycle here as unreviewed. Build 0/0; suite green apart from that one intermittent failure.
157 lines
6.5 KiB
Plaintext
157 lines
6.5 KiB
Plaintext
@using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites
|
|
|
|
@*
|
|
M10 residual: body component for the template "Add / Edit Native Alarm Source"
|
|
dialog hosted by IDialogService.ShowAsync. Renders ONLY the form fields +
|
|
action buttons inside the host's .modal-body; the host owns the backdrop,
|
|
header, focus trap, Escape, and focus restoration.
|
|
|
|
Native alarm sources are a READ-ONLY mirror of OPC UA A&C / MxAccess Gateway
|
|
alarms, so the connection picker is restricted to alarm-capable protocols —
|
|
the page filters that list via AlarmCapableProtocols.IsAlarmCapable and hands
|
|
it in. OnSaveAsync returns null on success (dialog closes with true) or the
|
|
error to render inline (dialog stays open).
|
|
*@
|
|
<div class="row g-2">
|
|
<div class="col-12">
|
|
<label class="form-label" for="nas-name">Name</label>
|
|
<input id="nas-name" type="text" class="form-control" @bind="_nasName" readonly="@Editing" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label" for="nas-connection">Connection</label>
|
|
<select id="nas-connection" class="form-select" @bind="_nasConnection">
|
|
<option value="">— select an alarm-capable connection —</option>
|
|
@foreach (var c in AlarmCapableConnections)
|
|
{
|
|
<option value="@c.Name">@c.Name (@c.Protocol)</option>
|
|
}
|
|
</select>
|
|
@if (AlarmCapableConnections.Count == 0)
|
|
{
|
|
<div class="form-text text-warning">No OPC UA or MxGateway connections defined yet.</div>
|
|
}
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label" for="nas-source-ref">Source Reference</label>
|
|
<input id="nas-source-ref" type="text" class="form-control font-monospace" @bind="_nasSourceRef"
|
|
placeholder="OPC UA SourceNode nodeId, or MxAccess object/area" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label" for="nas-filter">Condition Filter <span class="text-muted">(optional)</span></label>
|
|
<input id="nas-filter" type="text" class="form-control" @bind="_nasFilter"
|
|
placeholder="Blank = mirror all conditions under the source" />
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label" for="nas-description">Description <span class="text-muted">(optional)</span></label>
|
|
<input id="nas-description" type="text" class="form-control" @bind="_nasDescription" />
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" @bind="_nasIsLocked" id="nasLocked" />
|
|
<label class="form-check-label" for="nasLocked">Locked</label>
|
|
</div>
|
|
</div>
|
|
@if (_error != null)
|
|
{
|
|
<div class="col-12"><div class="text-danger small" data-test="nas-form-error">@_error</div></div>
|
|
}
|
|
</div>
|
|
|
|
<div class="modal-footer px-0 pb-0 mt-3">
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<button class="btn btn-outline-secondary" @onclick="() => Context.Cancel()" disabled="@_busy">Cancel</button>
|
|
<button class="btn btn-success" @onclick="Submit" disabled="@_busy">@(Editing ? "Save" : "Add")</button>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
/// <summary>Host-supplied context: Close(true) on a successful save, Cancel on dismiss.</summary>
|
|
[Parameter, EditorRequired] public DialogContext<bool> Context { get; set; } = default!;
|
|
|
|
/// <summary>True when editing an existing source (the name becomes read-only).</summary>
|
|
[Parameter] public bool Editing { get; set; }
|
|
|
|
[Parameter] public string InitialName { get; set; } = string.Empty;
|
|
[Parameter] public string InitialConnection { get; set; } = string.Empty;
|
|
[Parameter] public string InitialSourceRef { get; set; } = string.Empty;
|
|
[Parameter] public string? InitialFilter { get; set; }
|
|
[Parameter] public string? InitialDescription { get; set; }
|
|
[Parameter] public bool InitialIsLocked { get; set; }
|
|
|
|
/// <summary>Connections whose protocol can surface native alarms (OPC UA / MxGateway).</summary>
|
|
[Parameter] public IReadOnlyList<DataConnection> AlarmCapableConnections { get; set; }
|
|
= Array.Empty<DataConnection>();
|
|
|
|
/// <summary>
|
|
/// Persists the draft. Returns <c>null</c> on success (the dialog closes with
|
|
/// <c>true</c>) or the message to render inline, leaving the dialog open.
|
|
/// </summary>
|
|
[Parameter, EditorRequired] public Func<NativeSourceDraft, Task<string?>> OnSaveAsync { get; set; } = default!;
|
|
|
|
private string _nasName = string.Empty;
|
|
private string _nasConnection = string.Empty;
|
|
private string _nasSourceRef = string.Empty;
|
|
private string? _nasFilter;
|
|
private string? _nasDescription;
|
|
private bool _nasIsLocked;
|
|
private string? _error;
|
|
private bool _busy;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
_nasName = InitialName;
|
|
_nasConnection = InitialConnection;
|
|
_nasSourceRef = InitialSourceRef;
|
|
_nasFilter = InitialFilter;
|
|
_nasDescription = InitialDescription;
|
|
_nasIsLocked = InitialIsLocked;
|
|
}
|
|
|
|
private async Task Submit()
|
|
{
|
|
if (_busy) return;
|
|
_error = null;
|
|
|
|
_busy = true;
|
|
try
|
|
{
|
|
var draft = new NativeSourceDraft(
|
|
_nasName,
|
|
_nasConnection,
|
|
_nasSourceRef,
|
|
_nasFilter,
|
|
_nasDescription,
|
|
_nasIsLocked);
|
|
|
|
var error = await OnSaveAsync(draft);
|
|
if (error == null)
|
|
{
|
|
Context.Close(true);
|
|
}
|
|
else
|
|
{
|
|
_error = error;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_busy = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>The authored native alarm source as submitted; the page performs the add/update.</summary>
|
|
/// <param name="Name">Source name (ignored on edit — the stored name is fixed).</param>
|
|
/// <param name="ConnectionName">Alarm-capable data connection to mirror from.</param>
|
|
/// <param name="SourceReference">OPC UA SourceNode nodeId, or MxAccess object/area.</param>
|
|
/// <param name="ConditionFilter">Optional condition filter; blank mirrors every condition.</param>
|
|
/// <param name="Description">Optional free-text description.</param>
|
|
/// <param name="IsLocked">Whether instances may override the binding.</param>
|
|
public sealed record NativeSourceDraft(
|
|
string Name,
|
|
string ConnectionName,
|
|
string SourceReference,
|
|
string? ConditionFilter,
|
|
string? Description,
|
|
bool IsLocked);
|
|
}
|