Skip to content

feature: NUT (Network UPS Tools) support, HMIB-style UPSes table sorting, in-place auto-refresh (1.4.0) - #55

Merged
TheWitness merged 4 commits into
developfrom
feature/nut-support
Oct 16, 2026
Merged

TheWitness merged 4 commits into
developfrom
feature/nut-support

Conversation

@TheWitness

@TheWitness TheWitness commented Oct 16, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds Network UPS Tools (NUT) as a third collector type, brings the Fleet Dashboard UPSes table in line with the HMIB plugin's sorting, and makes dashboard auto-refresh update the cards in place. Plugin version is now 1.4.0.

NUT support (type_id 3)

  • Native PHP socket client (includes/nut.php) for the upsd protocol (TCP 3493). No upsc or other CLI is needed, so it works on any poller, including Windows and remote pollers. curl can't speak the protocol, so it isn't used.
  • Authentication is optional. NUT is normally read anonymously; an upsd.users username/password can be set. Optional STARTTLS verifies the certificate and host name by default. For homelab self-signed certificates there is a per-UPS CA / Certificate File (verified against that PEM, chain and host name still checked; recommended) and an Allow Self-Signed Certificate option (encrypts but does not verify the server, documented as such).
  • Hardening: host, UPS name and credentials are validated before connecting; credentials containing control characters are refused (no command injection); reads are bounded by line count, per-read timeout and an overall deadline; the server's data is only ever stored, never executed.
  • Schema: new apcupsd_ups columns nut_ups_name, nut_username, nut_password, nut_tls. Widened apcupsd_ups_stats ups_status, ups_model, ups_serialno, ups_firmware, ups_driver and ups_name so multi-flag NUT values aren't truncated (applied on upgrade by db_update_table()).
  • Poller: collects type 3 UPSes, translates NUT flags to the apcupsd vocabulary (OL->ONLINE, OB->ONBATT, LB->LOWBATT, RB->REPLACEBATT, OVER->OVERLOAD) so dashboards, fleet totals and the slave rule keep working unchanged, replaces the stats row each poll (no stale values) and clears the previous error. A Cacti device is auto-created like for apcupsd UPSes.
  • UI: "NUT Based" type with NUT-specific form fields, list label and dashboard label.
  • Timeouts: new per-UPS Timeout (seconds) setting (default 5, 1-120; a narrow field shown for the apcupsd and NUT types; SNMP keeps its own). NUT's connect and whole conversation are bounded by twice the timeout, and a timeout is reported as one. apcaccess now runs through Cacti's exec_with_timeout() instead of exec(), so a hung apcaccess is killed and recorded as an error rather than stalling the poller.
  • README documents NUT setup (including the upsd.conf LISTEN requirement) and replaces the old "considering NUT" note.

Fleet Dashboard UPSes card

  • Sorting now matches HMIB: click (or Enter/Space) a column header to sort, click again to reverse; arrows via CSS; works in the maximize dialog. The button/localStorage variant is removed.
  • Right-aligned: Load, Charge, Time Left, Est. Power and Last Updated. The old .apcupsdDashNum rule was being outranked by .apcupsdDashTable th/td { text-align: left }.

Auto-refresh refreshes panels, not the page

