ui: split /hosts/{id} into host page + /runs/{runID} run page
CI / Lint + build + test (push) Successful in 1m35s
Release / release (push) Successful in 23m47s

Host page owns host metadata, full runs table with per-row stage strip,
in-flight banner, and empty-state CTA. Run page owns pipeline, active
step, logs, sub-steps, spec diffs, and hold banner with a breadcrumb
back to the host. Dashboard tile reverts to host-only.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 20:37:57 -04:00
parent 5c6bfa5ffa
commit 19608bef1b
23 changed files with 3173 additions and 2827 deletions
+121 -92
View File
@@ -822,59 +822,6 @@ body.bare main { max-width: none; }
.log-line.log-debug { opacity: .6; }
.log-line.log-hit { background: rgba(228,169,75,.08); }
.runs-sidebar {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 12px;
position: sticky;
top: 16px;
max-height: calc(100vh - 32px);
overflow-y: auto;
}
.runs-sidebar-heading {
margin: 0 0 10px;
font-size: 12px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: .5px;
font-weight: 600;
}
.runs-sidebar-empty { color: var(--text-dim); font-size: 13px; margin: 0; }
.runs-sidebar-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.runs-sidebar-item a {
display: grid;
grid-template-columns: 16px auto 1fr auto;
align-items: center;
gap: 8px;
padding: 6px 8px;
border-radius: 6px;
color: var(--text);
text-decoration: none;
font-size: 12px;
}
.runs-sidebar-item a:hover { background: var(--bg-elev-2); text-decoration: none; }
.runs-sidebar-active a { background: rgba(60,130,246,.12); border: 1px solid rgba(60,130,246,.5); }
.runs-sidebar-dot {
width: 14px;
height: 14px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 9px;
font-weight: 700;
background: var(--bg-elev-2);
border: 1px solid var(--border);
color: var(--text-dim);
}
.runs-sidebar-dot-pass { background: var(--success); border-color: var(--success); color: #0b0d12; }
.runs-sidebar-dot-fail { background: var(--danger); border-color: var(--danger); color: #fff; }
.runs-sidebar-dot-active { background: var(--accent-strong); border-color: var(--accent); color: #fff; }
.runs-sidebar-id { font-family: var(--mono); font-weight: 600; }
.runs-sidebar-started { color: var(--text-dim); }
.runs-sidebar-duration { font-family: var(--mono); color: var(--text-dim); font-size: 11px; }
.btn-primary {
background: var(--accent-strong);
border-color: var(--accent-strong);
@@ -888,55 +835,137 @@ body.bare main { max-width: none; }
}
.btn-danger:hover { background: rgba(229,100,102,.1); }
/* ---------- Dashboard tile mini run-view (Phase 3) ---------------- */
/* ---------- Host page (/hosts/{id}) ------------------------------- */
/* Small variant of stage-dot for the compact step list. Same colour
rules as the full-size pipeline dot so operators read one language
everywhere; only the geometry shrinks. */
/* Small variant of stage-dot, reused by the runs-table stage-strip so
per-row progress reads with the same visual language as the pipeline
on the run page. Kept lean — no borders, no glyphs, just colour. */
.stage-dot-sm {
width: 14px;
height: 14px;
font-size: 9px;
width: 10px;
height: 10px;
font-size: 0;
border-width: 1px;
flex-shrink: 0;
border-radius: 50%;
}
.tile-meta-row {
display: flex;
gap: 8px;
align-items: baseline;
font-size: 12px;
color: var(--text-dim);
padding: 4px 0 6px;
}
.tile-run-id { font-variant-numeric: tabular-nums; }
.tile-run-duration { margin-left: auto; font-variant-numeric: tabular-nums; }
.host-page { display: flex; flex-direction: column; gap: 12px; }
.tile-steplist {
list-style: none;
margin: 0 0 8px;
padding: 0;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2px 10px;
.host-summary {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 14px 16px;
}
.tile-steplist .tile-step {
.host-summary-head {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
line-height: 1.4;
color: var(--text-dim);
min-width: 0;
gap: 10px;
margin-bottom: 10px;
}
.tile-steplist .tile-step-name {
.host-summary-name { margin: 0; font-size: 22px; font-weight: 600; }
.host-summary-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 6px 20px;
margin: 0 0 6px;
font-size: 13px;
}
.host-summary-meta dt { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .4px; }
.host-summary-meta dd { margin: 0; font-family: var(--mono); }
.host-summary-notes { margin-top: 8px; }
.host-summary-notes h3 { margin: 0 0 4px; font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .4px; }
.host-summary-spec summary { cursor: pointer; color: var(--text-dim); font-size: 12px; }
.host-summary-spec-yaml {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
padding: 8px 10px;
font-family: var(--mono);
font-size: 12px;
overflow-x: auto;
margin: 6px 0 0;
}
.host-actions { padding: 0; }
.host-actions-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.host-nd-toggle { display: inline-flex; gap: 6px; align-items: center; color: var(--text-dim); font-size: 13px; }
.in-flight-banner-wrap { display: contents; }
.in-flight-banner {
display: flex;
align-items: center;
gap: 10px;
background: rgba(60,130,246,.12);
border: 1px solid rgba(60,130,246,.5);
border-radius: var(--radius);
padding: 10px 14px;
color: var(--text);
text-decoration: none;
font-size: 14px;
}
.in-flight-banner:hover { background: rgba(60,130,246,.20); text-decoration: none; }
.in-flight-label { font-weight: 600; }
.in-flight-state { color: var(--text-dim); font-family: var(--mono); }
.in-flight-open { margin-left: auto; color: var(--accent); }
.host-empty-state {
text-align: center;
background: var(--bg-elev);
border: 1px dashed var(--border);
border-radius: var(--radius);
padding: 40px 20px;
}
.host-empty-title { font-size: 18px; font-weight: 600; margin: 0 0 4px; }
.host-empty-sub { color: var(--text-dim); margin: 0 0 16px; font-size: 13px; }
.btn-primary.big { font-size: 15px; padding: 10px 20px; }
.host-runs { }
.host-runs h2 { font-size: 14px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .4px; margin: 0 0 8px; }
.runs-table {
width: 100%;
border-collapse: collapse;
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
}
/* Passed/failed/running steps keep full-strength text so the eye jumps
to active work; pending/skipped fade back into the background. */
.tile-step-passed .tile-step-name,
.tile-step-failed .tile-step-name,
.tile-step-running .tile-step-name { color: var(--text); }
.tile-step-skipped { opacity: .5; }
.runs-table thead th {
text-align: left;
padding: 8px 10px;
color: var(--text-dim);
font-size: 11px;
text-transform: uppercase;
letter-spacing: .4px;
font-weight: 600;
border-bottom: 1px solid var(--border);
background: var(--bg-elev-2);
}
.runs-table tbody td {
padding: 8px 10px;
border-top: 1px solid var(--border);
vertical-align: middle;
}
.runs-table tbody tr:first-child td { border-top: none; }
.runs-table tbody tr:hover { background: var(--bg-elev-2); }
.runs-row-live { background: rgba(60,130,246,.08); }
.runs-row-live:hover { background: rgba(60,130,246,.14); }
.runs-col-id a { font-family: var(--mono); font-weight: 600; color: var(--text); text-decoration: none; }
.runs-col-id a:hover { color: var(--accent); }
.runs-col-started, .runs-col-duration { color: var(--text-dim); font-family: var(--mono); white-space: nowrap; }
.runs-open-link { color: var(--accent); text-decoration: none; font-size: 12px; white-space: nowrap; }
.runs-open-link:hover { text-decoration: underline; }
.stage-strip {
display: inline-flex;
gap: 3px;
align-items: center;
}
/* ---------- Run page (/runs/{runID}) ------------------------------ */
.run-page { display: flex; flex-direction: column; gap: 12px; }
.run-body { display: flex; flex-direction: column; gap: 10px; }
.run-header-name { margin: 0; font-size: 20px; font-weight: 600; }