Files
tiny-forge/plans/volume-snapshot-restore/phase-4-frontend.md
T
alexei.dolgolyov 1c47030854 feat(volsnap): volume snapshot restore (backlog #6)
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/
2026-06-22 17:23:52 +03:00

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}/restore with header X-Confirm-Restore: ${sid}. Check how the DB restore sends its X-Confirm-Restore header and reuse that fetch mechanism (the typed post<T> may need a header-capable variant or a raw fetch like download already 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, then load() to refresh.
    • update the file's top comment (currently "Restore is intentionally NOT here yet") to reflect that restore now ships.
  • i18n: add apps.detail.snapshots.restore, .restoring, .restored, .restoreFailed, .confirmRestoreTitle, .confirmRestoreMessage to BOTH web/src/lib/i18n/en.json and web/src/lib/i18n/ru.json. Verify parity manually (a missing key is NOT a build error — $t returns the key string).

Files to Modify/Create

  • web/src/lib/api.tsrestoreSnapshot
  • web/src/lib/components/WorkloadSnapshotsPanel.svelte — Restore button + ConfirmDialog + busy state
  • web/src/lib/i18n/en.json, web/src/lib/i18n/ru.json — restore keys (parity)

Acceptance Criteria

  • npm run check 0 errors; npm run build succeeds; npm run test green.
  • 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.