Files
restic-manager/web/templates/pages/host_detail.html
T
steve e6657c23ff P2 redesign · phase 2.5: tasks.md rewrite + UI patch-up
The store rewrite in e7eea7a left tasks.md describing a data shape
(fat schedules, host.repo_initialised_at, manual flag) that no longer
exists, and left the host-detail templates rendering against fields
the store no longer exposes. This commit reconciles both.

tasks.md
* Mid-phase pivot called out at the top of Phase 2 with commit hashes.
* P2-01..P2-05 kept as done but stamped ⚠️ "shipped against old shape
  — to re-validate under P2R-02".
* P2-04.5 (manual flag) struck as superseded.
* New P2R-NN section covering work that previously lived only in
  commit messages and code stubs:
    P2R-00.1/00.2/00.3/00.4 — phases already shipped (this commit
                              records 00.4)
    P2R-01 — REST + WS rewire against slim schedules + source groups
             + repo maintenance + auto-init
    P2R-02 — UI rewire against the v4 wireframes
    P2R-03..05 — prune / check / unlock command surfaces
    P2R-06 — server-side maintenance ticker (cadence-driven)
    P2R-07 — repo stats panel
    P2R-08 — pending_runs queue worker
    P2R-09 — auto-init UX polish
    P2R-10..12 — pre/post hooks rehomed from schedule onto source group
    P2R-13..14 — bandwidth + next/last-run surface
* P2-16/17/18 (Windows + announce-and-approve) untouched.
* Phase 2 acceptance criteria rewritten against the new model.

