162 lines
5.9 KiB
Plaintext
162 lines
5.9 KiB
Plaintext
@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
|
|
|
|
<h1 class="mb-4">LDAP group → Admin role grants</h1>
|
|
|
|
<div class="alert alert-info small mb-4">
|
|
Maps LDAP groups to Admin UI roles (ConfigViewer / ConfigEditor / FleetAdmin). Control-plane
|
|
only — OPC UA data-path authorization reads <code>NodeAcl</code> 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.
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-end mb-3">
|
|
<button class="btn btn-primary btn-sm" @onclick="StartAdd">Add grant</button>
|
|
</div>
|
|
|
|
@if (_rows is null)
|
|
{
|
|
<p>Loading…</p>
|
|
}
|
|
else if (_rows.Count == 0)
|
|
{
|
|
<p class="text-muted">No role grants defined yet. Without at least one FleetAdmin grant,
|
|
only the bootstrap admin can publish drafts.</p>
|
|
}
|
|
else
|
|
{
|
|
<table class="table table-sm table-hover">
|
|
<thead>
|
|
<tr><th>LDAP group</th><th>Role</th><th>Scope</th><th>Created</th><th>Notes</th><th></th></tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var r in _rows)
|
|
{
|
|
<tr>
|
|
<td><code>@r.LdapGroup</code></td>
|
|
<td><span class="badge bg-secondary">@r.Role</span></td>
|
|
<td>@(r.IsSystemWide ? "Fleet-wide" : $"Cluster: {r.ClusterId}")</td>
|
|
<td class="small">@r.CreatedAtUtc.ToString("yyyy-MM-dd")</td>
|
|
<td class="small text-muted">@r.Notes</td>
|
|
<td><button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteAsync(r.Id)">Revoke</button></td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
}
|
|
|
|
@if (_showForm)
|
|
{
|
|
<div class="card mt-3">
|
|
<div class="card-body">
|
|
<h5>New role grant</h5>
|
|
<div class="row g-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label">LDAP group (DN)</label>
|
|
<input class="form-control" @bind="_group" placeholder="cn=fleet-admin,ou=groups,dc=…"/>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Role</label>
|
|
<select class="form-select" @bind="_role">
|
|
@foreach (var r in Enum.GetValues<AdminRole>())
|
|
{
|
|
<option value="@r">@r</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2 pt-4">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="systemWide" @bind="_isSystemWide"/>
|
|
<label class="form-check-label" for="systemWide">Fleet-wide</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label">Cluster @(_isSystemWide ? "(disabled)" : "")</label>
|
|
<select class="form-select" @bind="_clusterId" disabled="@_isSystemWide">
|
|
<option value="">-- select --</option>
|
|
@if (_clusters is not null)
|
|
{
|
|
@foreach (var c in _clusters)
|
|
{
|
|
<option value="@c.ClusterId">@c.ClusterId</option>
|
|
}
|
|
}
|
|
</select>
|
|
</div>
|
|
<div class="col-12">
|
|
<label class="form-label">Notes (optional)</label>
|
|
<input class="form-control" @bind="_notes"/>
|
|
</div>
|
|
</div>
|
|
@if (_error is not null) { <div class="alert alert-danger mt-3">@_error</div> }
|
|
<div class="mt-3">
|
|
<button class="btn btn-sm btn-primary" @onclick="SaveAsync">Save</button>
|
|
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => _showForm = false">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private IReadOnlyList<LdapGroupRoleMapping>? _rows;
|
|
private List<ServerCluster>? _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();
|
|
}
|
|
}
|