mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-05 23:23:02 +02:00
webui: overhaul dashboard cards
Split the old monolithic Hardware card into dedicated Board, WiFi, and Sensors cards, matching the structure of `show system` / `show hardware` in the CLI: - System Information: add Current Time row, rename Firmware → Boot Partition - Board: model, manufacturer, Base MAC only (no sensors) - WiFi (new, conditional): all radios with supported bands, standards (11n/11ac/11ax), and max AP count; card spans 2 grid columns so the table never needs horizontal scroll - Sensors (new, conditional, collapsible): all hardware sensors grouped by parent component (SFP sub-sensors indent under sfp1/sfp2 headings); JS "Show more" button injected only when content overflows 300 px - Disk Usage: replaced wide multi-column table with a per-mount widget (path + % on one line, health bar, "X free of Y" below); health colour follows the same warn/crit thresholds as the memory bar - Fix humanKiB to output IEC units (MiB / GiB) with a space, matching the memory display style - Fix info-table th wrapping (width:1%; white-space:nowrap) - Fix JS collapsible init to run on DOMContentLoaded, not immediately in <head>, so scrollHeight check has a real DOM to inspect - CSS scroll-shadow on data-table-wrap: pure-CSS right-edge shadow indicates overflow without needing JS or a scroll listener - Equal-height grid rows via align-items:stretch; info-grid-span-2 for cards with wide tables Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -405,9 +405,21 @@ details[open].nav-group-top > summary.nav-group-summary-top::after {
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Cards inside a grid don't need their own bottom margin — the grid gap handles spacing */
|
||||
.info-grid > .info-card { margin-bottom: 0; }
|
||||
|
||||
/* Full-width card spanning all grid columns */
|
||||
.info-grid-full { grid-column: 1 / -1; }
|
||||
|
||||
/* Two-column card for wide tables — only kicks in once the grid has ≥2 columns */
|
||||
@media (min-width: 680px) {
|
||||
.info-grid-span-2 { grid-column: span 2; }
|
||||
}
|
||||
|
||||
.info-card {
|
||||
@@ -449,6 +461,7 @@ details[open].nav-group-top > summary.nav-group-summary-top::after {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================================================
|
||||
Tables
|
||||
========================================================================== */
|
||||
@@ -466,6 +479,60 @@ details[open].nav-group-top > summary.nav-group-summary-top::after {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.info-table th {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
color: var(--fg-muted);
|
||||
font-weight: 500;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Disk usage widget (one entry per mount point) */
|
||||
.disk-item {
|
||||
padding: 0.7rem 1.25rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.disk-item:last-child { border-bottom: none; }
|
||||
.disk-item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
.disk-mount {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: var(--fg);
|
||||
}
|
||||
.disk-pct { font-size: 0.8rem; color: var(--fg-muted); }
|
||||
.disk-stats {
|
||||
font-size: 0.78rem;
|
||||
color: var(--fg-muted);
|
||||
margin-top: 0.3rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.sensor-group-hdr th {
|
||||
padding: 0.35rem 1.25rem 0.2rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--fg-muted);
|
||||
background: var(--bg);
|
||||
border-bottom: none;
|
||||
}
|
||||
.sensor-child th {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.data-table th.num,
|
||||
.data-table td.num {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.info-table tr:last-child th, .info-table tr:last-child td,
|
||||
.disk-table tbody tr:last-child td,
|
||||
.counters-table tbody tr:last-child td {
|
||||
@@ -504,6 +571,22 @@ details[open].nav-group-top > summary.nav-group-summary-top::after {
|
||||
overflow-x: auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
/* Scroll-shadow: a right-edge shadow appears automatically when the table
|
||||
overflows, fading away once scrolled to the end. Pure CSS, no JS needed.
|
||||
The `local` gradients act as white masks pinned to the scroll content;
|
||||
the `scroll` gradients are the actual shadows fixed to the viewport edge. */
|
||||
background:
|
||||
linear-gradient(to right, var(--surface), var(--surface)) left / 24px 100% no-repeat local,
|
||||
linear-gradient(to left, var(--surface), var(--surface)) right / 24px 100% no-repeat local,
|
||||
linear-gradient(to right, rgba(0,0,0,0.07), transparent) left / 8px 100% no-repeat scroll,
|
||||
linear-gradient(to left, rgba(0,0,0,0.07), transparent) right / 8px 100% no-repeat scroll;
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
/* Remove the nested border when a table lives inside a card — the card already provides the frame */
|
||||
.info-card .data-table-wrap {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
@@ -531,9 +614,11 @@ details[open].nav-group-top > summary.nav-group-summary-top::after {
|
||||
background-color: var(--zebra-stripe, rgba(0,0,0,0.025));
|
||||
}
|
||||
|
||||
.data-table tbody tr:hover {
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
.data-table tbody tr:hover { background-color: var(--slate-100); }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.data-table tbody tr:hover { background-color: rgba(255,255,255,0.06); }
|
||||
}
|
||||
.dark .data-table tbody tr:hover { background-color: rgba(255,255,255,0.06); }
|
||||
|
||||
/* ==========================================================================
|
||||
Forms & Inputs
|
||||
|
||||
@@ -432,31 +432,6 @@
|
||||
});
|
||||
})();
|
||||
|
||||
// Card collapsible — "Show more / Show less" toggle injected when content overflows
|
||||
(function() {
|
||||
function initCollapsibles() {
|
||||
document.querySelectorAll('.card-collapsible:not([data-ci])').forEach(function(card) {
|
||||
card.setAttribute('data-ci', '1');
|
||||
var body = card.querySelector('.card-collapsible-body');
|
||||
if (!body || body.scrollHeight <= body.clientHeight + 4) return;
|
||||
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'card-expand-btn';
|
||||
btn.type = 'button';
|
||||
btn.textContent = 'Show more \u25be';
|
||||
btn.style.display = 'block';
|
||||
card.appendChild(btn);
|
||||
|
||||
btn.addEventListener('click', function() {
|
||||
var expanded = card.classList.toggle('is-expanded');
|
||||
btn.textContent = expanded ? 'Show less \u25b4' : 'Show more \u25be';
|
||||
});
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', initCollapsibles);
|
||||
document.addEventListener('htmx:afterSettle', initCollapsibles);
|
||||
})();
|
||||
|
||||
// Sidebar toggle (mobile)
|
||||
(function() {
|
||||
var MOBILE_BP = 1024;
|
||||
|
||||
Reference in New Issue
Block a user