refactor(adminui): delete alias-tag/relay-converter files (build red until spine stripped)

This commit is contained in:
Joseph Doherty
2026-06-12 21:20:41 -04:00
parent e2c6c15ae0
commit ca2698949b
6 changed files with 0 additions and 462 deletions
@@ -16,9 +16,6 @@
<NavRailItem Href="/hosts" Text="Host status" />
<NavRailItem Href="/clusters" Text="Clusters" />
<NavRailItem Href="/uns" Text="UNS" />
<AuthorizeView Policy="FleetAdmin">
<NavRailItem Href="/uns/convert-relays" Text="Convert relays" />
</AuthorizeView>
<NavRailItem Href="/reservations" Text="Reservations" />
<NavRailItem Href="/certificates" Text="Certificates" />
<NavRailItem Href="/role-grants" Text="Role grants" />
@@ -1,154 +0,0 @@
@page "/uns/convert-relays"
@* Fleet-wide relay→alias converter. Previews (dry-run) and applies the conversion of pure-relay
virtual tags into Galaxy alias tags across every equipment. Edits the draft config only; the
operator publishes normally afterward. FleetAdmin-gated (mirrors RoleGrants.razor). *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Policy = "FleetAdmin")]
@rendermode RenderMode.InteractiveServer
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
@inject IUnsTreeService Svc
<PageTitle>Convert relays</PageTitle>
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">Convert relay virtual-tags to aliases</h4>
<a href="/uns" class="btn btn-outline-secondary btn-sm">Back to UNS</a>
</div>
<p class="text-muted">
Sweeps every equipment for pure-relay virtual tags (a script body that simply returns another
tag's value) and rewrites each one as a Galaxy alias tag. This edits the draft configuration
only — publish afterward to take effect. Run a preview first to review what will change.
</p>
<div class="d-flex gap-2 align-items-center mb-3">
<button type="button" class="btn btn-primary" @onclick="PreviewAsync" disabled="@_busy">
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
Preview
</button>
@if (_applied)
{
<span class="text-success small">Conversion applied. Publish to take effect.</span>
}
</div>
@if (!string.IsNullOrWhiteSpace(_error))
{
<div class="text-danger small mb-3">@_error</div>
}
@if (_preview is not null)
{
<section class="panel rise mt-2">
<div class="panel-head">Will convert (@_preview.Converted.Count)</div>
@if (_preview.Converted.Count == 0)
{
<div style="padding:1rem" class="text-muted">No relay virtual-tags to convert.</div>
}
else
{
<div class="table-wrap">
<table class="data-table">
<thead><tr><th>Equipment</th><th>Virtual tag</th><th>Full name</th><th>Data type</th></tr></thead>
<tbody>
@foreach (var c in _preview.Converted)
{
<tr @key="(c.EquipmentId, c.FullName)">
<td class="mono">@c.EquipmentId</td>
<td>@c.VirtualTagName</td>
<td class="mono">@c.FullName</td>
<td>@c.DataType</td>
</tr>
}
</tbody>
</table>
</div>
}
</section>
<section class="panel rise mt-3">
<div class="panel-head">Skipped (@_preview.Skipped.Count)</div>
@if (_preview.Skipped.Count == 0)
{
<div style="padding:1rem" class="text-muted">Nothing skipped.</div>
}
else
{
<div class="table-wrap">
<table class="data-table">
<thead><tr><th>Equipment</th><th>Virtual tag</th><th>Reason</th></tr></thead>
<tbody>
@foreach (var s in _preview.Skipped)
{
<tr @key="(s.EquipmentId, s.VirtualTagName)">
<td class="mono">@s.EquipmentId</td>
<td>@s.VirtualTagName</td>
<td>@s.Reason</td>
</tr>
}
</tbody>
</table>
</div>
}
</section>
@if (!_preview.Applied && _preview.Converted.Count > 0)
{
<div class="d-flex gap-2 align-items-center mt-3">
@if (!_confirming)
{
<button type="button" class="btn btn-warning" @onclick="() => _confirming = true" disabled="@_busy">
Apply conversion…
</button>
}
else
{
<span class="small">Convert @_preview.Converted.Count relay virtual-tag(s) to aliases in the draft?</span>
<button type="button" class="btn btn-danger btn-sm" @onclick="ApplyAsync" disabled="@_busy">
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
Yes, apply
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="() => _confirming = false" disabled="@_busy">
Cancel
</button>
}
</div>
}
}
@code {
private RelayConversionResult? _preview;
private bool _busy;
private bool _confirming;
private bool _applied;
private string? _error;
private async Task PreviewAsync()
{
_error = null;
_confirming = false;
_applied = false;
_busy = true;
try
{
_preview = await Svc.ConvertRelayVirtualTagsToAliasesAsync(null, dryRun: true);
}
catch (Exception ex) { _error = ex.Message; }
finally { _busy = false; }
}
private async Task ApplyAsync()
{
_error = null;
_busy = true;
try
{
await Svc.ConvertRelayVirtualTagsToAliasesAsync(null, dryRun: false);
_applied = true;
_confirming = false;
// Re-run the preview so the Converted set is now empty and only remaining skips show.
_preview = await Svc.ConvertRelayVirtualTagsToAliasesAsync(null, dryRun: true);
}
catch (Exception ex) { _error = ex.Message; }
finally { _busy = false; }
}
}
@@ -1,249 +0,0 @@
@* Create/edit modal for a Galaxy alias tag — an equipment Tag bound to a GalaxyMxGateway driver that
surfaces a Galaxy attribute (its tag_name.AttributeName reference) under a friendly UNS name. A focused
sibling of TagModal: it drops the generic driver/tag-config surface and instead embeds the Galaxy
live-browse picker so the operator picks the Galaxy reference straight into FullName. The host page owns
visibility and supplies the owning equipment id (create) or the loaded AliasTagEditDto (edit), plus the
equipment's candidate Galaxy gateways. On a successful save it raises OnSaved so the host can refresh the
equipment's tags in place. *@
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Components.Forms
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
@inject IUnsTreeService Svc
@if (Visible)
{
<div class="modal-backdrop fade show" style="display:block"></div>
<div class="modal fade show" tabindex="-1" role="dialog" style="display:block">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<EditForm Model="_form" OnValidSubmit="SaveAsync" FormName="aliasTagModal">
<DataAnnotationsValidator />
<div class="modal-header">
<h5 class="modal-title">@(IsNew ? "New alias" : "Edit alias")</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="CancelAsync"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label" for="alias-name">Name</label>
<InputText id="alias-name" @bind-Value="_form.Name" class="form-control form-control-sm"
placeholder="Download path" />
<ValidationMessage For="@(() => _form.Name)" />
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="alias-gateway">Galaxy gateway</label>
<InputSelect id="alias-gateway" @bind-Value="_form.DriverInstanceId"
@bind-Value:after="OnGatewayChanged" class="form-select form-select-sm">
<option value="">— pick a gateway —</option>
@foreach (var (id, display, _) in Gateways)
{
<option value="@id">@display</option>
}
</InputSelect>
<ValidationMessage For="@(() => _form.DriverInstanceId)" />
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label" for="alias-dtype">Data type</label>
<InputSelect id="alias-dtype" @bind-Value="_form.DataType" class="form-select form-select-sm">
@foreach (var dt in DataTypes)
{
<option value="@dt">@dt</option>
}
</InputSelect>
</div>
<div class="col-md-6 mb-3">
<label class="form-label" for="alias-access">Access level</label>
<InputSelect id="alias-access" @bind-Value="_form.AccessLevel" class="form-select form-select-sm">
<option value="@TagAccessLevel.Read">Read</option>
<option value="@TagAccessLevel.ReadWrite">ReadWrite</option>
</InputSelect>
</div>
</div>
<div class="mb-3">
<label class="form-label" for="alias-fullname">Galaxy reference</label>
<div class="input-group input-group-sm">
<InputText id="alias-fullname" @bind-Value="_form.FullName" class="form-control form-control-sm mono"
placeholder="DelmiaReceiver_001.DownloadPath" />
<button type="button" class="btn btn-outline-secondary"
@onclick="@(() => _showPicker = true)"
disabled="@string.IsNullOrEmpty(_form.DriverInstanceId)">
Browse Galaxy
</button>
</div>
<div class="form-text">
The <code>tag_name.AttributeName</code> reference this alias surfaces. Type it directly
or pick a gateway above and browse.
</div>
<ValidationMessage For="@(() => _form.FullName)" />
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="Galaxy address"
CurrentAddress="@_form.FullName"
OnPickAddress="@OnAddressPicked">
<GalaxyAddressPickerBody CurrentAddress="@_form.FullName"
CurrentAddressChanged="@((s) => _form.FullName = s)"
GetConfigJson="@(() => _selectedGatewayConfig)" />
</DriverTagPicker>
}
@if (!string.IsNullOrWhiteSpace(_error))
{
<div class="text-danger small mt-2">@_error</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" @onclick="CancelAsync" disabled="@_busy">Cancel</button>
<button type="submit" class="btn btn-primary" disabled="@_busy">
@if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
@(IsNew ? "Create" : "Save changes")
</button>
</div>
</EditForm>
</div>
</div>
</div>
}
@code {
// Same OPC UA built-in type names the TagModal offers, kept in sync.
private static readonly string[] DataTypes =
["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
"Int64", "UInt64", "Float", "Double", "String", "DateTime", "Guid", "ByteString"];
/// <summary>Whether the modal is shown. The host owns this flag.</summary>
[Parameter] public bool Visible { get; set; }
/// <summary><c>true</c> to create a new alias; <c>false</c> to edit <see cref="Existing"/>.</summary>
[Parameter] public bool IsNew { get; set; }
/// <summary>The owning equipment id the created alias binds to (used only on create).</summary>
[Parameter] public string? EquipmentId { get; set; }
/// <summary>The alias being edited, when <see cref="IsNew"/> is <c>false</c>.</summary>
[Parameter] public AliasTagEditDto? Existing { get; set; }
/// <summary>The candidate Galaxy gateways — scoped to the equipment's cluster by the host — as
/// <c>(DriverInstanceId, Display, DriverConfig)</c> triples. The config feeds the live-browse picker.</summary>
[Parameter] public IReadOnlyList<(string DriverInstanceId, string Display, string DriverConfig)> Gateways { get; set; }
= Array.Empty<(string, string, string)>();
/// <summary>Raised after a successful create/save so the host can refresh the equipment's tags and close.</summary>
[Parameter] public EventCallback OnSaved { get; set; }
/// <summary>Raised when the user cancels so the host can close.</summary>
[Parameter] public EventCallback OnCancel { get; set; }
private FormModel _form = new();
private bool _busy;
private string? _error;
private bool _showPicker;
// The selected gateway's DriverConfig JSON — fed to the Galaxy picker so it browses the right gateway.
private string _selectedGatewayConfig = "{}";
// Tracks which open this modal last loaded for, so unrelated Blazor Server re-renders don't rebuild
// _form and clobber in-progress edits. Null while closed.
private string? _loadedKey;
protected override void OnParametersSet()
{
if (!Visible)
{
_loadedKey = null; // closed → next open reloads fresh
return;
}
// Guard against unrelated re-renders. In Blazor Server any live-status push re-invokes
// OnParametersSet; without this the rebuild below would silently discard whatever the operator
// has typed. Only rebuild when the modal OPENS or the target entity CHANGES.
var key = IsNew ? "<new>" : Existing?.TagId;
if (key == _loadedKey) return; // same open, re-render → preserve in-progress form edits
_loadedKey = key;
if (IsNew)
{
_form = new FormModel();
// Auto-select when exactly one gateway, so the picker can browse without an extra click.
if (Gateways.Count == 1) { _form.DriverInstanceId = Gateways[0].DriverInstanceId; }
}
else if (Existing is not null)
{
_form = new FormModel
{
Name = Existing.Name,
DriverInstanceId = Existing.DriverInstanceId,
DataType = Existing.DataType,
AccessLevel = Existing.AccessLevel,
FullName = Existing.FullName,
};
}
_error = null;
_showPicker = false;
SyncSelectedGatewayConfig();
}
// Keeps the picker's browse config aligned with the chosen gateway whenever the operator switches it.
private void OnGatewayChanged() => SyncSelectedGatewayConfig();
private void SyncSelectedGatewayConfig()
{
var match = Gateways.FirstOrDefault(g => g.DriverInstanceId == _form.DriverInstanceId);
_selectedGatewayConfig = string.IsNullOrEmpty(match.DriverConfig) ? "{}" : match.DriverConfig;
}
private void OnAddressPicked(string address) => _form.FullName = address;
private async Task SaveAsync()
{
// Client validation: Name, FullName, and a chosen gateway are all required for a usable alias.
if (string.IsNullOrWhiteSpace(_form.Name)) { _error = "Name is required."; return; }
if (string.IsNullOrWhiteSpace(_form.DriverInstanceId)) { _error = "Pick a Galaxy gateway."; return; }
if (string.IsNullOrWhiteSpace(_form.FullName)) { _error = "A Galaxy reference is required — type it or browse to pick one."; return; }
_busy = true;
_error = null;
try
{
// On create the TagId is system-minted (the alias has no operator-typed id field), matching the
// service's own NewTagId() shape: "TAG-" + 12 hex chars. Ignored on update.
var tagId = IsNew ? $"TAG-{Guid.NewGuid().ToString("N")[..12]}" : Existing!.TagId;
var input = new AliasTagInput(tagId, _form.Name, _form.DriverInstanceId, _form.DataType, _form.AccessLevel, _form.FullName);
var result = IsNew
? await Svc.CreateAliasTagAsync(EquipmentId!, input)
: await Svc.UpdateAliasTagAsync(Existing!.TagId, 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] public string Name { get; set; } = "";
[Required] public string DriverInstanceId { get; set; } = "";
public string DataType { get; set; } = "Float";
public TagAccessLevel AccessLevel { get; set; } = TagAccessLevel.Read;
[Required] public string FullName { get; set; } = "";
}
}
@@ -1,15 +0,0 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
/// <summary>The editable state of a Galaxy alias tag, loaded for the alias edit modal.
/// <paramref name="FullName"/> is parsed out of the tag's TagConfig {"FullName":…}.</summary>
/// <param name="TagId">The tag's stable id (read-only on edit).</param>
/// <param name="Name">The tag name.</param>
/// <param name="DriverInstanceId">The bound Galaxy gateway driver id.</param>
/// <param name="DataType">The OPC UA built-in type name.</param>
/// <param name="AccessLevel">The tag-level access baseline.</param>
/// <param name="FullName">The Galaxy object reference parsed from TagConfig; empty string when absent.</param>
/// <param name="RowVersion">The optimistic-concurrency token last read.</param>
public sealed record AliasTagEditDto(
string TagId, string Name, string DriverInstanceId, string DataType,
TagAccessLevel AccessLevel, string FullName, byte[] RowVersion);
@@ -1,18 +0,0 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
/// <summary>
/// Operator-editable fields for a Galaxy alias tag (an equipment Tag bound to the Galaxy gateway).
/// <paramref name="FullName"/> is the picked Galaxy reference (tag_name.AttributeName); it is stored
/// as the tag's TagConfig <c>{"FullName":…}</c>. AccessLevel defaults to ReadOnly at the call site.
/// </summary>
/// <param name="TagId">Stable unique tag id; only honoured on create.</param>
/// <param name="Name">Tag display name; unique within the owning equipment.</param>
/// <param name="DriverInstanceId">The bound Galaxy gateway driver instance.</param>
/// <param name="DataType">OPC UA built-in type name (Boolean / Int32 / Float / etc.).</param>
/// <param name="AccessLevel">Tag-level OPC UA access baseline (default ReadOnly).</param>
/// <param name="FullName">The Galaxy reference (tag_name.AttributeName) this alias surfaces.</param>
public sealed record AliasTagInput(
string TagId, string Name, string DriverInstanceId, string DataType,
TagAccessLevel AccessLevel, string FullName);
@@ -1,23 +0,0 @@
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// <summary>One relay VirtualTag that will/would become an alias Tag.</summary>
/// <param name="EquipmentId">The owning equipment whose relay virtual tag is converted.</param>
/// <param name="VirtualTagName">The relay virtual tag's name (becomes the alias tag's name).</param>
/// <param name="FullName">The resolved Galaxy reference the alias surfaces (any <c>{{equip}}</c> token expanded).</param>
/// <param name="DataType">The OPC UA built-in type name carried over from the virtual tag.</param>
public sealed record RelayConversionItem(string EquipmentId, string VirtualTagName, string FullName, string DataType);
/// <summary>One relay VirtualTag that cannot be converted, with the reason.</summary>
/// <param name="EquipmentId">The owning equipment whose virtual tag was skipped.</param>
/// <param name="VirtualTagName">The skipped virtual tag's name.</param>
/// <param name="Reason">A human-readable explanation of why the virtual tag was not converted.</param>
public sealed record RelayConversionSkip(string EquipmentId, string VirtualTagName, string Reason);
/// <summary>Outcome of a (dry-run or applied) conversion pass.</summary>
/// <param name="Converted">The relay virtual tags that were (or, on dry-run, would be) converted to alias tags.</param>
/// <param name="Skipped">The candidate virtual tags that could not be converted, each with a reason.</param>
/// <param name="Applied"><c>true</c> when the pass mutated the config; <c>false</c> for a dry-run preview.</param>
public sealed record RelayConversionResult(
IReadOnlyList<RelayConversionItem> Converted,
IReadOnlyList<RelayConversionSkip> Skipped,
bool Applied);