213 lines
12 KiB
HTML
213 lines
12 KiB
HTML
{{/*
|
||
host_chrome — header (status dot + name + tags + meta), vitals
|
||
strip, and the six sub-tab nav for any /hosts/{id}/... page.
|
||
|
||
Expects .Page to expose:
|
||
.Host — store.Host
|
||
.SubTab — "snapshots" | "sources" | "schedules" | "repo" | "jobs" | "settings"
|
||
.SourceGroupCount — int
|
||
.ScheduleCount — int
|
||
.ScheduleVersion — int64 (host_schedule_version)
|
||
.Crumb — string ("snapshots" / "sources" / etc — appended after host name)
|
||
*/}}
|
||
{{define "host_chrome"}}
|
||
{{$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>
|
||
{{if eq $page.SubTab "snapshots"}}
|
||
<span class="text-ink-mid">{{$host.Name}}</span>
|
||
{{else}}
|
||
<a href="/hosts/{{$host.ID}}">{{$host.Name}}</a><span class="sep">/</span>
|
||
<span class="text-ink-mid">{{$page.Crumb}}</span>
|
||
{{end}}
|
||
</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"}}
|
||
{{if $host.AlwaysOn}}
|
||
<span class="dot dot-offline"></span>
|
||
{{else}}
|
||
<span class="dot dot-asleep"></span>
|
||
{{end}}
|
||
{{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 items-center">
|
||
{{range $host.Tags}}<a href="/?tag={{.}}" class="tag" title="filter dashboard by this tag">{{.}}</a>{{end}}
|
||
<button type="button" class="text-ink-fade text-[11px] hover:text-ink-mid whitespace-nowrap"
|
||
style="padding: 2px 8px; border: 1px dashed var(--line); border-radius: 3px; cursor: pointer;"
|
||
onclick="document.getElementById('tags-edit-{{$host.ID}}').classList.toggle('hidden')"
|
||
title="Edit tags">{{if $host.Tags}}edit tags{{else}}add tags{{end}}</button>
|
||
{{if $host.AlwaysOn}}<span class="tag" title="Expected online 24×7 — offline raises an alert">24×7</span>{{end}}
|
||
<button type="button" class="text-ink-fade text-[11px] hover:text-ink-mid whitespace-nowrap"
|
||
style="padding: 2px 8px; border: 1px dashed var(--line); border-radius: 3px; cursor: pointer;"
|
||
onclick="document.getElementById('mode-edit-{{$host.ID}}').classList.toggle('hidden')"
|
||
title="Change presence mode">presence</button>
|
||
</div>
|
||
{{if gt $page.ScheduleVersion 0}}
|
||
<span class="mono text-[11px] text-ink-mute ml-2">
|
||
version {{$page.ScheduleVersion}}
|
||
{{if eq $page.ScheduleVersion $host.AppliedScheduleVersion}}
|
||
<span class="text-ok">· agent in sync</span>
|
||
{{else}}
|
||
<span class="text-warn">· agent at v{{$host.AppliedScheduleVersion}}</span>
|
||
{{end}}
|
||
</span>
|
||
{{end}}
|
||
</div>
|
||
{{/* Inline tags editor — hidden by default; toggled by the
|
||
"edit/add tags" button above. Comma-separated input with
|
||
autocomplete sourced from the fleet's distinct tags via a
|
||
<datalist>. The help line under the input is always visible
|
||
because the placeholder hint disappears once the field has
|
||
a value, and operators editing existing tags are exactly
|
||
the people most likely to forget the comma rule. */}}
|
||
<form id="tags-edit-{{$host.ID}}" method="post"
|
||
action="/hosts/{{$host.ID}}/tags"
|
||
class="hidden mt-3"
|
||
style="max-width: 640px;">
|
||
<div class="flex items-start gap-2">
|
||
<input type="text" name="tags" class="field mono text-[12px]"
|
||
value="{{joinComma $host.Tags}}"
|
||
list="known-tags"
|
||
placeholder="prod, london, db" />
|
||
<datalist id="known-tags">
|
||
{{range $page.KnownTags}}<option value="{{.}}">{{end}}
|
||
</datalist>
|
||
<button type="submit" class="btn btn-primary whitespace-nowrap">Save tags</button>
|
||
</div>
|
||
<div class="field-help">Comma-separated. Lowercased automatically.</div>
|
||
</form>
|
||
{{/* Presence-mode editor — hidden by default; toggled by the
|
||
"presence" button. Checkbox present => always-on (24×7);
|
||
unchecked => intermittent (laptop): no offline alerts, shows
|
||
"asleep", auto-catches-up a missed backup on reconnect. */}}
|
||
<form id="mode-edit-{{$host.ID}}" method="post"
|
||
action="/hosts/{{$host.ID}}/mode"
|
||
class="hidden mt-3" style="max-width: 640px;">
|
||
<label class="flex items-center gap-2 text-[12px] text-ink-mid">
|
||
<input type="checkbox" name="always_on" value="on" {{if $host.AlwaysOn}}checked{{end}} />
|
||
Always On — expected online 24×7
|
||
</label>
|
||
<div class="field-help">
|
||
Uncheck for an intermittent host (laptop/workstation): it won't
|
||
raise offline alerts when asleep, shows an "asleep" state, and
|
||
catches up a missed backup ~1 minute after it reconnects.
|
||
</div>
|
||
<button type="submit" class="btn btn-primary mt-2 whitespace-nowrap">Save presence</button>
|
||
</form>
|
||
<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>{{if $page.UpdateAvailable}} {{template "host_update_chip" $page}}{{end}}</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"}}
|
||
{{if $host.AlwaysOn}}
|
||
<span>last seen <span class="mono text-ink-mid">{{relTime $host.LastSeenAt}}</span></span>
|
||
{{else}}
|
||
<span>asleep · last seen <span class="mono text-ink-mid">{{relTime $host.LastSeenAt}}</span> · will catch up on return</span>
|
||
{{end}}
|
||
{{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">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>
|
||
|
||
{{/* ---------- repo init line (P2R-09) ---------- */}}
|
||
{{if $page.InitStatus}}
|
||
<div class="text-[11.5px] text-ink-mute mt-2.5 leading-[1.5]">
|
||
{{if eq $page.InitStatus "succeeded"}}
|
||
repo ready · initialised <span class="mono text-ink-mid" {{if $page.InitAt}}title="{{$page.InitAt.Format "2006-01-02 15:04:05 MST"}}"{{end}}>{{relTime $page.InitAt}}</span>
|
||
{{else if eq $page.InitStatus "failed"}}
|
||
<span class="text-bad font-medium">init failed</span> ·
|
||
<a href="/jobs/{{$page.InitJobID}}" class="link mono">job {{$page.InitJobID}}</a> · retry from the Repo tab's danger zone
|
||
{{else if eq $page.InitStatus "running"}}
|
||
<span class="text-accent">init running…</span> · <a href="/jobs/{{$page.InitJobID}}" class="link mono">live log →</a>
|
||
{{else if eq $page.InitStatus "queued"}}
|
||
<span class="text-ink-fade">init queued</span> · <a href="/jobs/{{$page.InitJobID}}" class="link mono">job {{$page.InitJobID}}</a>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
|
||
{{/* ---------- latest restore line (P3-X3) ---------- */}}
|
||
{{if $page.RestoreStatus}}
|
||
<div class="text-[11.5px] text-ink-mute mt-1 leading-[1.5]">
|
||
{{if eq $page.RestoreStatus "succeeded"}}
|
||
last restore · <span class="text-ok">succeeded</span> <span class="mono text-ink-mid">{{relTime $page.RestoreAt}}</span> ·
|
||
<a href="/jobs/{{$page.RestoreJobID}}" class="link mono">job log →</a>
|
||
{{else if eq $page.RestoreStatus "failed"}}
|
||
last restore · <span class="text-bad font-medium">failed</span> <span class="mono text-ink-mid">{{relTime $page.RestoreAt}}</span> ·
|
||
<a href="/jobs/{{$page.RestoreJobID}}" class="link mono">job log →</a>
|
||
{{else if eq $page.RestoreStatus "running"}}
|
||
<span class="text-accent">restore running…</span> · <a href="/jobs/{{$page.RestoreJobID}}" class="link mono">live log →</a>
|
||
{{else if eq $page.RestoreStatus "cancelled"}}
|
||
last restore · <span class="text-warn">cancelled</span> <span class="mono text-ink-mid">{{relTime $page.RestoreAt}}</span> ·
|
||
<a href="/jobs/{{$page.RestoreJobID}}" class="link mono">job log →</a>
|
||
{{else if eq $page.RestoreStatus "queued"}}
|
||
<span class="text-ink-fade">restore queued</span> · <a href="/jobs/{{$page.RestoreJobID}}" class="link mono">job {{$page.RestoreJobID}}</a>
|
||
{{end}}
|
||
</div>
|
||
{{end}}
|
||
|
||
{{/* ---------- secondary tabs ---------- */}}
|
||
<div class="flex items-end mt-1.5">
|
||
<a class="sub-tab {{if eq $page.SubTab "snapshots"}}active{{end}}" href="/hosts/{{$host.ID}}">Snapshots <span class="mono text-ink-fade text-[11px] ml-1">{{comma $host.SnapshotCount}}</span></a>
|
||
<a class="sub-tab {{if eq $page.SubTab "sources"}}active{{end}}" href="/hosts/{{$host.ID}}/sources">Sources <span class="mono text-ink-fade text-[11px] ml-1">{{$page.SourceGroupCount}}</span></a>
|
||
<a class="sub-tab {{if eq $page.SubTab "schedules"}}active{{end}}" href="/hosts/{{$host.ID}}/schedules">Schedules <span class="mono text-ink-fade text-[11px] ml-1">{{$page.ScheduleCount}}</span></a>
|
||
<a class="sub-tab {{if eq $page.SubTab "repo"}}active{{end}}" href="/hosts/{{$host.ID}}/repo">Repo</a>
|
||
<a class="sub-tab {{if eq $page.SubTab "jobs"}}active{{end}}" href="/hosts/{{$host.ID}}/jobs">Jobs</a>
|
||
</div>
|
||
</div>
|
||
{{end}}
|