Commit Graph
197 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.7 bb677aeebf Centralize status polling with last-known-good, stale, and error tiers
The four polling status cards (ARP, mDNS, scanners summary, devices
summary) each rolled their own Timer/_status/_error/_isLoading state and
flipped to a red 'Error' UI on the first failed poll, throwing away
last-known-good data that was still in memory. A momentary backend blip
made every card flash red.

Introduce PolledValue<T>, a ChangeNotifier that owns the periodic timer,
in-flight CancelToken, last value, last error, and a freshness
classification (initialLoading / fresh / stale / error). 'stale' kicks in
on the first failure but keeps the value visible; 'error' only takes over
after staleErrorAfter elapses without a success (30s for the 5s scanner
polls, 3min for the 1min device-summary poll). All four cards now branch
on freshness and show a small live-updating warning icon with a tooltip
('Last updated N ago — <error>') while stale, instead of flipping red.

Also threads CancelToken? through the three polled GET endpoints.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 10:03:19 -04:00
rzuastiandClaude Opus 4.7 d7276c8383 Replace per-method debugPrints with a debug-only Dio LogInterceptor
Every BackendAPI method had hand-rolled 'About to call ...' and
'Received: ...' debug lines, and the constructor printed the base URL and
a masked API-key marker. Replace all of that with a single LogInterceptor
attached under `if (kDebugMode)` to both the singleton Dio and the
ephemeral Dio used by BackendAPI.test(). Headers and bodies are kept off
so the Authorization bearer never lands in the console. The interceptor
is tree-shaken from release builds.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 09:39:06 -04:00
rzuastiandClaude Opus 4.7 30df22df56 Cancel in-flight paginated fetches to prevent stale-write races
Rapidly switching filter chips, sort columns, or pagination on the devices
and notifications lists could overlap fetches, and an older response
landing after a newer one would silently overwrite the list with results
that no longer match the active query.

