Adopt shared WebUI layout primitives

This commit is contained in:
2026-08-18 10:42:51 +02:00
parent 211cd2c950
commit f8c22396b3
2 changed files with 9 additions and 19 deletions
+9 -9
View File
@@ -10,7 +10,7 @@ import {
Trash2,
X
} from "lucide-react";
import {
import { FormGrid,
AdminPageLayout,
Button,
Card,
@@ -322,7 +322,7 @@ export default function EncryptionAdminPanel({ settings, auth }: Props) {
</Card>
<Dialog open={creating} title="Add encryption vault" onClose={() => !busy && setCreating(false)} closeDisabled={busy} footer={<><Button onClick={() => setCreating(false)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void submitVault()} disabled={busy || !vaultDraft.vault_id.trim() || !vaultDraft.name.trim() || !vaultDraft.policy_ref.trim()}>Create vault</Button></>}>
<div className="encryption-form-grid">
<FormGrid columns={2} gap="small" collapseAt="narrow" className="encryption-form-grid">
<FormField label="Vault ID"><input value={vaultDraft.vault_id} disabled={busy} onChange={(event) => setVaultDraft({ ...vaultDraft, vault_id: event.target.value })} /></FormField>
<FormField label="Name"><input value={vaultDraft.name} disabled={busy} onChange={(event) => setVaultDraft({ ...vaultDraft, name: event.target.value })} /></FormField>
<FormField label="Provider"><input value={vaultDraft.provider_id} disabled={busy} onChange={(event) => setVaultDraft({ ...vaultDraft, provider_id: event.target.value })} /></FormField>
@@ -331,45 +331,45 @@ export default function EncryptionAdminPanel({ settings, auth }: Props) {
<FormField label="Recovery quorum"><input type="number" min={1} max={32} value={vaultDraft.recovery_quorum} disabled={busy} onChange={(event) => setVaultDraft({ ...vaultDraft, recovery_quorum: Number(event.target.value) })} /></FormField>
<FormField label="Purpose"><input value={vaultDraft.purpose} disabled={busy} onChange={(event) => setVaultDraft({ ...vaultDraft, purpose: event.target.value })} /></FormField>
<FormField label="Policy reference"><input value={vaultDraft.policy_ref} disabled={busy} onChange={(event) => setVaultDraft({ ...vaultDraft, policy_ref: event.target.value })} /></FormField>
</div>
</FormGrid>
<p className="muted small-note">Selecting end-to-end records a profile label only. It does not install or certify a client E2EE protocol.</p>
</Dialog>
<Dialog open={Boolean(lifecycle)} title={`${humanize(lifecycle?.action ?? "key")} vault key`} onClose={() => !busy && setLifecycle(null)} closeDisabled={busy} footer={<><Button onClick={() => setLifecycle(null)} disabled={busy}>Cancel</Button><Button variant={lifecycle?.action === "rotate" ? "primary" : "danger"} onClick={() => void submitLifecycle()} disabled={busy || !lifecycle?.reason.trim() || !lifecycle?.policyRef.trim() || !lifecycle?.assuranceRef.trim()}>Confirm {lifecycle?.action}</Button></>}>
{lifecycle && <>
<p>{lifecycleText(lifecycle.action)}</p>
<div className="encryption-form-grid">
<FormGrid columns={2} gap="small" collapseAt="narrow" className="encryption-form-grid">
<FormField label="Reason"><textarea rows={3} value={lifecycle.reason} disabled={busy} onChange={(event) => setLifecycle({ ...lifecycle, reason: event.target.value })} /></FormField>
<FormField label="Policy decision reference"><input value={lifecycle.policyRef} disabled={busy} onChange={(event) => setLifecycle({ ...lifecycle, policyRef: event.target.value })} /></FormField>
<FormField label="High-assurance evidence reference"><input value={lifecycle.assuranceRef} disabled={busy} onChange={(event) => setLifecycle({ ...lifecycle, assuranceRef: event.target.value })} /></FormField>
{lifecycle.action === "destruction" && <FormField label="Destruction effective at"><input type="datetime-local" value={lifecycle.effectiveAt} disabled={busy} onChange={(event) => setLifecycle({ ...lifecycle, effectiveAt: event.target.value })} /></FormField>}
</div>
</FormGrid>
</>}
</Dialog>
<Dialog open={Boolean(migration)} title="Prepare protection migration" onClose={() => !busy && setMigration(null)} closeDisabled={busy} footer={<><Button onClick={() => setMigration(null)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void submitMigration()} disabled={busy || !migration?.targetVaultId || !migration?.policyRef.trim() || !migration?.assuranceRef.trim()}>Authorize migration</Button></>}>
{migration && <>
<p>This authorizes a two-phase content operation. The owning module must durably update or dispose of its content and record evidence before the migration can succeed.</p>
<div className="encryption-form-grid">
<FormGrid columns={2} gap="small" collapseAt="narrow" className="encryption-form-grid">
<FormField label="Source envelope"><input value={migration.envelope.envelope_id} disabled /></FormField>
<FormField label="Mode"><select value={migration.mode} disabled={busy} onChange={(event) => setMigration({ ...migration, mode: event.target.value as MigrationDraft["mode"] })}><option value="rewrap">Rewrap</option><option value="reencrypt">Re-encrypt</option><option value="decrypt">Decrypt</option><option value="export">Export</option><option value="destroy">Destroy</option></select></FormField>
<FormField label="Target vault"><select value={migration.targetVaultId} disabled={busy} onChange={(event) => setMigration({ ...migration, targetVaultId: event.target.value })}>{vaults.filter((vault) => vault.current_key_version && vault.state === "active").map((vault) => <option key={vault.vault_id} value={vault.vault_id}>{vault.name} · v{vault.current_key_version}</option>)}</select></FormField>
<FormField label="Policy decision reference"><input value={migration.policyRef} disabled={busy} onChange={(event) => setMigration({ ...migration, policyRef: event.target.value })} /></FormField>
<FormField label="High-assurance evidence reference"><input value={migration.assuranceRef} disabled={busy} onChange={(event) => setMigration({ ...migration, assuranceRef: event.target.value })} /></FormField>
</div>
</FormGrid>
</>}
</Dialog>
<Dialog open={requestingRecovery} title="Request recovery ceremony" onClose={() => !busy && setRequestingRecovery(false)} closeDisabled={busy} footer={<><Button onClick={() => setRequestingRecovery(false)} disabled={busy}>Cancel</Button><Button variant="primary" onClick={() => void submitRecovery()} disabled={busy || !recoveryDraft.vaultId || !recoveryDraft.reason.trim() || !recoveryDraft.requestedScope.trim() || !recoveryDraft.policyRef.trim() || !recoveryDraft.assuranceRef.trim()}>Request recovery</Button></>}>
<p>The request expires automatically and needs the vault's configured number of distinct custodians. You cannot approve your own request.</p>
<div className="encryption-form-grid">
<FormGrid columns={2} gap="small" collapseAt="narrow" className="encryption-form-grid">
<FormField label="Vault"><select value={recoveryDraft.vaultId} disabled={busy} onChange={(event) => setRecoveryDraft({ ...recoveryDraft, vaultId: event.target.value })}>{vaults.map((vault) => <option key={vault.vault_id} value={vault.vault_id}>{vault.name}</option>)}</select></FormField>
<FormField label="Requested scope"><input value={recoveryDraft.requestedScope} disabled={busy} onChange={(event) => setRecoveryDraft({ ...recoveryDraft, requestedScope: event.target.value })} /></FormField>
<FormField label="Reason"><textarea rows={3} value={recoveryDraft.reason} disabled={busy} onChange={(event) => setRecoveryDraft({ ...recoveryDraft, reason: event.target.value })} /></FormField>
<FormField label="Expires at"><input type="datetime-local" value={recoveryDraft.expiresAt} disabled={busy} onChange={(event) => setRecoveryDraft({ ...recoveryDraft, expiresAt: event.target.value })} /></FormField>
<FormField label="Policy decision reference"><input value={recoveryDraft.policyRef} disabled={busy} onChange={(event) => setRecoveryDraft({ ...recoveryDraft, policyRef: event.target.value })} /></FormField>
<FormField label="High-assurance evidence reference"><input value={recoveryDraft.assuranceRef} disabled={busy} onChange={(event) => setRecoveryDraft({ ...recoveryDraft, assuranceRef: event.target.value })} /></FormField>
</div>
</FormGrid>
</Dialog>
<Dialog open={Boolean(recoveryDecision)} title={`${humanize(recoveryDecision?.decision ?? "decide")} recovery`} onClose={() => !busy && setRecoveryDecision(null)} closeDisabled={busy} footer={<><Button onClick={() => setRecoveryDecision(null)} disabled={busy}>Cancel</Button><Button variant={recoveryDecision?.decision === "reject" ? "danger" : "primary"} onClick={() => void submitRecoveryDecision()} disabled={busy || !recoveryDecision?.reason.trim() || !recoveryDecision?.assuranceRef.trim()}>Record {recoveryDecision?.decision}</Button></>}>
-10
View File
@@ -4,12 +4,6 @@
overflow: auto;
}
.encryption-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.85rem 1rem;
}
.encryption-form-grid .form-field:has(textarea) {
grid-column: 1 / -1;
}
@@ -46,10 +40,6 @@
}
@media (max-width: 760px) {
.encryption-form-grid {
grid-template-columns: minmax(0, 1fr);
}
.encryption-form-grid .form-field:has(textarea) {
grid-column: auto;
}