6683 Commits
Author SHA1 Message Date
Jokob @NetAlertX e0b8abcdd8 Merge pull request #1770 from netalertx/next_release
Next release
v26.9
2026-09-02 08:06:42 +10:00
jokob-sk 5751811243 plugin template update 2026-09-02 07:58:31 +10:00
Jokob @NetAlertX 4b2da481ee Merge pull request #1769 from netalertx/main
Sync
2026-09-01 09:28:01 +10:00
Jokob @NetAlertX df0ef6ec17 Merge pull request #1765 from mauricio-camayo/add-pihole-monitor-plugin
Add pihole_monitor plugin: combined Pi-hole device import + query anomaly detection
2026-09-01 08:31:13 +10:00
Jokob @NetAlertX 72871e23b1 Merge pull request #1768 from netalertx/next_release
PLG+DOCS: plugins dev docs for temp files + adguard export cleanup
2026-09-01 07:47:07 +10:00
Mauricio Camayo d6b4696ac9 fix: track per-source delta so one instance's reset can't mask the other's spike
Addresses CodeRabbit review on PR #1765 (pullrequestreview-5069337680).

pihole_monitor.py:
- last_raw is now tracked per source ({"primary": N, "secondary": M}
  per device) instead of one combined value. Combining raw totals
  across sources before diffing let a counter reset on one instance
  silently net out against real traffic on the other - e.g. primary
  +2000 (a real spike) and secondary resetting 1000->5 (-995) would
  combine into a raw delta of only 1005, hiding most of the primary's
  actual spike behind the secondary's unrelated restart.
- New aggregate_source_deltas(): diffs each source independently via
  compute_delta(), then sums only the valid deltas. A source with no
  valid delta this run (bootstrapping or just reset) contributes
  nothing and doesn't block the others; each source keeps its own
  reference point going forward.
- State loaded from before this change (last_raw as a plain number,
  not per-source) is now tolerated instead of crashing - treated as no
  prior reference point, so every source just bootstraps fresh on the
  next run.

README.md:
- Fixed a self-contradicting line: a less frequent schedule means
  larger per-run deltas, so PIHOLEMON_MIN_BLOCKED may need *raising*,
  not lowering as it previously said.
- Corrected PIHOLEMON_HISTORY_DAYS guidance: it's a retention window,
  not a detection delay. A new device becomes evaluable on its 3rd
  successful run (1st anchors the counter, 2nd records the first
  delta, 3rd has a baseline to compare against), not after the full
  retention window.

Tests: 54 (up from 48). New coverage: aggregate_source_deltas() unit
tests including the exact dual-source reset-masking scenario, a
main()-level integration test for the same, and a regression test for
tolerating pre-per-source state. Both the reset-masking fix and the
legacy-state guard verified via mutation testing (reverted each,
confirmed the relevant tests fail, restored). 99% line+branch coverage
maintained.
2026-08-31 12:51:46 -05:00
Mauricio Camayo e543f14d08 fix: address round 2 of jokob-sk's maintainer review
References PR #1765.

Docs:
- Added PIHOLEMON to docs/PLUGINS.md and a new "Approach 4" section in
  docs/PIHOLE_GUIDE.md, leading with anomaly detection (the actual
  differentiator vs PIHOLEAPI) and explaining when to pick each plugin.
- README/PLUGINS.md/config.json's UI-facing description all reordered
  and shortened to lead with anomaly detection instead of device
  import, and to drop implementation detail that belongs in the
  README, not the Settings page.
- Trimmed the "Why not extend PIHOLEAPI" README section per feedback -
  useful context for a maintainer, not for an end user configuring
  the plugin.

config.json / pihole_monitor.py:
- RUN defaults to "disabled", matching every other non-core plugin.
- VERIFY_SSL split into PRIMARY_VERIFY_SSL / SECONDARY_VERIFY_SSL -
  each instance can be http/https independently. Settings reordered so
  each *_VERIFY_SSL sits right under its matching *_PASSWORD.
