9bcd8bc5fe
- Surface UpdateAvailable + TargetVersion on the dashboard host row, the host_chrome header, and the JSON Host shape. - New host_update_chip partial renders an amber out-of-date pill next to the agent-version display when the host's agent trails the server. - Host detail right-rail gains an admin-only Update agent button (disabled when host is offline or already updating). - New .update-chip and .btn-amber CSS tokens; tailwind output refreshed.
156 lines
7.3 KiB
HTML
156 lines
7.3 KiB
HTML
{{define "title"}}{{.Title}}{{end}}
|
|
|
|
{{define "content"}}
|
|
{{template "host_chrome" .}}
|
|
{{$page := .Page}}
|
|
{{$host := $page.Host}}
|
|
<div class="max-w-[1280px] mx-auto px-8 pb-14">
|
|
|
|
{{/* ---------- snapshots tab ---------- */}}
|
|
<div class="grid grid-cols-12 gap-6 pt-6 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">
|
|
<a href="/hosts/{{$host.ID}}/sources" class="btn">Open Sources →</a>
|
|
</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{{if $page.LegacyRestic}} cursor-help{{end}}"
|
|
{{if $page.LegacyRestic}}title="Needs restic 0.17+ on the agent host. This host runs {{$host.ResticVersion}}."{{end}}>Size</div>
|
|
<div class="text-right{{if $page.LegacyRestic}} cursor-help{{end}}"
|
|
{{if $page.LegacyRestic}}title="Needs restic 0.17+ on the agent host. This host runs {{$host.ResticVersion}}."{{end}}>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">
|
|
<a href="/hosts/{{$host.ID}}/snapshots/{{$s.ID}}/restore" class="btn">Restore →</a>
|
|
</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>
|
|
<p class="text-[12px] text-ink-mute leading-[1.55] mb-2">
|
|
Run-now lives on individual source groups now —
|
|
<a href="/hosts/{{$host.ID}}/sources" class="underline">open Sources →</a>
|
|
</p>
|
|
</div>
|
|
|
|
{{if and $page.CanAdmin $page.UpdateAvailable}}
|
|
<div class="panel rounded-[7px] px-4 py-3.5">
|
|
<div class="text-[11px] text-ink-fade uppercase tracking-[0.1em] mb-2.5">Agent update</div>
|
|
<p class="text-[12px] text-ink-mute leading-[1.55] mb-3">
|
|
Agent at <span class="mono text-ink-mid">{{$host.AgentVersion}}</span> ·
|
|
server at <span class="mono text-ink-mid">{{$page.TargetVersion}}</span>.
|
|
Pushes a self-update command; the agent re-launches into the new binary
|
|
and reconnects.
|
|
</p>
|
|
<form hx-post="/hosts/{{$host.ID}}/update" hx-swap="none">
|
|
<button class="btn btn-amber btn-block"
|
|
{{if not $page.Online}}disabled title="Agent must be online"
|
|
{{else if $page.UpdateInProgress}}disabled title="Update already in progress"
|
|
{{end}}>
|
|
Update agent
|
|
</button>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="panel rounded-[7px] px-4 py-3.5">
|
|
<div class="text-[11px] text-ink-fade uppercase tracking-[0.1em] mb-2.5">Restore</div>
|
|
<p class="text-[12px] text-ink-mute leading-[1.55] mb-3">
|
|
Pick a snapshot, choose paths, dispatch. Live progress streams once the
|
|
agent starts.
|
|
</p>
|
|
<a href="/hosts/{{$host.ID}}/restore"
|
|
class="btn btn-block">Restore from snapshot…</a>
|
|
</div>
|
|
|
|
{{if gt $host.SnapshotCount 1}}
|
|
<div class="panel rounded-[7px] px-4 py-3.5">
|
|
<div class="text-[11px] text-ink-fade uppercase tracking-[0.1em] mb-2.5">Compare snapshots</div>
|
|
<p class="text-[12px] text-ink-mute leading-[1.55] mb-3">
|
|
Diff two snapshots to see what changed. Output streams to a live
|
|
job page like a regular run.
|
|
</p>
|
|
<form method="post" action="/hosts/{{$host.ID}}/snapshots/diff"
|
|
hx-post="/hosts/{{$host.ID}}/snapshots/diff" hx-swap="none"
|
|
class="space-y-2">
|
|
<input type="text" name="snapshot_a" placeholder="snapshot A id"
|
|
class="field mono text-[11.5px]" />
|
|
<input type="text" name="snapshot_b" placeholder="snapshot B id"
|
|
class="field mono text-[11.5px]" />
|
|
<button type="submit" class="btn btn-block">Diff →</button>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
|
|
<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 and everything attached to it
|
|
(schedules, source groups, jobs, snapshots metadata, alerts).
|
|
The agent's bearer is revoked, so a re-installed instance
|
|
comes back through the normal pending-host accept flow.
|
|
The repo data on the rest-server is left intact — you delete
|
|
that yourself.
|
|
</p>
|
|
<form method="post" action="/hosts/{{$host.ID}}/delete"
|
|
class="space-y-2"
|
|
onsubmit="return confirm('Remove host "{{$host.Name}}"? This cascades to every dependent row and cannot be undone.');">
|
|
<input type="text" name="confirm_hostname" required autocomplete="off"
|
|
placeholder="type hostname to confirm"
|
|
class="field mono text-[12px]" />
|
|
<button type="submit" class="btn btn-danger w-full justify-center">Remove host…</button>
|
|
</form>
|
|
</div>
|
|
|
|
</aside>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
{{end}}
|