Files
restic-manager/web/templates/pages/dashboard.html
T
steve ee16bc7ce7
CI / Test (linux/amd64) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Build (windows/amd64) (push) Has been cancelled
CI / Build (linux/amd64) (push) Has been cancelled
CI / Build (linux/arm64) (push) Has been cancelled
P1-24: live dashboard — fleet summary tiles + host table
Server-rendered HTML view backed by:
  - new store.FleetSummary aggregating host counts + repo bytes +
    snapshot total + open alerts + last-24h job rollup in two queries.
  - GET /api/hosts (JSON list of hosts in the dashboard projection).
  - GET /api/fleet/summary (JSON aggregate, same shape as above).

The HTML page (web/templates/pages/dashboard.html) renders the four
summary tiles + host table directly from store data — no separate
fetch. Per-row state colour comes from .host-row.{degraded,failed,
offline} which paint a 3px left edge so problem hosts are scannable
without reading. HTMX is loaded into the base layout so per-row
"Run now" buttons can hx-post to /hosts/{id}/run-backup, a thin
HTML wrapper that funnels into a new dispatchJob helper shared
with the JSON /api/hosts/{id}/jobs endpoint.

Empty state (zero hosts) collapses to the "no hosts yet" prompt
with the + Add host CTA — matches the v1 mockup.

Template helpers (internal/server/ui/funcs.go) added for byte
formatting (412 GB / 3.7 TB), relative time (3m ago / 2d ago), and
comma grouping (1,847). Pure Go, no template-magic dependency.

Browser-verified end-to-end with seeded fixture data: five hosts
across all four states render with correct dots, accents, last-
backup pills, sizes, snapshot counts, alerts, tags, and the right
action button (Run now / Retry / Run first / View → / offline).

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

99 lines
5.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{define "title"}}Dashboard · restic-manager{{end}}
{{define "content"}}
<div class="max-w-[1280px] mx-auto px-8">
{{$page := .Page}}
{{if eq $page.HostCount 0}}
{{/* ---------- empty state ---------- */}}
<div class="pt-14 pb-24">
<div class="empty-state">
<h1 class="text-lg font-medium tracking-[-0.005em]">No hosts yet.</h1>
<p class="text-pretty text-ink-mid mt-3 mx-auto max-w-[520px] text-[13px] leading-[1.65]">
<span class="mono text-ink">restic-manager</span> tracks backups across a fleet —
but theres nothing to track until you enrol your first host. Mint a token
from <span class="mono text-ink">+ Add host</span>, paste the install command on
a Linux box, and the host will appear here within seconds.
</p>
<div class="mt-7 flex items-center justify-center gap-2">
<a href="/hosts/new" class="btn btn-primary btn-lg">+ Add your first host</a>
<a href="/" class="btn">Reload</a>
</div>
<div class="mt-8 text-[12px] text-ink-fade">
Prerequisite: <span class="mono text-ink-mute">restic</span> ≥ 0.16 already installed on the target host.
</div>
</div>
</div>
{{else}}
{{/* ---------- fleet summary ---------- */}}
<div class="grid grid-cols-12 gap-6 pt-7 pb-2">
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em] mb-2">Fleet</div>
<div class="mono text-[28px] font-medium tracking-[-0.02em]">{{$page.Summary.TotalHosts}} <span class="text-ink-mute text-[13px] font-normal">hosts</span></div>
<div class="flex items-center gap-3 mt-2.5 text-xs">
<span class="flex items-center gap-1.5"><span class="dot dot-online"></span><span class="mono text-ink-mid">{{$page.Summary.HostsOnline}}</span><span class="text-ink-mute">online</span></span>
<span class="flex items-center gap-1.5"><span class="dot dot-degraded"></span><span class="mono text-ink-mid">{{$page.Summary.HostsDegraded}}</span><span class="text-ink-mute">degraded</span></span>
<span class="flex items-center gap-1.5"><span class="dot dot-offline"></span><span class="mono text-ink-mid">{{$page.Summary.HostsOffline}}</span><span class="text-ink-mute">offline</span></span>
</div>
</div>
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em] mb-2">Backed up</div>
<div class="mono text-[28px] font-medium tracking-[-0.02em]">{{bytes $page.Summary.RepoBytesTotal}}</div>
<div class="text-xs text-ink-mute mt-2.5"><span class="mono text-ink-mid">{{comma $page.Summary.SnapshotsTotal}}</span> snapshots total</div>
</div>
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em] mb-2">Last 24h</div>
<div class="mono text-[28px] font-medium tracking-[-0.02em]">{{$page.Summary.JobsLast24h}} <span class="text-ink-mute text-[13px] font-normal">jobs</span></div>
<div class="text-xs mt-2.5">
<span class="mono text-ok">{{$page.Summary.JobsLast24hSucceeded}}</span> <span class="text-ink-mute">succeeded</span>
{{if gt $page.Summary.JobsLast24hFailed 0}} · <span class="mono text-bad">{{$page.Summary.JobsLast24hFailed}}</span> <span class="text-ink-mute">failed</span>{{end}}
{{if gt $page.Summary.JobsLast24hCancelled 0}} · <span class="mono text-ink-mid">{{$page.Summary.JobsLast24hCancelled}}</span> <span class="text-ink-mute">cancelled</span>{{end}}
</div>
</div>
<div class="col-span-3">
<div class="text-[11px] text-ink-fade uppercase tracking-[0.08em] mb-2">Open alerts</div>
{{if eq $page.Summary.OpenAlerts 0}}
<div class="mono text-[28px] font-medium tracking-[-0.02em] text-ink-mid">0 <span class="text-ink-mute text-[13px] font-normal">unresolved</span></div>
<div class="text-xs text-ink-mute mt-2.5">all clear</div>
{{else}}
<div class="mono text-[28px] font-medium tracking-[-0.02em] text-bad">{{$page.Summary.OpenAlerts}} <span class="text-ink-mute text-[13px] font-normal">unresolved</span></div>
<div class="text-xs text-ink-mute mt-2.5"><a href="/alerts" class="underline underline-offset-4 decoration-line">review →</a></div>
{{end}}
</div>
</div>
{{/* ---------- hosts table ---------- */}}
<div class="pt-6 pb-4">
<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]">Hosts</h2>
<div class="text-xs text-ink-fade">{{$page.HostCount}} of {{$page.HostCount}}</div>
</div>
</div>
<div class="panel rounded-[7px] overflow-hidden">
<div class="host-row head hairline">
<div></div>
<div>Host</div>
<div>OS · arch</div>
<div>Last backup</div>
<div class="text-right">Repo size</div>
<div class="text-right">Snapshots</div>
<div>Alerts</div>
<div>Tags</div>
<div></div>
</div>
{{range $page.Hosts}}{{template "host_row" .}}{{end}}
</div>
</div>
{{end}}
</div>
{{end}}