diff --git a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor index 90687dc..1007b88 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor +++ b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Layout/MainLayout.razor @@ -10,6 +10,7 @@ +
diff --git a/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/RoleGrants.razor b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/RoleGrants.razor new file mode 100644 index 0000000..a5f9a38 --- /dev/null +++ b/src/ZB.MOM.WW.OtOpcUa.Admin/Components/Pages/RoleGrants.razor @@ -0,0 +1,161 @@ +@page "/role-grants" +@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.Configuration.Services +@inject ILdapGroupRoleMappingService RoleSvc +@inject ClusterService ClusterSvc + +

LDAP group → Admin role grants

+ +
+ Maps LDAP groups to Admin UI roles (ConfigViewer / ConfigEditor / FleetAdmin). Control-plane + only — OPC UA data-path authorization reads NodeAcl rows directly and is + unaffected by these mappings (see decision #150). A fleet-wide grant applies across every + cluster; a cluster-scoped grant only binds within the named cluster. The same LDAP group + may hold different roles on different clusters. +
+ +
+ +
+ +@if (_rows is null) +{ +

Loading…

+} +else if (_rows.Count == 0) +{ +

No role grants defined yet. Without at least one FleetAdmin grant, + only the bootstrap admin can publish drafts.

+} +else +{ + + + + + + @foreach (var r in _rows) + { + + + + + + + + + } + +
LDAP groupRoleScopeCreatedNotes
@r.LdapGroup@r.Role@(r.IsSystemWide ? "Fleet-wide" : $"Cluster: {r.ClusterId}")@r.CreatedAtUtc.ToString("yyyy-MM-dd")@r.Notes
+} + +@if (_showForm) +{ +
+
+
New role grant
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ @if (_error is not null) {
@_error
} +
+ + +
+
+
+} + +@code { + private IReadOnlyList? _rows; + private List? _clusters; + private bool _showForm; + private string _group = string.Empty; + private AdminRole _role = AdminRole.ConfigViewer; + private bool _isSystemWide; + private string _clusterId = string.Empty; + private string? _notes; + private string? _error; + + protected override async Task OnInitializedAsync() => await ReloadAsync(); + + private async Task ReloadAsync() + { + _rows = await RoleSvc.ListAllAsync(CancellationToken.None); + _clusters = await ClusterSvc.ListAsync(CancellationToken.None); + } + + private void StartAdd() + { + _group = string.Empty; + _role = AdminRole.ConfigViewer; + _isSystemWide = false; + _clusterId = string.Empty; + _notes = null; + _error = null; + _showForm = true; + } + + private async Task SaveAsync() + { + _error = null; + try + { + var row = new LdapGroupRoleMapping + { + LdapGroup = _group.Trim(), + Role = _role, + IsSystemWide = _isSystemWide, + ClusterId = _isSystemWide ? null : (string.IsNullOrWhiteSpace(_clusterId) ? null : _clusterId), + Notes = string.IsNullOrWhiteSpace(_notes) ? null : _notes, + }; + await RoleSvc.CreateAsync(row, CancellationToken.None); + _showForm = false; + await ReloadAsync(); + } + catch (Exception ex) { _error = ex.Message; } + } + + private async Task DeleteAsync(Guid id) + { + await RoleSvc.DeleteAsync(id, CancellationToken.None); + await ReloadAsync(); + } +} diff --git a/src/ZB.MOM.WW.OtOpcUa.Admin/Program.cs b/src/ZB.MOM.WW.OtOpcUa.Admin/Program.cs index 1214033..fa9eaae 100644 --- a/src/ZB.MOM.WW.OtOpcUa.Admin/Program.cs +++ b/src/ZB.MOM.WW.OtOpcUa.Admin/Program.cs @@ -48,6 +48,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Cert-trust management — reads the OPC UA server's PKI store root so rejected client certs // can be promoted to trusted via the Admin UI. Singleton: no per-request state, just