Thread an optional CancelToken into listDevices and listNotifications.
Each State now keeps one active token, cancels it on every new fetch and
on dispose, and bails from both success and catch branches when its token
has been superseded. DioExceptionType.cancel is silenced so internal
cancellations don't surface as user-visible errors. The notifications
list's prior `if (_isLoading) return;` guard is removed so the 1-min
periodic refresh is no longer skipped during user activity.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 09:30:03 -04:00
rzuastiandClaude Opus 4.7 d3b6c64e8b Apply Settings changes to the live BackendAPI without restart
Settings._save wrote new base_url/api_key to prefs but the BackendAPI
singleton kept the Dio it built at first access, so URL/key changes only
took effect after an app restart. Expose reconfigureFromPrefs() on
BackendAPI and call it from Settings._save so subsequent requests pick up
the new config immediately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 09:17:15 -04:00
rzuastiandClaude Opus 4.7 7954a9c755 Add Dio timeouts and map backend errors to user-friendly messages
Set 5s/15s/15s connect/receive/send timeouts so calls fail fast instead of
hanging. Introduce dioErrorToUserMessage() and route DioException-throwing
call sites (device actions, devices list, notifications list, scanners
status card) through it, replacing raw e.toString() output. Notifications
list now surfaces load errors inline in the theme's error color, matching
the devices list pattern. Status dots show the mapped detail as a tooltip.
Stop logging the API key in cleartext.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 09:06:56 -04:00
rzuastiandClaude Opus 4.7 0f6296759d Add edit dialog for registered devices
Extend PUT /api/devices/{mac} to accept an optional name and surface an
Edit action from both the device detail screen and the per-row overflow
menu, letting users modify owner, device type, vendor and name without
forgetting and re-registering.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:27:52 -04:00
rzuastiandClaude Opus 4.7 3a579f0321 Redesign devices list as a sortable, responsive headered list
GET /api/devices accepts sort_by/sort_order (whitelisted columns; default
last_seen DESC, stable secondary sort on mac_address), device registration
captures an optional hostname, and the Flutter list switches between a
wide headered layout and a compact ListTile under 600px. Per-row overflow
menu retains View details / Register / Forget actions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:09:55 -04:00
rzuasti 29f836f281 Updated CLAUDE.md to avoid aliases in imports in Rust 2026-05-30 10:00:13 -04:00
rzuastiandClaude Opus 4.7 d60e8a7e2e Address clippy warnings in test code
Apply clippy auto-fixes (filter().next() → find(), bool assert_eq →
assert!, len() >= 1 → !is_empty(), map_or(false, …) → is_some_and, etc.)
and refactor validate_device to take an expected Device instead of 8
positional args. Production code was already clean; all 60 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:59:44 -04:00
rzuastiandClaude Opus 4.7 f9ad90d015 Drop redundant use ... as ... aliases in scanner imports
The aliases re-encoded the module path into a name that no longer
matched the source identifier (e.g. `use super::finder as mdns`). Use
the actual module names at call sites; in the two web_server handlers
the local `status` fn collides with the module, so the single call site
uses the fully-qualified path instead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:52:40 -04:00
rzuastiandClaude Opus 4.7 c12e700c27 Consolidate ARP/mDNS modules under scanners::{arp,mdns}::{finder,scanner,status}
Group each discovery protocol's primitive (finder), orchestration loop
(scanner), and status state under one module tree instead of splitting
them between device_finders/ and crate-root *_scanner / *_scanner_status
files. Also includes incidental rustfmt fixes to a few pre-existing
long lines.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:48:29 -04:00
rzuastiandClaude Opus 4.7 54211c57d7 Split db::devices update into seen/update and expose PUT endpoint
Scanners now call seen() (sighting semantics: stamp last_seen, preserve
registration, conditional vendor/device_type/name writes). update() is
the user-editable mutation (owner, device_type, vendor) wired to
PUT /api/devices/{mac_address}.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 09:27:44 -04:00
rzuasti 792a309136 Updated todo 2026-05-30 07:23:08 -04:00
rzuasti f02c9284d5 Updated todo 2026-05-30 07:21:57 -04:00
rzuastiandClaude Opus 4.7 926a902377 Refresh device and notification lists on return navigation
Subscribes the devices list and notifications list to a shared
RouteObserver so they refetch when a pushed route (e.g. device detail)
is popped, avoiding stale state after registering or updating a device.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:13:00 -04:00
rzuastiandClaude Opus 4.7 eb91804c97 Preserve existing device_type on scanner re-sighting
Why: a device manually registered as "Laptop" was being overwritten to
"Phone" when a later scan deduced a vendor that maps to a different type.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 15:06:21 -04:00
rzuastiandClaude Opus 4.7 2ba51769ec Show registered devices by owner's device type name
Display registered devices with a "John's Laptop" title and move their
IP address into the card body, instead of showing the owner separately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:24:40 -04:00
rzuastiandClaude Opus 4.7 ef420ad360 Do not notify when a device's vendor is first deduced
Treat an empty->non-empty vendor transition as not a change, so first
deducing a vendor for a device that previously had none no longer raises
a "vendor changed" notification.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:20:20 -04:00
rzuastiandClaude Opus 4.7 561fbdb548 Split device registration out of the update path
Add dedicated db::devices::register/unregister methods so registration
state (is_registered, owner) is owned by the API endpoints, and make
update sighting-only. This stops scanner re-sightings from wiping a
device's registration. Also preserve an existing mDNS hostname instead
of overwriting it on re-sighting.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 14:15:29 -04:00
rzuastiandClaude Opus 4.7 98a23aa7fd Deduce vendor from mDNS services for privacy MACs
Modern devices increasingly use randomized ("Private WiFi Address") MACs,
which are locally administered and have no real OUI, so the MAC-prefix
vendor lookup returns nothing. Such devices still advertise distinctive
mDNS service types, which we now use to deduce their vendor.

- Extend the mDNS parser to also capture PTR service types alongside
  A-record hostnames.
- Add a service-type -> vendor mapping (data/mdns-service-vendor.json) and
  a service_vendor_finder, using canonical vendor names so device-type
  resolution still chains.
