* Guard maint maintenance-window checks against a missing maint functions file
#858 corrected the maint functions include path but left three
plugin_maint_check_cacti_host() calls guarded only by
api_plugin_is_enabled('maint'). When maint is enabled but its functions
file cannot be included (the include block now logs and continues rather
than fataling), those calls hit an undefined function and fatal the poll.
Add a function_exists('plugin_maint_check_cacti_host') guard to all three
call sites (thold_check_threshold() and both the recovering- and down-device
loops in thold_update_host_status()) so a missing maint include degrades
gracefully, matching the guard other plugins use.
Cover both maintenance-gated notification branches with a new
MaintCheckGuardTest; add an html_split_string() test stub so the
recovering-device path can be driven in the isolated unit process.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Let unexpected errors fail the maint-guard tests
Drop the broad catch(Throwable) in the host-status test helper. A device
inside a maintenance window is logged and the loop continues without the
downstream notification work, so the call returns on its own; removing the
catch means an unexpected error (such as a removed guard fataling on an
undefined maint function) now fails the test instead of being swallowed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix include
* return back deleted file
* Fix maint include guard: test file_exists on path, not require_once return
file_exists() was wrapping require_once(), so the first require ran unconditionally and its integer return (1) was passed to file_exists(), which fell through to require the non-existent includes/functions.php and fatalled. Test the path strings directly.
* Cover maint include fallback and error branches
The maint-include lookup in thold_check_threshold() and thold_update_host_status() gained an includes/functions.php fallback and an error-log branch, but the suite only exercised the plugin-root path. Add MaintIncludeResolutionTest driving all three outcomes for both functions, with cacti-root fixtures for the includes/ layout and the missing-plugin case, so patch coverage of the changed lines reaches 100%.
---------
Co-authored-by: TheWitness <thewitness@cacti.net>
* thold: status pills with sortable/clickable Status column and zebra rows
Replace the whole-row status colour on the Thresholds (thold.php) and Device Status (thold_graph.php) pages with a rounded status pill in a new Status column right of Name; rows now fall back to zebra striping. The column is server-side sortable by severity via a new thold_severity_sql() CASE (a faithful SQL mirror of get_thold_severity(), legend-order rank 1=Alert..7=Disabled), the Status filter gains the seven per-severity options (values 101-107 handled in thold_get_state_filter()), and clicking a pill sets that filter and reloads. Pill shape lives in a theme-agnostic css/pills.css; all colours stay theme-aware (reusing each theme's .thold<State> backgrounds, with light pill text added to the saturated classic/modern/paw themes). Adds Pest coverage for thold_severity_sql() and the severity filter branch.
* thold: regenerate cacti.pot for the status-pill i18n strings
* thold: address review - pill contrast, time_warning_low '0' parity, keyboard a11y, stronger test
Light themes (classic/modern/paw all have white/snow backgrounds) now use dark pill text instead of white, which failed 4.5:1 at 11px. thold_severity_sql() treats time_warning_low '0' as falsy (NOT IN ('','0')) to match get_thold_severity(), so the pill never disagrees with sort/filter. Pills are now focusable role=button spans with Enter/Space activation on both pages. TholdSeveritySqlTest now executes the CASE over an in-memory SQLite row and asserts the SQL rank equals get_thold_severity() across branch/boundary scenarios incl. the '0' case.
* thold: keep legend chips equal width using the longest label
The Thold/Host/Log status legends used flex:1 1 0 with no shared minimum, so chips grew to unequal widths (and could clip their labels when tight). CSS can't measure the widest sibling, so the three legend renderers now compute the longest label and emit it as a --thold-chip-min CSS variable (calc(<longest>ch + 1.5rem)); every theme's .tholdLegendItem uses it as a shared min-width (falling back to max-content). All chips share one minimum, so they stay equal width as the legend wraps. Updated TholdLegendTest assertions to match the new container markup.
* thold: use CSS Grid for truly equal-width legend chips; assert exact chip min
Review follow-up (#847). The flex: 1 1 0 + flex-wrap layout distributes free space per wrapped line, so a partial last row stretched its chips wider than full rows - chips were not equal across rows. Switch .tholdLegend to a grid (repeat(auto-fill, minmax(var(--thold-chip-min), 1fr))) so every chip shares an equal column across all rows. The longest-label seed still drives the column minimum. Tests now assert the exact computed calc(Nch + 1.5rem) value instead of mere presence.
* thold: use auto-fit so chips fill the legend width when they fit on one row
Review follow-up: auto-fill keeps empty implicit tracks, so on a wide viewport where all chips fit in one row they no longer stretch to fill the legend (as they did under flex). auto-fit collapses the empty tracks while still sharing equal columns when the legend wraps to multiple rows.
* thold: escape status-legend chip labels at output
Defence-in-depth: wrap the legend chip display text in html_escape() so markup or HTML entities in a translated label cannot alter the legend DOM. Applied consistently across the thold/monitor/servcheck/mactrack/syslog fleet legends. The chip-min sizing still measures the unescaped label length, so layout is unchanged.
* INFO: set compat to 1.2.32
* docs: update Copilot instructions Cacti baseline to 1.2.32 (matches INFO compat)
* INFO: back off compat to 1.2.29
* Add manifest.json + upgrade-time file pruning
* Prune phpunit.xml on upgrade and ignore markdown-lint configs
phpunit.xml ships in the repo for CI but is a dev-only test artifact, so it
is removed from manifest.json 'expected', pruned from installs on upgrade
(like tests/), and excluded from the manifest drift check.
The retired markdown-lint configs (.mdlrc, .md_style.rb) are deleted, and
.md* is now ignored like .git*: protected from pruning and excluded from
drift if it reappears.
* Move thold_functions.php and thold_webapi.php into includes/; flatten css
The main threshold library becomes includes/functions.php and the web API
helper becomes includes/webapi.php; every caller (setup.php, includes/*, the
CLI/poller/notify entry points and the test suite) loads them from includes/.
Each per-theme stylesheet css/<theme>/main.css becomes css/<theme>.css so users
can drop their own css/<theme>.css into the css base, and the page_head hook
looks there. Old paths are tombstoned so existing installs drop the stale
copies on upgrade.
* Regenerate cacti.pot for the includes/ file relocations
Moving the plugin's library files into includes/ changed the source-reference
lines in the translation template; regenerate it so the 'Verify translation
template' CI check passes.
* Harden the hook-include test and make RPN integer ops explicit
The HookFunctionsIncludeTest invokes real hooks with placeholder input purely
to execute their relocated include_once lines; it now swallows engine
notices/warnings/deprecations during those calls (and restores the error
handler and config afterward) so it cannot trip the suite's failOnDeprecation
or leak state into other tests. The RPN % and ^ operators now cast their
operands to int explicitly, matching their long-standing integer-truncation
behaviour while silencing the implicit float-to-int conversion deprecation.
* Harden prune against tampered-manifest path traversal
The upgrade-time prune now rejects any tombstone containing '.'/'..' segments
(which could escape the plugin directory or resolve to its root) and treats
ancestors of whitelist entries as protected, so a tombstone on a parent
directory can no longer delete a whitelisted file beneath it.
* docs: update and align the Project Structure block
Point the structure tree at the relocated files (libraries now under
includes/, data files under docs/, stylesheets under css/) and align the
trailing '# ...' comments to a single column so they no longer drift right.
* docs/tests: standardize test header and correct manifest-contract notes
- Use the full license header (from the plugin's own setup.php) in
tests/Unit/PruneFilesTest.php instead of the abbreviated copyright banner.
- Document that the manifest drift check and the upgrade-time prune also
handle phpunit.xml and .md* files, matching the implemented behavior.
* refactor: rename prune helpers to the plugin-slug function prefix
The repository naming contract reserves the plugin_<name>_ prefix for plugin
lifecycle / hook-registration functions; all other functions use the plain
<name>_ prefix. Rename the internal upgrade helpers accordingly:
plugin_<name>_prune_files() -> <name>_prune_files()
plugin_<name>_rmtree() -> <name>_rmtree()
The call site, unit tests, and the copilot-instructions.md references are
updated to match. No behavioral change.
* chore: set the Cacti compatibility floor to 1.2.29
Align the compat metadata (and the contributor-guide references) with the
team decision to standardize the minimum supported Cacti version at 1.2.29;
the previously proposed 1.2.32 floor was not adopted.
---------
Co-authored-by: Copilot <copilot@users.noreply.github.com>