@using Microsoft.AspNetCore.SignalR.Client @using ZB.MOM.WW.OtOpcUa.Admin.Hubs @using ZB.MOM.WW.OtOpcUa.Admin.Services @using ZB.MOM.WW.OtOpcUa.Configuration.Entities @using ZB.MOM.WW.OtOpcUa.Configuration.Enums @using ZB.MOM.WW.OtOpcUa.Core.Authorization @inject NodeAclService AclSvc @inject PermissionProbeService ProbeSvc @inject NavigationManager Nav @implements IAsyncDisposable

Access-control grants

@if (_acls is null) {

Loading…

} else if (_acls.Count == 0) {

No ACL grants in this draft. Publish will result in a cluster with no external access.

} else { @foreach (var a in _acls) { }
LDAP groupScopeScope IDPermissions
@a.LdapGroup @a.ScopeKind @(a.ScopeId ?? "-") @a.PermissionFlags
} @* Probe-this-permission — task #196 slice 1 *@
Probe this permission Ask the trie "if LDAP group X asks for permission Y on node Z, would it be granted?" — answers the same way the live server does at request time.
@if (_probeResult is not null) { @if (_probeResult.Granted) { Granted } else { Denied } Required @_probeResult.Required, Effective @_probeResult.Effective }
@if (_probeResult is not null && _probeResult.Matches.Count > 0) { @foreach (var m in _probeResult.Matches) { }
LDAP group matchedLevelFlags contributed
@m.LdapGroup @m.Scope @m.PermissionFlags
} else if (_probeResult is not null) {
No matching grants for this (group, scope) — effective permission is None.
}
@if (_showForm) {
@if (_error is not null) {
@_error
}
} @code { [Parameter] public long GenerationId { get; set; } [Parameter] public string ClusterId { get; set; } = string.Empty; private List? _acls; private bool _showForm; private string _group = string.Empty; private NodeAclScopeKind _scopeKind = NodeAclScopeKind.Cluster; private string _scopeId = string.Empty; private string _preset = "Read"; private string? _error; // Probe-this-permission state private string _probeGroup = string.Empty; private string _probeNamespaceId = string.Empty; private string _probeUnsAreaId = string.Empty; private string _probeUnsLineId = string.Empty; private string _probeEquipmentId = string.Empty; private string _probeTagId = string.Empty; private NodePermissions _probePermission = NodePermissions.Read; private PermissionProbeResult? _probeResult; private bool _probing; private async Task RunProbeAsync() { if (string.IsNullOrWhiteSpace(_probeGroup)) { _probeResult = null; return; } _probing = true; try { var scope = new NodeScope { ClusterId = ClusterId, NamespaceId = NullIfBlank(_probeNamespaceId), UnsAreaId = NullIfBlank(_probeUnsAreaId), UnsLineId = NullIfBlank(_probeUnsLineId), EquipmentId = NullIfBlank(_probeEquipmentId), TagId = NullIfBlank(_probeTagId), Kind = NodeHierarchyKind.Equipment, }; _probeResult = await ProbeSvc.ProbeAsync(GenerationId, _probeGroup.Trim(), scope, _probePermission, CancellationToken.None); } finally { _probing = false; } } private static string? NullIfBlank(string s) => string.IsNullOrWhiteSpace(s) ? null : s; private HubConnection? _hub; protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender || _hub is not null) return; _hub = new HubConnectionBuilder() .WithUrl(Nav.ToAbsoluteUri("/hubs/fleet-status")) .WithAutomaticReconnect() .Build(); _hub.On("NodeAclChanged", async msg => { if (msg.ClusterId != ClusterId || msg.GenerationId != GenerationId) return; _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); await InvokeAsync(StateHasChanged); }); await _hub.StartAsync(); await _hub.SendAsync("SubscribeCluster", ClusterId); } public async ValueTask DisposeAsync() { if (_hub is not null) { await _hub.DisposeAsync(); _hub = null; } } protected override async Task OnParametersSetAsync() => _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); private NodePermissions ResolvePreset() => _preset switch { "Read" => NodePermissions.Browse | NodePermissions.Read, "WriteOperate" => NodePermissions.Browse | NodePermissions.Read | NodePermissions.WriteOperate, "Engineer" => NodePermissions.Browse | NodePermissions.Read | NodePermissions.WriteTune | NodePermissions.WriteConfigure, "AlarmAck" => NodePermissions.Browse | NodePermissions.Read | NodePermissions.AlarmRead | NodePermissions.AlarmAcknowledge, "Full" => unchecked((NodePermissions)(-1)), _ => NodePermissions.Browse | NodePermissions.Read, }; private async Task SaveAsync() { _error = null; if (string.IsNullOrWhiteSpace(_group)) { _error = "LDAP group is required"; return; } var scopeId = _scopeKind == NodeAclScopeKind.Cluster ? null : string.IsNullOrWhiteSpace(_scopeId) ? null : _scopeId; if (_scopeKind != NodeAclScopeKind.Cluster && scopeId is null) { _error = $"ScopeId required for {_scopeKind}"; return; } try { await AclSvc.GrantAsync(GenerationId, ClusterId, _group, _scopeKind, scopeId, ResolvePreset(), notes: null, CancellationToken.None); _group = string.Empty; _scopeId = string.Empty; _showForm = false; _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); } catch (Exception ex) { _error = ex.Message; } } private async Task RevokeAsync(Guid rowId) { await AclSvc.RevokeAsync(rowId, CancellationToken.None); _acls = await AclSvc.ListAsync(GenerationId, CancellationToken.None); } }