f9c2351ab6
- Single .host-row CSS rule replaces 13 inline grid-template-columns copies; column widths bumped so "backup running…" doesn't wrap. - Faint left-edge accent for degraded / failed / offline rows so problem hosts are scannable without reading. - Empty-state hero added: top-bar + nav still present (Dashboard active, others dimmed) but body collapses to a calm "no hosts yet" prompt with the install command as the load-bearing affordance. Prerequisite note keeps the deliberate "restic must already be installed" decision visible to first-time operators. This is the artefact P1-23/24/27 will template against. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
699 lines
40 KiB
HTML
699 lines
40 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>restic-manager · v1 Operator Console</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
/* Warm-dark slate, deliberately not pitch black — eyes prefer it on long sessions. */
|
||
--bg: oklch(0.17 0.006 250);
|
||
--panel: oklch(0.20 0.007 250);
|
||
--panel-hi: oklch(0.23 0.008 250);
|
||
--line: oklch(0.27 0.010 250);
|
||
--line-soft: oklch(0.23 0.008 250);
|
||
--ink: oklch(0.96 0.005 250);
|
||
--ink-mid: oklch(0.78 0.005 250);
|
||
--ink-mute: oklch(0.58 0.006 250);
|
||
--ink-fade: oklch(0.42 0.006 250);
|
||
|
||
--ok: oklch(0.78 0.14 155);
|
||
--warn: oklch(0.82 0.13 80);
|
||
--bad: oklch(0.70 0.20 25);
|
||
--off: oklch(0.50 0.005 250);
|
||
|
||
--accent: oklch(0.82 0.12 195);
|
||
--accent-dim:oklch(0.55 0.10 195);
|
||
}
|
||
html { background: var(--bg); }
|
||
body {
|
||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||
color: var(--ink);
|
||
font-feature-settings: 'cv11', 'ss01', 'ss03';
|
||
}
|
||
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
|
||
.hairline { box-shadow: inset 0 -1px 0 var(--line-soft); }
|
||
.panel { background: var(--panel); border: 1px solid var(--line-soft); }
|
||
.row-hover:hover { background: var(--panel-hi); }
|
||
.dot { width: 7px; height: 7px; border-radius: 9999px; display: inline-block; }
|
||
.dot-online { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ok), transparent 80%); }
|
||
.dot-degraded { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in oklch, var(--warn), transparent 80%); }
|
||
.dot-offline { background: var(--off); }
|
||
.dot-failed { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in oklch, var(--bad), transparent 80%); }
|
||
/* The 120% detail: status dots get a quiet pulse only when the host is currently running a job. */
|
||
.pulse {
|
||
animation: pulse 2.4s ease-in-out infinite;
|
||
}
|
||
@keyframes pulse {
|
||
0%, 100% { box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent), transparent 80%); }
|
||
50% { box-shadow: 0 0 0 6px color-mix(in oklch, var(--accent), transparent 92%); }
|
||
}
|
||
.tag {
|
||
font-size: 11px; line-height: 1; padding: 4px 7px;
|
||
border: 1px solid var(--line); color: var(--ink-mid);
|
||
border-radius: 3px; letter-spacing: 0.01em;
|
||
}
|
||
.btn {
|
||
font-family: 'Inter'; font-size: 12px; font-weight: 500;
|
||
padding: 6px 11px; border-radius: 5px;
|
||
color: var(--ink-mid);
|
||
background: transparent; border: 1px solid var(--line);
|
||
transition: all 120ms ease;
|
||
}
|
||
.btn:hover { background: var(--panel-hi); color: var(--ink); border-color: var(--line); }
|
||
.btn-primary {
|
||
color: oklch(0.18 0.01 195); background: var(--accent); border-color: var(--accent);
|
||
}
|
||
.btn-primary:hover { background: var(--accent); filter: brightness(1.08); color: oklch(0.16 0.01 195);}
|
||
.btn-ghost { border-color: transparent; }
|
||
.btn-ghost:hover { border-color: var(--line); }
|
||
.nav-tab {
|
||
font-size: 13px; padding: 18px 0; color: var(--ink-mute);
|
||
border-bottom: 2px solid transparent; margin-right: 28px;
|
||
cursor: pointer;
|
||
}
|
||
.nav-tab.active { color: var(--ink); border-color: var(--accent); }
|
||
.nav-tab:hover { color: var(--ink); }
|
||
|
||
.text-balance { text-wrap: balance; }
|
||
::selection { background: color-mix(in oklch, var(--accent), transparent 70%); }
|
||
|
||
/* doc shell — separates the philosophy preamble from the hi-fi screen */
|
||
.doc {
|
||
max-width: 1280px; margin: 0 auto; padding: 0 32px;
|
||
}
|
||
.philosophy {
|
||
border-bottom: 1px solid var(--line-soft);
|
||
padding: 56px 0 40px;
|
||
}
|
||
.philosophy h1 { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
|
||
.philosophy p {
|
||
color: var(--ink-mid); max-width: 680px;
|
||
margin-top: 14px; line-height: 1.65;
|
||
text-wrap: pretty;
|
||
}
|
||
.philosophy .meta { color: var(--ink-fade); font-size: 12px; margin-top: 14px; }
|
||
.stage-frame {
|
||
margin: 48px -32px;
|
||
border-top: 1px solid var(--line-soft);
|
||
border-bottom: 1px solid var(--line-soft);
|
||
}
|
||
.stage-frame::before, .stage-frame::after {
|
||
content: ''; display: block; height: 0;
|
||
}
|
||
.components-section { margin: 64px 0 96px; }
|
||
.components-section h2 { font-size: 14px; font-weight: 600; color: var(--ink-mute); text-transform: uppercase; letter-spacing: 0.08em; }
|
||
|
||
/* Single source of truth for the host-row grid. The "last backup"
|
||
column was previously 1.1fr — too narrow for "backup running…"
|
||
to fit on one line. Bumped to 1.5fr; trimmed name to compensate. */
|
||
.host-row {
|
||
display: grid; align-items: center;
|
||
grid-template-columns: 24px 1.4fr 0.95fr 1.5fr 0.75fr 0.7fr 0.7fr 1.1fr 92px;
|
||
padding: 11px 16px; font-size: 13px;
|
||
/* Reserve the 3px the row-accent will live in so degraded/failed
|
||
rows don't shift sideways relative to healthy ones. */
|
||
border-left: 3px solid transparent;
|
||
}
|
||
.host-row.head { padding-top: 10px; padding-bottom: 10px; font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; border-left-width: 3px; }
|
||
.host-row.degraded { border-left-color: color-mix(in oklch, var(--warn), transparent 50%); }
|
||
.host-row.failed { border-left-color: color-mix(in oklch, var(--bad), transparent 50%); }
|
||
.host-row.offline { border-left-color: color-mix(in oklch, var(--off), transparent 70%); }
|
||
|
||
/* Empty state: full-bleed quiet panel inside the stage. */
|
||
.empty-state {
|
||
text-align: center; padding: 88px 32px 96px;
|
||
border: 1px dashed var(--line); border-radius: 8px;
|
||
background:
|
||
radial-gradient(ellipse at top, color-mix(in oklch, var(--accent), transparent 95%), transparent 60%),
|
||
var(--panel);
|
||
}
|
||
.empty-state h3 { font-size: 18px; font-weight: 500; letter-spacing: -0.005em; }
|
||
.empty-state p {
|
||
color: var(--ink-mid); max-width: 520px; margin: 12px auto 0;
|
||
line-height: 1.65; font-size: 13px; text-wrap: pretty;
|
||
}
|
||
.empty-state .install-card {
|
||
max-width: 640px; margin: 28px auto 0;
|
||
text-align: left; background: var(--bg);
|
||
border: 1px solid var(--line-soft); border-radius: 6px;
|
||
overflow: hidden;
|
||
}
|
||
.empty-state .install-card-head {
|
||
font-size: 11px; color: var(--ink-fade);
|
||
padding: 10px 14px; border-bottom: 1px solid var(--line-soft);
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
text-transform: uppercase; letter-spacing: 0.1em;
|
||
}
|
||
.empty-state .install-card pre {
|
||
margin: 0; padding: 14px; font-family: 'JetBrains Mono', monospace;
|
||
font-size: 12px; color: var(--ink-mid); line-height: 1.7;
|
||
white-space: pre-wrap; word-break: break-all;
|
||
}
|
||
.empty-state .install-card pre .var { color: var(--accent); }
|
||
</style>
|
||
</head>
|
||
<body class="bg-[color:var(--bg)]">
|
||
|
||
<div class="doc">
|
||
|
||
<!-- Philosophy preamble -->
|
||
<header class="philosophy">
|
||
<div class="text-xs uppercase tracking-[0.18em] text-[color:var(--ink-fade)] mb-3">v1 · Direction A</div>
|
||
<h1>Operator console.</h1>
|
||
<p>
|
||
Optimised for the daily 30-second glance. Dense by default, never fussy. Every
|
||
pixel earns its place; colour is reserved for state, not decoration. Numerics
|
||
are monospaced and right-aligned so the eye can scan a column without
|
||
tracking labels. Hover reveals secondary actions; primary actions are always visible.
|
||
</p>
|
||
<p>
|
||
Reference: Linear, Datadog, Vercel observability. Built for someone who looks
|
||
at this every morning while their coffee brews.
|
||
</p>
|
||
<p class="meta">
|
||
Not for: a public-facing landing page, a marketing surface, a screen anyone
|
||
shows to non-technical stakeholders. This is a tool, not a product page.
|
||
</p>
|
||
</header>
|
||
|
||
<!-- Stage: full hi-fi dashboard -->
|
||
<div class="stage-frame">
|
||
<div style="background: var(--bg); padding: 0;">
|
||
|
||
<!-- Top bar -->
|
||
<div class="hairline" style="background: var(--bg);">
|
||
<div class="doc flex items-center justify-between" style="padding: 16px 0;">
|
||
<div class="flex items-center gap-3">
|
||
<div class="mono" style="font-size:13px; letter-spacing: 0.02em; color: var(--ink); font-weight:500;">restic-manager</div>
|
||
<div class="mono" style="font-size:11px; color: var(--ink-fade);">v0.1.0-alpha</div>
|
||
</div>
|
||
<div class="flex items-center gap-5">
|
||
<div class="mono" style="font-size:12px; color: var(--ink-mute);">steve@dcglab</div>
|
||
<button class="btn btn-ghost">Sign out</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Primary nav -->
|
||
<div class="hairline" style="background: var(--bg);">
|
||
<div class="doc flex items-end justify-between">
|
||
<nav class="flex items-end">
|
||
<div class="nav-tab active">Dashboard</div>
|
||
<div class="nav-tab">Repos</div>
|
||
<div class="nav-tab">Alerts <span class="mono ml-1.5" style="font-size:11px; color: var(--bad);">5</span></div>
|
||
<div class="nav-tab">Audit</div>
|
||
<div class="nav-tab">Settings</div>
|
||
</nav>
|
||
<div class="pb-3">
|
||
<button class="btn btn-primary">+ Add host</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Fleet summary strip -->
|
||
<div class="doc" style="padding: 28px 32px;">
|
||
<div class="grid grid-cols-12 gap-6">
|
||
<div class="col-span-3">
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px;">Fleet</div>
|
||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em;">12 <span style="color: var(--ink-mute); font-size:13px; font-weight:400;">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" style="color: var(--ink-mid);">10</span><span style="color: var(--ink-mute);">online</span></span>
|
||
<span class="flex items-center gap-1.5"><span class="dot dot-degraded"></span><span class="mono" style="color: var(--ink-mid);">1</span><span style="color: var(--ink-mute);">degraded</span></span>
|
||
<span class="flex items-center gap-1.5"><span class="dot dot-offline"></span><span class="mono" style="color: var(--ink-mid);">1</span><span style="color: var(--ink-mute);">offline</span></span>
|
||
</div>
|
||
</div>
|
||
<div class="col-span-3">
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px;">Backed up</div>
|
||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em;">4.9 <span style="color: var(--ink-mute); font-size:13px; font-weight:400;">TB across 12 repos</span></div>
|
||
<div class="text-xs mt-2.5" style="color: var(--ink-mute);"><span class="mono" style="color: var(--ink-mid);">23,649</span> snapshots total</div>
|
||
</div>
|
||
<div class="col-span-3">
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px;">Last 24h</div>
|
||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em;">147 <span style="color: var(--ink-mute); font-size:13px; font-weight:400;">jobs</span></div>
|
||
<div class="text-xs mt-2.5"><span class="mono" style="color: var(--ok);">144</span> <span style="color: var(--ink-mute);">succeeded</span> · <span class="mono" style="color: var(--bad);">2</span> <span style="color: var(--ink-mute);">failed</span> · <span class="mono" style="color: var(--ink-mid);">1</span> <span style="color: var(--ink-mute);">cancelled</span></div>
|
||
</div>
|
||
<div class="col-span-3">
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px;">Open alerts</div>
|
||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em; color: var(--bad);">5 <span style="color: var(--ink-mute); font-size:13px; font-weight:400;">unresolved</span></div>
|
||
<div class="text-xs mt-2.5" style="color: var(--ink-mute);">oldest <span class="mono" style="color: var(--ink-mid);">3h</span> · acknowledge in panel</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Hosts table -->
|
||
<div class="doc" style="padding: 8px 32px 24px;">
|
||
|
||
<!-- table head -->
|
||
<div class="flex items-center justify-between mb-3">
|
||
<div class="flex items-center gap-3">
|
||
<h2 style="font-size: 13px; font-weight: 600; color: var(--ink); letter-spacing: 0.01em;">Hosts</h2>
|
||
<div style="font-size: 12px; color: var(--ink-fade);">12 of 12</div>
|
||
</div>
|
||
<div class="flex items-center gap-2">
|
||
<input type="text" placeholder="Filter by name, tag, status…"
|
||
class="mono px-3 py-1.5 text-xs"
|
||
style="background: var(--panel); border: 1px solid var(--line-soft); color: var(--ink); border-radius: 5px; width: 280px; outline: none;"
|
||
/>
|
||
<button class="btn">Group: tag ▾</button>
|
||
<button class="btn">Sort: status ▾</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- table -->
|
||
<div class="panel" style="border-radius: 7px; overflow: hidden;">
|
||
<!-- header row -->
|
||
<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 class="text-right">Alerts</div>
|
||
<div>Tags</div>
|
||
<div></div>
|
||
</div>
|
||
|
||
<!-- row: prod-db-01 — online + currently running -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online pulse"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">prod-db-01</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--accent);">backup running…</span><br>
|
||
<span class="mono" style="color: var(--ink-fade);">started 3m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">412 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">1,847</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span><span class="tag">db</span></div>
|
||
<div class="text-right"><button class="btn btn-ghost">View →</button></div>
|
||
</div>
|
||
|
||
<!-- row: prod-db-02 -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">prod-db-02</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">4m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">389 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">1,802</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span><span class="tag">db</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: prod-web-01 -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">prod-web-01</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">11m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">87 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">2,103</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span><span class="tag">web</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: prod-web-02 -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">prod-web-02</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">12m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">87 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">2,098</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span><span class="tag">web</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: prod-cache-01 — degraded with alerts -->
|
||
<div class="row-hover host-row degraded hairline">
|
||
<div><span class="dot dot-degraded"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">prod-cache-01</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">1h ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">128 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">1,402</div>
|
||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">3</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span><span class="tag">cache</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: homelab-nas -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">homelab-nas</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/arm64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">2h ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">3.7 <span style="color: var(--ink-mute); font-size: 11px;">TB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">8,912</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">homelab</span><span class="tag">storage</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: homelab-pi -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">homelab-pi</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/arm64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">6h ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">8.4 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">421</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">homelab</span><span class="tag">iot</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: dev-laptop — offline -->
|
||
<div class="row-hover host-row offline hairline">
|
||
<div><span class="dot dot-offline"></span></div>
|
||
<div class="mono" style="color: var(--ink-mid); font-weight: 500;">dev-laptop</div>
|
||
<div class="mono" style="color: var(--ink-mute); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mute);">
|
||
<span>last seen <span class="mono">2d ago</span></span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">64 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">127</div>
|
||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">1</div>
|
||
<div class="flex gap-1.5"><span class="tag">dev</span><span class="tag">personal</span></div>
|
||
<div class="text-right"><span class="mono text-xs" style="color: var(--ink-fade);">offline</span></div>
|
||
</div>
|
||
|
||
<!-- row: windows-vm -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">windows-vm</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">windows/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">28m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">44 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">156</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">staging</span><span class="tag">vm</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
<!-- row: build-runner — failed -->
|
||
<div class="row-hover host-row failed hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">build-runner</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--bad); font-weight:500;">failed</span> · <span class="mono">47m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">97 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">2,847</div>
|
||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">1</div>
|
||
<div class="flex gap-1.5"><span class="tag">ci</span><span class="tag">build</span></div>
|
||
<div class="text-right"><button class="btn">Retry</button></div>
|
||
</div>
|
||
|
||
<!-- row: backup-test — never -->
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">backup-test</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mute);">
|
||
<span style="color: var(--ink-fade); font-style: italic;">never run</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink-fade);">—</div>
|
||
<div class="text-right mono" style="color: var(--ink-fade);">—</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">test</span></div>
|
||
<div class="text-right"><button class="btn">Run first</button></div>
|
||
</div>
|
||
|
||
<!-- row: edge-node-eu (last row, no hairline) -->
|
||
<div class="row-hover host-row">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">edge-node-eu</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/arm64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);">
|
||
<span style="color: var(--ok);">succeeded</span> · <span class="mono">7m ago</span>
|
||
</div>
|
||
<div class="text-right mono" style="color: var(--ink);">23 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">934</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">edge</span><span class="tag">prod</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Recent jobs strip -->
|
||
<div class="doc" style="padding: 16px 32px 56px;">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h2 style="font-size: 13px; font-weight: 600; color: var(--ink); letter-spacing: 0.01em;">Recent activity</h2>
|
||
<a class="text-xs" style="color: var(--ink-mute);">View all jobs →</a>
|
||
</div>
|
||
<div class="panel" style="border-radius: 7px;">
|
||
<div class="grid hairline" style="grid-template-columns: 1fr 1.5fr 0.8fr 1fr 1fr 1fr; padding: 9px 16px; font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em;">
|
||
<div>Job</div><div>Host</div><div>Kind</div><div>Status</div><div>Started</div><div class="text-right">Bytes / Δ</div>
|
||
</div>
|
||
<div class="grid hairline mono" style="grid-template-columns: 1fr 1.5fr 0.8fr 1fr 1fr 1fr; padding: 10px 16px; font-size: 12px;">
|
||
<div style="color: var(--ink-mute);">01KQH…E59B</div>
|
||
<div style="color: var(--ink);">prod-db-01</div>
|
||
<div style="color: var(--ink-mid);">backup</div>
|
||
<div style="color: var(--accent);">running · 38%</div>
|
||
<div style="color: var(--ink-mid);">3m ago</div>
|
||
<div class="text-right" style="color: var(--ink-mid);">1.4 GB</div>
|
||
</div>
|
||
<div class="grid hairline mono" style="grid-template-columns: 1fr 1.5fr 0.8fr 1fr 1fr 1fr; padding: 10px 16px; font-size: 12px;">
|
||
<div style="color: var(--ink-mute);">01KQH…D9XK</div>
|
||
<div style="color: var(--ink);">prod-db-02</div>
|
||
<div style="color: var(--ink-mid);">backup</div>
|
||
<div style="color: var(--ok);">succeeded</div>
|
||
<div style="color: var(--ink-mid);">4m ago</div>
|
||
<div class="text-right" style="color: var(--ink-mid);">1.2 GB</div>
|
||
</div>
|
||
<div class="grid hairline mono" style="grid-template-columns: 1fr 1.5fr 0.8fr 1fr 1fr 1fr; padding: 10px 16px; font-size: 12px;">
|
||
<div style="color: var(--ink-mute);">01KQH…7P2R</div>
|
||
<div style="color: var(--ink);">edge-node-eu</div>
|
||
<div style="color: var(--ink-mid);">backup</div>
|
||
<div style="color: var(--ok);">succeeded</div>
|
||
<div style="color: var(--ink-mid);">7m ago</div>
|
||
<div class="text-right" style="color: var(--ink-mid);">18 MB</div>
|
||
</div>
|
||
<div class="grid hairline mono" style="grid-template-columns: 1fr 1.5fr 0.8fr 1fr 1fr 1fr; padding: 10px 16px; font-size: 12px;">
|
||
<div style="color: var(--ink-mute);">01KQH…M4QQ</div>
|
||
<div style="color: var(--ink);">prod-web-01</div>
|
||
<div style="color: var(--ink-mid);">backup</div>
|
||
<div style="color: var(--ok);">succeeded</div>
|
||
<div style="color: var(--ink-mid);">11m ago</div>
|
||
<div class="text-right" style="color: var(--ink-mid);">42 MB</div>
|
||
</div>
|
||
<div class="grid mono" style="grid-template-columns: 1fr 1.5fr 0.8fr 1fr 1fr 1fr; padding: 10px 16px; font-size: 12px;">
|
||
<div style="color: var(--ink-mute);">01KQH…9F8C</div>
|
||
<div style="color: var(--ink);">build-runner</div>
|
||
<div style="color: var(--ink-mid);">backup</div>
|
||
<div style="color: var(--bad);">failed · repo locked</div>
|
||
<div style="color: var(--ink-mid);">47m ago</div>
|
||
<div class="text-right" style="color: var(--ink-fade);">—</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Empty state — the very first screen a new user sees. The chrome,
|
||
fleet stat blocks, and table are all replaced with a single
|
||
calm prompt: enrol your first host. The install command is the
|
||
hero affordance. -->
|
||
<div style="margin-top: 64px;">
|
||
<div class="text-xs uppercase tracking-[0.18em]" style="color: var(--ink-fade); margin-bottom: 12px;">Empty state · first run</div>
|
||
</div>
|
||
<div class="stage-frame">
|
||
<div style="background: var(--bg); padding: 0;">
|
||
|
||
<!-- Top bar (still present, just with no host context) -->
|
||
<div class="hairline" style="background: var(--bg);">
|
||
<div class="doc flex items-center justify-between" style="padding: 16px 0;">
|
||
<div class="flex items-center gap-3">
|
||
<div class="mono" style="font-size:13px; letter-spacing: 0.02em; color: var(--ink); font-weight:500;">restic-manager</div>
|
||
<div class="mono" style="font-size:11px; color: var(--ink-fade);">v0.1.0-alpha</div>
|
||
</div>
|
||
<div class="flex items-center gap-5">
|
||
<div class="mono" style="font-size:12px; color: var(--ink-mute);">steve@dcglab</div>
|
||
<button class="btn btn-ghost">Sign out</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Nav (Dashboard active, but everything else dimmed since there's nothing to see yet) -->
|
||
<div class="hairline" style="background: var(--bg);">
|
||
<div class="doc flex items-end justify-between">
|
||
<nav class="flex items-end">
|
||
<div class="nav-tab active">Dashboard</div>
|
||
<div class="nav-tab" style="opacity: 0.45;">Repos</div>
|
||
<div class="nav-tab" style="opacity: 0.45;">Alerts</div>
|
||
<div class="nav-tab" style="opacity: 0.45;">Audit</div>
|
||
<div class="nav-tab">Settings</div>
|
||
</nav>
|
||
<div class="pb-3">
|
||
<button class="btn btn-primary">+ Add host</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- The main empty body -->
|
||
<div class="doc" style="padding: 56px 32px 80px;">
|
||
<div class="empty-state">
|
||
<h3>No hosts yet.</h3>
|
||
<p>
|
||
<span class="mono" style="color: var(--ink);">restic-manager</span> tracks
|
||
backups across a fleet — but there’s nothing to track until you enrol
|
||
your first host. Run the snippet below on a Linux box, paste the
|
||
one-time token, and it’ll appear here within a few seconds.
|
||
</p>
|
||
|
||
<div class="install-card">
|
||
<div class="install-card-head">
|
||
<span>Install command · expires in 1h</span>
|
||
<button class="btn btn-ghost mono" style="font-size: 11px;">Copy</button>
|
||
</div>
|
||
<pre>curl -fsSL <span class="var">https://restic.lab.example/install.sh</span> | sudo \
|
||
RM_SERVER=<span class="var">https://restic.lab.example</span> \
|
||
RM_TOKEN=<span class="var">HdqFbQh8U-I1fb52iP1M8qxvoYS5t9VZ-T-yghr_CzA</span> sh</pre>
|
||
</div>
|
||
|
||
<div style="margin-top: 28px; display: flex; justify-content: center; gap: 10px;">
|
||
<button class="btn btn-primary">+ Add host (mint another token)</button>
|
||
<button class="btn">Read the install guide →</button>
|
||
</div>
|
||
|
||
<div style="margin-top: 36px; font-size: 12px; color: var(--ink-fade);">
|
||
Prerequisites: <span class="mono" style="color: var(--ink-mute);">restic</span> ≥ 0.16 already installed on the target host. The agent will not install it for you — see <a class="underline underline-offset-4 decoration-1" style="color: var(--ink-mid); text-decoration-color: var(--line);">why</a>.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Component vocabulary -->
|
||
<section class="components-section">
|
||
<h2>Component vocabulary</h2>
|
||
<p style="color: var(--ink-mute); font-size: 13px; margin-top: 8px; max-width: 620px;">
|
||
Pulled out of the live screen so the system reads cleanly. Anything that doesn’t appear
|
||
here doesn’t exist in this direction — and the goal is for the list below to stay short.
|
||
</p>
|
||
|
||
<div class="grid grid-cols-2 gap-10 mt-10">
|
||
|
||
<!-- status states -->
|
||
<div>
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px;">Status</div>
|
||
<div class="panel" style="border-radius: 7px; padding: 14px 18px;">
|
||
<div class="flex items-center justify-between text-sm py-2">
|
||
<span class="flex items-center gap-3"><span class="dot dot-online"></span><span>online</span></span>
|
||
<span class="mono text-xs" style="color: var(--ink-mute);">heartbeat < 90s</span>
|
||
</div>
|
||
<div class="flex items-center justify-between text-sm py-2 hairline">
|
||
<span class="flex items-center gap-3"><span class="dot dot-online pulse"></span><span>online · running job</span></span>
|
||
<span class="mono text-xs" style="color: var(--ink-mute);">pulse only when active</span>
|
||
</div>
|
||
<div class="flex items-center justify-between text-sm py-2 hairline">
|
||
<span class="flex items-center gap-3"><span class="dot dot-degraded"></span><span>degraded</span></span>
|
||
<span class="mono text-xs" style="color: var(--ink-mute);">open alerts > 0</span>
|
||
</div>
|
||
<div class="flex items-center justify-between text-sm py-2 hairline">
|
||
<span class="flex items-center gap-3"><span class="dot dot-offline"></span><span>offline</span></span>
|
||
<span class="mono text-xs" style="color: var(--ink-mute);">no heartbeat > 90s</span>
|
||
</div>
|
||
<div class="flex items-center justify-between text-sm py-2 hairline">
|
||
<span class="flex items-center gap-3"><span class="dot dot-failed"></span><span>last job failed</span></span>
|
||
<span class="mono text-xs" style="color: var(--ink-mute);">distinct from offline</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- buttons -->
|
||
<div>
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px;">Buttons</div>
|
||
<div class="panel" style="border-radius: 7px; padding: 16px 18px;">
|
||
<div class="flex items-center gap-2 flex-wrap">
|
||
<button class="btn btn-primary">+ Add host</button>
|
||
<button class="btn">Run now</button>
|
||
<button class="btn btn-ghost">View →</button>
|
||
<button class="btn" style="color: var(--bad); border-color: color-mix(in oklch, var(--bad), transparent 70%);">Cancel job</button>
|
||
<button class="btn" disabled style="opacity: 0.4; cursor: not-allowed;">Run now</button>
|
||
</div>
|
||
<div class="text-xs mt-3" style="color: var(--ink-mute);">
|
||
One <span style="color:var(--ink);">primary</span> per page (Add host). Everything else is the
|
||
neutral <span style="color:var(--ink);">secondary</span> or <span style="color:var(--ink);">ghost</span>; danger only on destructive verbs.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<!-- host card states (the rows compressed into 3 distinct examples) -->
|
||
<div class="mt-10">
|
||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px;">Host row · 3 states</div>
|
||
<div class="panel" style="border-radius: 7px;">
|
||
<div class="row-hover host-row hairline">
|
||
<div><span class="dot dot-online"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">healthy-host</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);"><span style="color: var(--ok);">succeeded</span> · <span class="mono">5m ago</span></div>
|
||
<div class="text-right mono" style="color: var(--ink);">87 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">2,103</div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
<div class="row-hover host-row degraded hairline">
|
||
<div><span class="dot dot-degraded"></span></div>
|
||
<div class="mono" style="color: var(--ink); font-weight: 500;">degraded-host</div>
|
||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mid);"><span style="color: var(--ok);">succeeded</span> · <span class="mono">1h ago</span></div>
|
||
<div class="text-right mono" style="color: var(--ink);">128 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">1,402</div>
|
||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">3</div>
|
||
<div class="flex gap-1.5"><span class="tag">prod</span></div>
|
||
<div class="text-right"><button class="btn">Run now</button></div>
|
||
</div>
|
||
<div class="row-hover host-row offline">
|
||
<div><span class="dot dot-offline"></span></div>
|
||
<div class="mono" style="color: var(--ink-mid); font-weight: 500;">offline-host</div>
|
||
<div class="mono" style="color: var(--ink-mute); font-size:12px;">linux/amd64</div>
|
||
<div class="text-xs" style="color: var(--ink-mute);">last seen <span class="mono">2d ago</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mid);">64 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||
<div class="text-right mono" style="color: var(--ink-mute);">127</div>
|
||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">1</div>
|
||
<div class="flex gap-1.5"><span class="tag">dev</span></div>
|
||
<div class="text-right"><span class="mono text-xs" style="color: var(--ink-fade);">offline</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|