@* TwinCAT address picker: 1. Static ADS variable name (free text) → verbatim (always available). 2. (DriverOperator-gated, when the driver supports online discovery) Browse the ADS symbol tree captured by the universal DiscoveryDriverBrowser. *@ @implements IAsyncDisposable @using Microsoft.AspNetCore.Authorization @using ZB.MOM.WW.OtOpcUa.AdminUI.Browsing @using ZB.MOM.WW.OtOpcUa.Commons.Browsing @inject IBrowserSessionService BrowserService @inject AuthenticationStateProvider AuthState @inject IAuthorizationService AuthorizationService
Full ADS symbol path, e.g. MAIN.fValue or GVL.iCounter.
@if (_canBrowse) {
@if (_token == Guid.Empty) { } else { Browser open } @if (_openError is not null) { @TruncatedError() }
@if (_token != Guid.Empty) {
Snapshot taken at open/refresh time — click Refresh to re-read the controller.
} }
Result: @_built
@code { [Parameter] public string CurrentAddress { get; set; } = ""; [Parameter] public EventCallback CurrentAddressChanged { get; set; } /// Driver type used to open a universal browse session (defaults to TwinCAT). [Parameter] public string DriverType { get; set; } = "TwinCAT"; /// Live accessor for the selected driver's DriverConfig JSON (browse connect config). [Parameter] public Func GetConfigJson { get; set; } = () => "{}"; private string _varName = ""; private string _built = ""; private Guid _token = Guid.Empty; private bool _opening; private bool _canBrowse; private string? _openError; protected override async Task OnInitializedAsync() { var auth = await AuthState.GetAuthenticationStateAsync(); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator); // CanBrowse constructs a throwaway driver — evaluate once, not per render. _canBrowse = authResult.Succeeded && BrowserService.CanBrowse(DriverType, GetConfigJson() ?? "{}"); _built = _varName; await CurrentAddressChanged.InvokeAsync(_built); } private async Task OpenBrowseAsync() { _opening = true; _openError = null; StateHasChanged(); try { var json = GetConfigJson() ?? "{}"; var result = await BrowserService.OpenAsync(DriverType, json, default); if (result.Ok) _token = result.Token; else _openError = result.Message; } finally { _opening = false; StateHasChanged(); } } private async Task RefreshBrowseAsync() { await CloseBrowseAsync(); await OpenBrowseAsync(); } private async Task CloseBrowseAsync() { var t = _token; _token = Guid.Empty; StateHasChanged(); if (t != Guid.Empty) await BrowserService.CloseAsync(t); } private async Task OnTreeSelectAsync(BrowseNode node) { // Folders fire the same callback — commit only on a leaf (the captured NodeId is the ADS symbol path). if (node.Kind != BrowseNodeKind.Leaf) return; _varName = node.NodeId; await OnChangedAsync(); } private async Task OnChangedAsync() { _built = _varName; await CurrentAddressChanged.InvokeAsync(_built); } private string TruncatedError() => _openError is null ? "" : (_openError.Length > 60 ? _openError[..60] + "…" : _openError); public ValueTask DisposeAsync() { if (_token != Guid.Empty) { // Fire-and-forget — don't block circuit teardown on a slow capture. _ = BrowserService.CloseAsync(_token); } return ValueTask.CompletedTask; } }