refactor(adminui): delete alias-tag/relay-converter files (build red until spine stripped)
This commit is contained in:
@@ -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);
|
||||
Reference in New Issue
Block a user