- GRAPHQL_TOKEN removed; graphql_token now reads the core API_TOKEN
  setting instead of a plugin-specific duplicate.
- GRAPHQL_URL replaced with a GET_OWNER boolean - the endpoint is now
  derived from this app's own GRAPHQL_PORT (single source of truth)
  instead of a URL the user had to keep in sync by hand.
- HISTORY_LENGTH (run count) replaced with HISTORY_DAYS (a real time
  window): state now stores [timestamp, delta] samples and
  trim_history() drops anything older than the window, so the
  baseline means the same thing regardless of schedule - a faster
  schedule adds more data points instead of shrinking the window.
- STATE_FILE moved from the log folder to dbFolderPath, so the rolling
  anomaly baseline survives NetAlertX upgrades instead of being wiped
  with the logs.
- netalertx_device_owner() (1 GraphQL call per device) replaced by
  netalertx_device_owners() (1 call per run, batched) - avoids N
  blocking round-trips on a large network.
- Fixed a zero-baseline bug: `bool(... and baseline and ...)` silently
  exempted a device with an all-zero blocked-query history (0.0 is
  falsy in Python) from ever being flagged, even on its first real
  spike. Now checks `baseline is not None`.
- Fixed the placeholder-MAC filter: only excluded the literal "ip-::",
  not Pi-hole's general "ip-<address>" placeholder pattern. Caught
  downstream by is_mac() either way, but now the actual placeholder
  check does what it looks like it does.
- Fixed a cumulative-counter bug: Pi-hole's /api/stats/top_clients
  returns a count that's cumulative since FTL last started, not a
  per-interval or daily-resetting one (confirmed against FTL's own
  source and long-standing user reports that it doesn't reset at
  midnight). Comparing that raw total directly against a rolling
  average made any device's ordinary growing traffic look like an
  escalating anomaly. compute_delta() now diffs each run's raw count
  against the previous run's (state gained a per-key last_raw
  reference point alongside the delta history) - None (not 0) on the
  first-ever run for a device or right after a counter reset, so
  those runs re-anchor the reference point instead of fabricating or
  swallowing a delta.
- RUN_SCHD default changed from every 6 hours to every 5 minutes now
  that the baseline window is real days, not run count, so a frequent
  schedule only adds data points instead of narrowing the window; also
  matches the default most other device-scanner plugins use.
- RUN_SCHD gained the same live cron-validity checkmark ARPSCAN and
  other scanner plugins use (a ✓/✗ icon next to the field, validated
  client-side against a regex) - reuses the existing generic
  validateRegex() widget, nothing plugin-specific to build.

