Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

--- 1.4.0 ---

* issue: Write a WARNING to the Cacti log (APCUPSD) naming the UPS, host, port and reason whenever a NUT query fails; before, the reason was only stored in the UPS's error message and shown with `-d`, so a NUT UPS that could not be reached left nothing in the log
* issue: Save the Poller ID chosen on the UPS form; the field was displayed but never stored, so every UPS was always assigned to poller 1. Changing the Poller ID of a UPS that is linked to a Cacti Device is refused with an explanation, since the device's poller has to be changed on the device itself
* feature: Add support for monitoring UPSes through Network UPS Tools (NUT) as a third collector type, "NUT Based". The poller talks to `upsd` (TCP 3493 by default) over a native PHP socket implementation of the NUT protocol, so no `upsc` or other CLI tool is needed. Optional username/password authentication and STARTTLS (with certificate verification) are supported, and one NUT server can serve several UPSes through the new NUT UPS Name setting. NUT status flags are translated to the existing apcupsd vocabulary (OL to ONLINE, OB to ONBATT, and so on) so the dashboards, fleet totals and alerts keep working unchanged
* issue: Widen the status, model, serial number, firmware, driver and name columns of `apcupsd_ups_stats` so multi-flag NUT statuses and longer NUT strings are not truncated
* feature: Let NUT STARTTLS work with self-signed certificates: a per-UPS CA / Certificate File to verify the server against (certificate and host name are still checked), or an Allow Self-Signed Certificate option that encrypts without verifying the server
Expand Down
6 changes: 5 additions & 1 deletion locales/po/cacti.pot
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Cacti 1.3.1\n"
"Report-Msgid-Bugs-To: developers@cacti.net\n"
"POT-Creation-Date: 2026-10-14 16:33-0400\n"
"POT-Creation-Date: 2026-10-16 16:37-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Cacti Developers <developers@cacti.net>\n"
"Language-Team: Cacti Developers <developers@cacti.net>\n"
Expand Down Expand Up @@ -750,6 +750,10 @@ msgstr ""
msgid "The hostname where the snmp agent is located."
msgstr ""

#: upses.php
msgid "The Poller ID cannot be changed while the UPS is linked to a Cacti Device. Change the poller on the device instead."
msgstr ""

#: upses.php
msgid "Template UPS was not found! Unable to duplicate."
msgstr ""
Expand Down
4 changes: 4 additions & 0 deletions poller_apcupsd.php
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,10 @@
if ($result['error'] !== '') {
debug('NUT query failed: ' . $result['error']);

// The poller runs in the background with its output discarded, so the Cacti log is
// the only place an operator can see why a NUT UPS is down.
cacti_log(sprintf('WARNING: NUT query for UPS %s (%s:%s) failed: %s', $ups['name'], $ups['hostname'], $ups['port'], $result['error']), false, 'APCUPSD');

Check warning on line 627 in poller_apcupsd.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this 155 characters long line (which is greater than 120 authorized).

See more on https://sonarcloud.io/project/issues?id=Cacti_plugin_apcupsd&issues=AaFGUw098wRgO2LjIr65&open=AaFGUw098wRgO2LjIr65&pullRequest=58

// These devices have availability checking disabled, so core polling cannot
// notice the failure: mark the linked host down here.
set_ups_host_state($ups['host_id'], false);
Expand Down
17 changes: 17 additions & 0 deletions tests/Unit/NutWiringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,23 @@
expect(strpos($poller, "set_ups_host_state(\$ups['host_id'], false);"))->toBeLessThan(strpos($poller, "substr(\$result['error'], 0, 255)"));
});

