diff --git a/src/ci_orchestrator/web/static/app.css b/src/ci_orchestrator/web/static/app.css
index f1b2236..869097f 100644
--- a/src/ci_orchestrator/web/static/app.css
+++ b/src/ci_orchestrator/web/static/app.css
@@ -91,10 +91,16 @@ dd code, dd .mono { overflow-wrap: anywhere; }
}
.navbar-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
color: var(--text);
font-weight: 600;
font-size: 1.05rem;
}
+/* Gear: automation/pipeline, fits an orchestrator's brand mark better than a
+ * subject-specific glyph (server/box/etc. already belong to their own card). */
+.navbar-brand .icon { width: 1.3em; height: 1.3em; }
.navbar-brand:hover { color: var(--accent-hover); text-decoration: none; }
.navbar-links {
@@ -120,63 +126,16 @@ dd code, dd .mono { overflow-wrap: anywhere; }
#action-slot:empty { display: none; }
#action-slot:not(:empty) { margin-bottom: 1.25rem; }
-/* ── status header (A3) ──────────────────────────────────────────────── */
+/* ── resource gauges inside cards (Stage F) ──────────────────────────── */
-.status-header {
- display: grid;
- /* Six tiles (VMs, Runners, CPU, RAM, Disk, Cache) -- a smaller min track
- * lets all six sit on one row on a wide screen, wrapping gracefully below. */
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
- gap: 1rem;
- margin-bottom: 1.25rem;
-}
+/* The Host card's CPU / RAM / disk readouts. These lived in the retired
+ * status-header tiles; the `tile-*` level class names survive as what the
+ * gauge fill colour keys off. Stage G dropped the level word + check-icon
+ * strip that used to follow each gauge: the state is stated as a number
+ * ("42%", "20.0 / 62 GB") right above the bar, and the level word moved into
+ * the gauge's aria-label -- so no reading of this card depends on colour. */
-.stat-tile {
- display: flex;
- align-items: flex-start;
- gap: 0.75rem;
- background: var(--panel);
- border: 1px solid var(--border);
- border-left: 3px solid var(--border);
- border-radius: var(--radius);
- padding: 0.9rem 1rem;
-}
-
-.stat-tile > .icon { flex-shrink: 0; margin-top: 0.15rem; color: var(--text-dim); }
-
-.stat-tile-body { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; flex: 1; }
-
-.stat-tile-label {
- font-size: 0.75rem;
- text-transform: uppercase;
- letter-spacing: 0.03em;
- color: var(--text-dim);
-}
-
-.stat-tile-value { font-size: 1.15rem; font-weight: 600; color: var(--text); }
-.stat-tile-sub { font-size: 0.8rem; font-weight: 400; color: var(--warn); }
-
-.stat-tile-status {
- display: inline-flex;
- align-items: center;
- gap: 0.3rem;
- font-size: 0.75rem;
- font-weight: 600;
- text-transform: uppercase;
- letter-spacing: 0.03em;
-}
-
-/* Health must never rely on color alone -- the border accent + icon +
- * label all move together, so a color-blind viewer still reads the state
- * from the text/icon. */
-.tile-ok { border-left-color: var(--success); }
-.tile-ok .stat-tile-status { color: var(--success); }
-.tile-warn { border-left-color: var(--warn); }
-.tile-warn .stat-tile-status { color: var(--warn); }
-.tile-bad { border-left-color: var(--danger); }
-.tile-bad .stat-tile-status { color: var(--danger); }
-.tile-unknown { border-left-color: var(--border); }
-.tile-unknown .stat-tile-status { color: var(--text-dim); }
+.resource-value { display: block; font-variant-numeric: tabular-nums; }
.gauge {
width: 100%;
@@ -184,24 +143,30 @@ dd code, dd .mono { overflow-wrap: anywhere; }
background: var(--panel-2);
border-radius: 999px;
overflow: hidden;
- margin: 0.15rem 0;
+ margin: 0.25rem 0;
}
.gauge-fill { height: 100%; background: var(--success); }
.tile-warn .gauge-fill { background: var(--warn); }
-.tile-bad .gauge-fill { background: var(--danger); }
+.tile-bad .gauge-fill { background: var(--danger); }
+
+/* Sub-line under a gauge (Host: the load average under CPU), same role as the
+ * dim "GB free" beside the disk percentage. */
+.resource-sub {
+ display: block;
+ font-size: 0.78rem;
+ font-variant-numeric: tabular-nums;
+}
.dashboard-grid {
display: grid;
/* FIXED four-column track, NOT auto-fit. A `.card--wide` spans `1 / -1`,
* i.e. occupies every column on its row -- with `auto-fit` that means no
- * track is ever globally empty, so auto-fit can't collapse the compact
- * row's spare tracks and the four info cards stayed narrow with dead
- * columns on the right. A fixed count makes the four compact cards fill
- * their row exactly, and the wide tables still span all four via
- * `grid-column: 1 / -1`. minmax(0,1fr) lets a card shrink below its
- * content width so a wide table scrolls inside its own .table-wrap
- * instead of forcing the page to scroll. Column count steps down with
- * width (below). */
+ * track is ever globally empty, so auto-fit could not collapse a compact
+ * row's spare tracks. Since Stage F every card is `.card--wide` (the four
+ * compact info cards became the one Platform card), so the track count only
+ * matters if a compact card is ever added back. minmax(0,1fr) lets a card
+ * shrink below its content width so a wide table scrolls inside its own
+ * .table-wrap instead of forcing the page to scroll. */
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1.25rem;
/* stretch: every card in a row shares the row's height, so the grid reads
@@ -285,6 +250,19 @@ dd code, dd .mono { overflow-wrap: anywhere; }
* the bar's right end. Compact and case-normal so it reads as a control rather
* than part of the uppercase heading text, and short enough not to grow the
* bar's height. */
+/* Right-hand end of a card header bar: a single near-static fact that would
+ * otherwise cost a whole
row (Host: uptime). `text-transform` must be
+ * reset -- the h2 uppercases, and "26H 03M" is not a duration anyone reads. */
+.head-meta {
+ margin-left: auto;
+ font-size: 0.8rem;
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
+ font-variant-numeric: tabular-nums;
+ color: var(--text-dim);
+}
+
.head-select {
margin-left: auto;
padding: 0.1rem 0.35rem;
@@ -295,7 +273,10 @@ dd code, dd .mono { overflow-wrap: anywhere; }
color: var(--text);
}
-/* Per-template expandable backup list (Templates panel). */
+/* Per-template expandable backup list (Templates panel). Each row is its own
+ * bordered line (not just a wrapping flex list run into the next) so a
+ * multi-backup template reads as a small table, not a paragraph the delete
+ * button happened to float in. */
.backups-details > summary {
cursor: pointer;
color: var(--link);
@@ -306,14 +287,19 @@ dd code, dd .mono { overflow-wrap: anywhere; }
.backups-list { list-style: none; margin: 0; padding: 0; }
.backups-list li {
display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- align-items: baseline;
- padding: 0.2rem 0;
- font-size: 0.82rem;
+ align-items: center;
+ gap: 0.75rem;
+ padding: 0.45rem 0;
+ font-size: 0.85rem;
+ border-top: 1px solid var(--border);
}
+.backups-list li:first-child { border-top: none; }
+/* Name + size/date as one shrinkable unit, so the row wraps as a block
+ * instead of the delete button drifting away from a name that wrapped. */
+.backup-info { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; min-width: 0; flex: 1 1 auto; }
+.backup-info code { overflow-wrap: anywhere; }
/* Per-backup delete button pinned to the right end of its row. */
-.backup-del { margin-left: auto; align-self: center; }
+.backup-del { margin-left: auto; flex: none; }
.card h2 {
display: flex;
@@ -339,7 +325,9 @@ dd code, dd .mono { overflow-wrap: anywhere; }
dl { display: grid; grid-template-columns: auto 1fr; gap: 0 0.9rem; }
dt, dd { padding: 0.45rem 0; border-bottom: 1px solid var(--border); }
dt { color: var(--text-dim); font-weight: 500; }
-dd { margin: 0; }
+/* Long unbreakable values (a cache root, a kernel string, a CPU model)
+ * wrap inside the card rather than widening its grid track. */
+dd { margin: 0; overflow-wrap: anywhere; }
/* ── tables ───────────────────────────────────────────────────────────── */
@@ -347,7 +335,13 @@ dd { margin: 0; }
table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
-th, td { text-align: left; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); }
+/* vertical-align:middle is explicit, not left to the UA default (baseline) --
+ * keeps every cell in a row anchored the same way regardless of its content
+ * (a row of buttons vs. one line of text). The Actions-column border step
+ * this was chasing turned out to be .actions-row's flex box sitting directly
+ * on the (see the comment there); this rule stays for alignment, not as
+ * that fix. */
+th, td { text-align: left; padding: 0.5rem 0.6rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
th {
color: var(--text-dim);
@@ -358,7 +352,7 @@ th {
}
tbody tr { transition: background-color 120ms ease; }
-tbody tr:hover { background: var(--panel-2); }
+tbody tr:hover { background: rgba(65, 131, 196, 0.12); }
tr.row-orphan td { color: var(--warn); }
tr.row-alert td { color: var(--danger); }
@@ -396,8 +390,37 @@ tr.row-alert td { color: var(--danger); }
.badge-info { background: rgba(65, 131, 196, 0.15); color: var(--accent); border: 1px solid rgba(65, 131, 196, 0.4); }
.badge-muted { background: var(--panel-2); color: var(--text-dim); border: 1px solid var(--border); }
+/* A badge sitting right before a dim sub-state ("loaded" + "kernel module",
+ * "active" + "running") only had the markup's one collapsed whitespace
+ * character between them -- reads as glued together. */
+.badge + .text-dim { margin-left: 0.4rem; }
+
+/* VMware/Runners (Stage H): "loaded"/"not loaded", "active"/"down" are
+ * different lengths, so the pill in each - row was a different width from
+ * its neighbours. Levelled to fit the longest word in either card, same
+ * technique as the Executions Status column. */
+#vmware-card .badge,
+#runner-card .badge {
+ min-width: 5rem;
+ justify-content: flex-start;
+}
+
+/* Executions card: the Status column mixes run outcomes (success/failure/
+ * queued/cancelled...) and VM-only states (orphan/running) of very different
+ * word lengths -- a ragged column of differently-sized pills. Both row kinds
+ * put their badge in the 8th
| (Run, Repo, Workflow, Branch, Commit,
+ * Trigger, Actor, Status, ...), so one rule levels every pill in the column
+ * to "cancelled"'s width (the longest word) with room to spare, and
+ * `justify-content` keeps icon+text hugging the left edge of the now-wider
+ * pill rather than centering. */
+#executions-card td:nth-child(8) { width: 5rem; }
+#executions-card td:nth-child(8) .badge { width: 100%; justify-content: flex-start; }
+
/* ── buttons ──────────────────────────────────────────────────────────── */
+/* Two filled button kinds, no neutral/outline third state: every action is
+ * either "normal" (blue) or destructive (red) -- the confirm gate is still
+ * the real safeguard on a destructive action, this is just legibility. */
.btn {
display: inline-flex;
align-items: center;
@@ -406,26 +429,31 @@ tr.row-alert td { color: var(--danger); }
border-radius: var(--radius);
padding: 0.45rem 0.9rem;
font-size: 0.875rem;
- font-weight: 600;
+ font-weight: 400;
font-family: inherit;
cursor: pointer;
- background: var(--panel-2);
- color: var(--text);
+ background: #4382c2;
+ color: #ffffff;
+ /* Same neutral gray as every table/card border, not the fill colour --
+ * a solid-colour border read as an outline duplicating the fill. */
+ border-color: var(--border);
transition: background-color 120ms ease, border-color 120ms ease, transform 80ms ease;
}
-.btn:hover { background: var(--border); }
+/* Hover tint: +25 per channel, same lighten-by magnitude as --accent-hover.
+ * Border stays the neutral gray -- only the fill shifts on hover. */
+.btn:hover { background: #5c9bdb; }
.btn:active { transform: translateY(1px); }
-.btn-primary { background: var(--accent); color: #ffffff; border-color: var(--accent); }
-.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
+/* A plain-weight button label leaves its icon looking thin/washed-out next
+ * to a solid fill -- bumped stroke-width and a couple px of extra size make
+ * it read as clearly as the bold text used to. */
+.btn .icon { stroke-width: 2.25; width: 1.15em; height: 1.15em; }
-/* Destructive actions: a NEUTRAL button at rest (same panel surface + border
- * as .btn) that only reads as red through its text/icon -- no red border or
- * fill glaring on the page. Danger becomes fully visible on hover (red border
- * + soft red wash). The real safeguard is the two-step confirm gate, not the
- * colour. */
-.btn-danger { background: var(--panel-2); color: var(--danger); border-color: var(--border); }
-.btn-danger:hover { background: rgba(248, 81, 73, 0.14); color: var(--danger); border-color: var(--danger); }
+.btn-primary { background: #4382c2; color: #ffffff; border-color: var(--border); }
+.btn-primary:hover { background: #5c9bdb; }
+
+.btn-danger { background: #cb4b4c; color: #ffffff; border-color: var(--border); }
+.btn-danger:hover { background: #e46465; }
/* Compact variant for buttons that live inside a table row (VM remove,
* per-service start/stop/restart) so a row of them stays low-profile. */
@@ -458,8 +486,17 @@ input[type="text"], input[type="number"], select {
/* Multiple one-button action forms side-by-side in one table cell (Stage D
* services panel: Start/Stop/Restart). Overrides the default form's
* margin-top so the row stays compact inside a | . */
-.service-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
-.service-actions form { margin-top: 0; }
+/* Wraps the row of action buttons INSIDE the td rather than making the td
+ * itself a flex box -- a table-cell that is also a flex container computes
+ * its content-box height slightly differently from a plain-text sibling
+ * cell in some engines, which showed up as the row's collapsed bottom
+ * border sitting 1px higher under this column than its neighbours. */
+.actions-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
+/* Each |
{{ state.sub_state }} |
-
- {% if not state.active %}
-
- {% endif %}
- {% if state.active and state.service.allow_stop %}
-
- {% endif %}
-
+ |
+
+ {% if not state.active %}
+
+ {% endif %}
+ {% if state.active and state.service.allow_stop %}
+
+ {% endif %}
+
+
|
{% endfor %}
|