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>
3.5 KiB
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__.pyis empty; the factory lives here)vigilar/web/blueprints/cameras.py— HLS streams, snapshots, per-camera viewvigilar/web/blueprints/events.py— event log list, acknowledgement, andtext/event-streamSSE endpoint for live updatesvigilar/web/blueprints/recordings.py— browse, download, delete recordings; serves decrypted.vgecontentvigilar/web/blueprints/sensors.py— sensor status and historyvigilar/web/blueprints/system.py— health, arm/disarm, UPS, admin settings, Web Push subscription registration, PIN management; publishesvigilar/camera/{id}/configfor threshold updatesvigilar/web/blueprints/kiosk.py— fullscreen 2x2 ambient gridvigilar/web/blueprints/pets.py— pet registration, sightings, labeling, training-image uploadvigilar/web/blueprints/visitors.py— face profiles, visits, labeling, privacy controlsvigilar/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 viaapp.config["DB_ENGINE"]/VIGILAR_CONFIGcamera— reads HLS segments fromcfg.system.hls_dirand decrypts recordings viavigilar.storage.encryptionalerts— usesvigilar.alerts.pin.hash_pin/verify_pinand managespush_subscriptionsconfig_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.