102 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.8 4b4828be89 Docs: complete sample config, fix README config reference
- Add screenshots to README "What is OOTT?" section
- Document the "push" notification method and notifications.push.relay_url
- Fix misleading retention duration example (m = minutes, not months)
- Add web_server.api_key to the Nix service example
- Make sample_oott.toml a complete, functional baseline: every option
  present, mutually-exclusive notification provider sections commented out
- Trim completed items from TODO.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:14:19 -04:00
rzuastiandClaude Opus 4.8 8b2c8e7285 Move welcome message into first-run backend config dialog
Drop the standalone welcome card from the Settings screen and show the
greeting inside the non-dismissible first-run configuration dialog,
noting that OOTT cannot function without a backend installed in the
network.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 12:33:31 -04:00
rzuastiandClaude Opus 4.8 55192d5665 Keep backend config dialog actions on a single row
The AlertDialog actions were stacked onto two lines on narrow phones
(e.g. iOS) because the default OverflowBar wraps once the buttons'
combined width exceeds the dialog width. Lay them out in a single Row
wrapped in a FittedBox(scaleDown) so they stay on one line, scaling
down uniformly on too-narrow screens rather than wrapping or overflowing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 12:27:01 -04:00
rzuasti 53433c437e Updated todo 2026-06-12 10:46:10 -04:00
rzuasti eecbe0d85d Updated todo 2026-06-12 10:41:45 -04:00
rzuastiandClaude Opus 4.8 dce683e396 Add Catppuccin Latte, Dracula, Alucard, Nord and Tokyo Night themes
Add five new selectable themes alongside the existing Catppuccin Mocha
and Gruvbox Dark, each built through the shared buildAppTheme so they
stay visually consistent. Palettes are taken from each project's
official source; a few intermediate surface shades are interpolated
where the canonical palette omits them (noted in the theme files).

