From d3ae6b558505a20dade749dd6d9ef78d13f88301 Mon Sep 17 00:00:00 2001 From: TheWitness Date: Sat, 10 Oct 2026 17:27:10 -0400 Subject: [PATCH 1/5] feature(hmib): add Fleet (Summary) Dashboard with min/avg/max bullet charts A dashboard glyph (same icon/first position as the per-device dashboard) on the Summary tab's Device Type rows opens a Fleet Dashboard scoped to all devices or one OS type: - Draggable add/remove card grid reusing the shared .hmibDash* card chrome, with its own self-contained controller (js/hmib_summary_dashboard.js) so the merged per-device dashboard JS is untouched. - Cards: Fleet Overview (status + resource stat tiles), Resource Utilization (min/avg/max BULLET CHARTS for CPU/memory/swap/processes), Device Status (stacked bar), Storage Utilization (bullet + capacity), Uptime Distribution (bullet), OS Type Distribution (bars), and Top Processes by CPU and by Memory (sortable, with relative mini-bars). - Aggregates come from hrSystem/hrStorage/hrSWRun scoped by host_type; utilization stats consider only up/recovering devices so down-device staleness does not skew min/avg/max. - CSRF AJAX endpoints summary_card/summary_layout/summary_refresh; scope (OS type), card order, expanded state and refresh interval persist per user in settings_user. A stateful 'Fleet Dashboard' tab appears after first use and remembers the last scope. - Visuals (bullet charts, stat tiles, stacked bar, distribution/mini bars) are theme-adaptive via color-mix; nonce'd inline init + JSON_HEX hardening. --- CHANGELOG.md | 1 + css/hmib.css | 199 +++++++++ hmib.php | 768 ++++++++++++++++++++++++++++++++++- js/hmib_summary_dashboard.js | 437 ++++++++++++++++++++ locales/po/cacti.pot | 98 ++++- 5 files changed, 1495 insertions(+), 8 deletions(-) create mode 100644 js/hmib_summary_dashboard.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 88dbb0d..9a14c06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ChangeLog --- develop --- +* feature: Add a Fleet (Summary) Dashboard reached by a dashboard glyph on the Summary tab's Device Type rows: a draggable, add/remove card grid scoped to all devices or one OS type, with min/avg/max bullet charts (CPU/memory/swap/processes/uptime), a device-status stacked bar, OS type distribution, storage utilization, fleet stat tiles, and top processes by CPU and by memory; scope, card order, expanded state and refresh interval persist per user * issue: Fix 'Uncaught TypeError: runCollector(): Argument #2 ($lastrun) must be of type float, null given' in the poller when a collector's *_lastrun/*_freq setting has never been written (e.g. a task's first run); runCollector() now accepts null and normalizes it * issue: Fix the per-device Dashboard Storage, Per-Processor Load, Hardware and Installed Software cards rendering empty because they filtered on 'present = 1' while the underlying Storage/Hardware/Inventory tabs never did, and add a Process Use History card * feature: Add a per-device Dashboard that drills into a single host with a draggable, add/remove card grid (CPU/memory/swap gauges, per-processor load, storage, top processes, hardware, installed software, logins and system information); card order, expanded state, the selected device and the auto-refresh interval persist per user, and a dashboard glyph on the Devices tab opens a stateful Dashboard tab that remembers the last host opened diff --git a/css/hmib.css b/css/hmib.css index ebc4a41..7851d03 100644 --- a/css/hmib.css +++ b/css/hmib.css @@ -496,3 +496,202 @@ grid-column: 1 / -1; } } + +/* + * Fleet (Summary) Dashboard visuals: min/avg/max bullet charts, stat tiles, a + * status stacked bar and in-table mini bars. Theme-adaptive via + * color-mix(currentColor ...); severity accents use fixed colours. + */ + +/* Min / avg / max bullet chart. The track is 0..scale; a translucent band spans + min..max; a strong marker sits at avg. */ +.hmibBullet { + margin: 9px 0; +} + +.hmibBulletHead { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 8px; + margin-bottom: 4px; +} + +.hmibBulletLabel { + font-size: 12px; + font-weight: 600; +} + +.hmibBulletAvg { + font-size: 12px; + font-variant-numeric: tabular-nums; + color: color-mix(in srgb, currentColor 78%, transparent); +} + +.hmibBulletTrack { + position: relative; + height: 16px; + border-radius: 8px; + background: color-mix(in srgb, currentColor 12%, transparent); + overflow: hidden; +} + +.hmibBulletRange { + position: absolute; + top: 0; + bottom: 0; + border-radius: 8px; +} + +.hmibBulletRange--up { background: color-mix(in srgb, #3c9a5f 55%, transparent); } +.hmibBulletRange--recovering { background: color-mix(in srgb, #d9830f 55%, transparent); } +.hmibBulletRange--down { background: color-mix(in srgb, #cc0000 55%, transparent); } +.hmibBulletRange--none { background: color-mix(in srgb, currentColor 32%, transparent); } + +.hmibBulletMarker { + position: absolute; + top: -2px; + bottom: -2px; + width: 3px; + transform: translateX(-1px); + background: currentColor; + border-radius: 2px; +} + +.hmibBulletScale { + display: flex; + justify-content: space-between; + margin-top: 2px; + font-size: 10px; + font-variant-numeric: tabular-nums; + color: color-mix(in srgb, currentColor 60%, transparent); +} + +/* Stat tiles for the fleet overview. */ +.hmibFleetTiles { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); + gap: 8px; +} + +.hmibFleetTile { + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + padding: 8px 6px; + border-radius: 7px; + border: 1px solid color-mix(in srgb, currentColor 14%, transparent); + background: color-mix(in srgb, currentColor 4%, transparent); +} + +.hmibFleetTileValue { + font-size: 22px; + font-weight: 700; + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.hmibFleetTileLabel { + font-size: 11px; + text-align: center; + color: color-mix(in srgb, currentColor 70%, transparent); +} + +.hmibFleetTile--up { border-color: color-mix(in srgb, #3c9a5f 55%, transparent); } +.hmibFleetTile--rec { border-color: color-mix(in srgb, #d9830f 55%, transparent); } +.hmibFleetTile--down { border-color: color-mix(in srgb, #cc0000 55%, transparent); } +.hmibFleetTile--up .hmibFleetTileValue { color: #3c9a5f; } +.hmibFleetTile--rec .hmibFleetTileValue { color: #d9830f; } +.hmibFleetTile--down .hmibFleetTileValue { color: #cc0000; } + +/* Horizontal status stacked bar (up / recovering / down / disabled). */ +.hmibStackBar { + display: flex; + height: 20px; + border-radius: 6px; + overflow: hidden; + background: color-mix(in srgb, currentColor 12%, transparent); +} + +.hmibStackSeg { + height: 100%; +} + +.hmibStackSeg--up { background: #3c9a5f; } +.hmibStackSeg--rec { background: #d9830f; } +.hmibStackSeg--down { background: #cc0000; } +.hmibStackSeg--dis { background: color-mix(in srgb, currentColor 35%, transparent); } + +.hmibStackLegend { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 8px; + font-size: 11px; +} + +.hmibStackLegend span { + display: inline-flex; + align-items: center; + gap: 5px; +} + +.hmibStackChip { + width: 10px; + height: 10px; + border-radius: 2px; + display: inline-block; +} + +/* Horizontal distribution bars (OS type counts) and in-table mini bars. */ +.hmibDistRow { + display: grid; + grid-template-columns: minmax(80px, 34%) 1fr auto; + align-items: center; + gap: 8px; + margin: 6px 0; + font-size: 12px; +} + +.hmibDistLabel { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hmibDistTrack { + height: 12px; + border-radius: 6px; + background: color-mix(in srgb, currentColor 12%, transparent); + overflow: hidden; +} + +.hmibDistFill { + height: 100%; + border-radius: 6px; + background: color-mix(in srgb, currentColor 45%, transparent); +} + +.hmibDistValue { + font-variant-numeric: tabular-nums; + color: color-mix(in srgb, currentColor 80%, transparent); +} + +.hmibMiniBar { + position: relative; + height: 10px; + min-width: 60px; + border-radius: 5px; + background: color-mix(in srgb, currentColor 12%, transparent); + overflow: hidden; +} + +.hmibMiniBarFill { + position: absolute; + top: 0; + left: 0; + bottom: 0; + border-radius: 5px; + background: color-mix(in srgb, currentColor 45%, transparent); +} diff --git a/hmib.php b/hmib.php index c49cf8a..235e666 100644 --- a/hmib.php +++ b/hmib.php @@ -81,6 +81,24 @@ exit; } +if (get_request_var('action') == 'summary_card') { + header('Content-Type: application/json; charset=UTF-8'); + print hmib_summary_card_ajax(); + exit; +} + +if (get_request_var('action') == 'summary_layout') { + header('Content-Type: application/json; charset=UTF-8'); + print hmib_summary_layout_save(); + exit; +} + +if (get_request_var('action') == 'summary_refresh') { + header('Content-Type: application/json; charset=UTF-8'); + print hmib_summary_refresh_save(); + exit; +} + general_header(); // Load this plugin's per-theme glyph styling: base rules plus the active @@ -126,6 +144,10 @@ case 'dashboard': hmib_dashboard(); + break; + case 'summary_dashboard': + hmib_summary_dashboard(); + break; case 'history': hmib_history(); @@ -2914,14 +2936,27 @@ function hmib_tabs(): void { // set the default tab $current_tab = get_request_var('action'); - // The stateful per-device Dashboard tab only appears once a device has been - // opened from the Devices tab (it remembers the last device viewed). - if ($current_tab == 'dashboard' || (int) read_user_setting('hmib_dashboard_host', 0) > 0) { - $tabs = array_slice($tabs, 0, 2, true) - + ['dashboard' => __esc('Dashboard', 'hmib')] - + array_slice($tabs, 2, null, true); + // The stateful Dashboard tabs only appear once opened from their glyph: the + // Fleet Dashboard from the Summary tab, the per-device Dashboard from Devices. + $show_fleet = ($current_tab == 'summary_dashboard' || read_user_setting('hmib_summary_type', '') !== ''); + $show_device = ($current_tab == 'dashboard' || (int) read_user_setting('hmib_dashboard_host', 0) > 0); + + $ordered = []; + + foreach ($tabs as $tab_key => $tab_label) { + $ordered[$tab_key] = $tab_label; + + if ($tab_key == 'summary' && $show_fleet) { + $ordered['summary_dashboard'] = __esc('Fleet Dashboard', 'hmib'); + } + + if ($tab_key == 'devices' && $show_device) { + $ordered['dashboard'] = __esc('Dashboard', 'hmib'); + } } + $tabs = $ordered; + // draw the tabs print "