Files
restic-manager/web/styles/input.css
T
steve 54528b9b15 P2R-02 follow-up: clickable rows on Sources/Schedules + cron-preset tooltips
Aligns Sources and Schedules tab rows with the dashboard's row-click
UX: whole-row click navigates to the row's edit page (mirroring
.host-row.clickable). Drops the redundant Edit buttons; Run-now and
Delete remain in .row-action cells that sit above the row-link
overlay via z-index.

Schedule edit form's cron preset chips now carry human-readable
title= tooltips ("Every day at 03:00", "Every Sunday at 03:00", etc).

tasks.md gets a binding row-design rule covering all current and
future list-row templates, and the P2R-02 entry is split into the
six slices already agreed with the operator (slices 1–3 marked
done, 4 next).
2026-05-03 12:01:55 +01:00

350 lines
13 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;
}
/* Whole-row click → edit page, mirroring .host-row.clickable on the
dashboard. Action cells sit above via z-index so their buttons
keep working. */
.src-row.clickable { position: relative; }
.src-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.src-row.clickable:hover { background: var(--panel-hi); cursor: pointer; }
.src-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.src-row.clickable > .row-link { pointer-events: auto; }
.src-row.clickable > .row-action { pointer-events: auto; }
/* ---------- schedule rows (Schedules tab) ---------- */
.schd-row {
display: grid; align-items: center;
grid-template-columns: 90px 1fr 2fr auto;
column-gap: 18px;
padding: 12px 18px; font-size: 13px;
}
.schd-row.head {
padding-top: 10px; padding-bottom: 10px;
font-size: 11px; color: var(--ink-fade);
text-transform: uppercase; letter-spacing: 0.08em;
}
/* Whole-row click → edit page (matches .host-row.clickable). */
.schd-row.clickable { position: relative; }
.schd-row.clickable .row-link {
position: absolute; inset: 0; z-index: 0;
text-indent: -9999px; overflow: hidden;
}
.schd-row.clickable:hover { background: var(--panel-hi); cursor: pointer; }
.schd-row.clickable > * { position: relative; z-index: 1; pointer-events: none; }
.schd-row.clickable > .row-link { pointer-events: auto; }
.schd-row.clickable > .row-action { pointer-events: auto; }
/* ---------- cron preset chips ---------- */
.preset-chip {
font-family: 'JetBrains Mono', monospace; font-size: 11.5px;
padding: 4px 9px; border-radius: 4px;
border: 1px solid var(--line-soft); color: var(--ink-mid);
background: var(--bg);
cursor: pointer; user-select: none;
transition: border-color 100ms ease, color 100ms ease;
}
.preset-chip:hover { border-color: var(--accent); color: var(--ink); }
/* ---------- source-group picker (Schedule new/edit) ---------- */
.picker {
display: flex; align-items: center; gap: 12px;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--line-soft);
border-radius: 5px;
font-size: 13px; cursor: pointer;
transition: border-color 100ms ease, background 100ms ease;
}
.picker:hover { border-color: var(--ink-mute); }
.picker .check {
display: inline-block; width: 14px; height: 14px;
border: 1px solid var(--line); border-radius: 3px;
flex-shrink: 0; position: relative;
}
.picker.checked {
border-color: color-mix(in oklch, var(--accent), transparent 50%);
background: color-mix(in oklch, var(--accent), transparent 92%);
}
.picker.checked .check {
background: var(--accent); border-color: var(--accent);
}
.picker.checked .check::after {
content: ""; position: absolute;
left: 4px; top: 1px; width: 4px; height: 8px;
border: solid oklch(0.18 0.01 195);
border-width: 0 1.5px 1.5px 0;
transform: rotate(45deg);
}
.picker input[type="checkbox"] {
position: absolute; opacity: 0; pointer-events: none;
}
/* ---------- 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);
}
}