- Add utils::network::is_locally_administered to detect randomized MACs.
- In the mDNS scanner, fall back to service-based deduction only when the
  OUI lookup is empty and the MAC is locally administered.
- Group the three finders under a new data module.
- Preserve a known vendor (and its derived device_type) when a later
  sighting cannot deduce one, and suppress the spurious "vendor changed"
  notification in that case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 13:56:39 -04:00
rzuastiandClaude Opus 4.7 7fb457ca4a Make device detail IP and info fields selectable
Use SelectableText for the IP address and all info-card values so users
can select and copy them (e.g. the MAC address).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 13:56:16 -04:00
rzuastiandClaude Opus 4.7 251c06277e Add pagination to the devices list screen
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>
2026-05-29 13:07:27 -04:00
rzuastiandClaude Sonnet 4.6 75f597bfe4 Add mDNS scanner status to Status and Home screens
Add an mDNS Scanner status card to the Status screen mirroring the ARP
card, and replace the ARP card on the Home screen with a combined Status
card that summarizes both scanners in one line each. Extract the shared
duration formatting helper into utils/duration_formatter.dart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 10:19:50 -04:00
rzuastiandClaude Sonnet 4.6 c6fdf9e181 Centralize networking helpers in utils::network module
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>
2026-05-29 09:58:45 -04:00
rzuastiandClaude Opus 4.7 ca83009944 Add passive mDNS/Bonjour discovery scanner
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>
2026-05-29 09:37:33 -04:00
rzuastiandClaude Sonnet 4.6 2fe850c070 Auto-focus owner field and submit on Enter in register device dialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 08:29:50 -04:00
rzuastiandClaude Sonnet 4.6 1da0306b61 Replace device detail popup menu with inline CTA buttons
Register and Forget Device actions are now always-visible buttons
(FilledButton and error-styled OutlinedButton) placed inline below
the device info card, following M3 guidelines for in-content actions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 08:25:11 -04:00
rzuastiandClaude Sonnet 4.6 39f382fd0d Extract NotificationsList widget from HomeScreen into its own file
HomeScreen is now a StatelessWidget responsible only for the responsive
layout structure. All notification state, pagination, filtering, and
mark-as-read logic lives in the new NotificationsList widget.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 08:06:30 -04:00
rzuastiandClaude Sonnet 4.6 85b86026cb Replace infinite-scroll notifications with button-based pagination
- Swap PagingController/PagedSliverList for explicit page state and
  Previous/Next icon buttons below the list
- Fetch pageSize+1 items to detect the last page without a total-count
  API; pagination controls are hidden when there is only one page
- Remove unused infinite_scroll_pagination dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 07:56:03 -04:00
rzuastiandClaude Sonnet 4.6 2077f970f9 Update README to reflect optional networking.interface config
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 07:36:31 -04:00
rzuastiandClaude Sonnet 4.6 08f2dc8340 Auto-detect network interface when not configured
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>
2026-05-29 07:35:25 -04:00
rzuasti 49adadf059 Updated todo 2026-05-28 21:18:20 -04:00
rzuastiandClaude Sonnet 4.6 e5eb9c777d Updated todo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 20:03:23 -04:00
rzuastiandClaude Sonnet 4.6 d14c8beb60 Fix empty-state layout: compact message instead of viewport-filling indicator
Replace Center-in-Expanded / SliverFillRemaining empty states in the
notifications and devices lists with compact, centered inline text so
status widgets below remain visible without scrolling. Also align
notification filter chips to match the devices list spacing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 20:03:11 -04:00
rzuastiandClaude Sonnet 4.6 e5c27f067c Align navigation with Material 3 responsive recommendations
- Replace single NavigationRail with adaptive navigation: NavigationBar
  (bottom) for compact <600dp, icon-only NavigationRail for medium
  600–840dp, and extended NavigationRail for expanded ≥840dp
- Remove nested Scaffold/AppBar from About, Settings, DeviceList, and
  DeviceDetail — all screens now render as plain widgets inside the
  single shell Scaffold; page titles surface as inline headings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:51:31 -04:00
