Files
restic-manager/web/styles/input.css
T
steve 0ed9c3d1ec P2R-02 slice 2: Sources tab — list, new/edit form, delete, Run-now
Sources tab now lists every source group on the host with per-row
counts (used-by-N-schedules, snapshot count by tag), the v4
conflict tag (keep-* dimension that has no compatible cadence),
and Run-now / Edit / Delete actions. Run-now reuses the existing
HTMX-aware /hosts/{id}/source-groups/{gid}/run handler.

New /hosts/{id}/sources/new and /sources/{gid}/edit form: name +
includes/excludes textareas + the 3×2 keep-* retention grid +
retry-on-offline knobs. Server-side validation re-renders with the
operator's input intact; the inline conflict banner shows above the
retention grid when ConflictDimension is set.

Delete blocks (UI + server) when the group is referenced by any
schedule. Every successful mutation calls pushScheduleSetAsync so
an online agent re-arms within seconds.

Adds .src-row and .keep-cell to input.css for the row + retention
grid layout.
2026-05-03 11:44:43 +01:00

270 lines
10 KiB
CSS
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.
/* ============================================================
* v1 design tokens + components.
*
* Source of truth for the operator-console register. Anything not
* defined here doesn't exist in v1. New components get added here
* first, then templated.
*
* Built via the Tailwind standalone CLI (no Node):
* make tailwind
* outputs:
* web/static/css/styles.css
* ============================================================ */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* surface */
--bg: oklch(0.17 0.006 250);
--panel: oklch(0.20 0.007 250);
--panel-hi: oklch(0.23 0.008 250);
/* line */
--line: oklch(0.27 0.010 250);
--line-soft: oklch(0.23 0.008 250);
/* ink */
--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);
/* state */
--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 */
--accent: oklch(0.82 0.12 195);
}
html, body {
background: var(--bg);
color: var(--ink);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
body {
font-feature-settings: 'cv11', 'ss01', 'ss03';
}
::selection { background: color-mix(in oklch, var(--accent), transparent 70%); }
}
@layer components {
/* ---------- typography helpers ---------- */
.mono {
font-family: 'JetBrains Mono', ui-monospace, monospace;
font-variant-numeric: tabular-nums;
}
/* ---------- surface helpers ---------- */
.panel { background: var(--panel); border: 1px solid var(--line-soft); }
.hairline { box-shadow: inset 0 -1px 0 var(--line-soft); }
/* ---------- status dots ---------- */
.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%); }
.pulse { animation: rm-pulse 2.4s ease-in-out infinite; }
@keyframes rm-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%); }
}
/* ---------- buttons ---------- */
.btn {
font-size: 12px; font-weight: 500;
padding: 6px 11px; border-radius: 5px;
background: transparent;
border: 1px solid var(--line);
color: var(--ink-mid);
transition: all 120ms ease;
cursor: pointer;
display: inline-flex; align-items: center; gap: 6px;
text-decoration: none;
}
.btn:hover { background: var(--panel-hi); color: var(--ink); }
.btn:disabled, .btn[disabled] { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
.btn-primary { color: oklch(0.18 0.01 195); background: var(--accent); border-color: var(--accent); }
.btn-primary:hover { filter: brightness(1.08); }
.btn-ghost { border-color: transparent; }
.btn-ghost:hover { background: var(--panel-hi); border-color: transparent; }
.btn-danger { color: var(--bad); border-color: color-mix(in oklch, var(--bad), transparent 70%); }
.btn-danger:hover {
background: color-mix(in oklch, var(--bad), transparent 88%);
border-color: color-mix(in oklch, var(--bad), transparent 50%);
color: oklch(0.85 0.10 25);
}
.btn-lg { font-size: 13px; padding: 9px 14px; }
.btn-block { width: 100%; justify-content: center; }
/* ---------- nav tabs ---------- */
.nav-tab {
font-size: 13px; padding: 18px 0;
color: var(--ink-mute);
border-bottom: 2px solid transparent;
margin-right: 28px;
cursor: pointer;
text-decoration: none;
}
.nav-tab.active { color: var(--ink); border-color: var(--accent); }
.nav-tab:hover { color: var(--ink); }
/* secondary tabs (host detail sub-nav) */
.sub-tab {
font-size: 13px; padding: 12px 0;
color: var(--ink-mute);
border-bottom: 1.5px solid transparent;
margin-right: 24px;
cursor: pointer;
text-decoration: none;
}
.sub-tab.active { color: var(--ink); border-color: var(--ink); }
/* ---------- tags ---------- */
.tag {
display: inline-flex; align-items: center; gap: 5px;
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;
}
.tag-removable .x { color: var(--ink-fade); cursor: pointer; padding-left: 2px; }
/* ---------- form fields ---------- */
.field-label { font-size: 12px; color: var(--ink-mid); margin-bottom: 6px; display: block; }
.field-help { font-size: 12px; color: var(--ink-mute); margin-top: 6px; line-height: 1.55; }
.field-error { font-size: 12px; color: oklch(0.85 0.10 25); margin-top: 6px; }
.field {
width: 100%; padding: 9px 12px;
background: var(--bg); border: 1px solid var(--line-soft);
color: var(--ink); border-radius: 5px;
font-size: 13px; font-family: inherit; outline: none;
transition: border-color 120ms ease;
}
.field:focus { border-color: var(--accent); }
.field.invalid { border-color: color-mix(in oklch, var(--bad), transparent 50%); }
.field.mono { font-family: 'JetBrains Mono', monospace; font-size: 12px; }
.field.with-prefix { padding-left: 64px; }
.field-prefix {
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
font-family: 'JetBrains Mono', monospace; font-size: 12px;
color: var(--ink-mute); pointer-events: none;
}
/* ---------- host row (the dashboard's load-bearing component) ---------- */
.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;
column-gap: 18px;
padding: 11px 16px; font-size: 13px;
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;
}
.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%); }
.host-row:hover { background: var(--panel-hi); }
/* Whole-row click → host detail. The action cell sits above via
z-index so its button keeps working. */
.host-row.clickable { position: relative; }
.host-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.host-row.clickable:hover { cursor: pointer; }
.host-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.host-row.clickable > .row-link { pointer-events: auto; }
.host-row.clickable > .row-action { pointer-events: auto; }
/* ---------- source-group rows (Sources tab) ---------- */
.src-row {
display: grid; align-items: center;
grid-template-columns: 1fr auto;
column-gap: 18px;
padding: 14px 18px;
}
/* ---------- retention 3×2 keep-* grid (source-group edit) ---------- */
.keep-cell {
background: var(--bg);
border: 1px solid var(--line-soft);
border-radius: 5px;
padding: 9px 11px;
display: flex; flex-direction: column; gap: 4px;
}
.keep-cell label {
font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em;
color: var(--ink-fade);
}
.keep-cell input {
background: transparent; border: none; outline: none;
font-family: 'JetBrains Mono', monospace; font-size: 14px;
color: var(--ink); padding: 0; width: 100%;
}
/* ---------- log viewer ---------- */
.log {
background: var(--bg); border: 1px solid var(--line-soft);
border-radius: 7px;
font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.7;
overflow: hidden;
}
.log-line { display: grid; grid-template-columns: 14ch 8ch 1fr; column-gap: 14px; padding: 1px 16px; align-items: baseline; }
.log-line:first-child { padding-top: 12px; }
.log-line:last-child { padding-bottom: 12px; }
.log-ts { color: var(--ink-fade); }
.log-tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-fade); }
.log-stream-stdout { color: var(--ink-mid); }
.log-stream-stderr { color: oklch(0.78 0.13 50); }
.log-stream-event { color: var(--accent); }
/* ---------- progress bar ---------- */
.progress-track {
background: var(--bg); border: 1px solid var(--line-soft);
height: 6px; border-radius: 9999px; overflow: hidden;
}
.progress-fill { height: 100%; background: var(--accent); border-radius: 9999px; transition: width 250ms ease; }
.progress-fill.ok { background: var(--ok); }
.progress-fill.bad { background: var(--bad); }
/* ---------- crumbs ---------- */
.crumbs { font-size: 12px; color: var(--ink-mute); }
.crumbs a { color: var(--ink-mute); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--line); }
.crumbs .sep { color: var(--ink-fade); margin: 0 8px; }
/* ---------- install snippet ---------- */
.snippet { border: 1px solid var(--line-soft); border-radius: 6px; overflow: hidden; }
.snippet-head {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 14px; border-bottom: 1px solid var(--line-soft);
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.1em;
}
.snippet 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;
}
.snippet pre .var { color: var(--accent); }
/* ---------- empty state ---------- */
.empty-state {
text-align: center; padding: 60px 40px;
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);
}
}