Commit Graph
268 Commits
Author SHA1 Message Date
rzuastiandClaude Opus 4.8 be2a8fce58 Make [notifications.pushover] optional for non-pushover methods
The pushover config section is now only required when notifications.method
is "pushover". Validation at startup rejects the missing-section case so a
misconfiguration fails fast instead of erroring on every notification.

Updates the sample TOML, README and nix module for consistency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 17:45:02 -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 51dc5495ec Fold scroll-to-top into _fetchPage
Replace the _goToPage wrapper with an optional scrollToTop flag on
_fetchPage, so there is a single fetch entry point. The pagination bar
passes scrollToTop: true; all other callers keep the current behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:12:36 -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
rzuastiandClaude Opus 4.8 aa5a29271b Keep notification cards collapsed when a sibling is removed
SliverList recycles State objects by index, so when an expanded
notification was marked as read and dropped from the filtered list, the
next notification inherited the expanded state. Give each card a
ValueKey(id) so its expand/collapse state is matched by notification,
not by list position.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:02:57 -04:00
rzuastiandClaude Opus 4.8 2a6d480428 Collapse notification cards on tap anywhere
Tapping a notification's body already toggled expand/collapse, but the
action area below it wasn't tappable. Wrap the whole card in an InkWell
so tapping anywhere (body or action row) toggles, while the buttons
still handle their own taps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:57:20 -04:00
rzuastiandClaude Opus 4.8 fbd9ffd834 Add OOTT-branded native splash for Android and iOS
Replace the vanilla Flutter splash with the OOTT wordmark (Barlow
Condensed Bold) on the Gruvbox-dark background, matching the in-app
AppBar badge. Generated via flutter_native_splash, covering legacy
Android, the Android 12+ SplashScreen API, and iOS (light + dark).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:50:45 -04:00
rzuastiandClaude Opus 4.8 c7d948abac Make Gruvbox Dark the default theme
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:38:23 -04:00
rzuastiandClaude Opus 4.8 2817fc7cab Fix Android emulator app launch
Three issues prevented `run_android_emulator.sh` from launching the app:

- gradlew's shebang was a hardcoded Nix-store bash path (copied verbatim
  from the nixpkgs Flutter SDK template) that breaks once that store path
  is garbage-collected, surfacing as a misleading "ProcessException: No
  such file or directory". The dev-shell now normalizes the gitignored
  android/gradlew shebang to the portable "#!/usr/bin/env sh" on entry.
- `flutter run -d android` never matched: -d resolves a device by id/name,
  not platform. Resolve the concrete id (e.g. emulator-5554) from
  `flutter devices --machine` instead.
- Install raced the boot ("device is still booting"); wait for
  sys.boot_completed=1 before launching.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:35:46 -04:00
rzuastiandClaude Opus 4.8 080222f121 Rename frontend run scripts and add Android emulator runner
Rename android-emulator.sh to run_android.sh and run.sh to run_web.sh,
add run_android_emulator.sh to launch the app on the emulator, and
update CLAUDE.md commands accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:05:44 -04:00
rzuasti 38946d3abe Make optional config fields fall back to defaults
Several settings were required at parse time even though they had a
sensible default, causing the backend to panic on startup when omitted:

- notifications.notify_when_not_seen_for now defaults to "1w"
- the whole [networking] section is now optional (no mandatory fields)
- scanner duration/timeout fields (ARP, mDNS, SSDP, SNMP) now fall back
  to their defaults when the section is present but the field is omitted.
  serde only applies a field default when marked #[serde(default)], so
  the per-field attributes were added and the Default impls now share the
  same default functions as the single source of truth.

Updates the sample TOML and README accordingly and adds tests covering
each defaulting case.
2026-06-04 09:02:36 -04:00
rzuastiandClaude Opus 4.8 9367c359d7 Polish frontend: design tokens, theming, AppBar titles, empty/loading states
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>
2026-06-04 08:39:37 -04:00
rzuasti 5f6f5ad679 Added push notifications plan 2026-06-04 07:22:39 -04:00
rzuasti 8bd2b16ea7 Updated CLAUDE.md 2026-06-03 19:43:56 -04:00
rzuastiandClaude Opus 4.8 98b5fa267f Add automated test suite for the Flutter frontend
Introduce a headless `flutter test` suite (83 tests) covering models,
utilities, every API endpoint, and the five screens, with shared helpers
and fixtures so new tests stay terse.

