@namespace ZB.MOM.WW.Secrets.Ui.Components @implements IDisposable @inject ISecretStore Store @inject IAuditWriter Audit @inject IServiceProvider Services @* Components/ConfirmDeleteModal.razor — an in-page Blazor overlay (NOT a JS confirm/alert dialog). Shown while Visible is true; Confirm tombstones + audits the delete, Cancel dismisses. *@ @if (Visible) { } @code { private readonly CancellationTokenSource _cts = new(); private string? _error; private bool _busy; /// Whether the modal overlay is rendered. [Parameter] public bool Visible { get; set; } /// The secret the modal offers to delete. [Parameter] public SecretName Name { get; set; } /// Raised after the secret has been tombstoned and audited. [Parameter] public EventCallback OnDeleted { get; set; } /// Raised when the user cancels the deletion. [Parameter] public EventCallback OnCancelled { get; set; } /// The cascading authentication state, used as the fallback source of the audit actor. [CascadingParameter] private Task? AuthState { get; set; } private async Task ConfirmAsync() { _error = null; _busy = true; bool deleted = false; try { string actor = await SecretActorResolver.ResolveAsync(Services, AuthState); try { await Store.DeleteAsync(Name, actor, _cts.Token); await Audit.WriteAsync( new AuditEvent { EventId = Guid.NewGuid(), OccurredAtUtc = DateTimeOffset.UtcNow, Actor = actor, Action = "secret.delete", Outcome = AuditOutcome.Success, Category = "Secrets", Target = Name.Value, }, _cts.Token); deleted = true; } catch (Exception) { // An unexpected store fault must still be audited as a Failure and surfaced to the // operator instead of tearing down the circuit. _error = "Unable to delete the secret."; await WriteFailureAuditAsync(actor); } } finally { _busy = false; } if (deleted) { // Evict any cached plaintext so the ${secret:} / ISecretResolver path does not serve a // stale value after this delete for the remainder of its TTL. Optional seam — a no-op // when the host has not registered an invalidator. Services.GetService()?.Invalidate(Name); await OnDeleted.InvokeAsync(); } } private async Task WriteFailureAuditAsync(string actor) { try { await Audit.WriteAsync( new AuditEvent { EventId = Guid.NewGuid(), OccurredAtUtc = DateTimeOffset.UtcNow, Actor = actor, Action = "secret.delete", Outcome = AuditOutcome.Failure, Category = "Secrets", Target = Name.Value, DetailsJson = "{\"reason\":\"error\"}", }, _cts.Token); } catch { // Best-effort audit of a failure; never let audit itself surface into the event pipeline. } } private Task CancelAsync() => OnCancelled.InvokeAsync(); /// public void Dispose() { _cts.Cancel(); _cts.Dispose(); } }