feat(adminui): wire Galaxy picker to live browser + attribute side-panel
This commit is contained in:
+2
-1
@@ -58,7 +58,8 @@ else
|
||||
CurrentAddress="@_pickedAddress"
|
||||
OnPickAddress="@OnAddressPicked">
|
||||
<GalaxyAddressPickerBody CurrentAddress="@_pickedAddress"
|
||||
CurrentAddressChanged="@((s) => _pickedAddress = s)" />
|
||||
CurrentAddressChanged="@((s) => _pickedAddress = s)"
|
||||
GetConfigJson="@SerializeCurrentConfig" />
|
||||
</DriverTagPicker>
|
||||
|
||||
@* mxaccessgw connection *@
|
||||
|
||||
+151
-9
@@ -1,10 +1,15 @@
|
||||
@* Static Galaxy address builder: tag_name.AttributeName free text → verbatim.
|
||||
Live Galaxy browse deferred to a follow-up phase. *@
|
||||
|
||||
<div class="alert alert-info py-2 px-3 mb-3 small">
|
||||
<strong>Note:</strong> Live Galaxy browse is deferred to a follow-up phase.
|
||||
Enter the tag and attribute name manually below.
|
||||
</div>
|
||||
@* Galaxy address picker:
|
||||
1. Manual tag/attribute entry (always available).
|
||||
2. (DriverOperator-gated) Live browse: object tree on the left,
|
||||
attribute side-panel on the right. Clicking an attribute commits
|
||||
tag_name.AttributeName into the result. *@
|
||||
@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
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-5">
|
||||
@@ -21,6 +26,70 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (_canOperate)
|
||||
{
|
||||
<div class="mt-3 d-flex align-items-center gap-2">
|
||||
@if (_token == Guid.Empty)
|
||||
{
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" disabled="@_opening"
|
||||
@onclick="OpenBrowseAsync">
|
||||
@if (_opening) { <span class="spinner-border spinner-border-sm me-1"></span> }
|
||||
Browse galaxy
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="chip chip-ok">Browser open</span>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" @onclick="CloseBrowseAsync">Close</button>
|
||||
}
|
||||
@if (_openError is not null) { <span class="chip chip-bad" title="@_openError">@TruncatedError()</span> }
|
||||
</div>
|
||||
|
||||
@if (_token != Guid.Empty)
|
||||
{
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-md-7">
|
||||
<label class="form-label small">Objects</label>
|
||||
<DriverBrowseTree SessionToken="_token" OnNodeSelected="OnObjectSelectAsync"
|
||||
SelectedNodeId="_tagName" />
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label small">Attributes of @(string.IsNullOrEmpty(_tagName) ? "—" : _tagName)</label>
|
||||
<div class="border rounded p-2" style="max-height:420px; overflow:auto; min-height:240px">
|
||||
@if (_attrsLoading)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||
}
|
||||
else if (_attrsError is not null)
|
||||
{
|
||||
<span class="text-danger small">@_attrsError</span>
|
||||
}
|
||||
else if (_attrs is null)
|
||||
{
|
||||
<span class="text-muted small">Pick an object.</span>
|
||||
}
|
||||
else if (_attrs.Count == 0)
|
||||
{
|
||||
<span class="text-muted small">No attributes.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var a in _attrs)
|
||||
{
|
||||
var sel = _attributeName == a.Name ? "bg-primary-subtle" : "";
|
||||
<div class="d-flex justify-content-between align-items-center py-1 @sel"
|
||||
style="cursor:pointer" @onclick="@(() => SelectAttributeAsync(a))">
|
||||
<span class="mono small">@a.Name</span>
|
||||
<span class="text-muted small">@a.DriverDataType@(a.IsArray ? "[]" : "") · @a.SecurityClass</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="mt-3">
|
||||
<span class="text-muted small">Result:</span>
|
||||
<code class="mono ms-2">@_built</code>
|
||||
@@ -29,15 +98,77 @@
|
||||
@code {
|
||||
[Parameter] public string CurrentAddress { get; set; } = "";
|
||||
[Parameter] public EventCallback<string> CurrentAddressChanged { get; set; }
|
||||
[Parameter, EditorRequired] public Func<string> GetConfigJson { get; set; } = () => "{}";
|
||||
|
||||
private string _tagName = "";
|
||||
private string _attributeName = "";
|
||||
private string _built = "";
|
||||
private Guid _token = Guid.Empty;
|
||||
private bool _opening;
|
||||
private bool _attrsLoading;
|
||||
private bool _canOperate;
|
||||
private string? _openError;
|
||||
private string? _attrsError;
|
||||
private IReadOnlyList<AttributeInfo>? _attrs;
|
||||
|
||||
protected override void OnInitialized()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var auth = await AuthState.GetAuthenticationStateAsync();
|
||||
var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator");
|
||||
_canOperate = authResult.Succeeded;
|
||||
_built = Build();
|
||||
_ = CurrentAddressChanged.InvokeAsync(_built);
|
||||
await CurrentAddressChanged.InvokeAsync(_built);
|
||||
}
|
||||
|
||||
private async Task OpenBrowseAsync()
|
||||
{
|
||||
_opening = true; _openError = null; StateHasChanged();
|
||||
try
|
||||
{
|
||||
var json = GetConfigJson() ?? "{}";
|
||||
var result = await BrowserService.OpenAsync("Galaxy", json, default);
|
||||
if (result.Ok) _token = result.Token;
|
||||
else _openError = result.Message;
|
||||
}
|
||||
finally { _opening = false; StateHasChanged(); }
|
||||
}
|
||||
|
||||
private async Task CloseBrowseAsync()
|
||||
{
|
||||
var t = _token;
|
||||
_token = Guid.Empty;
|
||||
_attrs = null;
|
||||
StateHasChanged();
|
||||
if (t != Guid.Empty) await BrowserService.CloseAsync(t);
|
||||
}
|
||||
|
||||
private async Task OnObjectSelectAsync(BrowseNode node)
|
||||
{
|
||||
_tagName = node.NodeId;
|
||||
_attributeName = "";
|
||||
_attrs = null;
|
||||
_attrsLoading = true;
|
||||
_attrsError = null;
|
||||
StateHasChanged();
|
||||
try
|
||||
{
|
||||
_attrs = await BrowserService.AttributesAsync(_token, _tagName, default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_attrsError = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_attrsLoading = false;
|
||||
await OnChangedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SelectAttributeAsync(AttributeInfo a)
|
||||
{
|
||||
_attributeName = a.Name;
|
||||
await OnChangedAsync();
|
||||
}
|
||||
|
||||
private async Task OnChangedAsync()
|
||||
@@ -54,4 +185,15 @@
|
||||
return _tagName;
|
||||
return $"{_tagName}.{_attributeName}";
|
||||
}
|
||||
|
||||
private string TruncatedError() =>
|
||||
_openError is null ? "" : (_openError.Length > 60 ? _openError[..60] + "…" : _openError);
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_token != Guid.Empty)
|
||||
{
|
||||
try { await BrowserService.CloseAsync(_token); } catch { /* circuit teardown */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user