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:
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create or update a browser portal user (admin).
|
||||
|
||||
Usage:
|
||||
python scripts/create_user.py <username> <password>
|
||||
|
||||
Run inside the running container or with POSTGRES_* env vars set:
|
||||
docker compose exec server python scripts/create_user.py admin yourpassword
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Allow importing modules from the app root when run directly.
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from modules import db
|
||||
from modules.user_auth import hash_password
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: create_user.py <username> <password>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
username, password = sys.argv[1], sys.argv[2]
|
||||
if len(password) < 8:
|
||||
print("Password must be at least 8 characters.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
hashed = hash_password(password)
|
||||
|
||||
# Try to create; if user already exists, update password.
|
||||
uid = db.create_user(username, hashed)
|
||||
if uid is None:
|
||||
# User exists – update password
|
||||
with db.get_cursor(commit=True) as cur:
|
||||
cur.execute(
|
||||
"UPDATE users SET password_hash = %s WHERE username = %s",
|
||||
(hashed, username),
|
||||
)
|
||||
print(f"Updated password for user '{username}'.")
|
||||
else:
|
||||
print(f"Created user '{username}' (id={uid}).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Recompute server-side trip boundaries from stored tracking_points.
|
||||
|
||||
Run after migrating to Phase 24 schema, or any time you want to refresh
|
||||
trips from raw data:
|
||||
docker compose exec server python scripts/recompute_trips.py
|
||||
"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from modules import db
|
||||
|
||||
|
||||
def main():
|
||||
with db.get_cursor() as cur:
|
||||
cur.execute("SELECT id, name FROM devices ORDER BY id")
|
||||
devices = cur.fetchall()
|
||||
|
||||
if not devices:
|
||||
print("No devices found in database.")
|
||||
return
|
||||
|
||||
for device_id, device_name in devices:
|
||||
print(f"Recomputing trips for device '{device_name}' (id={device_id})…", end=" ", flush=True)
|
||||
n = db.compute_trips_for_device(device_id)
|
||||
print(f"{n} trips computed.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user