@page "/apikeys" @attribute [Authorize(Policy = AdminUiPolicies.FleetAdmin)] @rendermode RenderMode.InteractiveServer @using ZB.MOM.WW.OtOpcUa.Configuration.Entities @using ZB.MOM.WW.OtOpcUa.Core.Authorization @using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Raw @inject IApiKeyAdminService Keys

OPC UA API keys

@if (_created is not null) { } @if (!string.IsNullOrWhiteSpace(_error)) { }
Create a key
Keys
@if (_keys is null) {
Loading…
} else if (_keys.Count == 0) {
No keys yet. Until one exists, only anonymous sessions can connect.
} else { @foreach (var k in _keys) { }
Name Principal Key id Status Created (UTC) Expires (UTC)
@k.Name @k.Principal @k.ApiKeyId @if (!k.Enabled) { revoked } else if (k.ExpiresUtc is { } e && e <= DateTime.UtcNow) { expired } else { active } @k.CreatedUtc.ToString("yyyy-MM-dd HH:mm") @(k.ExpiresUtc?.ToString("yyyy-MM-dd") ?? "—")
}
@code { private IReadOnlyList? _keys; private string _newName = ""; private string _newPrincipal = ""; private DateTime? _newExpires; private string? _created; private string? _error; private bool _busy; // Delete is irreversible (the key stops authenticating at once), so it goes through a confirm dialog // that names the key rather than firing from the row button. private bool _confirmDeleteVisible; private string? _confirmDeleteId; private string _confirmDeleteMessage = ""; protected override async Task OnInitializedAsync() => await ReloadAsync(); private async Task ReloadAsync() { try { _keys = await Keys.ListAsync(default); // The one-time banner is only meaningful while its key exists; the credential is // otua__, so drop it once no listed key matches (deleted here or elsewhere). if (_created is not null && !_keys.Any(k => _created.StartsWith($"otua_{k.ApiKeyId}_", StringComparison.Ordinal))) { _created = null; } } catch (Exception ex) { _error = ex.Message; _keys = []; } } private async Task CreateAsync() { _busy = true; _error = null; _created = null; try { // Shown once, held only in this component's state. There is no way to recover it afterwards // — the server stores a hash — so it is deliberately not written anywhere else. _created = await Keys.CreateAsync( _newName, _newPrincipal, // A date-only input means midnight; treat it as UTC rather than local so an operator // near a timezone boundary does not get a key that expired before it was issued. _newExpires is { } d ? DateTime.SpecifyKind(d, DateTimeKind.Utc) : null, notes: null, default); _newName = ""; _newPrincipal = ""; _newExpires = null; await ReloadAsync(); } catch (Exception ex) { _error = ex.Message; } finally { _busy = false; } } private async Task SetEnabledAsync(string apiKeyId, bool enabled) { _busy = true; _error = null; try { await Keys.SetEnabledAsync(apiKeyId, enabled, default); await ReloadAsync(); } catch (Exception ex) { _error = ex.Message; } finally { _busy = false; } } private void RequestDelete(ApiKey key) { _confirmDeleteId = key.ApiKeyId; _confirmDeleteMessage = $"Permanently delete API key '{key.Name}' ({key.ApiKeyId})? Clients using it will stop authenticating immediately. This cannot be undone."; _confirmDeleteVisible = true; } private async Task ConfirmDeleteAsync() { if (_confirmDeleteId is { } id) { await DeleteAsync(id); } } private async Task DeleteAsync(string apiKeyId) { _busy = true; _error = null; try { await Keys.DeleteAsync(apiKeyId, default); await ReloadAsync(); } catch (Exception ex) { _error = ex.Message; } finally { _busy = false; } } }