fix(adminui): confirm equipment-page and script deletes (R04 H1)
The equipment page's Remove reference / Delete virtual tag / Delete alarm row buttons and the ScriptEdit Delete ran on the first click. They now go through RawConfirmDialog, the same confirm /raw uses. The ScriptEdit change is limited to the delete button and its dialog. Refs #745 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
@using System.Text
|
@using System.Text
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration
|
@using ZB.MOM.WW.OtOpcUa.Configuration
|
||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
||||||
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
|
||||||
@inject IDbContextFactory<OtOpcUaConfigDbContext> DbFactory
|
@inject IDbContextFactory<OtOpcUaConfigDbContext> DbFactory
|
||||||
@inject NavigationManager Nav
|
@inject NavigationManager Nav
|
||||||
|
|
||||||
@@ -69,9 +70,14 @@ else
|
|||||||
<button type="submit" class="btn btn-primary" disabled="@_busy">@(IsNew ? "Create" : "Save changes")</button>
|
<button type="submit" class="btn btn-primary" disabled="@_busy">@(IsNew ? "Create" : "Save changes")</button>
|
||||||
<a href="/scripts" class="btn btn-outline-secondary">Cancel</a>
|
<a href="/scripts" class="btn btn-outline-secondary">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
@if (!IsNew) { <button type="button" class="btn btn-outline-danger ms-auto" @onclick="DeleteAsync" disabled="@_busy">Delete</button> }
|
@if (!IsNew) { <button type="button" class="btn btn-outline-danger ms-auto" @onclick="() => _confirmDeleteVisible = true" disabled="@_busy">Delete</button> }
|
||||||
</div>
|
</div>
|
||||||
</EditForm>
|
</EditForm>
|
||||||
|
|
||||||
|
@* R04 H1: the delete used to run on the first click. *@
|
||||||
|
<RawConfirmDialog @bind-Visible="_confirmDeleteVisible" Title="Delete script"
|
||||||
|
Message="@($"Delete the script '{_existing?.Name ?? _form.Name}'? This cannot be undone.")"
|
||||||
|
OnConfirm="DeleteAsync" />
|
||||||
}
|
}
|
||||||
</PageErrorBoundary>
|
</PageErrorBoundary>
|
||||||
|
|
||||||
@@ -84,6 +90,7 @@ else
|
|||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
private bool _busy;
|
private bool _busy;
|
||||||
private string? _error;
|
private string? _error;
|
||||||
|
private bool _confirmDeleteVisible;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
@using ZB.MOM.WW.OtOpcUa.Configuration.Validation
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Validation
|
||||||
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
|
||||||
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Uns
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Uns
|
||||||
|
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw
|
||||||
@inject IUnsTreeService Svc
|
@inject IUnsTreeService Svc
|
||||||
@inject NavigationManager Nav
|
@inject NavigationManager Nav
|
||||||
|
|
||||||
@@ -187,7 +188,7 @@ else
|
|||||||
<td class="text-end text-nowrap">
|
<td class="text-end text-nowrap">
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => SaveOverride(r)">Save name</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => SaveOverride(r)">Save name</button>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => RemoveReference(r)">Remove</button>
|
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmRemoveReference(r)">Remove</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -240,7 +241,7 @@ else
|
|||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditVirtualTag(v.VirtualTagId)">Edit</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditVirtualTag(v.VirtualTagId)">Edit</button>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => DeleteVirtualTag(v.VirtualTagId)">Delete</button>
|
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDeleteVirtualTag(v.VirtualTagId, v.Name)">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -295,7 +296,7 @@ else
|
|||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditAlarm(a.ScriptedAlarmId)">Edit</button>
|
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => OpenEditAlarm(a.ScriptedAlarmId)">Edit</button>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => DeleteAlarm(a.ScriptedAlarmId)">Delete</button>
|
<button type="button" class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDeleteAlarm(a.ScriptedAlarmId, a.Name)">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -310,6 +311,11 @@ else
|
|||||||
<ScriptedAlarmModal Visible="_alarmModalVisible" IsNew="_alarmModalIsNew" EquipmentId="@EquipmentId"
|
<ScriptedAlarmModal Visible="_alarmModalVisible" IsNew="_alarmModalIsNew" EquipmentId="@EquipmentId"
|
||||||
Existing="_alarmModalExisting" Scripts="_alarmScriptOptions"
|
Existing="_alarmModalExisting" Scripts="_alarmScriptOptions"
|
||||||
OnSaved="OnAlarmSavedAsync" OnCancel="@(() => { _alarmModalVisible = false; })" />
|
OnSaved="OnAlarmSavedAsync" OnCancel="@(() => { _alarmModalVisible = false; })" />
|
||||||
|
|
||||||
|
@* One confirm for every destructive row action on this page (R04 H1) — the same RawConfirmDialog
|
||||||
|
/raw routes its deletes through. A mis-click used to delete immediately. *@
|
||||||
|
<RawConfirmDialog @bind-Visible="_confirmVisible" Title="@_confirmTitle" Message="@_confirmMessage"
|
||||||
|
ConfirmLabel="@_confirmLabel" OnConfirm="RunConfirmedAsync" />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</PageErrorBoundary>
|
</PageErrorBoundary>
|
||||||
@@ -403,6 +409,47 @@ else
|
|||||||
else { _refError = res.Error; }
|
else { _refError = res.Error; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Delete confirmation (R04 H1) ---
|
||||||
|
private bool _confirmVisible;
|
||||||
|
private string _confirmTitle = "";
|
||||||
|
private string _confirmMessage = "";
|
||||||
|
private string _confirmLabel = "Delete";
|
||||||
|
private Func<Task>? _confirmAction;
|
||||||
|
|
||||||
|
private void AskConfirm(string title, string message, string label, Func<Task> action)
|
||||||
|
{
|
||||||
|
_confirmTitle = title;
|
||||||
|
_confirmMessage = message;
|
||||||
|
_confirmLabel = label;
|
||||||
|
_confirmAction = action;
|
||||||
|
_confirmVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunConfirmedAsync()
|
||||||
|
{
|
||||||
|
var action = _confirmAction;
|
||||||
|
_confirmAction = null;
|
||||||
|
if (action is not null) await action();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmRemoveReference(EquipmentReferenceRow r) => AskConfirm(
|
||||||
|
"Remove reference",
|
||||||
|
$"Remove the reference '{r.EffectiveName}'? After the next deploy its UNS node is gone, and any client subscribed to it loses it. The raw tag is not affected.",
|
||||||
|
"Remove",
|
||||||
|
() => RemoveReference(r));
|
||||||
|
|
||||||
|
private void ConfirmDeleteVirtualTag(string vtagId, string name) => AskConfirm(
|
||||||
|
"Delete virtual tag",
|
||||||
|
$"Delete the virtual tag '{name}'? After the next deploy its OPC UA node is gone, and any client subscribed to it loses it.",
|
||||||
|
"Delete",
|
||||||
|
() => DeleteVirtualTag(vtagId));
|
||||||
|
|
||||||
|
private void ConfirmDeleteAlarm(string scriptedAlarmId, string name) => AskConfirm(
|
||||||
|
"Delete scripted alarm",
|
||||||
|
$"Delete the scripted alarm '{name}'? After the next deploy its condition is gone, and any client subscribed to it loses it.",
|
||||||
|
"Delete",
|
||||||
|
() => DeleteAlarm(scriptedAlarmId));
|
||||||
|
|
||||||
private async Task RemoveReference(EquipmentReferenceRow r)
|
private async Task RemoveReference(EquipmentReferenceRow r)
|
||||||
{
|
{
|
||||||
_refError = null;
|
_refError = null;
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// R04 H1 source pin (no bUnit): every destructive button on the equipment page and the script editor
|
||||||
|
/// opens a confirm dialog instead of deleting on the first click. The live check (each delete confirms)
|
||||||
|
/// is in the Wave 4 docker-dev pass.
|
||||||
|
/// </summary>
|
||||||
|
[Trait("Category", "Unit")]
|
||||||
|
public sealed class DeleteConfirmationWiringTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Equipment_page_routes_every_destructive_row_action_through_the_confirm()
|
||||||
|
{
|
||||||
|
var src = AdminUiSource.Read("Components", "Pages", "Uns", "EquipmentPage.razor");
|
||||||
|
|
||||||
|
src.ShouldContain("<RawConfirmDialog @bind-Visible=\"_confirmVisible\"");
|
||||||
|
src.ShouldContain("@onclick=\"() => ConfirmRemoveReference(r)\"");
|
||||||
|
src.ShouldContain("@onclick=\"() => ConfirmDeleteVirtualTag(v.VirtualTagId, v.Name)\"");
|
||||||
|
src.ShouldContain("@onclick=\"() => ConfirmDeleteAlarm(a.ScriptedAlarmId, a.Name)\"");
|
||||||
|
|
||||||
|
// No danger button may call a delete handler directly.
|
||||||
|
Regex.Matches(src, @"btn-outline-danger[^>]*@onclick=""\(\) => (Delete|Remove)\w*\(").Count.ShouldBe(0);
|
||||||
|
// Positive control: the page still has its three danger buttons.
|
||||||
|
Regex.Matches(src, "btn-outline-danger").Count.ShouldBe(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Script_editor_delete_confirms()
|
||||||
|
{
|
||||||
|
var src = AdminUiSource.Read("Components", "Pages", "ScriptEdit.razor");
|
||||||
|
|
||||||
|
src.ShouldContain("@onclick=\"() => _confirmDeleteVisible = true\"");
|
||||||
|
src.ShouldContain("OnConfirm=\"DeleteAsync\"");
|
||||||
|
src.ShouldNotContain("@onclick=\"DeleteAsync\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user