@* OPC UA Client address picker: 1. Manual NodeId entry (always available) 2. (DriverOperator-gated) Browse remote server with live tree, lazy expand. *@ @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
OPC UA NodeId string, e.g. ns=2;s=Channel.Device.Tag or i=1001. Use Browse to navigate the remote server.
@if (_canOperate) {
@if (_token == Guid.Empty) { } else { Browser open } @if (_openError is not null) { }
@if (_token != Guid.Empty) {
} }
Result: @DisplayText.Abbreviate(_built, 64)
@code { [Parameter] public string CurrentAddress { get; set; } = ""; [Parameter] public EventCallback CurrentAddressChanged { get; set; } [Parameter, EditorRequired] public Func GetConfigJson { get; set; } = () => "{}"; private string _nodeId = ""; private string _built = ""; private Guid _token = Guid.Empty; // Set first thing in DisposeAsync; an open that lands afterwards closes its own session. private volatile bool _disposed; private bool _opening; private bool _canOperate; private string? _openError; protected override async Task OnInitializedAsync() { var auth = await AuthState.GetAuthenticationStateAsync(); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, AdminUiPolicies.DriverOperator); _canOperate = authResult.Succeeded; _built = _nodeId; await CurrentAddressChanged.InvokeAsync(_built); } private async Task OpenBrowseAsync() { _opening = true; _openError = null; StateHasChanged(); try { var json = GetConfigJson() ?? "{}"; var result = await BrowserService.OpenAsync("OpcUaClient", json, default); // Host disposed mid-open: close the session nobody will (R04 L4). if (BrowseOpenGuard.CloseIfOrphaned(result, _disposed, BrowserService)) { return; } if (result.Ok) _token = result.Token; else _openError = result.Message; } finally { _opening = false; StateHasChanged(); } } 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) { _nodeId = node.NodeId; await OnChangedAsync(); } private async Task OnChangedAsync() { _built = _nodeId; await CurrentAddressChanged.InvokeAsync(_built); } public ValueTask DisposeAsync() { _disposed = true; if (_token != Guid.Empty) { // Fire-and-forget — don't block circuit teardown on a slow remote. _ = BrowserService.CloseAsync(_token); } return ValueTask.CompletedTask; } }