Auto-refresh now updates each card in place instead of reloading the page. Cacti 1.3+ has a refreshFunction hook in its page timer (the Graphs page uses it), so the dashboard uses that there. Cacti 1.2.x (this plugin's floor) has no such hook (its timer reloads #main), so on 1.2.x the dashboard stops core's page timer and runs a small timer of its own, which ends for good once the dashboard leaves the screen. Support is detected at runtime, and without the dashboard script core's normal page refresh still applies. The chosen table sort is kept across a refresh (the live one, captured when the card is swapped), and overlapping rounds are skipped.

Review follow-ups included

  • NUT reads are non-blocking and polled against the deadline, so a server trickling bytes can't hold a read open past it.
  • A failed NUT query marks the linked Cacti host down.
  • The UPS edit form no longer rewrites an existing UPS's port when it opens.
  • Unreported start / manufacture / master update / last on battery / last self test times are stored as NULL instead of the poll time.

Testing

  • NutClientTest (protocol logic over a scripted server, a connected stream pair, and loopback TCP), NutWiringTest (schema, poller, form, docs, version), and updated EstimatedWattsTest / DashboardRefreshTest.
  • includes/nut.php is added to the coverage config so CI's 100% patch-coverage gate applies to it.
  • Also exercised end to end against a mock upsd over TCP with authentication.
  • check-i18n-pot and validate-manifest pass.

Notes for review

  • Not changed: measured real power (ups.realpower) is not stored yet; the plugin keeps estimating watts from nominal power x load.
  • The sort follows HMIB's semantics, so an empty numeric value (N/A) sorts as 0 rather than always last.

…rting, in-place auto-refresh

NUT support (type_id 3, version 1.4.0):
- Native PHP implementation of the upsd protocol (includes/nut.php): no upsc
  or other CLI needed. Optional USERNAME/PASSWORD, STARTTLS with certificate
  and host name verification, bounded reads, and strict validation of the
  host, UPS name and credentials before anything is sent.
- New apcupsd_ups columns nut_ups_name, nut_username, nut_password, nut_tls;
  widen ups_status/model/serialno/firmware/driver/name on apcupsd_ups_stats.
- Poller collects type 3 UPSes, translates NUT flags to the apcupsd status
  vocabulary, replaces the stats row each poll and clears stale errors.
- UPS form, list and dashboard know the new type; README, CHANGELOG, INFO
  (1.4.0) and the i18n template updated.

Fleet Dashboard UPSes card:
- Sort like the HMIB dashboards (click or Enter/Space on a header, asc then
  desc, also in the maximize dialog); drop the button/localStorage variant.
- Right-align Load, Charge, Time Left, Est. Power and Last Updated; the lone
  .apcupsdDashNum rule was outranked by .apcupsdDashTable th/td.
- Auto-refresh refreshes the cards in place through Cacti's refreshFunction
  hook instead of reloading the page, keeps the chosen sort, skips
  overlapping rounds and falls back to the full-page refresh without the JS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 16, 2026 17:47

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

Unresolved polling reliability, configuration preservation, data accuracy and refresh compatibility defects block approval.

6 open findings
What changed in this PR

Adds NUT monitoring to the Cacti UPS plugin and updates dashboard interactions for version 1.4.0.

Changes:

  • Adds a native NUT client, collector settings, polling and schema support.
  • Reworks table sorting and introduces in-place dashboard refresh.
  • Updates documentation, translations and tests.
File Description
upses.php Adds NUT configuration and collector labels.
tests/​Unit/​NutWiringTest.php Checks NUT integration wiring.
tests/​Unit/​NutClientTest.php Tests protocol handling and sockets.
tests/​Unit/​EstimatedWattsTest.php Updates sorting and alignment checks.
tests/​Unit/​DashboardRefreshTest.php Adds refresh integration checks.
README.md Documents NUT setup and requirements.
poller_apcupsd.php Adds NUT collection and device creation.
phpunit.xml Includes NUT client in coverage.
locales/​po/​cacti.pot Adds NUT translation strings.
js/​apcupsd_dashboard.js Implements header sorting and card refresh.
INFO Bumps version to 1.4.0.
includes/​nut.php Implements NUT transport and data mapping.
includes/​database.php Adds settings and widens stats columns.
includes/​dashboard.php Updates collector display and refresh wiring.
css/​apcupsd.css Styles sorting indicators and numeric alignment.
CHANGELOG.md Records release changes.

🧠 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 includes/nut.php Outdated
Comment thread poller_apcupsd.php
Comment thread upses.php Outdated
Comment thread includes/dashboard.php Outdated
Comment thread js/apcupsd_dashboard.js
Comment thread poller_apcupsd.php
TheWitness and others added 3 commits October 16, 2026 13:58
- New apcupsd_ups.timeout (seconds, default 5, 1-120) with a narrow form
  field shown for the apcupsd and NUT types; apcupsd_normalize_timeout()
  clamps it everywhere it is used.
- NUT: connect plus conversation now end within twice the timeout (sub-second
  per-read waits, so the deadline holds), and a timeout is reported as such
  instead of as a closed connection.
- apcaccess runs through Cacti core's exec_with_timeout() instead of exec(),
  so a hung apcaccess is killed; a timed out or empty run now records an
  error on the UPS.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Per-UPS CA / Certificate File: the server is verified against that PEM file
  (chain and host name still checked), which is how a homelab self-signed
  certificate is trusted without weakening verification.
- Per-UPS Allow Self-Signed Certificate: encrypts without verifying the
  server, for networks where a certificate file is impractical.
- The CA file path must be a plain absolute path (no stream wrappers or
  control characters) and readable, otherwise the poll fails with a clear
  error before connecting. The form hides both options unless TLS is on.
- Verified over a real TLS handshake: default fails, CA file and the
  self-signed option succeed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- NUT reads are now non-blocking and polled against the deadline, so a
  server that trickles bytes without finishing a line cannot hold a read
  open past it (fgets() blocked for the whole stream timeout per read).
- A failed NUT query now marks the linked Cacti host down, like a status
  change does (those hosts have availability checking disabled).
- The UPS edit form no longer rewrites an existing UPS's port when it
  opens; the default port is only swapped on a manual type change.
- Auto-refresh no longer depends on Cacti's refreshFunction hook, which
  Cacti 1.2.x lacks: detect support, and otherwise run a timer of our own
  that stops when the dashboard leaves the screen.
- Capture the live sort when a refreshed card replaces the old one, so a
  sort made while the request was in flight is kept.
- Unreported start, manufacture, master update, last on battery and last
  self test times are stored as NULL instead of defaulting to the poll time.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@TheWitness
TheWitness merged commit b494cb6 into develop Oct 16, 2026
3 checks passed
@TheWitness
TheWitness deleted the feature/nut-support branch October 16, 2026 18:31
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.

4 participants