Mirror the notifications list paging pattern (offset/limit, fetch one
extra row to detect the next page, First/Prev/Next controls) across the
devices DB query, REST endpoint, API client and UI. Also add a First
page button to the notifications pagination row.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move select_interface and MAC-resolution logic (resolve_mac_address,
parse_proc_net_arp, probe_mac) out of the device_finders modules into a
single utils::network utility so future scanners can reuse them. Rename
resolve to resolve_mac_address and make parse_proc_net_arp private.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduce a second discovery module that passively listens for mDNS
multicast announcements (224.0.0.251:5353) and feeds discovered devices
into the existing devices/events/notifications pipeline, running in its
own task alongside the ARP scanner.
Since mDNS carries an IP and hostname but no MAC (the device key), the
module resolves IP->MAC via the OS ARP cache with a targeted ARP-probe
fallback. The advertised hostname is stored in a new optional `name`
column on devices (blank for ARP-only devices); the single `update`
writes `name` only when set so ARP rescans never clobber it. Device
names are included in event/notification messages.
Adds a GET /api/mdns_scanner/status endpoint (is_listening, devices_seen,
last_device_seen_seconds_ago) wired into OpenAPI, an optional
[mdns_scanner] config section, and the corresponding Nix module option.
Also aligns the Nix module's stale `timings` section with the current
`arp_scanner` schema.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
If no interface is set in the config, the ARP scanner now picks the
first non-loopback, up, IPv4-enabled interface automatically. The
setting is optional in the TOML config, sample config, and Nix module.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a responsive Home screen combining the notification list with a
device summary panel and ARP scanner status, visible side-by-side on
wide screens and stacked on narrow ones.
Backend:
- New GET /api/devices/summary endpoint returning registered device
counts and "seen in last 24h / 7 days" breakdowns by registration
status
- Migration 07 adds indexes on (is_registered) and (last_seen,
is_registered) so summary queries use index range scans instead of
full table scans
Frontend:
- HomeScreen replaces NotificationList, embedding notifications,
device summary card, and ArpScannerCard in a LayoutBuilder-driven
two-column (≥700 px) or single-column layout
- ArpScannerCard extracted to a shared public widget reused by both
HomeScreen and StatusScreen
- Nav rail entry renamed to "Home" with home icon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renames scanner.rs to arp_scanner.rs to future-proof for additional scanner
types. Renames the [timings] config section to [arp_scanner] and simplifies
field names (arp_sender_timeout -> sender_timeout, arp_scan_duration ->
scan_duration). Introduces arp_scanner_status module to track whether the
scan is actively running or sleeping, and exposes this via a new
GET /api/arp_scanner/status endpoint returning is_running,
running_for_seconds, and next_run_in_seconds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Loads vendor-device-type.json at compile time (same pattern as mac-vendors-export.json)
and sets device_type when a new device is detected by the ARP scanner.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds --llm flag to update_mac_vendors to classify unmatched vendors via
Claude Haiku 4.5, and --skip-download to reuse existing vendor DB.
Adds the anthropic Python package to the Nix dev shell. Updates CLAUDE.md
with the new script command.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces backend/data/update_mac_vendors, an executable Python 3 script
that downloads the latest MAC vendor database from maclookup.app and
generates vendor-device-type.json — a mapping of vendor names to device
types (phone, laptop, tablet, server, tv, printer, network_appliance,
home_security, home_appliance, watch, pc, gaming_console) used for
auto-assigning device type on first detection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The device event history chart previously fetched all events and filtered
client-side. This wires the existing created_from API parameter to the
frontend so filtering happens in the backend.
Also fixes a bug where the Today filter returned no results: rusqlite was
storing datetimes with a space separator ("YYYY-MM-DD HH:MM:SS+00:00")
while SQL filters used RFC 3339 with a T separator, causing string
comparisons to fail for same-day events. All datetime storage across
device_events, devices, and notifications is now consistently RFC 3339.
A migration converts existing records.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Filters events at the query level using the existing composite index
(mac_address, created_on DESC), so no schema changes are needed.
Adds two unit tests covering the filtered and unfiltered cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds two indexes to eliminate full table scans at scale:
- idx_notifications_created_on (created_on DESC) for purge and unfiltered listing
- idx_notifications_is_new_created_on (is_new, created_on DESC) for filtered listing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Purges records older than a configurable window (default 365d) once per day.
Also adds a composite index on device_events(mac_address, created_on DESC) for
efficient scan history queries at scale, and fixes a non-deterministic pagination
order by adding id as a tiebreaker to ORDER BY.
Co-Authored-By: Claude Sonnet 4.6 <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>
Owner filter uses a server-side substring match (LIKE '%VALUE%');
device type filter matches the stored value exactly, sending an
empty string for unknown/unclassified devices. Both filters combine
with the existing registration status chip via AND logic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /devices/:macAddress route showing all device fields with Register/Forget actions
- Device list items are now tappable; popup menu gains a "View details" item
- Notification list items with a linked device are tappable; popup menu gains a "View device" item
- Backend: new DB migration adds optional mac_address column to notifications
- Device-related notifications (NewDeviceFound, DeviceOnlineAfterTime, DeviceChanged) now store the triggering device's MAC address
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a context menu to each device card with contextual actions:
- Unregistered devices show a Register option (dialog with owner field
and device type dropdown)
- Registered devices show a Forget option (confirmation dialog)
Also fixes CORS to allow PUT and DELETE methods, adds device type
tooltips on the icon, and renames the "New" filter/badge to
"Not registered".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Addresses all 31 warnings emitted by cargo clippy: redundant field
names in struct initialisers, unnecessary unwrap after is_some checks
(replaced with if-let), needless borrows, filter+next replaced with
find, iter().count() replaced with len(), clone on Copy type, and
manual match-to-Option replaced with .ok().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Annotates all API handlers and model structs with Utoipa macros to generate
an OpenAPI 3.0 spec at runtime. Serves an interactive Swagger UI at /api/docs
and the raw JSON spec at /api/docs/openapi.json, both publicly accessible
outside the auth middleware.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add run.sh script to start the backend (erases db, runs cargo with sudo
using user's CARGO_HOME to avoid full recompilation). Remove sudo from
cargo test in run_tests.sh for the same reason.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
POST /api/notifications/{id}/mark_as_new sets is_new=1, allowing a notification to be flagged as unread after it has been read.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>