Commit Graph
325 Commits
Author SHA1 Message Date
jokob-sk 7aca7c17f3 BE: FREEBOX dual stack IP fix #1804 2026-09-23 08:17:23 +10:00
jokob-sk 24301cbdbc BE: FREEBOX dual stack IP fix #1804 2026-09-23 07:52:30 +10:00
jokob-sk a573eeb0e7 BE: FREEBOX dual stack IP fix #1804 2026-09-22 08:43:41 +10:00
jokob-sk d72aea21f5 BE: re-enable GRAPHQL_PORT #1803 2026-09-21 17:30:25 +10:00
jokob-sk 0fc6082c73 BE: IP ordering fixes #1797 2026-09-19 09:48:33 +10:00
jokob-sk 52d1698221 DOCS+BE: skill updates, write_notifications fix #1793 2026-09-18 08:09:22 +10:00
Mauricio CamayoandClaude Sonnet 5 091e648e88 Fix DOCKERDISC_HOST_MAC docs and strengthen case-insensitivity test
resolve_host_mac() returns the manually configured MAC immediately,
with no Socket Proxy /info call at all - the config.json text still
described it as a fallback used only when auto-detection fails.
Reworded both the setting's own description and the parent "Docker
hosts" description to match actual behavior.

The case-insensitivity regression test for lookup_device_mac() stubbed
DeviceInstance.getByMac() to return a fixed row regardless of input,
so it passed even without exercising real collation - functionally a
duplicate of test_lookup_device_mac_found. Replaced it with a
delegation check, and added real SQLite-backed coverage for
DeviceInstance.getByMac()'s case-insensitivity in
test/backend/test_device_instance.py. That surfaced a gap in the
shared db_test_helpers.py fixture: its Devices.devMac column was
missing the COLLATE NOCASE that the real schema declares, so it could
not have exercised this behavior. Fixed the fixture to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011meLPKCzVpdZyAUfv5U6mm
2026-09-15 12:57:04 -05:00
Mauricio Camayo d512e5d84e Add regression test for lookup_device_mac() case handling
CodeRabbit flagged a possible case-sensitivity gap in getByMac() usage.
No functional change needed - Devices.devMac is COLLATE NOCASE at the
schema level, so getByMac()'s plain equality lookup is already
case-insensitive (that's exactly why getAllByName() has to apply it
explicitly and getByMac() doesn't - devName has no column collation).
This test guards that lookup_device_mac() doesn't do anything of its
own that would undo that.
2026-09-15 12:19:30 -05:00
Mauricio Camayo 776b462001 Merge remote-tracking branch 'upstream/main' into add-dockerdisc-plugin 2026-09-15 11:58:18 -05:00
Mauricio Camayo 3b83d2403b Address jokob-sk review: DeviceInstance instead of raw SQL, drop HTML entity/partial translations/dead spec-file reference
- resolve_host_mac()/lookup_device_mac() now use the new
  DeviceInstance.getAllByName()/getByMac() core methods instead of
  querying Devices directly - no more direct SQL access from the plugin.
- config.json: removed the &rarr; HTML entity from a description (plain
  ASCII ->, matching e.g. pihole_monitor's convention), and dropped the
  partial es_es/de_de translations scattered through settings/columns
  (English only now, matching e.g. rest_import) instead of leaving some
  strings translated and others not.
- script.py: removed the two remaining references to
  PLUGIN_DOCKERDISC_SPEC.md, a file that was never included in this PR.
2026-09-15 11:58:10 -05:00
jokob-sk e237b92657 BE+FE: plugin view fixes, skills, new core method and tests 2026-09-15 08:46:44 +10:00
Mauricio Camayo 6a26804a5e Address CodeRabbit review: request timeout budget, shape validation, ambiguous devName, README fix
- DockerHost now takes a shared run deadline instead of a per-request
  timeout duration - every _get() call is capped by whatever's left of
  that budget (and REQUEST_TIMEOUT_DEFAULT as an upper bound), so one
  slow/hanging host can't burn the whole RUN_TIMEOUT and starve every
  other configured host. config.json's hosts param now also sets
  timeoutMultiplier, scaling the outer kill-timeout by host count.
- _get() validates the parsed response's shape (dict for /info, list for
  /containers/json and /networks) before returning it, rejecting a
  malformed/unexpected payload the same as a network failure instead of
  letting a caller crash on it further down.
- resolve_host_mac()'s hostname match now detects more than one device
  sharing that name and treats it as ambiguous (falls back to manual),
  instead of silently picking an arbitrary one via LIMIT 1.
- README: the Socket Proxy is only reachable at 127.0.0.1:2375 under the
  network_mode: host case described above it, not under normal compose
  networking - fixed the doc to not imply either URL works there.
2026-09-14 10:01:40 -05:00
Mauricio Camayo 94a5cd4968 Add DOCKERDISC plugin: enrich existing devices with their Docker containers
Read-only enrichment plugin, not an import/discovery plugin. For each
configured Docker host (via Docker Socket Proxy, never /var/run/docker.sock
directly), lists that host's containers under the host device's own
Device Details -> Plugins -> DOCKERDISC tab.

- Never creates a device, for either a host or a container - matches
  against hosts already discovered the normal way (ARP/Nmap).
- Every container is listed (bridge/overlay included), not only
  macvlan/ipvlan ones - a container only gets its own MAC/IP shown when
  it has a macvlan/ipvlan network.
- Host MAC auto-detected via the Socket Proxy's /info -> Devices.devName
  match, with a manual fallback.
2026-09-14 09:11:34 -05:00
jokob-sk 6ab220fd8c BE+FE: async event execution 2026-09-14 18:17:06 +10:00
jokob-sk 52ddd7f9d2 BE: review fixes 2026-09-14 16:59:49 +10:00
jokob-sk 7277a07660 BE: review fixes 2026-09-14 16:51:35 +10:00
jokob-sk e30a0ea93b BE: events, device pagination for performance 2026-09-14 14:51:43 +10:00
jokob-sk 321824db8e BE: events, device pagination for performance 2026-09-14 14:36:27 +10:00
jokob-sk dc7274a2e9 DOCS: skill cleanup, lower case mac fixes, trigger performance 2026-09-14 14:26:14 +10:00
jokob-sk 1e2ac94496 FE+BE: skill cleanup, review fixes 2026-09-14 09:30:51 +10:00
jokob-sk f7fef5d196 FE+BE: plugins config fixes #1784 2026-09-14 08:49:15 +10:00
jokob-sk f52de6cbdc FE+BE: performance improvements 2026-09-14 08:29:02 +10:00
jokob-sk 37bf86a805 BE: conditional device import support, notification supression support during scan #1721 2026-09-11 08:26:16 +10:00
jokob-sk 6dab348de2 BE: conditional device import support, notification supression support during scan #1721 2026-09-11 08:01:41 +10:00
jokob-sk dd4fc057c8 BE: SQL refactor presence 2026-09-10 22:11:46 +10:00
jokob-sk d9947a2829 BE: conditional device import support, notification supression support during scan #1721 2026-09-10 21:00:50 +10:00
jokob-sk de730e85e9 BE: conditional device import support, notification supression support during scan #1721 2026-09-10 18:41:07 +10:00
jokob-sk 34993ed77b BE: conditional device import support, notification supression support during scan #1721 2026-09-10 17:40:31 +10:00
jokob-sk 44c52a3cf4 BE: conditional device import support, notification supression support during scan #1721 2026-09-10 14:27:44 +10:00
jokob-sk c3bdd92b8d BE: conditional device import support, notification supression support during scan #1721 2026-09-10 13:51:09 +10:00
jokob-sk 4d0175c660 BE+FE+DOCS: run plugin from custom props/actions 2026-09-05 10:54:56 +10:00
jokob-sk 939e6494ba BE+PLG: mac case sensitivity causing double detection #1775 2026-09-04 08:18:02 +10:00
jokob-sk 4be05c958a BE+PLG: mac case sensitivity causing double detection #1775 2026-09-04 08:08:22 +10:00
jokob-sk 5751811243 plugin template update 2026-09-02 07:58:31 +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 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
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
Jokob @NetAlertX 7a8ea5d829 feat(database): add cleanup for dangling parentMAC references and related tests 2026-08-21 23:22:40 +00:00
Jokob @NetAlertX 8000d9b453 refactor(tests): improve module stubbing in ntfy custom header tests 2026-08-21 22:38:11 +00:00
Jokob @NetAlertX 99897988e4 Merge pull request #1760 from netalertx/main
sync
2026-08-22 08:12:23 +10:00