it('keeps the NUT failure log line and the Poller ID save in place', function (string $file, string $needle) use ($read) {

Check warning on line 171 in tests/Unit/NutWiringTest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this 123 characters long line (which is greater than 120 authorized).

See more on https://sonarcloud.io/project/issues?id=Cacti_plugin_apcupsd&issues=AaFGcSUHCBoOIVIXYd6e&open=AaFGcSUHCBoOIVIXYd6e&pullRequest=58
expect($read($file))->toContain($needle);
})->with([
'failure logged' => ['poller_apcupsd.php', "cacti_log(sprintf('WARNING: NUT query for UPS %s (%s:%s) failed: %s', \$ups['name'], \$ups['hostname'], \$ups['port'], \$result['error']), false, 'APCUPSD');"],

Check warning on line 174 in tests/Unit/NutWiringTest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this 215 characters long line (which is greater than 120 authorized).

See more on https://sonarcloud.io/project/issues?id=Cacti_plugin_apcupsd&issues=AaFGcSUHCBoOIVIXYd6f&open=AaFGcSUHCBoOIVIXYd6f&pullRequest=58
'poller id saved' => ['upses.php', "if (isset_request_var('poller_id')) {"],
'poller id digits only' => ['upses.php', "get_nfilter_request_var('poller_id'), 'poller_id', '^[0-9]+\$', false, 3);"],

Check warning on line 176 in tests/Unit/NutWiringTest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this 123 characters long line (which is greater than 120 authorized).

See more on https://sonarcloud.io/project/issues?id=Cacti_plugin_apcupsd&issues=AaFGcSUHCBoOIVIXYd6g&open=AaFGcSUHCBoOIVIXYd6g&pullRequest=58
'linked poller move held' => ['upses.php', "form_input_validate('', 'poller_id', '^\$', false, 3);"],
]);

it('logs the NUT failure before recording it and never logs credentials', function () use ($read) {
$poller = $read('poller_apcupsd.php');

expect(preg_match("/WARNING: NUT query.*?substr\\(\\\$result\\['error'\\], 0, 255\\)/s", $poller))->toBe(1);
expect(preg_match('/WARNING: NUT query[^\n]*nut_(password|username)/', $poller))->toBe(0);
expect(preg_match("/\\\$current_poller != \\\$save\\['poller_id'\\].*?sql_save\\(\\\$save, 'apcupsd_ups'\\)/s", $read('upses.php')))->toBe(1);

Check warning on line 185 in tests/Unit/NutWiringTest.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this 144 characters long line (which is greater than 120 authorized).

See more on https://sonarcloud.io/project/issues?id=Cacti_plugin_apcupsd&issues=AaFGcSUHCBoOIVIXYd6h&open=AaFGcSUHCBoOIVIXYd6h&pullRequest=58
});

it('does not rewrite an existing UPS\'s port when the edit form opens', function () use ($read) {
$page = $read('upses.php');

Expand Down
16 changes: 16 additions & 0 deletions upses.php
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,11 @@
$save['description'] = form_input_validate(get_nfilter_request_var('description'), 'description', '', true, 3);
$save['site_id'] = form_input_validate(get_nfilter_request_var('site_id'), 'site_id', '^[0-9]+$', true, 3);

// Without this the Poller ID field is shown but never stored, so every UPS falls back to the column default.
if (isset_request_var('poller_id')) {
$save['poller_id'] = form_input_validate(get_nfilter_request_var('poller_id'), 'poller_id', '^[0-9]+$', false, 3);
Comment thread
TheWitness marked this conversation as resolved.
}

if ($save['type_id'] == 2) {
$save['hostname'] = form_input_validate(get_nfilter_request_var('snmp_hostname'), 'snmp_hostname', '', true, 3);
} else {
Expand Down Expand Up @@ -415,6 +420,17 @@
$host = [];
}

// Moving a device to another poller needs Cacti's own device handling (poller cache and remote
// replication), which this form does not do, so a linked UPS keeps the poller its device is on.
if (cacti_sizeof($host) && !empty($save['id']) && isset($save['poller_id'])) {
$current_poller = (int) db_fetch_cell_prepared('SELECT poller_id FROM apcupsd_ups WHERE id = ?', [$save['id']]);

if ($current_poller > 0 && $current_poller != $save['poller_id']) {
form_input_validate('', 'poller_id', '^$', false, 3);
raise_message('apcupsd_poller_linked', __('The Poller ID cannot be changed while the UPS is linked to a Cacti Device. Change the poller on the device instead.', 'apcupsd'), MESSAGE_LEVEL_ERROR);

Check warning on line 430 in upses.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Split this 199 characters long line (which is greater than 120 authorized).

See more on https://sonarcloud.io/project/issues?id=Cacti_plugin_apcupsd&issues=AaFGcSX0CBoOIVIXYd6i&open=AaFGcSX0CBoOIVIXYd6i&pullRequest=58
}
}

if (!is_error_message()) {
$ups_id = sql_save($save, 'apcupsd_ups');

Expand Down
Loading