UI patch-up (P2R-00.4)
* host_detail.html + host_row.html: removed every $host.RepoInitialisedAt
  reference (column dropped in migration 0008 — render was 500'ing).
* Removed manual init-repo branches; the auto-init path replaces them.
* Schedules sub-tab demoted from active link to inert div until P2R-02
  rebuilds the page (it was linking to a raw 501 from the stubbed
  ui_schedules.go handlers).
* Disabled the four per-host Run-now buttons (dashboard row + host
  detail header + empty-snapshots state + right-rail) with a
  "lands in P2 Phase 4" hint — handler is 501-stubbed pending P2R-01,
  so leaving them clickable produced silent failures over htmx.
* Dashboard row-action becomes "Open →" instead of Run-now.

Project tooling
* .mcp.json at repo root: project-scoped Playwright MCP override.
  Forces --headless (so I don't pop a browser at the operator) and
  --output-dir _diag (so screenshots / traces land in the gitignored
  _diag/ directory rather than scattered at the repo root).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 09:13:05 +01:00

177 lines
8.6 KiB
HTML

{{define "title"}}{{.Title}}{{end}}
{{define "content"}}
{{$page := .Page}}
{{$host := $page.Host}}
<div class="max-w-[1280px] mx-auto px-8 pt-7">
<div class="crumbs"><a href="/">Dashboard</a><span class="sep">/</span><span class="text-ink-mid">{{$host.Name}}</span></div>
{{/* ---------- header ---------- */}}
<div class="flex items-start justify-between mt-3.5">
<div>
<div class="flex items-center gap-3">
{{if eq $host.Status "online"}}
<span class="dot dot-online{{if $host.CurrentJobID}} pulse{{end}}"></span>
{{else if eq $host.Status "degraded"}}
<span class="dot dot-degraded"></span>
{{else if eq $host.Status "offline"}}
<span class="dot dot-offline"></span>
{{else}}
<span class="dot dot-failed"></span>
{{end}}
<h1 class="mono text-[26px] font-medium tracking-[0.005em] text-ink">{{$host.Name}}</h1>
<div class="flex gap-1.5">{{range $host.Tags}}<span class="tag">{{.}}</span>{{end}}</div>
</div>
<div class="flex items-center gap-3 mt-3 text-[13px] text-ink-mute">
<span class="mono text-ink-mid">{{$host.OS}}/{{$host.Arch}}</span>
<span class="text-ink-fade">·</span>
<span>agent <span class="mono text-ink-mid">{{if $host.AgentVersion}}{{$host.AgentVersion}}{{else}}—{{end}}</span></span>
<span class="text-ink-fade">·</span>
<span>restic <span class="mono text-ink-mid">{{if $host.ResticVersion}}{{$host.ResticVersion}}{{else}}—{{end}}</span></span>
<span class="text-ink-fade">·</span>
{{if eq $host.Status "offline"}}
<span>last seen <span class="mono text-ink-mid">{{relTime $host.LastSeenAt}}</span></span>
{{else}}
<span>online · last heartbeat <span class="mono text-ink-mid">{{relTime $host.LastSeenAt}}</span></span>
{{end}}
</div>
</div>
<div class="flex items-center gap-2">
<button class="btn" disabled title="per-source-group Run-now lands in P2 Phase 4">Run&nbsp;backup&nbsp;now</button>
<button class="btn">Edit credentials</button>
<button class="btn btn-ghost text-base px-2.5"></button>
</div>
</div>
{{/* ---------- vitals strip ---------- */}}
<div class="grid grid-cols-12 gap-6 mt-6 py-5 border-y border-line-soft">
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em]">Last backup</div>
<div class="mono text-[18px] text-ink mt-1">
{{if eq (deref $host.LastBackupStatus) "succeeded"}}
<span class="text-ok">succeeded</span>
{{else if eq (deref $host.LastBackupStatus) "failed"}}
<span class="text-bad">failed</span>
{{else if eq (deref $host.LastBackupStatus) "cancelled"}}
<span class="text-warn">cancelled</span>
{{else}}
<span class="text-ink-fade italic">never run</span>
{{end}}
{{if $host.LastBackupAt}} · {{relTime $host.LastBackupAt}}{{end}}
</div>
</div>
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em]">Repo size</div>
<div class="mono text-[18px] text-ink mt-1">{{bytes $host.RepoSizeBytes}}</div>
</div>
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em]">Snapshots</div>
<div class="mono text-[18px] text-ink mt-1">{{comma $host.SnapshotCount}}</div>
</div>
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em]">Open alerts</div>
<div class="mono text-[18px] mt-1 {{if gt $host.OpenAlertCount 0}}text-bad{{else}}text-ok{{end}}">
{{if eq $host.OpenAlertCount 0}}0 · all clear{{else}}{{$host.OpenAlertCount}} · review →{{end}}
</div>
</div>
</div>
{{/* ---------- secondary tabs ---------- */}}
<div class="flex items-end mt-1.5">
<a class="sub-tab active" href="/hosts/{{$host.ID}}">Snapshots <span class="mono text-ink-fade text-[11px] ml-1">{{comma $host.SnapshotCount}}</span></a>
<div class="sub-tab" title="schedules UI lands in P2 Phase 4">Schedules</div>
<div class="sub-tab">Jobs</div>
<div class="sub-tab">Repo</div>
<div class="sub-tab">Settings</div>
</div>
{{/* ---------- snapshots tab ---------- */}}
<div class="grid grid-cols-12 gap-6 pt-6 pb-14 items-start">
<div class="col-span-9">
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-3">
<h2 class="text-[13px] font-semibold tracking-[0.01em]">Snapshots</h2>
<div class="text-xs text-ink-fade">showing {{$page.SnapshotsShown}} of {{comma $host.SnapshotCount}}</div>
</div>
</div>
<div class="panel rounded-[7px] overflow-hidden">
{{if eq (len $page.Snapshots) 0}}
<div class="empty-state" style="border: none; background: var(--panel);">
<h3 class="text-base font-medium tracking-[-0.005em]">No snapshots yet.</h3>
<p class="text-pretty text-ink-mute text-[13px] mt-2 mx-auto max-w-[440px] leading-[1.65]">
Once a backup completes, the agent will refresh this list automatically.
</p>
<div class="mt-5">
<button class="btn" disabled title="per-source-group Run-now lands in P2 Phase 4">Run now</button>
</div>
</div>
{{else}}
<div class="hairline grid items-baseline px-4 py-2.5 text-[11px] text-ink-fade uppercase tracking-[0.08em]"
style="grid-template-columns: 0.8fr 1fr 2fr 0.7fr 0.7fr 0.7fr; column-gap: 18px;">
<div>Snapshot id</div>
<div>Time</div>
<div>Paths</div>
<div class="text-right">Size</div>
<div class="text-right">Files</div>
<div></div>
</div>
{{range $i, $s := $page.Snapshots}}
<div class="grid items-center px-4 py-2.5 text-[13px] hairline"
style="grid-template-columns: 0.8fr 1fr 2fr 0.7fr 0.7fr 0.7fr; column-gap: 18px;">
<div class="mono text-ink font-medium">{{$s.ShortID}}</div>
<div class="mono text-ink-mid text-[12px]">{{absTime $s.Time}}</div>
<div class="mono text-ink-mid text-[12px] truncate" title="{{joinDot $s.Paths}}">{{joinDot $s.Paths}}</div>
<div class="text-right mono text-ink">{{bytes $s.SizeBytes}}</div>
<div class="text-right mono text-ink-mid">
{{if eq $s.FileCount 0}}<span class="text-ink-fade"></span>{{else}}{{comma $s.FileCount}}{{end}}
</div>
<div class="text-right">
<button class="btn btn-ghost" disabled title="restore wizard lands in P3">Restore →</button>
</div>
</div>
{{end}}
{{end}}
</div>
{{if eq $host.SnapshotCount 0}}
{{else if gt $host.SnapshotCount $page.SnapshotsShown}}
<div class="text-xs text-ink-mute mt-3 px-1">showing the {{$page.SnapshotsShown}} most-recent snapshots — full pagination lands later</div>
{{end}}
</div>
{{/* ---------- right rail ---------- */}}
<aside class="col-span-3 flex flex-col gap-4">
<div class="panel rounded-[7px] px-4 py-3.5">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.1em] mb-2.5">Run-now</div>
<div class="flex flex-col gap-1.5">
<button class="btn justify-start w-full" disabled title="per-source-group Run-now lands in P2 Phase 4">backup <span class="text-[10px] text-ink-fade ml-1.5">P2</span></button>
<button class="btn justify-start w-full" disabled title="lands with P2-05">forget <span class="text-[10px] text-ink-fade ml-1.5">P2</span></button>
<button class="btn justify-start w-full" disabled title="lands with P2-06">prune <span class="text-[10px] text-ink-fade ml-1.5">admin</span></button>
<button class="btn justify-start w-full" disabled title="lands with P2-07">check <span class="text-[10px] text-ink-fade ml-1.5">P2</span></button>
<button class="btn justify-start w-full" disabled title="lands with P2-08">unlock <span class="text-[10px] text-ink-fade ml-1.5">P2</span></button>
</div>
</div>
<div class="panel rounded-[7px] px-4 py-3.5">
<div class="text-[11px] text-bad uppercase tracking-[0.1em] font-semibold mb-2.5">Danger zone</div>
<p class="text-pretty text-[12px] text-ink-mute leading-[1.55] mb-3">
Removes the host record. The repo data on the rest-server is left intact —
you delete that yourself.
</p>
<button class="btn btn-danger w-full justify-center" disabled title="lands later in Phase 1">Remove host…</button>
</div>
</aside>
</div>
</div>
{{end}}