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

71 lines
3.6 KiB
Markdown

# Phase 4: UI Restore button + ConfirmDialog + i18n en+ru
**Status:** ⬜ Not Started
**Parent plan:** [PLAN.md](./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.ts``restoreSnapshot`
- `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.