Adds scanner_http_ranges / scanner_http_probe_enabled / scanner_http_verify_tls
to Settings (persisted in scan_config.json, Options page defaults). /scan/trigger
accepts an optional body to override these per-scan; /scan/config GET/POST read
and persist the defaults. Port ranges validated at the API boundary.
ha-relevant: yes
run_scan() accepts DeepScanOptions: user port ranges are validated and appended
to the nmap -p list; when http_probe_enabled, open ports are probed for HTTP
signals before fingerprinting. Defaults (no options) reproduce the standard
scan exactly — probe is never called, port list unchanged.
ha-relevant: yes
The network scan captures each device's MAC, but approving a pending
device dropped it: the new Node had mac=NULL and no MAC in its
properties pane. Now both approve paths (single + bulk) copy the
pending device's MAC to Node.mac and add a MAC row to the node
properties so it shows in the standard properties pane with toggleable
visibility (hidden by default, consistent with zigbee props).
- build_mac_property: MAC NodeProperty row (hidden by default)
- merge_mac_property: append MAC to user-supplied props, idempotent,
preserves existing visibility
- single approve prefers the pending device MAC, falls back to payload
Adds unit tests for the helpers and API tests for both approve paths.
ha-relevant: yes
- Replace passlib.CryptContext with bcrypt 4.2.1 directly (passlib is
unmaintained and only emitted warnings when paired with bcrypt 4+).
hash_password / verify_password now call bcrypt.checkpw / .hashpw, and
verify_password is hardened against empty inputs without raising.
scripts/hash_password.py + tests/conftest.py updated to match.
- status_checker.check_node() now rejects targets starting with '-' before
any subprocess invocation, defending ping/tcp paths against arg-injection
if an admin sets a check_target like '-O'.
Tests added:
- test_auth: expired JWT, malformed JWT, wrong-secret JWT, empty password
vs empty server hash, verify_password edge cases.
- test_scan: _background_scan failure path marks ScanRun failed, leaves
non-running terminal status alone, success path invokes run_scan.
- test_status_checker: ping/tcp invocations are bypassed when target or
ip starts with '-'.
Backend coverage 89% → 90%.
- Approval (single + bulk) of zigbee pending devices now writes IEEE, Vendor,
Model, LQI into Node.properties so they show in the right panel.
- Zigbee re-import refreshes properties on existing canvas Nodes and skips
creating a pending row when the device was already approved — keeps
approved devices out of pending/hidden modals.
- Coordinator Node also receives the same properties on first creation and
on re-import.
- Remove the Parent Container selector from the add/edit node modal.
Approving a pending device created a Node with check_method=NULL, so
the status scheduler silently skipped it (scheduler.py:64 filters
falsy check_method) and the bubble stayed grey forever.
Default to 'ping' when the device has an IP, in both single and bulk
approve paths. Also persist caller-supplied check_method/check_target
in the single-approve path.
Add regression tests asserting default check_method='ping' after
approval for both endpoints.
Replaces sidebar pending/hidden panels with a wide modal showing
devices as cards. Adds search, segmented source/status filters,
type filter, select mode for bulk approve/hide/restore, and
keyboard shortcuts. Hidden cards click-to-restore (no approval
detour); approval no longer pops the edit modal.
Backend: new restore + bulk-restore endpoints (hidden -> pending).
Coordinator auto-approves to a canvas Node; routers/end devices land in
pending_devices keyed by IEEE. Discovered parent->child edges are stored
in pending_device_links so that approving a pending device later
auto-creates the Edge once both endpoints exist as canvas Nodes.
- new POST /api/v1/zigbee/import-pending (default mode in modal)
- new pending_device_links table; ieee_address on nodes + pending_devices
- pending_devices.ip migrated to nullable (table rebuild on existing DBs)
- approve / bulk-approve return auto-created edges; sidebar pushes them
into the canvas store with bottom -> top-t handles
- ZigbeeImportModal: radio toggle pending vs canvas; reset on close
- PendingDeviceModal: zigbee badge, IEEE/LQI/vendor/model rows, services
hidden for zigbee
- Sidebar pending row: ZIG source badge, LQI badge, friendly_name fallback
- SearchBar: null-safe IP, also searches friendly_name and ieee_address
- Tooltip trigger uses asChild to avoid nested-button hydration error
- db.flush() ensures node.id is populated before reading — fixes bulk approve
where node_ids were null, causing frontend to skip addNode for every device
- approve_device raises 404 on missing device, 409 on already-processed device
- _background_scan rollbacks dirty session then marks run as "failed"
- Explicit Node() field mapping instead of **model_dump() to prevent injection
- update_scan_config rolls back in-memory change if save_overrides() fails
- clear_pending uses bulk DELETE instead of N individual row deletes
- Backend: POST /scan/pending/bulk-approve and /scan/pending/bulk-hide endpoints (registered before dynamic routes to avoid conflict); bulk-approve response includes device_ids for frontend mapping
- Frontend: PendingDevicesPanel gains per-row checkboxes, select-all, and a bulk action bar (Approve N / Hide N) that appears when ≥1 device is selected
- Tests: 6 new backend API tests + 7 new frontend UI tests for bulk selection flows
- POST /scan/{run_id}/stop endpoint signals running scan to cancel
- Scanner checks cancellation flag between CIDR ranges and hosts, exits early
- Cancelled scans get status 'cancelled' instead of 'done'
- Stop button (red StopCircle) shown in Scan History panel for running scans
- 6 new backend tests, 5 new frontend tests
All settings (auth credentials, scanner ranges, status_checker interval)
now live in a single .env file via pydantic-settings. config.yml and
config.yml.example are deleted.
- Settings: add auth_username, auth_password_hash, scanner_ranges,
status_checker_interval; add load_overrides()/save_overrides() for
persisting runtime changes to data/scan_config.json
- auth.py: read credentials directly from settings
- scan.py: read ranges/interval from settings; write-back via save_overrides()
- scheduler.py: read interval directly from settings
- main.py: call settings.load_overrides() at startup
- docker-compose.yml: remove config.yml volume mount, add new env vars
- conftest.py: set settings fields directly instead of writing a temp config.yml