import { MetricGrid } from "@govoplan/core-webui"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ArrowRightLeft, Check, Plus, RefreshCw, RotateCw, ShieldAlert, ShieldOff, Trash2, X } from "lucide-react"; import { FormGrid, AdminPageLayout, Button, Card, DataGrid, Dialog, FormField, MetricCard, StatusBadge, TableActionGroup, hasScope, type ApiSettings, type AuthInfo, type DataGridColumn } from "@govoplan/core-webui"; import { changeVaultKey, createVault, decideRecovery, listEnvelopes, listMigrations, listRecoveries, listVaults, loadDisablePreflight, reconcileMigration, reconcileVault, requestMigration, requestRecovery, rotateVault, type DisablePreflight, type EnvelopeSummary, type MigrationSummary, type RecoverySummary, type VaultCreatePayload, type VaultSummary } from "../api/encryption"; type Props = { settings: ApiSettings; auth: AuthInfo }; type LifecycleAction = "rotate" | "revoke" | "destruction"; type LifecycleDraft = { action: LifecycleAction; vault: VaultSummary; reason: string; policyRef: string; assuranceRef: string; effectiveAt: string; }; type MigrationDraft = { envelope: EnvelopeSummary; targetVaultId: string; mode: "rewrap" | "reencrypt" | "decrypt" | "export" | "destroy"; policyRef: string; assuranceRef: string; }; type RecoveryDecisionDraft = { recovery: RecoverySummary; decision: "approve" | "reject"; reason: string; assuranceRef: string; }; const EMPTY_VAULT: VaultCreatePayload = { vault_id: "", name: "", provider_id: "local_aesgcm", purpose: "feature-content", algorithm_suite: "AES-256-GCM", scope_type: "tenant", scope_id: null, policy_ref: "", recovery_quorum: 2, profile_kind: "server_envelope", idempotency_key: "" }; export default function EncryptionAdminPanel({ settings, auth }: Props) { const canAdmin = hasScope(auth, "encryption:vault:admin"); const canRecover = hasScope(auth, "encryption:recovery:approve"); const [vaults, setVaults] = useState([]); const [envelopes, setEnvelopes] = useState([]); const [migrations, setMigrations] = useState([]); const [recoveries, setRecoveries] = useState([]); const [preflight, setPreflight] = useState(null); const [loading, setLoading] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const [creating, setCreating] = useState(false); const [vaultDraft, setVaultDraft] = useState(EMPTY_VAULT); const [lifecycle, setLifecycle] = useState(null); const [migration, setMigration] = useState(null); const [requestingRecovery, setRequestingRecovery] = useState(false); const [recoveryDraft, setRecoveryDraft] = useState({ vaultId: "", reason: "", requestedScope: "vault-status-and-rewrap", policyRef: "", assuranceRef: "", expiresAt: futureLocalTime(24) }); const [recoveryDecision, setRecoveryDecision] = useState(null); const load = useCallback(async () => { setLoading(true); setError(""); try { const [nextVaults, nextEnvelopes, nextMigrations, nextRecoveries, nextPreflight] = await Promise.all([ listVaults(settings), canAdmin ? listEnvelopes(settings) : Promise.resolve([]), canAdmin ? listMigrations(settings) : Promise.resolve([]), listRecoveries(settings), canAdmin ? loadDisablePreflight(settings) : Promise.resolve(null) ]); setVaults(nextVaults); setEnvelopes(nextEnvelopes); setMigrations(nextMigrations); setRecoveries(nextRecoveries); setPreflight(nextPreflight); } catch (caught) { setError(errorMessage(caught)); } finally { setLoading(false); } }, [canAdmin, settings]); useEffect(() => { void load(); }, [load]); async function perform(operation: () => Promise, message: string): Promise { if (busy) return false; setBusy(true); setError(""); setSuccess(""); try { await operation(); setSuccess(message); await load(); return true; } catch (caught) { setError(errorMessage(caught)); return false; } finally { setBusy(false); } } const vaultColumns = useMemo[]>(() => [ { id: "name", header: "Vault", width: 190, sortable: true, filterable: true, render: (row) => <>{row.name}
{row.vault_id}
, value: (row) => `${row.name} ${row.vault_id}` }, { id: "provider", header: "Provider", width: 145, sortable: true, filterable: true, render: (row) => row.provider_id, value: (row) => row.provider_id }, { id: "profile", header: "Profile", width: 145, sortable: true, filterable: true, render: (row) => humanize(row.profile_kind), value: (row) => row.profile_kind }, { id: "scope", header: "Scope", width: 155, sortable: true, filterable: true, render: (row) => `${humanize(row.scope_type)}${row.scope_id ? `: ${row.scope_id}` : ""}`, value: (row) => `${row.scope_type}:${row.scope_id ?? ""}` }, { id: "key", header: "Current key", width: 145, sortable: true, filterable: true, render: (row) => row.current_key_version ? `v${row.current_key_version} · ${humanize(row.current_key_state ?? "unknown")}` : "Not provisioned", value: (row) => row.current_key_version ?? 0 }, { id: "state", header: "State", width: 125, sortable: true, filterable: true, render: (row) => , value: (row) => row.state }, { id: "revision", header: "Revision", width: 95, sortable: true, filterable: true, filterType: "integer", render: (row) => row.revision, value: (row) => row.revision }, { id: "actions", header: "Actions", width: 210, sticky: "end", align: "right", render: (row) =>