API endpoint methods are statically-dispatched extensions on the
BackendAPI singleton, so they cannot be mocked via `implements`. Mock at
the Dio HTTP-adapter layer (http_mock_adapter) instead, enabled by two
small @visibleForTesting seams: BackendAPI.dioForTesting swaps the
singleton's Dio, and BackendReachability.forceOnlineForTesting() forces a
deterministic online state so polling widgets load.

Add frontend/run_tests.sh and document it in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 19:41:52 -04:00
rzuastiandClaude Opus 4.8 e6813a78fb Enable emulated hardware keyboard for the dev Android emulator
The google_apis android-36 image always exposes a phantom AT hardware
keyboard that cannot be removed via hw.keyboard=no, the lid switch, or
any emulator flag. With hw.keyboard=no the host keyboard is not forwarded
into the guest, and Gboard hides the on-screen keyboard for non-password
fields while it detects that phantom keyboard, leaving plain text fields
(e.g. Settings > Base URL) untypable.

Enforce hw.keyboard=yes in the AVD's config.ini on every dev-shell entry
so the host keyboard is forwarded to all fields. Applied idempotently so
it also repairs a pre-existing AVD; the changed hardware config makes the
emulator cold-boot once for the setting to take effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 21:31:38 -04:00
rzuastiandClaude Opus 4.8 4924146075 Pin Android SDK components and aapt2 override for Flutter builds
The Android SDK lives in the read-only Nix store, so the Gradle build
cannot auto-install missing components. Pin everything the Flutter
Android build needs in the dev shell's composeAndroidPackages:
platforms 33-36 (app targets 36; plugins pin 34 and 35), build-tools
35.0.0 (AGP 8.11.1), NDK 28.2.13676358 (flutter.ndkVersion), and
cmake 3.22.1 (plugin native builds).

Also redirect the Android Gradle Plugin to the Nix-patched aapt2 from
the SDK via android.aapt2FromMavenOverride, written to the user-global
~/.gradle/gradle.properties on shell entry (the project's tracked
gradle.properties must not contain a machine-specific store path).
AGP's Maven-downloaded aapt2 cannot run on NixOS.

Ignore the generated frontend/android/build/ directory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 20:49:28 -04:00
rzuastiandClaude Opus 4.8 ada989c11b Add reproducible Android emulator to the dev shell
Pin the Android SDK composition (platform 36 + google_apis x86_64 system
image + emulator) via composeAndroidPackages so the emulator image is
always available and reproducible across machines. Auto-create the
"oott_api36" AVD idempotently on dev-shell entry, and add
frontend/android-emulator.sh to launch it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 19:23:33 -04:00
rzuastiandClaude Opus 4.8 c3acd29e97 Rework storage considerations for default multi-scanner config
Recompute the storage estimates around the deduplication window as the
governing cap across all four default-enabled scanners (ARP, mDNS, SSDP,
DHCP), using the documented defaults rather than the old single-ARP
720-scans/day worst case. Present typical (not worst-case) figures, add a
per-scanner event breakdown, rework the tuning levers around retention and
disabling scanners, and note that the SNMP scanner is disabled by default
and excluded from the figures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:54:43 -04:00
rzuastiandClaude Opus 4.8 bee492b703 Default web_server.ip_address and port
Fall back to "0.0.0.0" and 3000 when these fields are omitted from the
[web_server] section (api_key remains required). Update the sample TOML
and README to reflect the new defaults; the Nix module already used them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:33:35 -04:00
rzuastiandClaude Opus 4.8 0393378d24 Default log.level to "warn"
Make the [log] section and its level field optional, falling back to
"warn" when omitted. Update the sample TOML, Nix module and README to
reflect the new default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:05:59 -04:00
rzuastiandClaude Opus 4.8 920279e9b3 Document actual config defaults in the options table
Replace the README config table's "Sample value" column with
"Default value" reflecting the real defaults from settings.rs:
mark options with no built-in default as Required and list the
code defaults for the scanners, retention and deduplication.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 17:50:52 -04:00
rzuastiandClaude Opus 4.8 5872fcf8db Restructure README into a getting-started-first layout
Reorganize into What is OOTT?, Getting started (Docker + mobile apps),
Deeper dive (Nix flake, config options, HTTPS), and Things to keep in
mind (storage, privileges/ports). Inline a minimal oott.toml and the
compose file in the Docker quickstart, and document the missing
web_server.* options.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 17:37:10 -04:00
rzuastiandClaude Opus 4.8 8df21e41b1 Add release.sh to automate steps 1-3 of the release process
Adds a repo-root release.sh that prompts for the version, bumps it in
Cargo.toml/pubspec.yaml and the About release date, refreshes lockfiles,
runs tests, commits/pushes, tags, creates the GitHub release, and builds
the Docker image, pausing for confirmation between sections.

