390 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.8 96b328ae87 Deduplicate SNMP ARP rows per MAC to stop IP flap
A router's neighbour cache can hold the same MAC at several IPs at once
(e.g. a device that changed address before the old entry aged out).
Walking ipNetToMediaPhysAddress then yields multiple rows for one MAC,
and persisting each in turn made the stored IP flap every scan, emitting
a DeviceChanged notification each cycle.

Collapse the parsed rows to one IP per MAC: keep the IP OOTT already has
on record when it is among those found (stability for a known device),
otherwise fall back to the numerically lowest IP (deterministic, lets a
new or genuinely-moved device converge). Add debug logging that reports
the MAC, all IPs seen, the IP kept, and why.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 08:22:09 -04:00
rzuasti 2a40efaf9e Release v0.2.4 v0.2.4 2026-06-22 12:00:17 -04:00
rzuastiandClaude Opus 4.8 7ea5867ec9 Add v0.2.4 release notes
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:59:04 -04:00
rzuastiandClaude Opus 4.8 ecc9a34504 Stack backend config dialog actions on narrow screens
The Test/Cancel/Save row was scaled down uniformly by a FittedBox on
narrow phones, making the buttons tiny. Lay them out at full size in a
Row on wide screens and stack them vertically (Test, Save, Cancel) on
narrow ones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:56:10 -04:00
rzuasti a052c67eb6 Refs #7 2026-06-22 11:34:19 -04:00
rzuasti d327f37fff Catch SIGTERM/SIGINT for graceful shutdown
Kubernetes and `docker stop` send SIGTERM and then SIGKILL once the grace
period expires; previously oott ignored it and was always force-killed.

Add a shutdown module that watches for SIGTERM (and SIGINT/Ctrl-C) and
cancels a CancellationToken shared by every long-running task. Each scanner,
the retention cleaner, and the notification delivery loop now select on the
token and stop at their next safe point (abandoning in-flight probes and
skipping inter-scan sleeps), the web server drains in-flight requests via
axum's with_graceful_shutdown, and main checkpoints the WAL via db::close()
once all tasks have stopped.

No internal deadline is added: the orchestrator's grace period already bounds
shutdown, and tasks stop on iteration boundaries so there is no half-written
DB state.

Refs #7
2026-06-22 11:33:08 -04:00
rzuasti c7ea2a3a98 Give every API operation a unique operationId and surface scanner errors
Scanner status handlers all derived operationId "status" from their fn
name, so Swagger UI's "Try it out" executed the first one (ARP) — the
DHCP doc hit /api/arp_scanner/status. Same collision for read/list/
register/unregister. Each path now sets an explicit unique operation_id.

main's tokio::join!(...).0 kept only the ARP result and silently dropped
the other tasks' errors, so a DHCP scanner that failed to bind port 67
just showed "off" with no log. Each task is now wrapped to log its error.

