Skip to content

Add a Fleet (Summary) Dashboard with min/avg/max bullet charts - #71

Merged
TheWitness merged 5 commits into
developfrom
feature/summary-dashboard
Oct 10, 2026
Merged

TheWitness merged 5 commits into
developfrom
feature/summary-dashboard

Conversation

@TheWitness

Copy link
Copy Markdown
Member

Description

Adds a Fleet (Summary) Dashboard — the aggregate counterpart to the per-device Dashboard — reached by the same dashboard glyph (same icon, first position) on the Summary tab's Device Type Summary Statistics rows. It replaces "boring tables" with visual, min/avg/max bullet charts and other graphics.

A draggable, add/remove card grid scoped to all devices or a single OS type (switchable in the toolbar). Cards:

  • Fleet Overview — stat tiles: devices, up / recovering / down / disabled (colour-coded), CPUs, logins, processes, total RAM.
  • Resource Utilization — min/avg/max bullet charts for CPU %, Memory %, Swap % (severity-tinted band + average marker) and Processes/host.
  • Device Status — a horizontal stacked bar (up/recovering/down/disabled) with a counted legend.
  • Storage Utilization — a used-% bullet plus total/used capacity.
  • Uptime Distribution — a min/avg/max uptime bullet.
  • OS Type Distribution — ranked horizontal bars per OS type.
  • Top Processes by CPU and Top Processes by Memory — fleet-wide, sortable, each row with a relative mini-bar.

Behaviour mirrors the per-device dashboard: drag/keyboard reorder, per-card refresh/maximize/remove, an "Add card" catalogue, an auto-refresh interval, and client-side sortable tables. Scope (OS type), card order, expanded state and the refresh interval all persist per user in settings_user. A stateful Fleet Dashboard tab appears after first use and remembers the last scope.

Implementation notes:

  • Aggregates come from hrSystem / hrStorage / hrSWRun scoped by host_type; the utilization stats only count up/recovering devices so stale values from down devices don't skew min/avg/max.
  • To avoid any risk to the merged per-device dashboard JS, this ships a self-contained controller (js/hmib_summary_dashboard.js) that reuses the shared .hmibDash* card CSS. (A future refactor could unify the two controllers behind one config-driven module.)
  • CSRF-checked AJAX endpoints summary_card / summary_layout / summary_refresh; nonce'd inline init with JSON_HEX_* hardening. All visuals are theme-adaptive via color-mix.

Related Issue

Follows the per-device Host Dashboard (#68) and its fixes (#69, #70).

Motivation and Context

The Summary tab's dense statistics tables are hard to scan. A card dashboard with bullet charts (which naturally express the min/avg/max spread across a fleet) and top-process panels gives an at-a-glance fleet view, customizable per user and per OS type.

How Has This Been Tested?

php -l clean on hmib.php; tests/bin/validate-manifest.php passes (new JS lives under the already-listed js/); locales/po/cacti.pot regenerated; JS/CSS brace-balanced. Needs a browser pass on a themed install to confirm the bullet charts, stacked/mini bars, drag/add/remove/refresh and the stateful tab, since CI doesn't exercise the UI.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation repository accordingly.

…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.
@TheWitness
TheWitness requested review from bmfmancini and xmacan and a balanced review from Copilot October 10, 2026 21:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Several aggregates ignore the selected scope or include stale data from unavailable devices.

4 open findings
What changed in this PR

Adds a customizable fleet-wide Host MIB dashboard with OS-type scoping and aggregate visualizations.

Changes:

  • Adds aggregate cards, charts, process rankings, and persistence endpoints.
  • Adds dashboard interactions, styling, and Summary-tab navigation.
  • Updates translations and changelog.
File Description
hmib.php Implements fleet dashboard rendering, queries, persistence, and routing.
js/​hmib_summary_dashboard.js Controls card interactions, refresh, sorting, and layout persistence.
css/​hmib.css Styles fleet charts, tiles, bars, and legends.
locales/​po/​cacti.pot Adds translatable dashboard strings.
CHANGELOG.md Documents the new feature.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread hmib.php
Comment thread hmib.php Outdated
Comment thread hmib.php Outdated
Comment thread hmib.php Outdated
…, snake_case)

- Storage bullet: gate the per-volume used% min/avg/max on host_status >= 2 so stale values from down/disabled hosts don't skew the live distribution, while keeping capacity totals over all volumes.
- OS Type Distribution card: apply the \ scope predicate and switch to db_fetch_assoc_prepared(), binding the translated 'Unknown' fallback so a locale apostrophe can't break the SQL.
- Top Processes by CPU/Memory: always join hrSystem and require host_status >= 2 so the AVG/MAX rankings use only live devices (the all-types scope previously had no host-status column at all).
- Rename the new camelCase locals (cpuSev/memSev/swSev/procScale/stoSev/upScale) to snake_case per the repo convention.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unknown OS scoping, truncated distributions, asynchronous state handling, and invisible bar styling require correction.

1 open finding
4 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Make distribution bar fill block-level

css/​hmib.css:674

The fill is an empty <span> in normal inline flow, so its inline width and height: 100% are ignored and only the unfilled track is visible. Give the fill a block-level display so each OS distribution bar reflects its calculated width.

This issue also appears on line 681 of the same file.

Medium severity Remove OS type limit or aggregate excess types

hmib.php:5149