Release notes live in RELEASE_NOTES.md (newest first, one section per
version); the script extracts the section for the version being cut.
Seeds the file with the v0.1.0 notes and adds gh to the Nix dev shell.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 17:22:18 -04:00
rzuastiandClaude Opus 4.8 49544f0a20 Make the status screen scrollable to avoid bottom overflow
The status screen was a non-scrolling Column, so it overflowed
whenever the offline banner consumed vertical space or the viewport
was too short to fit all scanner cards. Wrap it in a
SingleChildScrollView, matching the other screens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:58:25 -04:00
rzuastiandClaude Opus 4.8 4a1fe60ec3 Derive the version from the Cargo/pubspec manifests
The version string was duplicated across six places. Collapse it to two
ecosystem sources of truth and derive the rest:

- backend/src/web_server.rs: omit the OpenAPI info.version so utoipa fills
  it from CARGO_PKG_VERSION (backend/Cargo.toml); add a test pinning this.
- nix/package.nix: read the version from backend/Cargo.toml via fromTOML.
- nix/frontend.nix: read the version from frontend/pubspec.yaml by splitting
  into lines (a whole-file regex triggers catastrophic backtracking in Nix's
  regex engine).
- frontend/lib/about/about.dart: read the version at runtime via
  package_info_plus instead of a hardcoded constant.

Also drop frontend/pubspec.lock.json: it is unreferenced (Nix's
autoPubspecLock generates its own JSON from pubspec.lock) and was going stale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:49:52 -04:00
rzuastiandClaude Opus 4.8 dd089a9cbc Deduplicate device_events within a time window
When the same scanner sees the same device (same MAC and IPv4) again within
a configurable window (default 1 minute), only one device_events row is
recorded, keeping the events table from filling with near-identical rows.
Device last_seen updates and notifications are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 16:11:27 -04:00
rzuastiandClaude Opus 4.8 c9e79e028f Split oott_api.dart into per-domain modules
Break the 372-line frontend API client into focused files under
lib/utils/api/: error mapping (api_error.dart), Dio setup behind a
buildDio factory (dio_config.dart), and device/scanner/notification
endpoints as extensions in part files. Collapse the five near-identical
scanner-status methods via a generic _getModel helper and de-duplicate
the pagination logic via a shared _paginate helper.

The BackendAPI singleton and dioErrorToUserMessage remain importable
from oott_api.dart unchanged, so no call sites are affected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 10:24:37 -04:00
rzuastiandClaude Opus 4.8 5f7a1287a1 Factor shared scanner logic into scanners/common
The five scanners (ARP, SNMP, mDNS, SSDP, DHCP) duplicated their
persist-and-notify pipeline, device enrichment, and status state
machines across same-family files. Extract the shared logic so a change
lands in one place instead of three to five.

