feat(adminui): relay->alias converter UI (per-equipment + fleet-wide /uns/convert-relays)

This commit is contained in:
Joseph Doherty
2026-06-11 21:49:43 -04:00
parent 299eaa461c
commit 7682d185fb
3 changed files with 267 additions and 0 deletions
@@ -145,6 +145,7 @@ else
}
<button type="button" class="btn btn-outline-primary btn-sm" @onclick="OpenAddAlias" disabled="@(_gateways.Count == 0)">Add alias (browse Galaxy)</button>
<button type="button" class="btn btn-outline-primary btn-sm" @onclick="OpenAddTag">Add tag</button>
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="PreviewConvertRelaysAsync" disabled="@(_gateways.Count == 0 || _convertBusy)">Convert relay virtual-tags…</button>
</div>
@if (!string.IsNullOrWhiteSpace(_tagError))
{
@@ -196,6 +197,76 @@ else
</table>
}
@if (!string.IsNullOrWhiteSpace(_convertMessage))
{
<div class="text-success small mb-2">@_convertMessage</div>
}
@if (_convertPreview is not null)
{
<section class="panel rise mt-2">
<div class="panel-head d-flex justify-content-between align-items-center">
<span>Convert relay virtual-tags to aliases</span>
<button type="button" class="btn btn-sm btn-link" @onclick="@(() => { _convertPreview = null; })">Close</button>
</div>
<div style="padding:1rem">
<h6 class="text-muted">Will convert (@_convertPreview.Converted.Count)</h6>
@if (_convertPreview.Converted.Count == 0)
{
<p class="text-muted small mb-3">No relay virtual-tags to convert.</p>
}
else
{
<table class="table table-sm mb-3">
<thead><tr><th>Virtual tag</th><th>Full name</th><th>Data type</th></tr></thead>
<tbody>
@foreach (var c in _convertPreview.Converted)
{
<tr>
<td>@c.VirtualTagName</td>
<td class="mono">@c.FullName</td>
<td>@c.DataType</td>
</tr>
}
</tbody>
</table>
}
<h6 class="text-muted">Skipped (@_convertPreview.Skipped.Count)</h6>
@if (_convertPreview.Skipped.Count == 0)
{
<p class="text-muted small mb-0">Nothing skipped.</p>
}
else
{
<table class="table table-sm mb-0">
<thead><tr><th>Virtual tag</th><th>Reason</th></tr></thead>
<tbody>
@foreach (var s in _convertPreview.Skipped)
{
<tr>
<td>@s.VirtualTagName</td>
<td>@s.Reason</td>
</tr>
}
</tbody>
</table>
}
<div class="mt-3 d-flex gap-2">
@if (_convertPreview.Converted.Count > 0)
{
<button type="button" class="btn btn-primary btn-sm" @onclick="ApplyConvertRelaysAsync" disabled="@_convertBusy">
@if (_convertBusy) { <span class="spinner-border spinner-border-sm me-1"></span> }
Apply
</button>
}
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="@(() => { _convertPreview = null; })" disabled="@_convertBusy">Cancel</button>
</div>
</div>
</section>
}
<TagModal Visible="_tagModalVisible" IsNew="_tagModalIsNew" EquipmentId="@EquipmentId"
Existing="_tagModalExisting" Drivers="_tagDriverOptions"
OnSaved="OnTagSavedAsync" OnCancel="@(() => { _tagModalVisible = false; })" />
@@ -331,6 +402,12 @@ else
private bool _aliasModalIsNew;
private AliasTagEditDto? _aliasModalExisting;
// --- Relay→alias converter (per-equipment). _convertPreview holds the dry-run result while the
// inline preview panel is open; null = panel closed. _convertMessage is a brief post-apply summary. ---
private RelayConversionResult? _convertPreview;
private bool _convertBusy;
private string? _convertMessage;
// --- Virtual Tags tab state. _vtags is null until the tab is first activated. ---
private IReadOnlyList<EquipmentVirtualTagRow>? _vtags;
private string? _vtagError;
@@ -440,6 +517,36 @@ else
await ReloadTagsAsync();
}
// --- Relay→alias converter handlers (per-equipment, scoped to EquipmentId). Dry-run first to fill
// the inline preview panel; Apply mutates the draft then reloads the tags + virtual-tags lists so
// the converted vtags vanish and the new aliases appear. ---
private async Task PreviewConvertRelaysAsync()
{
_convertMessage = null;
_convertBusy = true;
try
{
_convertPreview = await Svc.ConvertRelayVirtualTagsToAliasesAsync(EquipmentId!, dryRun: true);
}
finally { _convertBusy = false; }
}
private async Task ApplyConvertRelaysAsync()
{
_convertBusy = true;
try
{
var r = await Svc.ConvertRelayVirtualTagsToAliasesAsync(EquipmentId!, dryRun: false);
_convertPreview = null;
_convertMessage = $"Converted {r.Converted.Count}, skipped {r.Skipped.Count}.";
// The converted virtual tags become aliases, so both lists change.
await ReloadTagsAsync();
await ReloadVirtualTagsAsync();
}
finally { _convertBusy = false; }
}
// --- Virtual Tags tab handlers ---
private async Task ReloadVirtualTagsAsync()
@@ -542,6 +649,9 @@ else
_tags = null;
_vtags = null;
_alarms = null;
// Drop any open relay-conversion preview/summary so it can't leak across equipment changes.
_convertPreview = null;
_convertMessage = null;
if (!IsNew)
{
_equipment = await Svc.LoadEquipmentAsync(EquipmentId!);