Backend: notifications now show a plain "-" for an absent name, vendor, or
device type (was empty string / "(unknown)" / "Unknown"), via a single
UNKNOWN_PLACEHOLDER constant.
Frontend:
- Empty/unknown values render as an em dash everywhere, centralised in a new
Placeholders.emptyValue constant (replaces inline '—' and '(unknown)').
- Route paths moved to a new Routes class, used by the router and every
navigation call site.
- Device event type modelled as a DeviceEventType enum mirroring the backend
(NewDevice/DeviceSeen) instead of bare string comparisons.
- Hardcoded EdgeInsets/SizedBox spacing replaced with existing Insets tokens.
Tests and formatting updated; all backend and frontend tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Collapse the five scanner status models into two shared shapes,
ActiveScannerStatus and PassiveScannerStatus, mirroring the backend's
active/passive vocabulary. Replace the five near-identical per-scanner
detail card files with a single scanner_status_cards.dart (two shared
resolvers plus a config list), and rebuild the combined home card to
iterate a list of scanners with two shape resolvers instead of five
copy-pasted resolve methods.
Extract two reusable mixins:
- PeriodicRebuild: the shared once-a-second "rebuild to refresh elapsed
text" timer used by the scanner cards and the stale indicator.
- PaginatedListState: the shared pagination state, page-size/page-count
getters, cancel-token-aware fetch orchestration, and disposal used by
the device and notification lists.
No behaviour change; ~900 lines removed. Tests and analyzer pass.
The list endpoints now return a total count alongside the page so the
front-end can show how many pages exist and offer a last-page jump.
Backend: add count(is_new) and count_devices(...) (sharing a WHERE-builder
with list_devices so page and count can't drift), wrap both list responses
in {items, total_count} structs, and register them with utoipa.
Front-end: parse the wrapper shape (dropping the fetch-one-extra trick),
add a Last-page button and a responsive "Page X of Y" / "X / Y" label to
the shared PaginationBar, and track the total in both lists. Notifications
re-sync the count on every fetch and decrement it locally on mark-read/
unread removals so the count stays accurate without a re-fetch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The device detail chart's time-range selector (Today / Last week / ...)
rendered as a SegmentedButton that overflowed on narrow phone layouts.
Reuse the responsive FilterSelector widget so the same control is used
for both the list filters and the chart: segmented pills on wide layouts,
a compact dropdown combo box on phones.
To keep the two controls consistent, FilterSelector now renders a
SegmentedButton (instead of ChoiceChips) on wide layouts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Changing pages in the notifications or devices list gave no cue that
the next page was loading. Render an indeterminate progress bar at the
shell level, pinned flush against the bottom of the page body (above
the nav bar on phones, the screen bottom on wide layouts), driven by a
shared paginationLoading notifier the lists set while fetching. The
pagination bar keeps disabling its buttons during the fetch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On narrow layouts the devices list's filter chips (Not registered /
Registered / All) competed for horizontal space with the Sort and Filter
icon buttons and overlapped. Introduce a reusable FilterSelector that
keeps the chips on wide layouts but collapses to a compact dropdown
button on phones, and use it for both the devices and notifications
lists for consistency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend usability and aesthetics pass across web and mobile:
- Add shared layout tokens (theme/dimens.dart: Insets + Breakpoints) and
replace magic-number spacing and per-file breakpoint consts.
- Route both themes through buildAppTheme (theme/theme_builder.dart): explicit
useMaterial3 and a shared branded textTheme (Barlow Condensed for
display/headline/title styles); logo now reads its style from the theme.
- Move screen titles into the shared AppBar (route-derived) and drop the
redundant in-body headers; upgrade Settings buttons to M3 FilledButton.
- Add reusable EmptyState and skeleton loaders; Devices empty state links to
scanner status, notifications get filter-aware messages.
- Add a first-run welcome intro and Save-disabled helper text in Settings.
- Make device Filter/Sort adaptive: bottom sheet on phones, dialog on wide.
- Collapse the device-list filter chips and Sort/Filter buttons into one row;
rename the home Scanners card title and align its style.
Tests: add EmptyState/skeleton widget tests and Settings first-run cases;
update tests for the FilledButton swap and relocated titles. 89 passing,
dart analyze clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the lifetime "devices seen since start" counter in the mDNS, SSDP
and DHCP scanners with a rolling count of distinct devices (deduped by
MAC) seen within the last hour.
Each scanner's status now tracks a MAC -> last-seen-time map; the snapshot
prunes entries older than 60 minutes and reports the remaining count. The
API field name (devices_seen) is unchanged, so only its meaning and the
frontend labels ("N devices in the last hour") are updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ARP and SNMP scanner status endpoints and front-end cards now expose
the number of devices found by the most recent successful scan, following
the existing mDNS device-count pattern. The count persists across the
running/waiting transitions, and for SNMP a failed poll keeps the last
good count rather than overwriting it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a sixth scanner that periodically queries an SNMP agent
(typically the router/firewall) for its ARP/neighbour cache via
SNMPv2c and feeds discovered devices into the shared devices, events
and notifications pipeline. Unlike the ARP scanner it generates no
traffic on the local segment and can surface devices across all
subnets the agent routes.
Scope is intentionally minimal: SNMPv2c only, a single target, and the
ipNetToMediaTable (ARP) only. SNMPv3 and switch MAC/forwarding-table
polling are left as follow-ups in TODO.md.
- backend: csnmp dependency; SnmpScanner config (opt-in, off unless a
[snmp_scanner] section is present); DeviceEventScanner::Snmp;
scanners/snmp/{finder,scanner,status}; main.rs wiring; status API
endpoint wired into OpenAPI
- frontend: SNMP scanner status model, card, API method, status screen
and summary card rows, and device-event label
- docs/config: sample TOML, README options, NixOS module option, and
setup notes for enabling SNMP on pfSense/OPNsense
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a DHCP scanner card to the Status screen and a DHCP row to the
consolidated Status card on the Home screen, mirroring the existing
mDNS/SSDP implementations. Also label DHCP-discovered device events as
"DHCP" instead of the raw "Dhcp".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surface the SSDP/UPnP scanner alongside ARP and mDNS: a dedicated card on
the Status screen and a row in the consolidated Status card on Home. Mirrors
the existing mDNS implementation and reuses the generic ScannerStatusCard and
PolledValue infrastructure. Also maps the 'Ssdp' device-event scanner to the
"SSDP/UPnP" label.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extract ScannerStatusCard<T> and PaginationBar so the ARP/mDNS cards and
the two paginated lists share one implementation each. Also fold the
four UISnackbars methods over a severity enum and fix Notification.toJson
serializing a method tear-off instead of the enum name.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a centralized BackendReachability singleton fed by a Dio
interceptor (connection-level failures only) and connectivity_plus.
PolledValue subscribes to it and pauses on offline / reloads
immediately on reconnect, replacing the previous per-card error
cascade. MainShell renders a global OfflineBanner with Retry and
Settings actions; cards downgrade error→stale when offline so the
banner is the only red element.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PolledValue gains pause()/resume() methods. ScannersStatusCard and
NotificationsList now implement RouteAware (didPushNext/didPopNext)
and WidgetsBindingObserver to stop API calls and tick timers when
the widget is not visible, resuming with an immediate fetch on return.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
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>
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>
- 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>
- 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>
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>
Lists devices with New/Registered/All filtering, pull-to-refresh,
device-type icons, and visual differentiation between new and registered
devices. Introduces a reusable StatusBadge widget for colour-coded labels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>