dashboard: add missing dash-* CSS classes; cluster: add stat-row/stat-card CSS

The Dashboard page was rendering as plaintext because all .dash-* CSS
classes (dash-section, dash-onair-*, dash-jobs-*, dash-cluster-*,
dash-statusbar, etc.) were missing. Added them with the full dark-theme
design-system styling matching the rest of the app.

The Cluster page's .stat-row and .stat-card classes were also missing,
causing node statistics (counts, CPU, GPUs, memory) to render unstyled.
Added grid-based stat row and card styles.
This commit is contained in:
Zac Gaetano 2026-05-27 04:09:15 +00:00
parent 4172b0d70a
commit 48d54a32cf
2 changed files with 551 additions and 0 deletions

View file

@ -1019,6 +1019,38 @@
overflow: hidden;
}
.stat-row {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
}
.stat-card {
background: var(--bg-1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-card .label {
font-size: 10.5px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-4);
display: flex;
align-items: center;
gap: 5px;
}
.stat-card .value {
font-size: 28px;
font-weight: 600;
letter-spacing: -0.02em;
color: var(--text-1);
line-height: 1.1;
}
/* ========== Settings ========== */
.settings-nav {
display: flex; flex-direction: column; gap: 2px;

View file

@ -470,6 +470,525 @@
text-align: right;
}
/* ========== Dashboard page ========== */
.page.dash {
padding: 0 28px 12px;
}
.dash-section {
margin-bottom: 20px;
}
.dash-section:last-child { margin-bottom: 0; }
.dash-sectionhead {
display: flex;
align-items: center;
gap: 8px;
padding: 16px 0 10px;
position: relative;
}
.dash-sectionhead[data-accent=live] .dash-sectionhead-title { color: var(--live); }
.dash-sectionhead[data-accent=danger] .dash-sectionhead-title { color: var(--danger); }
.dash-sectionhead-title {
font-size: 14px;
font-weight: 600;
color: var(--text-1);
letter-spacing: -0.01em;
}
.dash-sectionhead-count {
display: flex;
align-items: center;
gap: 4px;
margin-left: 2px;
}
.dash-sectionhead-num {
font-family: var(--font-mono);
font-size: 12px;
font-weight: 600;
color: var(--text-2);
}
.dash-sectionhead-label {
font-size: 11.5px;
color: var(--text-3);
}
.dash-sectionhead-more {
margin-left: auto;
font-size: 11.5px;
color: var(--text-3);
background: none;
border: 0;
padding: 4px 6px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: background 100ms, color 100ms;
}
.dash-sectionhead-more:hover {
background: var(--bg-2);
color: var(--text-1);
}
/* On-air grid */
.dash-onair-grid {
display: grid;
gap: 10px;
}
.dash-onair-grid[data-count="1"] { grid-template-columns: 1fr; }
.dash-onair-grid[data-count="2"] { grid-template-columns: 1fr 1fr; }
.dash-onair-grid[data-count="3"] { grid-template-columns: 1fr 1fr 1fr; }
.dash-onair-grid[data-count="4"] { grid-template-columns: 1fr 1fr; }
.dash-onair-tile {
background: var(--bg-1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
overflow: hidden;
cursor: pointer;
transition: border-color 120ms;
}
.dash-onair-tile:hover { border-color: var(--border-stronger); }
.dash-onair-video {
position: relative;
aspect-ratio: 16/9;
background: var(--bg-2);
overflow: hidden;
}
.dash-onair-rec-pip {
position: absolute;
top: 8px; left: 8px;
display: flex;
align-items: center;
gap: 5px;
font-size: 10.5px;
font-weight: 600;
color: white;
background: var(--danger);
padding: 2px 7px;
border-radius: 4px;
line-height: 1.3;
}
.dash-onair-rec-dot {
width: 6px; height: 6px;
border-radius: 50%;
background: white;
animation: dash-rec-pulse 1.5s ease-in-out infinite;
}
@keyframes dash-rec-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.dash-onair-time {
position: absolute;
bottom: 8px; right: 8px;
font-family: var(--font-mono);
font-size: 11px;
color: white;
background: rgba(0,0,0,0.65);
padding: 2px 7px;
border-radius: 4px;
backdrop-filter: blur(4px);
}
.dash-onair-meta {
padding: 10px 12px;
}
.dash-onair-name {
font-size: 13px;
font-weight: 500;
color: var(--text-1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-onair-sub {
display: flex;
align-items: center;
gap: 4px;
margin-top: 3px;
font-size: 11.5px;
color: var(--text-3);
}
.dash-onair-dot {
color: var(--text-4);
}
.dash-onair-source,
.dash-onair-res,
.dash-onair-codec { font-family: var(--font-mono); font-size: 10.5px; }
/* Inline empty state */
.dash-inline-empty {
display: flex;
align-items: center;
gap: 8px;
padding: 14px 14px;
background: var(--bg-1);
border: 1px dashed var(--border);
border-radius: var(--r-md);
color: var(--text-3);
font-size: 12.5px;
}
.dash-inline-empty-cta {
margin-left: auto;
font-size: 11.5px;
color: var(--accent);
background: none;
border: 0;
padding: 4px 6px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
}
.dash-inline-empty-cta:hover {
background: var(--accent-soft);
}
/* Up next strip */
.dash-next-row {
display: flex;
gap: 10px;
}
.dash-next-card {
flex: 1;
background: var(--bg-1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
overflow: hidden;
transition: border-color 120ms;
}
.dash-next-card.imminent {
border-color: var(--accent);
}
.dash-next-time {
padding: 8px 12px 4px;
display: flex;
align-items: center;
gap: 6px;
}
.dash-next-clock {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 600;
color: var(--text-2);
}
.dash-next-rel {
font-size: 10.5px;
color: var(--accent-text);
}
.dash-next-body {
padding: 4px 12px 10px;
}
.dash-next-name {
font-size: 12.5px;
font-weight: 500;
color: var(--text-1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-next-sub {
display: flex;
align-items: center;
gap: 4px;
margin-top: 3px;
font-size: 11px;
color: var(--text-3);
}
/* Attention section */
.dash-attention {
}
.dash-attention-panel {
background: var(--bg-1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
overflow: hidden;
}
.dash-attention-row {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background 100ms;
}
.dash-attention-row:last-child { border-bottom: 0; }
.dash-attention-row.level-danger {
border-left: 3px solid var(--danger);
}
.dash-attention-row.level-warning {
border-left: 3px solid var(--warning);
}
.dash-attention-row:hover { background: var(--bg-2); }
.dash-attention-icon {
width: 24px; height: 24px;
display: grid; place-items: center;
flex-shrink: 0;
}
.dash-attention-title {
font-size: 12px;
font-weight: 500;
color: var(--text-1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-attention-detail {
font-size: 11px;
color: var(--text-3);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.dash-attention-arrow {
flex-shrink: 0;
color: var(--text-4);
}
.dash-attention-more {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px;
font-size: 11.5px;
color: var(--text-3);
cursor: pointer;
justify-content: center;
}
.dash-attention-more:hover { color: var(--text-1); }
/* Two-column work + cluster grid */
.dash-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
.dash-col {
display: flex;
flex-direction: column;
gap: 8px;
min-width: 0;
}
/* Job queue panel */
.dash-jobs-panel {
background: var(--bg-1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
overflow: hidden;
}
.dash-jobs-head {
display: grid;
grid-template-columns: 20px 1fr 1fr 1fr;
gap: 6px;
padding: 6px 12px;
border-bottom: 1px solid var(--border);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-4);
}
.dash-jobs-row {
display: grid;
grid-template-columns: 20px 1fr 1fr 1fr;
gap: 6px;
padding: 7px 12px;
border-bottom: 1px solid var(--border);
align-items: center;
font-size: 12px;
}
.dash-jobs-row:last-child { border-bottom: 0; }
.dash-jobs-statr { justify-content: center; }
.dash-jobs-kind {
display: flex;
align-items: center;
gap: 5px;
color: var(--text-2);
overflow: hidden;
}
.dash-jobs-kind span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-jobs-asset {
color: var(--text-1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-jobs-progress {
display: flex;
align-items: center;
gap: 6px;
}
.dash-jobs-bar {
flex: 1;
height: 4px;
background: var(--bg-3);
border-radius: 99px;
overflow: hidden;
}
.dash-jobs-bar-fill {
height: 100%;
background: var(--accent);
border-radius: 99px;
transition: width 300ms;
}
.dash-jobs-pct {
font-family: var(--font-mono);
font-size: 10px;
color: var(--text-3);
min-width: 28px;
text-align: right;
}
.dash-jobs-state {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-4);
}
.dash-jobs-state.done { color: var(--success); }
.dash-jobs-state.failed { color: var(--danger); }
/* Cluster panel (dashboard) */
.dash-cluster-panel {
background: var(--bg-1);
border: 1px solid var(--border);
border-radius: var(--r-lg);
overflow: hidden;
}
.dash-cluster-head {
display: grid;
grid-template-columns: 20px 1fr 1fr 1fr;
gap: 6px;
padding: 6px 12px;
border-bottom: 1px solid var(--border);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-4);
}
.dash-cluster-row {
display: grid;
grid-template-columns: 20px 1fr 1fr 1fr;
gap: 6px;
padding: 7px 12px;
border-bottom: 1px solid var(--border);
align-items: center;
font-size: 12px;
}
.dash-cluster-row:last-child { border-bottom: 0; }
.dash-cluster-row[data-online=false] { opacity: 0.55; }
.dash-cluster-status { display: flex; align-items: center; }
.dash-cluster-name {
display: flex;
align-items: center;
gap: 6px;
overflow: hidden;
}
.dash-cluster-host {
font-weight: 500;
color: var(--text-1);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dash-cluster-role {
font-size: 9.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--accent-text);
background: var(--accent-soft);
padding: 1px 5px;
border-radius: 3px;
flex-shrink: 0;
}
.dash-cluster-metric {
display: flex;
align-items: center;
gap: 5px;
}
.dash-cluster-bar {
flex: 1;
height: 4px;
background: var(--bg-3);
border-radius: 99px;
overflow: hidden;
}
.dash-cluster-bar-fill {
height: 100%;
border-radius: 99px;
transition: width 200ms;
}
.dash-cluster-val {
font-family: var(--font-mono);
font-size: 10.5px;
color: var(--text-2);
min-width: 32px;
text-align: right;
}
.dash-cluster-val.muted { color: var(--text-4); }
/* Panel empty state */
.dash-panel-empty {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 18px 12px;
color: var(--text-3);
font-size: 12px;
}
/* Status bar */
.dash-statusbar {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 0 6px;
border-top: 1px solid var(--border);
margin-top: 6px;
font-family: var(--font-mono);
font-size: 11px;
}
.dash-stat-pip {
display: flex;
align-items: center;
gap: 5px;
}
.dash-stat-pip[data-tone=live] .dash-pip-dot { background: var(--live); }
.dash-stat-pip[data-tone=accent] .dash-pip-dot { background: var(--accent); }
.dash-stat-pip[data-tone=warning] .dash-pip-dot { background: var(--warning); }
.dash-stat-pip[data-tone=success] .dash-pip-dot { background: var(--success); }
.dash-stat-pip[data-tone=idle] .dash-pip-dot { background: var(--text-4); }
.dash-pip-dot {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--text-4);
}
.dash-pip-num {
font-weight: 600;
color: var(--text-2);
}
.dash-pip-label {
color: var(--text-4);
text-transform: lowercase;
}
.dash-statusbar-sep {
color: var(--text-5, var(--text-4));
opacity: 0.5;
}
.dash-statusbar-spacer { flex: 1; }
.dash-statusbar-clock {
color: var(--text-3);
margin-left: auto;
}
/* ========== Empty states ========== */
.dash-empty {
padding: 16px 0;