Closes #1. The Flask event-timeline was dead: `broadcast_sse_event` existed in `vigilar/web/blueprints/events.py` but had zero call sites. Clients subscribed to `/events/stream`, received the initial "connected" message, and then only keepalives — a page refresh was required to see new events. (Web Push via VAPID was independent and already worked.) The root cause was a process-boundary gap: the events subsystem runs in its own OS process and emits to MQTT, while the Flask app runs in a separate process with no MQTT client of its own. This change adds a thin bridge: - EventProcessor._handle_event now publishes a classified summary (id, ts, type, severity, source_id, payload) to a new topic `Topics.EVENTS_PUBLISHED = "vigilar/events/published"` right after `insert_event()`. Classification logic stays in one place. - A new module `vigilar/web/sse_bridge.py` provides `forward_event` (MQTT handler) and `start_sse_bridge(cfg)` (creates a MessageBus, subscribes forward_event to EVENTS_PUBLISHED, connects, returns the bus). - `vigilar/main.py:_run_web` starts the bridge after `create_app(cfg)` and disconnects it on shutdown. Bridge failure is logged but does not kill the web process — the UI still works without live updates. - `create_app` is deliberately NOT changed. Keeping the bridge out of the app factory means no existing test triggers a real MQTT connection, and the bridge stays a production-only concern wired by the supervisor. Tests (all added with TDD, RED verified before GREEN): - tests/unit/test_events.py::TestEventsPublishedBroadcast — asserts `_handle_event` publishes the classified payload for a motion event and does NOT publish for unclassified topics (heartbeats). - tests/unit/test_sse_bridge.py — asserts `forward_event` reaches SSE subscribers, and `start_sse_bridge` wires the handler to `Topics.EVENTS_PUBLISHED` on a connected bus (fake bus, no real MQTT in tests). Also refreshes the docs that previously flagged the dead SSE as a known limitation (operator guide, web architecture doc). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
44 lines
3.5 KiB
Markdown
44 lines
3.5 KiB
Markdown
# web
|
|
|
|
## Purpose
|
|
|
|
Flask application that exposes everything to humans: camera grid and single-camera views, event timeline, recordings browser, sensor dashboard, system/arm controls, kiosk mode, and the pets / visitors / wildlife journals. The web process runs as one `SubsystemProcess` supervised by `vigilar/main.py`, reads state directly from SQLite, and publishes a handful of config/control MQTT messages on behalf of the user.
|
|
|
|
## Key files
|
|
|
|
- `vigilar/web/app.py` — `create_app(cfg)` factory (note: `vigilar/web/__init__.py` is empty; the factory lives here)
|
|
- `vigilar/web/blueprints/cameras.py` — HLS streams, snapshots, per-camera view
|
|
- `vigilar/web/blueprints/events.py` — event log list, acknowledgement, and `text/event-stream` SSE endpoint for live updates
|
|
- `vigilar/web/blueprints/recordings.py` — browse, download, delete recordings; serves decrypted `.vge` content
|
|
- `vigilar/web/blueprints/sensors.py` — sensor status and history
|
|
- `vigilar/web/blueprints/system.py` — health, arm/disarm, UPS, admin settings, Web Push subscription registration, PIN management; publishes `vigilar/camera/{id}/config` for threshold updates
|
|
- `vigilar/web/blueprints/kiosk.py` — fullscreen 2x2 ambient grid
|
|
- `vigilar/web/blueprints/pets.py` — pet registration, sightings, labeling, training-image upload
|
|
- `vigilar/web/blueprints/visitors.py` — face profiles, visits, labeling, privacy controls
|
|
- `vigilar/web/blueprints/wildlife.py` — wildlife sightings journal, stats, CSV export
|
|
|
|
## MQTT topics
|
|
|
|
**Subscribes:** none. The web process does not subscribe to the bus at import time; the SSE endpoint accumulates messages in an in-process queue but nothing wires the MQTT bus into that queue today (see Notes).
|
|
**Publishes:**
|
|
- `vigilar/camera/{camera_id}/config` — runtime camera config updates (sensitivity / motion thresholds) from the system settings page
|
|
|
|
## Database tables
|
|
|
|
All access is read-mostly via `vigilar.storage.queries`. Touches essentially every table: `cameras`, `events`, `recordings`, `sensors`/`sensor_states`, `system_events`, `arm_state_log`, `alert_log`, `push_subscriptions`, `pets`/`pet_sightings`/`pet_training_images`/`pet_rules`, `face_profiles`/`face_embeddings`/`visits`, `wildlife_sightings`, `timelapse_schedules`.
|
|
|
|
## Depends on
|
|
|
|
- `storage` — engine passed via `app.config["DB_ENGINE"]` / `VIGILAR_CONFIG`
|
|
- `camera` — reads HLS segments from `cfg.system.hls_dir` and decrypts recordings via `vigilar.storage.encryption`
|
|
- `alerts` — uses `vigilar.alerts.pin.hash_pin`/`verify_pin` and manages `push_subscriptions`
|
|
- `config_writer` — persists camera/alert config changes back to TOML
|
|
|
|
## Consumed by
|
|
|
|
- Browsers and phones — HTML dashboard, PWA with Web Push (VAPID), HLS grid via `hls.js`, MJPEG fallback for single-camera low-latency view, SSE for the live event timeline
|
|
|
|
## Notes
|
|
|
|
Templates use Jinja2 with a Bootstrap 5 dark theme; the camera grid uses `hls.js` for multi-camera HLS playback with an MJPEG fallback on the single-camera page. The app is a PWA (service worker + manifest) with VAPID Web Push for mobile notifications. The event-timeline SSE endpoint lives around line 93 of `blueprints/events.py` and holds a per-client `queue.Queue` fed by a module-level `broadcast_sse_event` function. A dedicated MQTT → SSE bridge (`vigilar/web/sse_bridge.py`) runs inside the web process, subscribes to `Topics.EVENTS_PUBLISHED`, and calls `broadcast_sse_event` for every classified event emitted by the events subsystem — so the in-browser event timeline updates live without a page refresh.
|