P1-25: host detail page (snapshots tab default)
GET /hosts/{id} renders the v1 host detail layout:
- persistent header: status dot (pulse if a job is in flight),
monospace name, tags, plus a metadata strip (os/arch, agent
version, restic version, "last seen Xs ago" or "online · last
heartbeat …").
- vitals strip: four tiles for last backup (status + relative
time), repo size, snapshot count, open alerts.
- sub-tabs: Snapshots is active; Jobs / Repo / Settings are
visible but inert until P2.
- snapshot table: short id, time (absolute), paths joined with
" · ", size, file count, restore button (disabled — wires up
in P3).
- right rail: run-now stack (backup live, forget/prune/check/
unlock disabled with the Phase tag), danger-zone remove panel
(also disabled for now).
Empty state: when a host has no snapshots yet, the table replaces
itself with a "no snapshots yet" prompt that includes the run-now
button (provided the agent is online).
Pagination cap of 50 most-recent snapshots; full pagination lands
when fleet sizes demand it.
Template helpers grew: comma() now accepts int / int32 / int64 so
templates don't fight Go's type inference; joinDot() concatenates
a []string with " · "; absTime() formats time.Time as
YYYY-MM-DD HH:MM:SS; the existing relTime() already accepts T or
*T after P1-27.
Browser-verified end-to-end with seeded fixture data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,13 @@ func funcMap() template.FuncMap {
|
||||
"comma": formatComma,
|
||||
"deref": derefStr,
|
||||
"timeNotZero": func(t *time.Time) bool { return t != nil && !t.IsZero() },
|
||||
"joinDot": func(parts []string) string { return strings.Join(parts, " · ") },
|
||||
"absTime": func(t time.Time) string {
|
||||
if t.IsZero() {
|
||||
return "—"
|
||||
}
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +119,20 @@ func formatRelTime(v any) string {
|
||||
|
||||
// formatComma renders 1847 as "1,847". Used for snapshot counts and
|
||||
// any other count that benefits from grouping at this scale.
|
||||
func formatComma(n int) string {
|
||||
s := strconv.Itoa(n)
|
||||
// Accepts int / int64 / int32 — anything else returns "—".
|
||||
func formatComma(v any) string {
|
||||
var n int64
|
||||
switch x := v.(type) {
|
||||
case int:
|
||||
n = int64(x)
|
||||
case int32:
|
||||
n = int64(x)
|
||||
case int64:
|
||||
n = x
|
||||
default:
|
||||
return "—"
|
||||
}
|
||||
s := strconv.FormatInt(n, 10)
|
||||
if n < 1000 && n > -1000 {
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user