From a58351b7ad098a0769c4ec83b50a45229c332616 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Sun, 2 Aug 2026 00:06:08 +0200 Subject: [PATCH] feat(storage key rot): add admin panel --- frontend/src/lib/api/endpoints/admin.ts | 16 +++ .../src/lib/components/AdminJobsPanel.svelte | 8 +- frontend/src/lib/components/AppShell.svelte | 7 ++ .../src/lib/components/ReadOnlyBanner.svelte | 88 +++++++++----- .../src/lib/stores/serverStatus.svelte.ts | 41 +++++-- .../src/routes/admin/[[tab]]/+page.svelte | 107 ++++++++++++++++++ frontend/static/locales/en.json | 4 +- frontend/static/locales/fr.json | 3 + src/infrastructure/scheduler/handler.rs | 16 +++ src/infrastructure/scheduler/recoverable.rs | 8 ++ src/infrastructure/scheduler/registry.rs | 6 + src/interfaces/api/handlers/admin_handler.rs | 21 ++++ 12 files changed, 282 insertions(+), 43 deletions(-) diff --git a/frontend/src/lib/api/endpoints/admin.ts b/frontend/src/lib/api/endpoints/admin.ts index a4001438..9f9da765 100644 --- a/frontend/src/lib/api/endpoints/admin.ts +++ b/frontend/src/lib/api/endpoints/admin.ts @@ -538,6 +538,22 @@ export function migrationAction( return mutate(`/api/admin/storage/migration/${action}`, 'POST', body); } +/** + * K4 (storage-key-rotation): trigger `storage_rotate` on a specific + * storage entry. Normalises every blob on `` to that entry's + * head-pair format: legacy → v1, plaintext ↔ encrypted, old-key → + * new-key. Fire-and-forget — poll `GET /api/admin/jobs/storage_rotate` + * for status. + * + * Backend: `POST /api/admin/storage/entries/{name}/rotate` + * (`admin_handler::trigger_storage_rotate`). Refuses (400) on unknown + * entry name or when a `storage_rotate` / `storage_migration` run is + * already in flight. + */ +export function rotateStorageEntry(name: string): Promise { + return mutate(`/api/admin/storage/entries/${encodeURIComponent(name)}/rotate`, 'POST', undefined); +} + // verifyMigration + MigrationVerifyResult retired in slice 7 of // docs/plan/storage-multi-entry.md — the corresponding backend // endpoint's sample-based check is superseded by diff --git a/frontend/src/lib/components/AdminJobsPanel.svelte b/frontend/src/lib/components/AdminJobsPanel.svelte index 0d55d949..f7140657 100644 --- a/frontend/src/lib/components/AdminJobsPanel.svelte +++ b/frontend/src/lib/components/AdminJobsPanel.svelte @@ -470,7 +470,13 @@ return name_is_recoverable(job.name); } function name_is_recoverable(name: string): boolean { - return name.endsWith('_consistency') || name === 'storage_migration'; + // K3 storage-key-rotation adds `storage_rotate` to the recoverable + // tenant set. Same shape as `storage_migration` — walks blobs, + // records findings, supports resume from cursor — so it needs the + // same expand/runs/findings surface. + return ( + name.endsWith('_consistency') || name === 'storage_migration' || name === 'storage_rotate' + ); } // Consistency batch shortcut — top button. Only shown when the diff --git a/frontend/src/lib/components/AppShell.svelte b/frontend/src/lib/components/AppShell.svelte index d669b4dd..5dc8f067 100644 --- a/frontend/src/lib/components/AppShell.svelte +++ b/frontend/src/lib/components/AppShell.svelte @@ -1041,6 +1041,13 @@ different copy. --> {#if serverStatus().readonly} + {:else if serverStatus().rotation} + + {/if} {@render children()} diff --git a/frontend/src/lib/components/ReadOnlyBanner.svelte b/frontend/src/lib/components/ReadOnlyBanner.svelte index d7fd5491..847ec41d 100644 --- a/frontend/src/lib/components/ReadOnlyBanner.svelte +++ b/frontend/src/lib/components/ReadOnlyBanner.svelte @@ -1,37 +1,27 @@