@page "/admin/api-keys/create" @page "/admin/api-keys/{KeyId}/edit" @using ZB.MOM.WW.ScadaBridge.Security @using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi @using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories @using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Security @attribute [Authorize(Policy = AuthorizationPolicies.RequireAdmin)] @inject IInboundApiKeyAdmin ApiKeyAdmin @inject IInboundApiRepository InboundApiRepository @inject NavigationManager NavigationManager @inject IJSRuntime JS
← Back ยท

@if (_saved) { @:API Key Created } else if (IsEditMode) { @:Edit API Key } else { @:Add API Key }

@* Bundle D (#23 M7-T12) drill-in: deep-link into the central Audit Log pre-filtered to this API key's inbound calls. Inbound audit rows record the key Name as Actor and live on the ApiInbound channel. *@ @if (IsEditMode && !string.IsNullOrWhiteSpace(_formName)) { Recent audit activity }
@if (_loading) { } else if (_saved && _newlyCreatedToken != null) {
New API Key Created
Key ID: @_newlyCreatedKeyId
@_newlyCreatedToken
Save this token now โ€” it will not be shown again.
Back to API Keys } else if (_errorMessage != null) {
@_errorMessage
} else {
@* Name is fixed on edit โ€” the seam has no rename. *@
@if (_allMethods.Count == 0) {
No API methods configured. Create one to grant access.
} else {
@foreach (var method in _allMethods.OrderBy(m => m.Name)) { var checkboxId = $"method-access-{method.Id}";
}
Callers using this key can invoke any checked method. At least one is required.
}
@if (_formError != null) {
@_formError
}
}
@code { // Inbound-API key re-arch (C3): this form drives the IInboundApiKeyAdmin seam. // Keys are identified by an opaque string KeyId; method access is a set of method // NAMES (scopes) carried on the key, replacing the old ApiMethod.ApprovedApiKeyIds CSV. // The list of all methods still comes from IInboundApiRepository (methods stay in SQL). [Parameter] public string? KeyId { get; set; } private bool IsEditMode => _editingKey != null; private InboundApiKeyInfo? _editingKey; private string _formName = string.Empty; private string? _formError; private string? _errorMessage; private string? _newlyCreatedToken; private string? _newlyCreatedKeyId; private bool _loading = true; private bool _saved; private List _allMethods = new(); // Selection set is method NAMES (scopes), not method ids. private HashSet _selectedMethodNames = new(StringComparer.Ordinal); private ToastNotification _toast = default!; protected override async Task OnInitializedAsync() { try { // Methods always come from SQL Server (methods stay on the repository). _allMethods = (await InboundApiRepository.GetAllApiMethodsAsync()).ToList(); if (!string.IsNullOrWhiteSpace(KeyId)) { // No single-key getter on the seam โ€” locate this key in the full list. var all = await ApiKeyAdmin.ListAsync(); _editingKey = all.FirstOrDefault(k => string.Equals(k.KeyId, KeyId, StringComparison.Ordinal)); if (_editingKey == null) { _errorMessage = $"API key '{KeyId}' not found."; } else { _formName = _editingKey.Name; var methods = await ApiKeyAdmin.GetMethodsForKeyAsync(KeyId); _selectedMethodNames = new HashSet(methods, StringComparer.Ordinal); } } } catch (Exception ex) { _errorMessage = $"Failed to load API key: {ex.Message}"; } _loading = false; } private async Task SaveKey() { _formError = null; if (!IsEditMode && string.IsNullOrWhiteSpace(_formName)) { _formError = "Name is required."; return; } // The seam/server reject empty scope sets; validate in the UI for a clear message. if (_selectedMethodNames.Count == 0) { _formError = "Select at least one API method for this key."; return; } try { if (_editingKey != null) { // Edit: name is fixed; only the method-scope set is mutable. var ok = await ApiKeyAdmin.SetMethodsAsync(_editingKey.KeyId, _selectedMethodNames.ToList()); if (!ok) { _formError = $"API key '{_editingKey.Name}' was not found. Reload and retry."; return; } NavigationManager.NavigateTo("/admin/api-keys"); } else { var created = await ApiKeyAdmin.CreateAsync(_formName.Trim(), _selectedMethodNames.ToList()); _newlyCreatedKeyId = created.KeyId; _newlyCreatedToken = created.Token; // shown once; never persisted client-side. _saved = true; } } catch (Exception ex) { _formError = $"Save failed: {ex.Message}"; } } private void GoBack() => NavigationManager.NavigateTo("/admin/api-keys"); private void ToggleMethod(string methodName, bool isChecked) { if (isChecked) _selectedMethodNames.Add(methodName); else _selectedMethodNames.Remove(methodName); } private async Task CopyKeyToClipboard() { if (_newlyCreatedToken == null) return; try { await JS.InvokeVoidAsync("navigator.clipboard.writeText", _newlyCreatedToken); _toast.ShowSuccess("Copied to clipboard."); } catch { _toast.ShowError("Copy failed."); } } }