This limit silently omits OS types after the first 15, and expanding the card cannot reveal them, so fleets with more than 15 types get an incomplete distribution. Remove the limit since the card already clips and expands its body, or explicitly aggregate the remainder into an “Other” row.

Medium severity Preserve latest expanded state when replacing AJAX content

js/​hmib_summary_dashboard.js:243

The request snapshots the expanded state before the AJAX call. If the user expands or collapses this card while the request is pending, the returned section restores the stale state even though the newer state was already persisted. Copy the current card's expanded class to fresh immediately before replacing it.

🧠 Review effort: Balanced

Comment thread hmib.php Outdated
… bar fills, refresh race)

- Use -1 as the all-types sentinel instead of 0, since Cacti already uses 0 for the Unknown OS type. The Unknown Device Type row's glyph (ostype=0) now correctly scopes to untyped devices, and the scope picker gains an explicit Unknown option. All aggregate queries scope on host_type when ostype >= 0 (Unknown or a specific type) and span the whole fleet only for -1.
- OS Type Distribution: drop the LIMIT 15 so fleets with more than 15 types are fully represented (the card already clips/expands).
- Make the distribution-bar and top-process mini-bar fills block-level so their percentage widths actually render (an inline <span> ignored width/height).
- Top-process refresh: copy the card's current expanded class onto the fresh node before swapping, so toggling expand/collapse while a refresh is in flight is not reverted by the stale snapshot.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Fleet scoping can omit devices from grouped rows and can be overwritten by stale AJAX requests.

0 open findings

1 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Summary row groups host types but link uses one arbitrary ID

hmib.php:3349

This link does not necessarily represent the devices in its Summary row. The row query groups by name, version (hmib.php:3231) but selects an ungrouped host_type; the shipped mappings already contain IDs 11 and 12 with the same name/version (includes/database.php:200-201). When both IDs have hosts, the row combines them while this glyph passes one arbitrary ID, so the Fleet Dashboard omits part of that row. Group the Summary by host-type ID, or pass a scope that matches the row's full name/version grouping.

Medium severity AJAX card requests overwrite the remembered dashboard scope

hmib.php:4810

Persisting the scope inside this resolver also runs for summary_card AJAX calls. A refresh from an older dashboard tab can therefore overwrite the user's remembered scope, so another tab's next auto-refresh (whose URL omits ostype) unexpectedly jumps back. Only the main summary_dashboard navigation should update the preference; card requests should use their posted scope without saving it.

🧠 Review effort: Balanced

- Summary 'Device Type Summary Statistics' now groups by hrs.host_type instead of name/version, so each row maps to a single host_type and its Fleet Dashboard glyph scopes to exactly that type (previously rows merging two OID mappings with the same name/version, e.g. shipped ids 11 and 12, passed one arbitrary id and omitted the rest).\n- hmib_summary_resolve_type() only persists the chosen scope on the main summary_dashboard navigation (new \ arg); summary_card AJAX refreshes resolve without saving, so an older tab's refresh can no longer overwrite another tab's remembered scope.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The dashboard has keyboard accessibility defects and can display an incorrect scope after an OS type becomes unavailable.

0 open findings

Previously missed (3)

In code that hasn't changed since last review

Medium severity Handle stale remembered types in the dashboard picker

hmib.php:5472

This picker only includes OS types that currently have devices, while hmib_summary_resolve_type() accepts any remembered positive ID without checking that it remains selectable. If the last device leaves a type—or an administrator deletes it (hmib_types.php:133-141)—the dashboard stays filtered to that stale ID, but no option is selected and the browser displays “All Types,” yielding misleading empty cards. Either validate/fall back the remembered scope or ensure the active scope remains represented in the picker.

Medium severity Preserve focus when replacing a refreshed card

js/​hmib_summary_dashboard.js:250

Replacing the card also removes the focused refresh button, so a keyboard user who refreshes one card loses focus to the document body. Preserve whether focus is still on a card tool immediately before the swap and move it to the corresponding tool in the fresh card; toolbar “Refresh all” should not steal focus because its focus remains outside the card.

Medium severity Retain columnheader semantics for sortable table headers

js/​hmib_summary_dashboard.js:318

Assigning role="button" removes the <th> element’s native columnheader role, but aria-sort is only valid on columnheader/rowheader; assistive technology may therefore ignore the announced sort state. Keep the header semantics and place the interactive sorting control inside it (or otherwise retain columnheader while exposing activation).

🧠 Review effort: Balanced

- resolve_type falls back to all-types when a remembered positive OS type no longer has any devices, so the picker (which lists only populated types) and the data stay consistent instead of showing empty cards mislabelled 'All Types'.\n- Card refresh preserves keyboard focus: if a tool button in the card was focused, focus moves to the same tool on the replacement node (refresh-all focuses a toolbar button outside the card and is unaffected).\n- Sortable headers keep their native columnheader role (dropped role=button) so aria-sort stays valid; tabindex + Enter/Space keydown already make them operable.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The extensive interactive UI and visual behavior still require the documented themed-browser validation.

0 open findings

Previously missed (1)

In code that hasn't changed since last review

Low severity Add distinct styling for Disabled status tile

hmib.php:5068

The Fleet Overview promises colour-coded status tiles, but Disabled uses the empty modifier and therefore has the same neutral styling as non-status tiles such as Devices and CPUs. Give it a dedicated disabled modifier and add matching disabled styling (consistent with the gray disabled segment in the status card).

🧠 Review effort: Balanced

@TheWitness
TheWitness merged commit 1252f65 into develop Oct 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants