@page "/role-grants"
@attribute [Microsoft.AspNetCore.Authorization.Authorize(Policy = "CanPublish")]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Web
@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.Configuration.Services
@inject ILdapGroupRoleMappingService RoleSvc
@inject ClusterService ClusterSvc
@inject AclChangeNotifier Notifier
@inject NavigationManager Nav
@inject AdminHubConnectionFactory HubFactory
@implements IAsyncDisposable
LDAP group → Admin role grants
@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
{
Grants
| LDAP group | Role | Scope | Created | Notes | |
@foreach (var r in _rows)
{
| @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) {
}
}
@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);
await Notifier.NotifyRoleGrantsChangedAsync(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 Notifier.NotifyRoleGrantsChangedAsync(CancellationToken.None);
await ReloadAsync();
}
private HubConnection? _hub;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender || _hub is not null) return;
_hub = HubFactory.Create("/hubs/fleet");
_hub.On("RoleGrantsChanged", async _ =>
{
await ReloadAsync();
await InvokeAsync(StateHasChanged);
});
// Best-effort: FleetStatusHub requires an authenticated caller, and the server-side
// HubConnection cannot forward the browser auth cookie — swallow connect failures so
// the page still renders. Live role-grant updates degrade.
try
{
await _hub.StartAsync();
await _hub.SendAsync("SubscribeFleet");
}
catch
{
// best-effort live updates — see comment above
}
}
public async ValueTask DisposeAsync()
{
if (_hub is not null) { await _hub.DisposeAsync(); _hub = null; }
}
}