Fixes #5
2026-06-22 11:12:47 -04:00
rzuastiandClaude Opus 4.8 3621973201 Add OOTT icon SVGs for dashboardicons submission
True-vector recreation of the app icon wordmark (measured from the
source PNG, not traced). Includes base, light, and dark variants
with transparent backgrounds, per dashboardicons.com guidelines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:49:39 -04:00
rzuastiandClaude Opus 4.8 347e16346d Document skopeo --insecure-policy for the Fly push on NixOS
NixOS ships no default containers policy.json, so skopeo copy aborts with a
trust-policy error. Add --insecure-policy to the documented push command and
gitignore the result-fly build symlink.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:35:58 -04:00
rzuasti 31791fe0f2 Adjusted fly.toml 2026-06-18 09:16:12 -04:00
rzuastiandClaude Opus 4.8 f0395b317e Seed the Fly test server with demo data on deploy
Adds deploy/fly/seed.sql: a pruning, idempotent demo dataset (devices,
notifications, device events) spanning every workflow state for App Store
reviewers, with timestamps relative to now so it always looks fresh. The
flyImage entrypoint applies it on each boot when OOTT_SEED is set: it starts
the backend so migrations create the schema, waits for the tables, then prunes
and reloads. push_tokens is preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:12:23 -04:00
rzuastiandClaude Opus 4.8 386e459c12 Add Fly.io deployment for the App Store review test server
Adds a flyImage Nix output (backend + bundled web UI with an env-driven
startup config wrapper), a fly.toml with a persistent volume, and a deploy
README. Scanners are disabled and the API key comes from a Fly secret so
nothing sensitive is baked into the image. Also adds flyctl + skopeo to the
dev shell for daemon-less build/push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 09:00:07 -04:00
rzuasti ab8cdcc275 Removed claude from flake 2026-06-18 08:06:13 -04:00
rzuasti f2a1fbcf97 Merge branch 'main' of github.com:rzuasti/oott 2026-06-18 08:00:23 -04:00
rzuasti 15cb0c88a9 Adjusted flake to export DEV_SHELL outside the fish command and removed fish as last exec to support tmux 2026-06-18 08:00:15 -04:00
rzuasti ff5329bdab Adjusted screenshots in readme 2026-06-17 17:16:13 -04:00
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
rzuasti dfa4e541c9 Added screenshots 2026-06-17 16:59:54 -04:00
rzuastiandClaude Opus 4.8 2fc7f36a46 Add Play Store listing graphics
512x512 app icon (downscaled from the icon master) and a 1024x500
feature graphic (logo on the Catppuccin Mocha base background), generated
for the Google Play store listing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:58:56 -04:00
rzuastiandClaude Opus 4.8 c43c8f27a0 Add cupertino_icons dependency
navigation.dart imports package:flutter/cupertino.dart for
CupertinoPageTransition, which made the asset bundler warn that the
CupertinoIcons font was expected but missing. Declaring cupertino_icons
provides the font and clears the warning; it tree-shakes away in release
builds since no Cupertino icons are actually rendered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:46:10 -04:00
rzuastiandClaude Opus 4.8 11a964bd37 Add Android release signing config for Play Store
Release builds were signed with the debug key, which Play rejects. Load
an upload keystore from android/key.properties (gitignored) and use it
for the release signing config, falling back to the debug key when the
file is absent so `flutter run --release` still works without a keystore.

Add key.properties.example documenting the keytool setup and a
build_android_release.sh helper that builds the signed AAB and refuses
to run when key.properties is missing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:42:40 -04:00
rzuastiandClaude Opus 4.8 468bf7d777 Make iOS app iPhone-only to drop iPad screenshot requirement
Set TARGETED_DEVICE_FAMILY to "1" across all build configs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:14:15 -04:00
rzuasti a0241deefe Release v0.2.3 v0.2.3 2026-06-15 13:49:31 -04:00
rzuasti 8c615d6881 Add v0.2.3 release notes 2026-06-15 13:48:37 -04:00
rzuastiandClaude Opus 4.8 916b081225 Fix "device changed" notification showing stored devices as unregistered
record_sighting only carried the stored name and IP onto the reconciled
sighting, leaving is_registered, owner, and device_type at their bare
defaults. The change handed to the notification layer therefore rendered a
registered, typed device as "Not registered" with type "-". Carry those
fields over too, mirroring how db::devices::seen() preserves them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 13:46:30 -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
rzuasti c13c839f58 Release v0.2.2 v0.2.2 2026-06-12 10:31:04 -04:00
rzuasti 34764bb15c Updated release notes 2026-06-12 10:30:18 -04:00
rzuastiandClaude Opus 4.8 2c3dc30811 Wrap device detail action buttons on narrow screens
The action button row used a fixed Spacer that couldn't shrink, causing a
horizontal overflow on phone widths. Keep the spread-out Row on wide screens
and fall back to a Wrap on narrow ones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 10:27:44 -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
rzuastiandClaude Opus 4.8 ae13e228f9 Reflect one-time paid app model and reserve OOTT trademark
The mobile apps move to a one-time paid purchase on the App Store and
Google Play instead of a subscription. Document this in the README and
make clear the source stays fully open under the AGPL-3.0 for anyone who
prefers to build the apps themselves.

