Extract inline widget closures into named StatelessWidgets (_DeviceCard,
_DeviceFilterSheet, _NotificationCard), replace magic ints and hardcoded
repeated widgets with enum-driven loops, fix the InputDecorator/DropdownButton
hack in device_actions, merge symmetric _markAsRead/_markAsNew into _setRead,
and remove dead _isLoading/_isSaving state in settings where operations are
synchronous.
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>
Displays a scatter chart timeline of when the device was seen online,
with a time range selector (Today → Last Year). Tooltips show event
date/time, type, and highlight any IP or vendor changes relative to
the current device data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tapping a notification row or selecting "View device" from the popup
menu now marks the notification as read (if unread) before navigating
to the device detail page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the retention.window option to the NixOS module and documents it
in the README alongside a new Storage considerations section covering
per-network-size estimates and guidance on tuning scan timings and the
retention window to meet storage requirements.
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>
Replace the Column+Divider wrapper with a Card widget and move the
dynamic background color from ListTile.tileColor to Card.color,
matching the pattern used in the device list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move status filter chips into AppBar bottom slot (left-aligned, sticky) on
both list screens. On the device list, relocate owner and type filters into
a modal bottom sheet behind a badged filter icon, and dismiss the sheet on
clear so fields always reflect the current filter state.
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>
Shows the green 'Registered' badge next to the IP address for registered
devices, matching the existing behaviour in the device detail screen.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mirrors the NotificationType pattern. Removes duplicated _deviceTypes
list and _deviceIcon() helper from both device screens, centralising
all type-to-icon logic in the model.
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>
Centralises all SnackBar calls behind UISnackbars.showError/showSuccess/
showWarning/showInfo, each themed via AppColorExtension. New warning and
info colour tokens added to both themes. Calling any method clears any
visible snackbar before showing the new one.
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>
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>
- Add linter commands to CLAUDE.md
- Add clippy to Nix dev shell
- Add TODO.md with project task list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Show "Just now" for notifications 2 minutes old or less
- Auto-refresh the list every minute, preserving the active filter
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>
Shows a done_all icon button in the AppBar when the New filter is active
and the list is non-empty, calling the notifications/mark_all_as_old endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a dropdown in the settings page to switch between available themes
(Catppuccin Mocha, Gruvbox Dark). The selected theme is applied immediately
on save and persisted across restarts via SharedPreferences.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Unread notifications (isNew=true) now show a secondaryContainer background,
bold title, and primary-colored icon per Material 3 guidelines. State changes
via mark-as-read/unread update the item's styling immediately in the All filter
view using mapItems with a copyWith, ensuring PagingStateBase's deep equality
check detects the change and triggers a rebuild.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Mark as unread" option to the notification popup menu (shown only
for already-read items) and a swipe-right gesture, mirroring the existing
"Mark as read" actions. Swipe/menu actions respect the active filter:
items are removed from the list only when filtered to New or Old, and
snap back when viewing All.
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>
Users can now mark a notification as read via a popup menu on each list
item or by swiping left. Both actions call the backend API, remove the
item from the list, and show a snackbar confirmation. Swiping an already
read notification shows an error snackbar and bounces the item back.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>