@page "/admin/api-keys/create" @page "/admin/api-keys/{Id:int}/edit" @using ScadaLink.Security @using ScadaLink.Commons.Entities.InboundApi @using ScadaLink.Commons.Interfaces.Repositories @attribute [Authorize(Policy = AuthorizationPolicies.RequireAdmin)] @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 && _newlyCreatedKeyValue != null) {
New API Key Created
@_newlyCreatedKeyValue
Save this key now. It will not be shown again in full.
Back to API Keys } else if (_errorMessage != null) {
@_errorMessage
} else {
@if (IsEditMode) {
@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.
}
} @if (_formError != null) {
@_formError
}
}
@code { [Parameter] public int? Id { get; set; } private bool IsEditMode => _editingKey != null; private ApiKey? _editingKey; private string _formName = string.Empty; private string? _formError; private string? _errorMessage; private string? _newlyCreatedKeyValue; private bool _loading = true; private bool _saved; private List _allMethods = new(); private HashSet _initialMethodIds = new(); private HashSet _selectedMethodIds = new(); private ToastNotification _toast = default!; protected override async Task OnInitializedAsync() { try { if (Id.HasValue) { _editingKey = await InboundApiRepository.GetApiKeyByIdAsync(Id.Value); if (_editingKey == null) { _errorMessage = $"API key with ID {Id.Value} not found."; } else { _formName = _editingKey.Name; _allMethods = (await InboundApiRepository.GetAllApiMethodsAsync()).ToList(); _initialMethodIds = _allMethods .Where(m => ParseApprovedKeyIds(m.ApprovedApiKeyIds).Contains(_editingKey.Id)) .Select(m => m.Id) .ToHashSet(); _selectedMethodIds = new HashSet(_initialMethodIds); } } } catch (Exception ex) { _errorMessage = $"Failed to load API key: {ex.Message}"; } _loading = false; } private async Task SaveKey() { _formError = null; if (string.IsNullOrWhiteSpace(_formName)) { _formError = "Name is required."; return; } try { if (_editingKey != null) { _editingKey.Name = _formName.Trim(); await InboundApiRepository.UpdateApiKeyAsync(_editingKey); var changedIds = _selectedMethodIds .Except(_initialMethodIds) .Concat(_initialMethodIds.Except(_selectedMethodIds)) .ToHashSet(); foreach (var method in _allMethods.Where(m => changedIds.Contains(m.Id))) { var ids = ParseApprovedKeyIds(method.ApprovedApiKeyIds); if (_selectedMethodIds.Contains(method.Id)) ids.Add(_editingKey.Id); else ids.Remove(_editingKey.Id); method.ApprovedApiKeyIds = ids.Count == 0 ? null : string.Join(",", ids.OrderBy(x => x)); await InboundApiRepository.UpdateApiMethodAsync(method); } await InboundApiRepository.SaveChangesAsync(); NavigationManager.NavigateTo("/admin/api-keys"); } else { var keyValue = GenerateApiKey(); var key = new ApiKey(_formName.Trim(), keyValue) { IsEnabled = true }; await InboundApiRepository.AddApiKeyAsync(key); await InboundApiRepository.SaveChangesAsync(); _newlyCreatedKeyValue = keyValue; _saved = true; } } catch (Exception ex) { _formError = $"Save failed: {ex.Message}"; } } private void GoBack() => NavigationManager.NavigateTo("/admin/api-keys"); private void ToggleMethod(int methodId, bool isChecked) { if (isChecked) _selectedMethodIds.Add(methodId); else _selectedMethodIds.Remove(methodId); } private static HashSet ParseApprovedKeyIds(string? value) { if (string.IsNullOrWhiteSpace(value)) return new HashSet(); return value.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(s => int.TryParse(s.Trim(), out var id) ? id : -1) .Where(id => id > 0) .ToHashSet(); } private async Task CopyKeyToClipboard() { if (_newlyCreatedKeyValue == null) return; try { await JS.InvokeVoidAsync("navigator.clipboard.writeText", _newlyCreatedKeyValue); _toast.ShowSuccess("Copied to clipboard."); } catch { _toast.ShowError("Copy failed."); } } private static string GenerateApiKey() { var bytes = new byte[32]; using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); rng.GetBytes(bytes); return Convert.ToBase64String(bytes).Replace("+", "").Replace("/", "").Replace("=", "")[..40]; } }