- scanners/common/pipeline.rs: single record_sighting() persist+notify
  path, replacing the per-scanner match blocks. ARP/SNMP now use the
  same merge rules as the passive scanners (keep a stored hostname,
  never overwrite a known IP with an empty one).
- scanners/common/enrichment.rs: build_device() for vendor/device-type
  lookup with the privacy-MAC service fallback.
- scanners/common/{active,passive}_status.rs: the two status state
  machines plus their tests, written once. Each scanner status.rs is now
  a thin wrapper over its own static.
- utils/network::format_mac(): replaces three identical copies.
- web_server/scanner_status.rs: Active/Passive response types and two
  handler helpers, replacing five near-identical structs+handlers. JSON
  field names are unchanged so the frontend is unaffected; only OpenAPI
  schema names change.

27 files changed, ~900 lines net removed. Build, clippy and all 113
tests (4 new) pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 10:14:50 -04:00
rzuastiandClaude Opus 4.8 72dbbe5e9a Count passive scanner devices seen in the last hour
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>
2026-06-02 08:57:01 -04:00
rzuastiandClaude Opus 4.8 d15411a889 Report devices seen on last scan in ARP/SNMP status
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>
2026-06-02 08:45:45 -04:00
rzuastiandClaude Opus 4.8 38158a4ffc Give the ARP scanner code defaults and make its config section optional
Add an ArpScanner Default impl (30m/1m/10m, enabled) and mark the
arp_scanner field with serde default, so the [arp_scanner] section can
now be omitted entirely and fall back to code defaults — matching the
pattern used by the SNMP scanner. Previously these three durations were
mandatory and the backend would not start without them.

Reconcile the documentation to the canonical 30m/1m/10m: fix the README
NixOS example (was 15m/20m/30m) and options table (was 15m), and note
that the section is optional in both the README and sample TOML. The
NixOS module and sample TOML already used these values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 08:31:17 -04:00
rzuastiandClaude Opus 4.8 b283699b3c Set SNMP scanner default timings to 10m/5s
A typical router keeps active devices' ARP entries fresh continuously,
so a 10-minute poll interval stays well within common ARP cache TTLs
while keeping device-event churn modest (each poll records a DeviceSeen
event per device). A 5s per-request timeout adds margin for a busy
agent or large ARP table at no cost on the happy path.

Updates the default in settings.rs and the sample TOML, README and
NixOS module. TODO timing-review item narrowed to the ARP scanner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 08:23:20 -04:00
rzuastiandClaude Opus 4.8 2987f66363 Add SNMP scanner that polls a gateway's ARP table
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>
2026-06-02 08:03:20 -04:00
rzuastiandClaude Opus 4.8 20c51faaea Build front-end via Nix and harden Docker/Nix image generation
- Build the Flutter web app at release time (nix/frontend.nix) and bundle
  it next to the binary at $out/share/oott/web; resolve it at runtime
  relative to the executable. Remove the prebuilt backend/web from git.
- Add web_server.{ip_address,port,api_key} options to the NixOS module so
  the generated config deserializes (was missing, causing a startup panic).
- Docker image: set SSL_CERT_FILE for outbound TLS, drop the heavy
  nixos/nix base image, trim contents to [oott cacert], and ensure /tmp
  exists.
- Remove the unused "nix" flake input and commit flake.lock.
- Provide Swagger UI to utoipa-swagger-ui offline via a pinned fetchurl so
  the package builds in the Nix sandbox; skip the redundant check phase
  (tests run via backend/run_tests.sh).
- sample_oott.toml: set database.path to /db/oott.db for the Docker image.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:43:08 -04:00
rzuastiandClaude Opus 4.8 a90bcc360d Document backend connection options and allow iOS local networking
Add NSAllowsLocalNetworking ATS exception so the iOS app can reach a
backend over HTTP on the local network, and document the supported
connection paths (direct private-IP HTTP vs. reverse proxy with HTTPS)
in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 19:38:43 -04:00
rzuastiandClaude Opus 4.8 abeba88fec Change scanner status label "Not yet started" to "Not started"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 19:25:26 -04:00
rzuastiandClaude Opus 4.8 08ee4fc0ea Add per-scanner enable/disable configuration
Each scanner (ARP, mDNS, SSDP/UPnP, DHCP) can now be turned off via an
`enabled` flag in its config section, defaulting to true so existing
deployments are unchanged. A disabled scanner's entry function returns
early and never starts.