Tests: 48 tests (up from 37), 99% line+branch coverage. Every fix
above verified via mutation testing (deliberately broken, confirmed
the relevant test fails, then restored).
2026-08-31 11:49:02 -05:00
jokob-sk 7bf6256788 better scaffolding, robustness 2026-08-31 15:20:54 +10:00
jokob-sk a014140e50 FE: dashed links in network view for non-ethernet connections #1763 2026-08-31 14:57:40 +10:00
jokob-sk 5e615ddff0 FE: dashed links in network view for non-ethernet connections #1763 2026-08-31 14:39:46 +10:00
jokob-sk a394d16fb6 better scaffolding, robustness 2026-08-31 14:01:35 +10:00
jokob-sk d155633164 better scaffolding, robustness 2026-08-31 13:45:30 +10:00
jokob-sk 81202afa31 PLG+DOCS: better scaffolding 2026-08-31 11:27:25 +10:00
jokob-sk e44b17faa7 PLG+DOCS: better scaffolding 2026-08-31 10:01:09 +10:00
jokob-sk 72729d8624 PLG+DOCS: plugins dev docs for temp files + adguard export cleanup 2026-08-31 08:50:30 +10:00
Mauricio CamayoandClaude Sonnet 5 ed21c86214 test: assert exact history window instead of len() >= 1
CodeRabbit follow-up on PR #1765
(https://github.com/netalertx/NetAlertX/pull/1765#discussion_r3888374014):
test_main_history_length_never_produces_empty_or_growing_unbounded only
asserted len(history) >= 1, which a mis-clamped history_length (e.g.
keeping 4 items instead of 1) would still pass unnoticed.

Replaced with test_main_history_length_clamps_and_trims_exactly,
seeding distinct ordered values and asserting the exact retained
history against each PIHOLEMON_HISTORY_LENGTH boundary. Verified it
actually catches a broken clamp: temporarily reverted the
max(1, ...) fix in pihole_monitor.py, confirmed this test fails
([] == [40]) while the rest of the suite still passes, then restored
the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHJAArRiet4GmXUsxnNLdW
2026-08-30 11:31:19 -05:00
Mauricio CamayoandClaude Sonnet 5 f564448617 fix: address CodeRabbit review findings on pihole_monitor plugin
Addresses 5 of the 6 actionable comments from CodeRabbit's review of
PR #1765 (netalertx/NetAlertX#1765), plus adds test coverage:

- fetch_top_blocked_clients() returns None on failure instead of {},
  so a failed request can no longer be mistaken for "genuinely zero
  blocked queries this run" and silently write a false 0 into a
  device's rolling history baseline. main() now tracks a
  stats_complete flag and skips anomaly evaluation + history
  persistence entirely for a run with incomplete blocked-query data.
- fetch_top_blocked_clients() is now called with count=max_clients
  (the existing PIHOLEMON_API_MAXCLIENTS setting) instead of a
  hardcoded default of 50, so clients beyond the top 50 are no longer
  silently dropped from anomaly detection.
- New build_ip_to_mac() derives the IP->MAC identity map from every
  gathered device entry instead of from merge_device_entries()'s
  by-MAC-deduplicated output, which only kept one IP per device and
  silently lost a multi-IP device's other IPs (misattributing their
  blocked-query traffic to a bare IP instead of the real MAC).
- PIHOLEMON_HISTORY_LENGTH is clamped to at least 1, so a negative
  setting can no longer reach the history[-history_length:] slice
  with a nonsensical negative-of-negative length.
