Restore a captured volume snapshot onto an image workload's live host-bind
data volumes, then redeploy — the most destructive workload action, built to
the adversarially-reviewed design (C1–C6) with all data-loss guards.
- Engine.Restore (engine-owned): all-or-nothing pre-flight re-resolution from
the workload's CURRENT config (never the tamperable manifest), per-filesystem
disk pre-check, per-workload lock, container quiesce, extract-to-tmp, durable
pre-restore snapshot, write-ahead journal, atomic rename swap, redeploy, and
crash-recovery sweep (RecoverInterruptedRestores) wired before serving.
- internal/keyedmutex: shared per-key lock; deployer now serializes every
deploy entrypoint per workload via DispatchPlugin (+ LockWorkload/RedeployLocked
for the restore re-dispatch, no deadlock).
- Untrusted-archive extractor: zip-slip containment, type allow-list (reg/dir
only), decompression-bomb cap, manifest-index bounds.
- POST /api/workloads/{id}/snapshots/{sid}/restore: admin, X-Confirm-Restore
header (CSRF), per-workload single-flight (409).
- WebUI: Restore button + danger ConfirmDialog + busy state + i18n (en/ru).
Scope: image-source only; scopes absolute/stage/project (driven off the same
supportedScopes constant capture uses).
Plan-reviewed before coding; per-phase go/security/ts reviews; final review
READY TO MERGE. Security review caught + fixed a CRITICAL manifest-Source path
traversal (re-derive target from current config + base containment).
Plan: plans/volume-snapshot-restore/
3.6 KiB
Phase 4: UI Restore button + ConfirmDialog + i18n en+ru
Status: ⬜ Not Started Parent plan: PLAN.md Domain: frontend
Built by the frontend implementer agent. Must follow project conventions: Svelte 5
runes, ConfirmDialog for the destructive action (NEVER window.confirm), $t with
en+ru parity, the existing .panel/.forge-btn-ghost vocabulary in
WorkloadSnapshotsPanel.svelte.
Tasks
web/src/lib/api.ts:restoreSnapshot(workloadId: string, sid: string): Promise<void>— POST/api/workloads/${workloadId}/snapshots/${sid}/restorewith headerX-Confirm-Restore: ${sid}. Check how the DB restore sends itsX-Confirm-Restoreheader and reuse that fetch mechanism (the typedpost<T>may need a header-capable variant or a rawfetchlikedownloadalready uses).WorkloadSnapshotsPanel.svelte:- add a Restore action per snapshot row (beside Download/Delete) → opens
ConfirmDialog. - ConfirmDialog: strong destructive copy — title + message making clear it overwrites
live data and restarts the app, and that a pre-restore snapshot is auto-captured;
confirmVariant="danger". - on confirm: call
restoreSnapshot, show a "restoring…" busy state (disable row actions), toast success/failure, thenload()to refresh. - update the file's top comment (currently "Restore is intentionally NOT here yet") to reflect that restore now ships.
- add a Restore action per snapshot row (beside Download/Delete) → opens
- i18n: add
apps.detail.snapshots.restore,.restoring,.restored,.restoreFailed,.confirmRestoreTitle,.confirmRestoreMessageto BOTHweb/src/lib/i18n/en.jsonandweb/src/lib/i18n/ru.json. Verify parity manually (a missing key is NOT a build error —$treturns the key string).
Files to Modify/Create
web/src/lib/api.ts—restoreSnapshotweb/src/lib/components/WorkloadSnapshotsPanel.svelte— Restore button + ConfirmDialog + busy stateweb/src/lib/i18n/en.json,web/src/lib/i18n/ru.json— restore keys (parity)
Acceptance Criteria
npm run check0 errors;npm run buildsucceeds;npm run testgreen.- en/ru key parity equal (every new key in both files).
- ConfirmDialog used (no native confirm/alert); danger variant; copy warns about data overwrite + app restart.
- Restore button disabled while a restore is in flight.
- Restart dev server (
./scripts/dev-server.sh).
Notes
- Only image-source workloads expose snapshots, so no source-kind gating is needed in the panel beyond what already exists.
- Keep the Restore button visually subordinate to Download but clearly destructive (danger styling) — it's the most dangerous action in the panel.
Review Checklist
- All tasks completed
- Code follows project conventions (ToggleSwitch/ConfirmDialog rules, runes)
- No unintended side effects
- Build passes (
npm run check+npm run build) - Tests pass; i18n parity verified
Handoff to Next Phase
Implemented: api.restoreSnapshot(workloadId, sid) (POST + X-Confirm-Restore header,
mirrors restoreBackup); WorkloadSnapshotsPanel.svelte Restore button per row →
ConfirmDialog (danger, warns: overwrites live data + restarts app + auto pre-restore
snapshot) → doRestore with busy state (restoringId disables all row actions, active row
shows "Restoring…"); i18n apps.detail.snapshots.restore* in en+ru (parity verified).
Verify: npm run check 0 errors, npm run build OK, npm run test 26 pass; i18n parity
equal; dev server restarted on :9000. typescript-reviewer: APPROVE (no blockers; one cosmetic
wording note on the success toast addressed). Final phase — done.