P3-09 + P3-X3: snapshot diff + recent-restores line
P3-09 — snapshot diff dispatcher.
- POST /api/hosts/{id}/snapshots/diff (and the unprefixed HTMX-form
variant) takes {snapshot_a, snapshot_b}, validates both belong to
the host (long id / short id / prefix match), checks the agent is
online, mints a JobDiff, ships command.run with DiffPayload, writes
a host.snapshot_diff audit row, returns HX-Redirect to the live
job page (or JSON {job_id, job_url} for REST callers).
- Two-snapshot guard: POSTing diff(a,a) returns 422.
- UI: small panel on the host_detail right rail (visible when the
host has 2+ snapshots) with two short-id inputs and a Diff button.
Output renders on the standard live job page where the operator
reads the per-line diff text directly.
P3-X3 — recent-restores line.
- hostChromeData grows RestoreStatus / RestoreAt / RestoreJobID
populated via store.LatestJobByKind(host_id, 'restore') (already
exists, used by the init line).
- host_chrome.html renders a small line below the existing init-status
one with status-coloured copy + a link to the job log. Hidden when
no restore has ever run on this host.
Tests:
- diff_test covers happy path (correct DiffPayload + HX-Redirect),
same-id rejection (422), unknown-id rejection (422). Adds a
seedTwoSnapshots helper since ReplaceHostSnapshots is atomic-swap
(calling seedSnapshot twice would only leave the second).
Restage block (CLAUDE.md) deferred to the end of the restore phase.
This commit is contained in:
@@ -33,6 +33,28 @@ func seedSnapshot(t *testing.T, st *store.Store, hostID, hostname string) string
|
||||
return id
|
||||
}
|
||||
|
||||
// seedTwoSnapshots seeds two snapshots in one ReplaceHostSnapshots call
|
||||
// so both end up in the host's list. ReplaceHostSnapshots is atomic-
|
||||
// swap, so calling seedSnapshot twice would only leave the second.
|
||||
func seedTwoSnapshots(t *testing.T, st *store.Store, hostID, hostname string) (string, string) {
|
||||
t.Helper()
|
||||
a := strings.ReplaceAll(ulid.Make().String(), "-", "")
|
||||
b := strings.ReplaceAll(ulid.Make().String(), "-", "")
|
||||
if err := st.ReplaceHostSnapshots(context.Background(), hostID, []store.Snapshot{
|
||||
{
|
||||
ID: a, ShortID: a[:8], Time: time.Now().UTC().Add(-3 * time.Hour),
|
||||
Hostname: hostname, Paths: []string{"/etc"}, Tags: []string{"system-config"},
|
||||
},
|
||||
{
|
||||
ID: b, ShortID: b[:8], Time: time.Now().UTC().Add(-1 * time.Hour),
|
||||
Hostname: hostname, Paths: []string{"/etc"}, Tags: []string{"system-config"},
|
||||
},
|
||||
}, time.Now().UTC()); err != nil {
|
||||
t.Fatalf("seed snapshots: %v", err)
|
||||
}
|
||||
return a, b
|
||||
}
|
||||
|
||||
// TestRestoreWizardGetRendersStep1 verifies the snapshot picker is on
|
||||
// the page when no snapshot is pre-selected.
|
||||
func TestRestoreWizardGetRendersStep1(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user