rzuasti ebcde0ff4a Updated todo 2026-05-28 19:35:29 -04:00
rzuastiandClaude Sonnet 4.6 ff83ada576 Improve home screen widget UX: visual hierarchy and navigation
- DeviceSummaryCard and ArpScannerCard are now tappable, navigating to
  the devices list and status screen respectively
- Elevated "Devices" title to titleLarge; section subtitles promoted to
  bodyMedium/w600 while row content is demoted to bodySmall
- Renamed top-level row label to "Registered in the system"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:34:42 -04:00
rzuastiandClaude Sonnet 4.6 f113da9cd0 Refactor frontend: extract widgets and eliminate duplicated scanner state
- Convert ArpScannerCard to a self-managing StatefulWidget (owns its own 5s refresh + 1s tick timers), removing the duplicated state management that existed in both HomeScreen and StatusScreen
- Extract NotificationCard to home/notification_card.dart
- Extract DeviceSummaryCard (with its 1-min refresh timer) to widgets/device_summary_card.dart
- HomeScreen drops from 515 to 253 lines; StatusScreen from 82 to 19 lines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:23:27 -04:00
rzuastiandClaude Sonnet 4.6 251d8a479e Redesign Home screen with device summary and scanner status
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>
2026-05-28 19:10:08 -04:00
rzuastiandClaude Sonnet 4.6 dc8f47e66d Add Status screen with ARP scanner live status display
Shows ARP scanner state (running/waiting/error) with a colored indicator
and time that ticks locally every second, refreshing from the API every 5s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:16:04 -04:00
rzuastiandClaude Sonnet 4.6 020c7d2c49 Add ARP scanner status API endpoint and rename scanner module
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>
2026-05-28 17:57:26 -04:00
rzuastiandClaude Sonnet 4.6 97e4bb1000 Add About OOTT page with version, links, and legal notices
Implements the /about route with app description, version info,
clickable links to the source repo and AGPL-3.0 license, and
third-party copyright notices. Adds url_launcher for cross-platform
link handling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:29:55 -04:00
rzuastiandClaude Sonnet 4.6 93f40b892d Add NOTICE file for Apache-2.0 dependencies
Lists the four Apache-2.0-only backend dependencies (openssl,
rusqlite_migration, sync_wrapper, zopfli) with their copyright holders,
as required by the Apache License 2.0 for distributed works.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:07:14 -04:00
rzuastiandClaude Sonnet 4.6 60dbf56327 Add AGPL v3 license
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:00:26 -04:00
rzuastiandClaude Sonnet 4.6 e28eb349ba Align frontend device types with backend canonical list
Replaces the old frontend-only `router` type and adds the full set used
by the backend: network_appliance, home_security, home_appliance, watch,
pc, gaming_console. Adds an `apiName` getter to map camelCase enum values
to the snake_case strings the API expects. Updates the unknown device icon
to a question mark.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:44:34 -04:00
rzuastiandClaude Sonnet 4.6 23b8858259 Auto-populate device_type from vendor mapping on first device discovery
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>
2026-05-28 16:30:54 -04:00
rzuastiandClaude Sonnet 4.6 b4e226d716 Update TODO and regenerate vendor-device-type mapping with LLM classifications
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:17:16 -04:00
rzuastiandClaude Sonnet 4.6 55b5f9ad30 Add LLM classification to vendor update script and update dev shell
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>
2026-05-28 16:08:14 -04:00
rzuastiandClaude Sonnet 4.6 752b6f95fd Expand vendor keyword rules for broader device-type coverage
Adds high-precision keyword fallbacks to the classification rules:
- network_appliance: broadband, wifi, wlan, modem (standalone)
- server: storage, nas
- home_security: surveillance (standalone)
- tv: streaming (standalone)
- phone: handset, cellular

Classified vendors: 374 → 502 (+34%).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:50:14 -04:00
rzuastiandClaude Sonnet 4.6 50cc2a9d53 Add MAC vendor update script and vendor-to-device-type mapping
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>
2026-05-28 14:43:04 -04:00