Add a Trademark section (and a matching note above the AGPL text in
LICENSE) reserving the "OOTT" name and logo, so third-party builds must
rebrand. The AGPL body itself is left verbatim and unmodified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 11:30:34 -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 7241a964ef Release v0.2.1 v0.2.1 2026-06-09 14:11:04 -04:00
rzuasti 6a27f81a4f Updated release notes 2026-06-09 14:10:15 -04:00
rzuastiandClaude Opus 4.8 475edcb2cf Report delivered device count from the test-notification endpoint
POST /api/notifications/test previously returned a blanket 200 even when there
were no registered devices, so a test that reached nobody looked like a success.
push::send now returns the number of devices the relay confirmed delivery to, and
the endpoint returns it as {"delivered": N}. Settings shows "sent to N device(s)"
on success and an explicit "No devices are registered..." warning when N is 0,
which is the case that previously masqueraded as success.

Backend, API and widget tests updated; clippy and dart analyze clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:06:56 -04:00
rzuastiandClaude Opus 4.8 0075072196 Re-register push token at launch; refactor push_service
After a cold restart the app considered push enabled (from prefs) but never
re-registered its FCM token, so a rotated token (or a backend that lost its token
store) left the backend delivering to nothing while the test button still
reported success. Re-register the current token at launch so the backend
converges to the live token on every start, and re-attach the foreground display
handler then too (it was previously only wired during enable()).

Also tidy push_service.dart: a single initPushOnLaunch() startup entry point so
main.dart needs no push internals, one owner for the persisted push-enabled flag
(pushEnabledOnThisDevice / setPushEnabledOnThisDevice) instead of a hard-coded key
in four places, private internal helpers, deduped platform checks, and section
grouping. enable() now reuses the shared token-registration path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 13:56:13 -04:00
rzuastiandClaude Opus 4.8 817ec5872c Add a "Send test notification" button to verify push delivery
Adds POST /api/notifications/test, which delivers a canned test push to every
registered device through the existing relay path (not persisted to the
notifications list), wired into the router and OpenAPI. In Settings, a "Send test
notification" button appears under the push toggle, only when push is enabled on
this device, so the full backend -> relay -> FCM -> APNs -> device path can be
verified on demand without waiting for a real device event.

Backend, API and widget tests added; clippy and dart analyze clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 12:42:20 -04:00
rzuastiandClaude Opus 4.8 c7c5b45ae8 Surface native iOS APNs registration outcome for diagnostics
Enabling push still fails on iOS with getAPNSToken() returning null, so capture
the APNs registration result natively to find out why. AppDelegate now overrides
didRegister/didFailToRegister, logs the outcome, and exposes it over a
oott/push_diagnostics method channel (super still calls through so Firebase
swizzling is unaffected).

apnsRegistrationStatus() reads that channel, and the settings push toggle now
shows Apple's actual rejection reason in the error message when enabling fails,
so it can be diagnosed without a Mac to read the device console.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 12:06:11 -04:00
rzuastiandClaude Opus 4.8 3d190e847e Initialize Firebase at startup so iOS APNs token is captured
Firebase was only initialized lazily when the user toggled push on. On iOS the
firebase_messaging plugin installs its APNs swizzling at app launch, but the
delivered device token can only be forwarded to FCM if a FirebaseApp is already
configured at that point. With lazy init there was none, so getAPNSToken() never
resolved and enabling push failed with "Could not enable push".

Add initFirebaseForPush() (mobile-only, idempotent) and call it from main() at
launch. Extract a shared pushSupportedOnThisPlatform getter so isSupported and
the initializer stay in sync; _ensureFirebase() now delegates to it as a safety
net.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 11:51:28 -04:00
rzuastiandClaude Opus 4.8 7d7f0197cd Wait for iOS APNs token before requesting FCM token
On iOS, FirebaseMessaging.getToken() throws apns-token-not-set when called
before Apple has asynchronously delivered the APNs token, which happens after
the permission prompt. This surfaced as "Failed to update push settings" with
the toggle stuck off. Poll getAPNSToken() with a short bounded wait first, and
return false (toggle stays off) rather than throwing if it never arrives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 11:19:46 -04:00
rzuasti 5fbee67a24 Release v0.2.0 v0.2.0 2026-06-09 10:50:02 -04:00