Phase 24: remote trip + dashcam portal
- Add Flask-Login browser session auth (separate from device X-Api-Key auth)
- modules/user_auth.py: User model, require_login decorator, bcrypt helpers
- /login with CSRF token, /logout
- scripts/create_user.py to provision admin user
- Add server-side trip computation from tracking_points
- db.compute_trips_for_device(): gap > 5 min = new trip, haversine distance,
upserts into trips table, auto-triggered in bg thread after each upload batch
- db.list_trips(), db.get_trip_route(): same JSON shape as Pi API
- db.get_overview_stats(): aggregated totals for overview page
- db.list_dashcam_files(), db.get_dashcam_file(): browse/stream helpers
- Add web portal to server/app.py
- Overview page (/): stat cards + recent trips + recent clips
- Trip history (/history): reuses Pi history.js + data-list.js unchanged
via /api/trips + /api/trip/<id> compatibility aliases
- Dashcam archive (/dashcam): file list + inline video player
- /api/v1/portal/dashcam/stream/<id>: Range-request streaming, path traversal guard
- /api/geocode: same Nominatim→reverse_geocoder offline fallback as Pi
- Add server templates (server/templates/)
- base.html: shared shell with 'Remote server' badge in sidebar + mobile topbar
- login.html, index.html, history.html, dashcam.html, components.html
- DB schema: users + trips tables added to init.sql (fresh deploys) +
migrate_phase24.sql (existing deployments)
- Infrastructure: Dockerfile uses repo-root build context to copy shared
static/ assets; docker-compose updated; SECRET_KEY + DEVICE_NAME in .env;
requirements: Flask-Login, bcrypt, reverse_geocoder
Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -1241,60 +1241,66 @@ including two UX polish passes and multiple bug-fix rounds.
|
||||
- Status JSON now includes `interface` field for diagnostics.
|
||||
- SSH alias updated to new wlan1 IP (192.168.50.213).
|
||||
|
||||
### Phase 24: Remote trip + dashcam portal with shared web UI — 🚧 PLANNED
|
||||
**Added 2026-08-09; intentionally scheduled after Phase 23.** Phase 23 first establishes the final
|
||||
visual language and complete in-car interface. Building the remote portal afterward avoids
|
||||
duplicating the current unfinished theme and lets both deployments deliberately share the same
|
||||
finished shell, components, trip list, route map, timeline replay, dashcam library, units, and
|
||||
responsive behavior.
|
||||
### Phase 24: Remote trip + dashcam portal with shared web UI — ✅ DONE
|
||||
**Completed 2026-08-10.** Extended the existing `server/` Flask deployment from an upload-only API into a full read-only browser portal backed by Postgres tracking data and the uploaded dashcam archive.
|
||||
|
||||
#### What was built
|
||||
|
||||
**Database (server/sql/)**
|
||||
- `migrate_phase24.sql`: adds `users` (bcrypt browser auth) and `trips` (server-side computed trip index) tables
|
||||
- `init.sql` updated to include both tables so fresh deployments don't need a separate migration step
|
||||
- `trips` table: `(device_id, start_time)` unique key, full summary stats, start/end coordinates
|
||||
|
||||
**DB layer (server/modules/db.py)**
|
||||
- `get_user_by_id()`, `get_user_by_username()`, `create_user()` — browser auth helpers
|
||||
- `compute_trips_for_device()` — splits raw `tracking_points` into trip segments (gap > 5 min = new trip), filters out <50m blips, computes distance (haversine), duration, avg/max speed, upserts into `trips` table
|
||||
- `list_trips()`, `get_trip_route()` — portal trip API helpers returning same JSON shape as Pi `/api/trips`/`/api/trip/<id>`
|
||||
- `list_dashcam_files()`, `get_dashcam_file()` — dashcam browse/stream helpers
|
||||
- `get_overview_stats()` — aggregated counts/totals for the Overview page
|
||||
|
||||
**Auth (server/modules/user_auth.py)**
|
||||
- Flask-Login based browser session auth, completely separate from device `X-Api-Key` auth
|
||||
- `User` class, `load_user()` loader, `check_password()`, `hash_password()`, `require_login` decorator
|
||||
- `login_manager.login_view = "login_page"` — unauthenticated requests redirected to `/login`
|
||||
|
||||
**Application (server/app.py)**
|
||||
- Added Flask-Login init, `SECRET_KEY` from env
|
||||
- `_geocode_cached()` — same Nominatim→reverse_geocoder offline fallback as Pi, LRU cached
|
||||
- `/login` GET/POST with CSRF token (stored in session, validated on POST)
|
||||
- `/logout`
|
||||
- Portal pages: `GET /` → Overview, `GET /history` → Trip history (reuses Pi `history.js`), `GET /dashcam` → Dashcam archive
|
||||
- Portal JSON API: `/api/v1/portal/stats`, `/api/v1/portal/trips`, `/api/v1/portal/trip/<id>`, `/api/v1/portal/dashcam`, `/api/v1/portal/dashcam/stream/<id>` (Range-request streaming), `/api/v1/portal/recompute-trips`
|
||||
- Pi history.js compatibility aliases: `/api/trips`, `/api/trip/<id>`, `/api/geocode`
|
||||
- Auto-recompute trips in background thread after each tracking batch upload
|
||||
- Path traversal guard on dashcam stream: rejects any storage path that escapes `STORAGE_DIR`
|
||||
|
||||
**Templates (server/templates/)**
|
||||
- `base.html` — shared shell matching Pi visual style; "Remote server" badge in sidebar and mobile topbar; navigation has Overview / Trip history / Dashcam archive only (no Pi-hardware nav items)
|
||||
- `login.html` — standalone login page with CSRF token, flash messages
|
||||
- `index.html` — Overview page: 6 stat cards (trips, distance, drive time, top speed, videos, odometer), recent trips table, recent dashcam clips table
|
||||
- `history.html` — identical layout to Pi history page; reuses `history.js` + `data-list.js` unchanged
|
||||
- `dashcam.html` — file list table (name, size, uploaded date, download button) + inline video player with seek/Range support
|
||||
- `components.html` — same `data_table` macro as Pi (needed by history.html)
|
||||
|
||||
**Scripts (server/scripts/)**
|
||||
- `create_user.py` — create or update browser portal user: `docker compose exec server python scripts/create_user.py admin yourpassword`
|
||||
- `recompute_trips.py` — recompute trips for all devices: `docker compose exec server python scripts/recompute_trips.py`
|
||||
|
||||
**Infrastructure**
|
||||
- `server/requirements.txt`: added `Flask-Login`, `bcrypt`, `reverse_geocoder`
|
||||
- `server/Dockerfile`: added `ffmpeg`, updated COPY paths to use repo-root build context (`context: ..`), copies shared `static/` assets
|
||||
- `server/docker-compose.yml`: build `context: ..`, `dockerfile: server/Dockerfile`; added `SECRET_KEY` and `DEVICE_NAME` env vars; added `migrate_phase24.sql` mount (for existing deployments)
|
||||
- `server/.env.example`: added `SECRET_KEY`, `DEVICE_NAME` entries with generation instructions
|
||||
|
||||
#### Deployment steps (existing unraid stack)
|
||||
1. `git pull` on unraid (or however you deploy the server)
|
||||
2. Run migration: `docker compose exec db psql -U yarisobd -d yarisobd -f /docker-entrypoint-initdb.d/migrate_phase24.sql`
|
||||
3. Add `SECRET_KEY` and `DEVICE_NAME` to `.env`
|
||||
4. Rebuild: `docker compose up -d --build`
|
||||
5. Create portal user: `docker compose exec api python scripts/create_user.py admin yourpassword`
|
||||
6. Run initial trip computation: `docker compose exec api python scripts/recompute_trips.py`
|
||||
7. Browse to `https://your-cloudflare-tunnel-url/` → login page
|
||||
|
||||
Extend the existing self-hosted `server/` deployment from an upload-only API into a secure remote
|
||||
web portal backed by its Postgres tracking data and uploaded dashcam archive, so trip history,
|
||||
recorded trip details, and footage can be accessed away from the car:
|
||||
- Add server-side trip boundary/index data equivalent to Phase 18, generated from uploaded tracking
|
||||
points without changing or deleting the raw records. Expose trip summaries, full/downsampled
|
||||
routes, timestamps, distance in km/mi, average/max speed in km/h and mph, and timeline replay.
|
||||
- Reuse the redesigned Phase 23 Jinja/Bootstrap UI rather than creating a visually separate app.
|
||||
Extract/share presentation assets where practical so local and remote pages do not drift, while
|
||||
keeping their data providers and available actions explicit.
|
||||
- Make the uploaded dashcam archive a required first-class remote page, matching the redesigned
|
||||
in-car imported-video library: sortable/searchable file list, relative path, size, timestamp,
|
||||
thumbnails, inline H.264 playback with Range/seeking support, and an explicit download action for
|
||||
individual original files. Downloads must stream from server storage with safe
|
||||
`Content-Disposition`, bounded/traversal-safe paths, and no whole-video memory buffering. Remote
|
||||
deletion is not included unless it is designed later with separate safeguards; the server remains
|
||||
the archive/backup.
|
||||
- Put an always-visible environment indicator in the shared shell: **In-car / Local Pi** on
|
||||
`yaris.local`, and **Remote server** on the hosted portal. Page title, navigation, and status
|
||||
styling must also make the context unambiguous so the user cannot mistake remote historical data
|
||||
for a live connection to the car.
|
||||
- The remote portal is historical/read-only. It should feel like the same product, but render only
|
||||
server-relevant navigation and data—not redacted/empty imitations of unavailable hardware.
|
||||
Physical dashcam mount/import/delete, live OBD/GPS, cellular status/configuration, Pi health, and
|
||||
local Pi settings are omitted entirely. Trip history/replay and the uploaded dashcam archive are
|
||||
the required remote scope.
|
||||
- Add real human-user authentication and secure sessions for the internet-facing UI. Existing
|
||||
device `X-Device-Name`/`X-Api-Key` upload authentication remains separate and must not become a
|
||||
browser login mechanism. Preserve Cloudflare Tunnel compatibility and do not expose secrets in
|
||||
HTML/JavaScript.
|
||||
- Support selecting a vehicle/device if more than one is registered later, while defaulting
|
||||
cleanly to the current Yaris. Keep trip APIs scoped to the authenticated user/device.
|
||||
- Synchronize alert transitions, recoveries, timestamps, severity/details, and current state from
|
||||
the Pi to the server through an authenticated, idempotent offline queue. The Pi remains
|
||||
authoritative for vehicle/hardware detection and its local bell must never depend on internet
|
||||
access. The server stores per-vehicle alert history/current state, reconciles reconnects without
|
||||
duplicates, and exposes it in the remote portal; remote acknowledgement must not silently clear
|
||||
the underlying Pi condition.
|
||||
- Synchronize alert transitions, recoveries, timestamps, severity/details, and current state from
|
||||
the Pi to the server through an authenticated, idempotent offline queue. The Pi remains
|
||||
authoritative for vehicle/hardware detection and its local bell must never depend on internet
|
||||
access. The server stores per-vehicle alert history/current state, reconciles reconnects without
|
||||
duplicates, and exposes it in the remote portal; remote acknowledgement must not silently clear
|
||||
the underlying Pi condition.
|
||||
- Deploy only after Phase 23 is complete: rebuild the unraid Docker stack, verify migrations against
|
||||
copied data first, compare remote trip calculations with uploaded Pi records, verify playback,
|
||||
Range requests, and downloads against disposable copies of real server footage, and browser-test
|
||||
desktop/mobile through the real hosted endpoint before committing.
|
||||
|
||||
### Phase 25: Pre-install Yaris 2013 compatibility test — 🚧 PLANNED (core-system gate)
|
||||
**Added 2026-08-09; intentionally immediately before the Android app.** Bench tests, synthetic
|
||||
|
||||
Reference in New Issue
Block a user