Documented in the README options table (also fixing the stale `timings.*`
key names to the actual `arp_scanner.*` keys), and added to the TOML
samples and the NixOS module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 19:00:56 -04:00
rzuastiandClaude Opus 4.8 4e975598f9 Add DHCP scanner to frontend status displays
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>
2026-06-01 18:45:38 -04:00
rzuastiandGitHub 91eb0f17ba Merge pull request #1 from rzuasti/add-dhcp-scanner
Add dhcp scanner
2026-06-01 18:35:34 -04:00
rzuasti 7ed47bc005 Updated todo 2026-06-01 18:34:47 -04:00
rzuastiandClaude Opus 4.8 189b79233b Add passive DHCP-snooping scanner
Listen for DHCP DISCOVER/REQUEST broadcasts on UDP 67 to catch devices as
early as possible — a device must request an address before doing almost
anything else, often before it has an IP.

Follows the mDNS/SSDP scanner pattern (finder/scanner/status modules) and
feeds the shared devices/events/notifications pipeline. The client MAC is
taken directly from the packet's chaddr, so no ARP probe is needed; a
DISCOVER with no assigned IP reuses any previously recorded address rather
than clobbering it. Exposes GET /api/dhcp_scanner/status, wired into the
OpenAPI generation, and adds a Dhcp variant to DeviceEventScanner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:34:02 -04:00
rzuastiandClaude Opus 4.8 440959b10d Add SSDP/UPnP scanner to frontend status displays
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>
2026-06-01 18:13:33 -04:00
rzuastiandClaude Opus 4.8 e8159f2b40 Document scanner network ports in README
Add a Network ports section noting that the mDNS (UDP 5353) and SSDP
(UDP 1900) ports are fixed by their protocols and must be available on
the host, alongside the configurable web server port.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 18:02:07 -04:00
rzuastiandClaude Opus 4.8 3f60eec311 Add SSDP scanner config to sample TOML and Nix module
Expose the ssdp_scanner.probe_timeout option (default 2s, mirroring the
mDNS scanner) in the sample config and the NixOS service module.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 17:58:13 -04:00
rzuastiandClaude Opus 4.8 936e9ffe79 Add SSDP/UPnP scanner
Introduce a new SSDP/UPnP network scanner alongside the existing ARP and
mDNS scanners:

- New scanner module under scanners/ssdp (finder, scanner, status)
- Wire the scanner into the main scan loop
- Add Ssdp variant to DeviceEventScanner
- Add SsdpScanner settings with a configurable probe timeout
- Expose /api/ssdp_scanner/status and wire it into OpenAPI generation
- Add a lint.sh helper and point CLAUDE.md at it

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 17:55:33 -04:00
rzuasti bf2f18b075 Updated todo 2026-06-01 16:18:57 -04:00
rzuastiandClaude Sonnet 4.6 29a5b7cf3e Expand notification cards in-place instead of navigating on tap
Tapping a notification card now toggles an expanded view showing the
full body text and M3 action buttons (Mark as read/unread, View device),
replacing the former popup menu and direct navigation on tap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 16:18:40 -04:00
rzuastiandClaude Opus 4.7 3122ab1f0f Redesign triggered notification titles and bodies
Make each Pushover alert triageable from a lock-screen preview: titles now
carry the device's identity (hostname, vendor, or MAC suffix) and a concise
verb, and bodies are sectioned (Device / Status / Activity or Changes) with
the registration line and device type so a user can decide whether to act
without opening the app. Adds a security hint on new-unregistered devices
and on vendor-change-on-same-MAC (a MAC-spoofing tell); IP-only changes
stay quiet since DHCP rotation is normal. Rendering moves into pure helpers
covered by unit tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 08:40:32 -04:00