Tune SQLite and remove blocking calls from async/scan paths:
- Enable WAL + synchronous=NORMAL + busy_timeout + foreign_keys on each
pooled connection, so the five scanners, web server, and retention no
longer contend on the default rollback journal / FULL fsync.
- Run DB work in axum handlers via spawn_blocking (db::run_blocking) so
synchronous rusqlite calls no longer block tokio worker threads.
- Deliver notifications on a dedicated task fed by a bounded channel; the
blocking Pushover HTTP call runs in spawn_blocking, so a slow or
unreachable Pushover can never stall device discovery.
- Make get_db_connection() return Result instead of panicking, so pool
exhaustion surfaces as a 500 rather than crashing the process.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add ActiveStatusCell/PassiveStatusCell wrappers in the scanners common
module so the five per-scanner status.rs files reduce to a single static;
replace parse_parameter_bool/int/string with one generic parse_parameter
over FromStr; extract the shared LIMIT/OFFSET paging clause into
db::apply_paging; and drop a no-op for-loop in the ARP sender.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Records a DeviceEvent row on every scan: NewDevice when a device is first
seen, DeviceSeen on subsequent scans. Exposes GET /api/devices/{mac}/events
with pagination, ordered by date descending.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>