Register the new themes in main.dart and add their dropdown entries in
settings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:19:56 -04:00
rzuastiandClaude Opus 4.8 3d1f27b02e Enlarge OOTT wordmark in Android adaptive launcher icon
Scale the adaptive foreground content 145% so the dark wordmark reads
larger on the home screen while staying within the adaptive safe zone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:06:01 -04:00
rzuastiandClaude Opus 4.8 46d0778ebc Replace source/license links with website link on About screen
Remove the source-code link and AGPL license row, and point users to
the project website (https://oott-security.net) instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:00:15 -04:00
rzuastiandClaude Opus 4.8 d30130ed01 Restructure CLAUDE.md for brevity and clarity
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:55:32 -04:00
rzuastiandClaude Opus 4.8 587e097291 Add permanent device deletion and refine frontend UI/snackbars
Backend:
- Add db::devices::delete to erase a device and its events atomically
- Expose DELETE /api/devices/{mac}/permanently, wired to OpenAPI
- Cover the new db method and endpoint with tests

Frontend:
- Delete action for not-registered devices (detail screen + list row)
  and an opt-in "permanently delete" checkbox in the Forget dialog
- Navigate to the devices list after deleting from the detail screen
- Refine button emphasis to M3: single filled primary, error-colored
  text buttons for destructive actions, Test demoted to filled-tonal
- Flash the backend-config Test button red on a failed connection test
- Render snackbars through a top-level ScaffoldMessenger host so they
  show above dialogs; keep the built-in SnackBar (with an Overlay host)

Docs:
- CLAUDE.md: rustfmt edition 2024, don't revert formatter-only changes,
  prefer built-in Flutter components, follow existing patterns + M3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:49:11 -04:00
rzuasti 2541d0989c Updated todo 2026-06-10 17:35:00 -04:00
rzuasti 17f051013e Updated todo 2026-06-09 14:49:27 -04:00
rzuastiandClaude Opus 4.8 8c7e98ae5d Show foreground push notifications on iOS
iOS suppresses an incoming push banner while the app is foregrounded
unless the app opts in. _ensureForegroundDisplay now calls
setForegroundNotificationPresentationOptions on iOS so the OS presents
the push directly, avoiding a double notification / delegate clash with
flutter_local_notifications. Android keeps the onMessage local-notification
path, which it still requires.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:44:44 -04:00
rzuasti 4192626690 Updated release notes for 0.2.0 2026-06-09 10:47:49 -04:00
rzuastiandClaude Opus 4.8 122624baf1 Bump frontend and backend dependencies within constraints
Tier-1 dependency refresh (lockfiles only, no manifest/version-range
changes):

- frontend/pubspec.lock: dio, go_router, shared_preferences and others
  to their latest in-range versions.
- backend/Cargo.lock: tokio 1.49->1.52.3, clap 4.5->4.6, tower-http,
  serde_json and a batch of transitive crates.

All frontend (152) and backend (165) tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 10:30:07 -04:00
rzuastiandClaude Opus 4.8 67b94b89a1 Move backend config to a dialog; group settings into cards
Settings screen now splits responsibilities:
- Backend URL + API key move into a Test/Save popup dialog
  (backend_config_dialog.dart), reachable via a "Re-configure" action.
  Per M3 the Test button is left-aligned (neutral) with Cancel/Save
  grouped trailing. On first run the dialog auto-opens non-dismissible.
- The screen shows the connection read-only (URL plain, key masked with
  reveal) in a "Backend connection" card, and groups Theme + Push into an
  "App settings" card. Theme and push apply immediately, no Save button.
- Align all card titles to titleLarge across home/status/settings.

Adds a test seam (dioBuilderForTesting) so reconfigure keeps the mock Dio
in widget tests instead of issuing real requests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 18:15:10 -04:00
rzuastiandClaude Opus 4.8 1d85ec6f83 Gate push toggle on backend notification method
Add a GET /api/config endpoint exposing the front-end-facing backend
configuration (currently the notification delivery method, grouped under
a nested "notifications" object so the shape can grow). The settings
screen fetches it on init and only shows the per-device push toggle when
the backend method is "push" (and the platform supports push, which keeps
it off the browser).

Also fold in related push-notifications cleanups: fix the Android app
label ("frontend" -> "OOTT") so the notification permission dialog reads
correctly, remove the completed push_notifications.md plan, and update
TODO.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:26:53 -04:00
rzuastiandClaude Opus 4.8 bef1a2987d TODO: note Android push permission dialog shows the placeholder app label
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 15:01:08 -04:00
rzuastiandClaude Opus 4.8 d81eba3b37 Deploy push relay: dev-shell tooling, real relay URL, /health route
- flake.nix: add nodejs_22, firebase-tools, and google-cloud-sdk to the dev
  shell so the relay can be tested, built, deployed, and administered locally
- backend settings: point default_relay_url at the deployed relay
- rename the relay liveness route /healthz -> /health: Google Front End
  reserves /healthz and returns its own 404 before the request reaches Cloud
  Run, so the probe was unreachable (verified live; /v1/push and the FCM path
  work end-to-end)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 14:00:09 -04:00
rzuastiandClaude Opus 4.8 4f3fe10332 Implement push notifications Phase 1 (FCM + project relay)
Backend (Rust):
- push_tokens migration, model (PushToken/PushPlatform), and db layer
  (upsert/list/delete/delete_many)
- PUT/DELETE /api/push_tokens endpoints wired into the router + OpenAPI
- "push" notification method: relay sender (reqwest) that forwards only the
  sanitized title/body and prunes dead tokens, plus settings with a default
  relay_url
- 164 tests pass, clippy clean

Relay (push_relay/, TypeScript Firebase Cloud Function):
- POST /v1/push (firebase-admin sendEach + per-token status mapping),
  GET /healthz, payload validation, per-IP Firestore rate limiting
- Jest tests + README documenting the manual project-owned setup

Frontend (Flutter):
- oott_api_push.dart (register/unregister), push_service.dart behind a
  PushService abstraction, and a per-device push toggle in settings
- firebase_core/firebase_messaging/flutter_local_notifications deps
- 145 tests pass, dart analyze clean

Dev shell:
- add nodejs_22 to the Nix dev shell so the relay tests/build run locally

Remaining (manual, project-owned): create the Firebase project, deploy the
relay and set the real default_relay_url, add native Firebase config, and test
on real devices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:28:03 -04:00
rzuastiandClaude Opus 4.8 dedfe1c838 Fix ghosting in native screen transitions
Routed screens are transparent fragments rendered into the shell's single
Scaffold. A screen pushed on top (e.g. device detail) was therefore
see-through, leaving the screen beneath visible as it slid in, and the fade
page ignored secondaryAnimation so the covered screen sat frozen instead of
parallaxing out.

- Paint each routed page opaque (theme surface) so pushes cover cleanly.
- Drill into detail screens with a CupertinoPageTransition slide; the covered
  page parallaxes out via the same route animation, so both move in lockstep
  on the native iOS curve. Tabs keep their crossfade.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:29:57 -04:00
rzuastiandClaude Opus 4.8 4a19ab34e2 Crossfade between top-level tabs instead of iOS slide
Switching between the top-level destinations went through the default
CupertinoPage slide, which is meant for forward pushes. For a peer
tab-switch it slid the incoming screen in over the outgoing one without
animating the old screen away, leaving it visible in the background.

Give the top-level routes a CustomTransitionPage crossfade; keep the
device-detail route on the default slide since it is a genuine drill-in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:44:48 -04:00
rzuastiandClaude Opus 4.8 d4e00515fc Remove Notifications title from home page body
Match the structure of other pages by dropping the standalone title and
placing the filter selector and "mark all as read" action in one row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:36:05 -04:00
rzuasti 9099d0f6a7 Updated todo 2026-06-07 15:33:21 -04:00
rzuastiandClaude Opus 4.8 87af782985 Use the OOTT brand icon as the app launcher icon on all platforms
The app shipped Flutter's default icon on every platform. Add
flutter_launcher_icons (mirroring the existing flutter_native_splash
setup) with an OOTT wordmark on the brand orange (#fe8019) and
regenerate the native icon sets for iOS, Android, macOS, Windows and
web.

Android uses an adaptive icon (orange background + inset transparent
foreground) so the mark survives any launcher mask; the square
full-bleed source is used elsewhere where the platform applies its own
corner mask. Source images live under assets/icon/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:24:38 -04:00
rzuasti 032923cdae Updated todo 2026-06-07 12:40:48 -04:00
rzuasti af3faa2786 Updated todo 2026-06-07 12:40:31 -04:00
rzuasti 2b808a0147 Updated todo 2026-06-06 16:51:01 -04:00
rzuasti 3f575f816d Updated todo 2026-06-06 16:25:21 -04:00
rzuastiandClaude Opus 4.8 3ebb35933e Drop the status block from new-device notifications
New devices are never registered, so the status line only ever read
"Not registered" and added no information. Remove it from the single
new-device notification body (the consolidated summary never had one).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:22:38 -04:00
rzuastiandClaude Opus 4.8 f0d939f685 Add API Docs link to the wide navigation rail
Add an "API Docs" navigation entry that opens /api/docs in a new tab.
It is shown only in the wide-mode navigation rail (not the compact
bottom bar) and sits just before About.

Generalises the navigation destination model so an entry can be an
in-app route or an external link, and can be restricted to wide layouts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:20:29 -04:00
rzuastiandClaude Opus 4.8 cf7dd96415 Redirect bare "/" to the /web UI
The root URI had no content of its own and only showed a pointer
message; send visitors straight to the front-end instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:11:38 -04:00
rzuastiandClaude Opus 4.8 a51b9c06dc Display unknown values as a dash and centralise repeated literals
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>
2026-06-06 16:07:05 -04:00
rzuastiandClaude Opus 4.8 cf8979f63d Fix blank /web UI and /api route under the bundled server
The Flutter web bundle was built with the default base href of "/" but is
served under "/web/", so index.html loaded while every asset reference
resolved to the site root and 404'd, leaving a blank page. Build the bundle
with --base-href=/web/ so asset URLs match the mount point.

Also correct the root guidance text (the API explorer is at /api/docs, not
/api) and add an /api -> /api/docs redirect for convenience.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:25:07 -04:00
rzuastiandClaude Opus 4.8 5d080bb4c6 Simplify README storage section and add auth/access-control docs
Trim the storage considerations down to assumptions, per-scenario
estimates, and the levers to control DB size. Add an authentication
and access-control section noting OOTT has no built-in user management
and warning not to gate /api behind an external auth layer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:42:55 -04:00
rzuasti 393e545501 Updated todo 2026-06-06 09:39:15 -04:00
rzuasti c022e672d1 Updated todo 2026-06-05 21:58:52 -04:00
rzuastiandClaude Opus 4.8 6b044a6739 Count distinct devices (by MAC) in active scanner status
The ARP and SNMP scanners reported every online sighting, so a device
seen on multiple IPs or via duplicate ARP replies was counted more than
once. Fold the dedup into ActiveStatus::record_scan, which now takes the
device slice and reports the number of distinct MAC addresses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 21:19:06 -04:00
rzuastiandClaude Opus 4.8 844d7d189c Add "go to last page" and page count to notifications and devices lists
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>
2026-06-05 21:09:25 -04:00
rzuastiandClaude Opus 4.8 3cb104e364 Add sort-by-device-type to the devices list
Wide layout makes the leading device-type icon column header tappable to
sort; narrow layout gains a "Device Type" option in the sort sheet. The
backend already whitelisted device_type as a sort column.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:37:38 -04:00
rzuastiandClaude Opus 4.8 b3a4e4cb0b Collapse chart time-range pills to a dropdown on phones
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>
2026-06-05 19:29:38 -04:00
rzuasti 4bbaa6d3e1 Updated todo 2026-06-04 20:20:05 -04:00
rzuastiandClaude Opus 4.8 f847b60c21 Animate notification list inserts, removals and arrivals
Replace the notifications list's wholesale redraw with a SliverAnimatedList
driven by a GlobalKey, keeping `_items` in lockstep with the animated state.

- Background refreshes (poll, pull-to-refresh, resume, route pop, mark-all)
  reconcile against the fetched page: departed rows slide out, newly fetched
  rows slide in at the top with a theme-coloured arrival highlight, and
  surviving rows stay put (with in-place read-state recolouring under "All").
- Filter/page changes and the initial load reset the list (fresh key) so the
  new dataset appears instantly without per-row animation.
- Read/unread removals are owned by the list: buttons play a slide/fade exit,
  while swipes let Dismissible animate and then reconcile, avoiding double
  animation and the disposed-widget race.

Add the arrival highlight overlay to NotificationCard and cover the new
behaviour with widget tests (swipe-out, flash-in, external removal, in-place
"All" mark, filter reset).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:35:18 -04:00
rzuastiandClaude Opus 4.8 c949deedb6 Show a page-level progress bar while paginating lists
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>
2026-06-04 18:45:08 -04:00
rzuastiandClaude Opus 4.8 f64b4d3559 Collapse list filters to a dropdown on phones
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>
2026-06-04 18:22:01 -04:00
rzuastiandClaude Opus 4.8 0a899b7890 Shrink notification and device list page sizes on phones
On phone-width layouts (< Breakpoints.medium) the notification and device
lists now request fewer items per page so the list and its pagination bar
fit on screen together on common current phones. Notifications use 4 items
and devices 6 on phones; wider layouts keep 5 and 10 respectively. The
initial fetch is deferred to didChangeDependencies so the page size can read
the screen width from MediaQuery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:13:37 -04:00
rzuastiandClaude Opus 4.8 8b8fa5ce89 Add pull-to-refresh to the notifications list
The devices list already supported pull-to-refresh; mirror that on the
notifications list by wrapping its CustomScrollView in a RefreshIndicator
with AlwaysScrollableScrollPhysics. Keep the existing list visible during
a refresh (_isLoading = _items.isEmpty) instead of flashing the skeleton,
matching the devices list behaviour.

Add widget tests covering pull-to-refresh for both lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:02:07 -04:00
rzuasti 6c4f8c22bb Updated todo 2026-06-04 13:09:54 -04:00
rzuasti a597eb37f1 Add frontend TODO items 2026-06-04 10:16:31 -04:00
rzuastiandClaude Opus 4.8 aa5f65de2b Scroll to top of list when changing pages
The notifications and devices lists kept their scroll offset when paging,
so a new page would open partway down. Attach a ScrollController to each
CustomScrollView and animate back to the top whenever the page changes
via the pagination bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:10:13 -04:00