feat(adminui): Build-address pickers in the protocol-driver Tag editors

This commit is contained in:
Joseph Doherty
2026-06-16 16:54:29 -04:00
parent c00a547191
commit 9a8b8ff6f6
6 changed files with 176 additions and 10 deletions
@@ -1,23 +1,43 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Driver.AbCip
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
<div class="row g-2">
<div class="col-md-4"><label class="form-label">Device host</label>
<input class="form-control form-control-sm mono" value="@_m.DeviceHostAddress" placeholder="ab://gateway[:port]/cip-path" @onchange="@(e => Update(() => _m.DeviceHostAddress = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5"><label class="form-label">Tag path</label>
<input class="form-control form-control-sm mono" value="@_m.TagPath" placeholder="e.g. Motor1.Speed" @onchange="@(e => Update(() => _m.TagPath = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5">
<label class="form-label">Tag path</label>
<div class="input-group input-group-sm">
<input class="form-control form-control-sm mono" value="@_m.TagPath" placeholder="e.g. Motor1.Speed" @onchange="@(e => Update(() => _m.TagPath = e.Value?.ToString() ?? string.Empty))" />
<button type="button" class="btn btn-outline-secondary" @onclick="@(() => _showPicker = true)">Build</button>
</div>
</div>
<div class="col-md-3"><label class="form-label">Data type</label>
<select class="form-select form-select-sm" value="@_m.DataType" @onchange="@(e => Update(() => _m.DataType = ParseEnum(e.Value, AbCipDataType.DInt)))">
@foreach (var v in Enum.GetValues<AbCipDataType>()) { <option value="@v">@v</option> }
</select></div>
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="AB CIP address builder"
CurrentAddress="@_pickerAddress"
OnPickAddress="@OnAddressPicked">
<AbCipAddressPickerBody CurrentAddress="@_pickerAddress"
CurrentAddressChanged="@((s) => _pickerAddress = s)" />
</DriverTagPicker>
}
@code {
[Parameter] public string? ConfigJson { get; set; }
[Parameter] public EventCallback<string> ConfigJsonChanged { get; set; }
private AbCipTagConfigModel _m = new();
private string? _lastConfigJson;
private bool _showPicker;
private string _pickerAddress = "";
// Re-parse only when the incoming JSON actually changes, so an unrelated parent re-render
// (Blazor Server live-status pushes do this) can't reset the user's in-progress edits.
@@ -37,4 +57,10 @@
apply();
await ConfigJsonChanged.InvokeAsync(_m.ToJson());
}
private async Task OnAddressPicked(string address)
{
if (!string.IsNullOrWhiteSpace(address))
await Update(() => _m.TagPath = address);
}
}
@@ -1,23 +1,43 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
<div class="row g-2">
<div class="col-md-4"><label class="form-label">Device host</label>
<input class="form-control form-control-sm mono" value="@_m.DeviceHostAddress" placeholder="ab://host" @onchange="@(e => Update(() => _m.DeviceHostAddress = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5"><label class="form-label">Address</label>
<input class="form-control form-control-sm mono" value="@_m.Address" placeholder="e.g. N7:0, B3:0/0" @onchange="@(e => Update(() => _m.Address = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5">
<label class="form-label">Address</label>
<div class="input-group input-group-sm">
<input class="form-control form-control-sm mono" value="@_m.Address" placeholder="e.g. N7:0, B3:0/0" @onchange="@(e => Update(() => _m.Address = e.Value?.ToString() ?? string.Empty))" />
<button type="button" class="btn btn-outline-secondary" @onclick="@(() => _showPicker = true)">Build</button>
</div>
</div>
<div class="col-md-3"><label class="form-label">Data type</label>
<select class="form-select form-select-sm" value="@_m.DataType" @onchange="@(e => Update(() => _m.DataType = ParseEnum(e.Value, AbLegacyDataType.Int)))">
@foreach (var v in Enum.GetValues<AbLegacyDataType>()) { <option value="@v">@v</option> }
</select></div>
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="AB Legacy address builder"
CurrentAddress="@_pickerAddress"
OnPickAddress="@OnAddressPicked">
<AbLegacyAddressPickerBody CurrentAddress="@_pickerAddress"
CurrentAddressChanged="@((s) => _pickerAddress = s)" />
</DriverTagPicker>
}
@code {
[Parameter] public string? ConfigJson { get; set; }
[Parameter] public EventCallback<string> ConfigJsonChanged { get; set; }
private AbLegacyTagConfigModel _m = new();
private string? _lastConfigJson;
private bool _showPicker;
private string _pickerAddress = "";
// Re-parse only when the incoming JSON actually changes, so an unrelated parent re-render
// (Blazor Server live-status pushes do this) can't reset the user's in-progress edits.
@@ -37,4 +57,10 @@
apply();
await ConfigJsonChanged.InvokeAsync(_m.ToJson());
}
private async Task OnAddressPicked(string address)
{
if (!string.IsNullOrWhiteSpace(address))
await Update(() => _m.Address = address);
}
}
@@ -1,23 +1,43 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Driver.FOCAS
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
<div class="row g-2">
<div class="col-md-4"><label class="form-label">Device host</label>
<input class="form-control form-control-sm mono" value="@_m.DeviceHostAddress" placeholder="host[:port]" @onchange="@(e => Update(() => _m.DeviceHostAddress = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5"><label class="form-label">Address</label>
<input class="form-control form-control-sm mono" value="@_m.Address" placeholder="e.g. X0.0, R100, PARAM:1815/0, MACRO:500" @onchange="@(e => Update(() => _m.Address = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5">
<label class="form-label">Address</label>
<div class="input-group input-group-sm">
<input class="form-control form-control-sm mono" value="@_m.Address" placeholder="e.g. X0.0, R100, PARAM:1815/0, MACRO:500" @onchange="@(e => Update(() => _m.Address = e.Value?.ToString() ?? string.Empty))" />
<button type="button" class="btn btn-outline-secondary" @onclick="@(() => _showPicker = true)">Build</button>
</div>
</div>
<div class="col-md-3"><label class="form-label">Data type</label>
<select class="form-select form-select-sm" value="@_m.DataType" @onchange="@(e => Update(() => _m.DataType = ParseEnum(e.Value, FocasDataType.Int32)))">
@foreach (var v in Enum.GetValues<FocasDataType>()) { <option value="@v">@v</option> }
</select></div>
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="FOCAS address builder"
CurrentAddress="@_pickerAddress"
OnPickAddress="@OnAddressPicked">
<FOCASAddressPickerBody CurrentAddress="@_pickerAddress"
CurrentAddressChanged="@((s) => _pickerAddress = s)" />
</DriverTagPicker>
}
@code {
[Parameter] public string? ConfigJson { get; set; }
[Parameter] public EventCallback<string> ConfigJsonChanged { get; set; }
private FocasTagConfigModel _m = new();
private string? _lastConfigJson;
private bool _showPicker;
private string _pickerAddress = "";
// Re-parse only when the incoming JSON actually changes, so an unrelated parent re-render
// (Blazor Server live-status pushes do this) can't reset the user's in-progress edits.
@@ -37,4 +57,10 @@
apply();
await ConfigJsonChanged.InvokeAsync(_m.ToJson());
}
private async Task OnAddressPicked(string address)
{
if (!string.IsNullOrWhiteSpace(address))
await Update(() => _m.Address = address);
}
}
@@ -1,5 +1,7 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Driver.Modbus
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
<div class="row g-2">
<div class="col-md-4"><label class="form-label">Region</label>
@@ -20,14 +22,30 @@
<input type="number" class="form-control form-control-sm" value="@_m.BitIndex" @onchange="@(e => Update(() => _m.BitIndex = ParseInt(e.Value)))" /></div>
<div class="col-md-2"><label class="form-label">String len</label>
<input type="number" class="form-control form-control-sm" value="@_m.StringLength" @onchange="@(e => Update(() => _m.StringLength = ParseInt(e.Value)))" /></div>
<div class="col-12 mt-1">
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="@(() => _showPicker = true)">Build address</button>
</div>
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="Modbus address builder"
CurrentAddress="@_pickerAddress"
OnPickAddress="@OnAddressPicked">
<ModbusAddressPickerBody CurrentAddress="@_pickerAddress"
CurrentAddressChanged="@((s) => _pickerAddress = s)" />
</DriverTagPicker>
}
@code {
[Parameter] public string? ConfigJson { get; set; }
[Parameter] public EventCallback<string> ConfigJsonChanged { get; set; }
private ModbusTagConfigModel _m = new();
private string? _lastConfigJson;
private bool _showPicker;
private string _pickerAddress = "";
// Re-parse only when the incoming JSON actually changes, so an unrelated parent re-render
// (Blazor Server live-status pushes do this) can't reset the user's in-progress edits.
@@ -49,4 +67,22 @@
apply();
await ConfigJsonChanged.InvokeAsync(_m.ToJson());
}
// Parse the builder string (e.g. "4x00001-1") back into Region + Address fields.
// Format: {prefix}{offset:00000}-{length} where prefix is 0x/1x/3x/4x.
private async Task OnAddressPicked(string address)
{
if (string.IsNullOrWhiteSpace(address)) return;
var region = address.StartsWith("0x") ? ModbusRegion.Coils
: address.StartsWith("1x") ? ModbusRegion.DiscreteInputs
: address.StartsWith("3x") ? ModbusRegion.InputRegisters
: ModbusRegion.HoldingRegisters;
var rest = address.Length > 2 ? address[2..] : address;
var dashIdx = rest.IndexOf('-');
var offsetStr = dashIdx >= 0 ? rest[..dashIdx] : rest;
if (int.TryParse(offsetStr, out var offset))
{
await Update(() => { _m.Region = region; _m.Address = offset; });
}
}
}
@@ -1,9 +1,16 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Driver.S7
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
<div class="row g-2">
<div class="col-md-5"><label class="form-label">Address</label>
<input class="form-control form-control-sm mono" value="@_m.Address" placeholder="e.g. DB1.DBW0, M0.0, I0.0, QD4" @onchange="@(e => Update(() => _m.Address = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5">
<label class="form-label">Address</label>
<div class="input-group input-group-sm">
<input class="form-control form-control-sm mono" value="@_m.Address" placeholder="e.g. DB1.DBW0, M0.0, I0.0, QD4" @onchange="@(e => Update(() => _m.Address = e.Value?.ToString() ?? string.Empty))" />
<button type="button" class="btn btn-outline-secondary" @onclick="@(() => _showPicker = true)">Build</button>
</div>
</div>
<div class="col-md-4"><label class="form-label">Data type</label>
<select class="form-select form-select-sm" value="@_m.DataType" @onchange="@(e => Update(() => _m.DataType = ParseEnum(e.Value, S7DataType.Int16)))">
@foreach (var v in Enum.GetValues<S7DataType>()) { <option value="@v">@v</option> }
@@ -12,12 +19,25 @@
<input type="number" class="form-control form-control-sm" value="@_m.StringLength" @onchange="@(e => Update(() => _m.StringLength = ParseInt(e.Value)))" /></div>
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="S7 address builder"
CurrentAddress="@_pickerAddress"
OnPickAddress="@OnAddressPicked">
<S7AddressPickerBody CurrentAddress="@_pickerAddress"
CurrentAddressChanged="@((s) => _pickerAddress = s)" />
</DriverTagPicker>
}
@code {
[Parameter] public string? ConfigJson { get; set; }
[Parameter] public EventCallback<string> ConfigJsonChanged { get; set; }
private S7TagConfigModel _m = new();
private string? _lastConfigJson;
private bool _showPicker;
private string _pickerAddress = "";
// Re-parse only when the incoming JSON actually changes, so an unrelated parent re-render
// (Blazor Server live-status pushes do this) can't reset the user's in-progress edits.
@@ -39,4 +59,10 @@
apply();
await ConfigJsonChanged.InvokeAsync(_m.ToJson());
}
private async Task OnAddressPicked(string address)
{
if (!string.IsNullOrWhiteSpace(address))
await Update(() => _m.Address = address);
}
}
@@ -1,23 +1,43 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors
@using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers
@using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers
<div class="row g-2">
<div class="col-md-4"><label class="form-label">Device host</label>
<input class="form-control form-control-sm mono" value="@_m.DeviceHostAddress" placeholder="AmsNetId[:port]" @onchange="@(e => Update(() => _m.DeviceHostAddress = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5"><label class="form-label">Symbol path</label>
<input class="form-control form-control-sm mono" value="@_m.SymbolPath" placeholder="e.g. MAIN.bStart, GVL.Counter" @onchange="@(e => Update(() => _m.SymbolPath = e.Value?.ToString() ?? string.Empty))" /></div>
<div class="col-md-5">
<label class="form-label">Symbol path</label>
<div class="input-group input-group-sm">
<input class="form-control form-control-sm mono" value="@_m.SymbolPath" placeholder="e.g. MAIN.bStart, GVL.Counter" @onchange="@(e => Update(() => _m.SymbolPath = e.Value?.ToString() ?? string.Empty))" />
<button type="button" class="btn btn-outline-secondary" @onclick="@(() => _showPicker = true)">Build</button>
</div>
</div>
<div class="col-md-3"><label class="form-label">Data type</label>
<select class="form-select form-select-sm" value="@_m.DataType" @onchange="@(e => Update(() => _m.DataType = ParseEnum(e.Value, TwinCATDataType.DInt)))">
@foreach (var v in Enum.GetValues<TwinCATDataType>()) { <option value="@v">@v</option> }
</select></div>
</div>
@if (_showPicker)
{
<DriverTagPicker @bind-Visible="_showPicker"
Title="TwinCAT address builder"
CurrentAddress="@_pickerAddress"
OnPickAddress="@OnAddressPicked">
<TwinCATAddressPickerBody CurrentAddress="@_pickerAddress"
CurrentAddressChanged="@((s) => _pickerAddress = s)" />
</DriverTagPicker>
}
@code {
[Parameter] public string? ConfigJson { get; set; }
[Parameter] public EventCallback<string> ConfigJsonChanged { get; set; }
private TwinCATTagConfigModel _m = new();
private string? _lastConfigJson;
private bool _showPicker;
private string _pickerAddress = "";
// Re-parse only when the incoming JSON actually changes, so an unrelated parent re-render
// (Blazor Server live-status pushes do this) can't reset the user's in-progress edits.
@@ -37,4 +57,10 @@
apply();
await ConfigJsonChanged.InvokeAsync(_m.ToJson());
}
private async Task OnAddressPicked(string address)
{
if (!string.IsNullOrWhiteSpace(address))
await Update(() => _m.SymbolPath = address);
}
}