- PIHOLEMON_VERIFY_SSL now defaults to true (was false, matching the
  official PIHOLEAPI plugin's convention). README documents the
  http:// vs https:// credentials trade-off explicitly rather than
  forcing https:// - most home Pi-hole setups, including the one this
  plugin targets, run over plain HTTP on a trusted LAN.
- Added test/plugins/test_pihole_monitor.py (37 tests, 99% line and
  branch coverage of pihole_monitor.py per pytest-cov - only the
  `if __name__ == '__main__':` entry-point guard is unreached):
  auth and deauth success/failure paths, the None-sentinel-on-failure
  contract, fetch_devices()'s own failure path, build_ip_to_mac()'s
  multi-IP fix, gather_device_entries()'s skip branches and fake-MAC
  fallback, netalertx_device_owner()'s success/failure/no-URL paths,
  and main()-level coverage for source aggregation, the
  stats_complete gate, the history_length boundary clamp, the
  CONSIDER_ONLINE fallback, an unconfigured-sources run, and the
  offline-device / invalid-MAC / unknown-IP / owner-lookup branches
  together in one run.

Not addressed: CodeRabbit's suggestion to hard-reject http:// URLs in
auth(). Diverges deliberately - it would break the plugin's majority
use case (Pi-hole admin API on a trusted home LAN without TLS), which
this repo's own PIHOLEAPI plugin also targets over plain HTTP.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHJAArRiet4GmXUsxnNLdW
2026-08-30 11:03:15 -05:00
Jokob @NetAlertX 257431b330 Merge pull request #1767 from andrewbeaton/patch-1
Update download link for Proxmox installer
2026-08-30 21:26:08 +10:00
Andrew Beaton a72426704f Update download link for Proxmox installer
Updated download link to point to new netalertx repository instead of older jokob-sk repository URL.
2026-08-30 10:22:25 +01:00
mauricio-camayo 8d5eab41b5 Add pihole_monitor plugin: combined Pi-hole device import + query anomaly detection
Does two jobs against the same Pi-hole connection(s), instead of two
separately configured plugins:

1. Device import - same job as the official PIHOLEAPI (pihole_api_scan)
   plugin, but supports an optional secondary/failover Pi-hole natively
   (accepts two sets of credentials instead of forking the official
   plugin, which hardcodes its settings-key prefix and doesn't support
   multiple instances).
2. Query anomaly detection - flags a device whose blocked-query count
   spikes well above its own recent rolling average (signature of
   malware/a compromised device beaconing out), keyed by MAC address
   (not IP, which changes under DHCP) and combined across both Pi-hole
   instances so a compromised device can't evade detection by switching
   resolvers.

Notifications are delegated entirely to NetAlertX's own Watched/Report
on mechanism - the plugin never calls a notification service directly.

Live-tested against a two-Pi-hole home setup (v26.8.5) for several days,
including two real bugs found and fixed during that testing (an
offline-filtered device losing its MAC and falling back to a bare-IP
identifier, and a boolean-expression flake8 style fix).
2026-08-29 22:28:37 -05:00
Jokob @NetAlertX 1bbd1f16dc Merge pull request #1764 from vaibhav8a/fix-typo-should
docs: fix typo shoudl -> should
2026-08-30 07:36:50 +10:00
Vaibhav Srivastava 18b8f74563 docs: fix typo shoudl -> should
Signed-off-by: Vaibhav Srivastava <vaibhavsri1712@gmail.com>
2026-08-29 14:11:58 +05:30
Hosted Weblate user 54392 af195d0bd1 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (836 of 836 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2026-08-28 01:51:26 +00:00
叫我Romeo 988d0a4768 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (836 of 836 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/zh_Hans/
2026-08-28 01:51:25 +00:00
Jokob @NetAlertX 716a41aa9a Merge pull request #1761 from justadityaraj/fix/mikrotik-missing-mac
fix(mikrotik): continue scans past leases without MAC addresses
2026-08-27 21:34:19 +10:00
Aditya Raj Singh bcd44381c6 test: cover bound MikroTik leases without MACs 2026-08-26 03:05:02 +05:30
Aditya Raj Singh ea7afd6c83 test: isolate MikroTik scanner module stubs 2026-08-26 02:59:20 +05:30
Aditya Raj Singh ece9788b89 fix: keep MikroTik scans running past MAC-less leases 2026-08-26 02:47:22 +05:30
Massimo Pissarello a0270c76a4 Translated using Weblate (Italian)
Currently translated at 100.0% (836 of 836 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2026-08-23 12:51:29 +00:00
Sylvain Pichon fa37284574 Translated using Weblate (French)
Currently translated at 100.0% (836 of 836 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/fr/
2026-08-23 12:51:27 +00:00
Ettore Atalan 8c1cf9911d Translated using Weblate (German)
Currently translated at 88.1% (737 of 836 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/de/
2026-08-23 12:51:24 +00:00
Hosted Weblate ac796301b3 Merge branch 'origin/main' into Weblate. 2026-08-22 04:53:42 +02:00
Massimo Pissarello ee09fe9a2f Translated using Weblate (Italian)
Currently translated at 100.0% (832 of 832 strings)

Translation: NetAlertX/core
Translate-URL: https://hosted.weblate.org/projects/pialert/core/it/
2026-08-22 04:53:42 +02:00
Jokob @NetAlertX afd81c2ddb Merge pull request #1753 from netalertx/next_release
FE: Implement pause/resume functionality for automatic scans with API…
2026-08-22 12:53:27 +10:00
jokob-sk f9be6af26c Merge branch 'next_release' of github.com:netalertx/NetAlertX into next_release 2026-08-22 09:50:22 +10:00
Jokob @NetAlertX 26404bb5e2 feat(ui): replace atob with safeAtob for decoding device icons 2026-08-21 23:46:20 +00:00
Jokob @NetAlertX 7a8ea5d829 feat(database): add cleanup for dangling parentMAC references and related tests 2026-08-21 23:22:40 +00:00
Jokob @NetAlertX 7cbdeb9855 docs(tests): add guidelines for stubbing modules in standalone-capable tests 2026-08-21 22:44:08 +00:00
Jokob @NetAlertX 8000d9b453 refactor(tests): improve module stubbing in ntfy custom header tests 2026-08-21 22:38:11 +00:00
jokob-sk bf24e9f4a8 Merge branch 'next_release' of github.com:netalertx/NetAlertX into next_release 2026-08-22 08:27:35 +10:00
Jokob @NetAlertX 474c3574ee feat(models): add initial models package with domain model descriptions 2026-08-21 22:23:54 +00:00
Jokob @NetAlertX 99897988e4 Merge pull request #1760 from netalertx/main
sync
2026-08-22 08:12:23 +10:00
Jokob @NetAlertX 64ae530d36 Merge pull request #1759 from Valiante/Valiante-patch-1
Fix: theme reverts to Light after header reload (#943)
2026-08-22 08:11:10 +10:00
Jokob @NetAlertX fe2c3f8948 Merge pull request #1756 from justadityaraj/feat/ntfy-multiple-custom-headers
feat(ntfy): allow more than one custom header
2026-08-22 08:07:45 +10:00
Jokob @NetAlertX fe3cfd6e48 Merge pull request #1757 from netalertx/copilot/fix-comprehensive-test-failure
fix: lazy-import write_notification to prevent partial module load in CI tests
2026-08-22 08:04:42 +10:00
Aditya Raj Singh 63b20d060e docs(ntfy): document per-entry header skipping
The whitespace note contradicted itself: it said surrounding whitespace is
trimmed and then that leading or trailing whitespace is invalid. The parser
strips, so trimming is what actually happens.

Also lists the new non-ASCII/newline skip reason and states that skipping is
per entry, and adds the language identifier markdownlint wants on the two
fenced blocks (MD040).
2026-08-21 23:18:09 +05:30
Aditya Raj Singh 2d4e2d5f05 fix(ntfy): skip custom headers that cannot be sent
A custom header carrying a non-ASCII character raised UnicodeEncodeError from
inside http.client. That is a ValueError, not a RequestException, so it escaped
both handlers in send() and took the whole publisher down - every notification
lost because of one typo in one header.

build_custom_headers now rejects newlines and non-ASCII the same way it already
rejects malformed and colliding entries: warn, skip that entry, keep the rest.
UnicodeEncodeError is still caught at the request, as a backstop for the
plugin's own headers, since REPORT_DASHBOARD_URL feeds one of them.

Verified against real requests: before, send() raised UnicodeEncodeError; after,
the bad header is dropped and the notification is still posted.
2026-08-21 23:17:28 +05:30
Valiante 9fedd49e4a Fix: theme reverts to Light after header reload (#943)
Theme was applied on raw $(document).ready, which fires before the async cacheSettings() call has necessarily populated the settings cache. clearCache() (the header reload button) wipes localStorage immediately before reloading, so that reload could read an empty cache and hard-override the theme to Light via setCookie, even with Dark or System configured. Gated the theme application on callAfterAppInitialized(), which is already used elsewhere for this class of race.

Fixes #943
2026-08-21 17:44:21 +01:00
jokob-sk 4a31f83d19 DOCS: readme cleanup 2026-08-21 10:48:15 +10:00
copilot-swe-agent[bot]andjokob-sk 05bdf1985e fix: lazy-import write_notification to prevent partial module load in tests
Co-authored-by: jokob-sk <96159884+jokob-sk@users.noreply.github.com>
2026-08-20 22:09:27 +00:00