feat: portal-wide device selector — session-based, all pages now use selected device

- _portal_device_id() reads from session, falls back to DEVICE_NAME env var
- GET/POST /api/v1/portal/select-device for selector data + set
- Dropdown in server base.html topbar, JS populates from API, page reloads on change
- All portal pages (overview, trips, dashcam, DTC, device settings) now use selected device
This commit is contained in:
Mathieu Moran
2026-08-12 01:40:51 -04:00
parent 1556c65131
commit e30b85d218
6 changed files with 133 additions and 87 deletions
+46 -1
View File
@@ -74,12 +74,57 @@ login_manager.init_app(app)
def _portal_device_id():
"""Return the device id for the configured device name (single Pi setup)."""
"""Return the device id for the user's selected device, falling back to
the DEVICE_NAME env var default. Selection is stored in the Flask session
so it persists across pages without a URL parameter on every link."""
# Check session first (user explicitly picked a device)
session_id = session.get("selected_device_id")
if session_id is not None:
dev = db.get_device_by_id(session_id)
if dev is not None:
return dev["id"]
# Selected device was deleted — clear stale session entry
session.pop("selected_device_id", None)
# Fall back to configured default
name = os.environ.get("DEVICE_NAME", "yaris-2013")
dev = db.get_device_by_name(name)
return dev["id"] if dev else None
@app.get("/api/v1/portal/select-device")
@require_login
def portal_select_device_state():
"""Return current selection and available devices for the dropdown."""
devices = device_registry.list_devices()
configured_name = os.environ.get("DEVICE_NAME", "yaris-2013")
for device in devices:
device["portal_default"] = device["name"] == configured_name
current_id = session.get("selected_device_id")
return jsonify(
devices=devices,
selected_device_id=current_id,
configured_device_name=configured_name,
)
@app.post("/api/v1/portal/select-device")
@require_login
def portal_select_device():
"""Set the active device for the current session."""
body = request.get_json(silent=True) or {}
device_id = body.get("device_id")
if device_id is None:
session.pop("selected_device_id", None)
session.pop("selected_device_name", None)
return jsonify(status="ok", message="Device selection cleared.")
dev = db.get_device_by_id(int(device_id))
if dev is None:
return jsonify(error="Device not found"), 404
session["selected_device_id"] = dev["id"]
session["selected_device_name"] = dev["device_name"] or dev["name"]
return jsonify(status="ok", device_id=dev["id"], device_name=dev["device_name"] or dev["name"])
@app.get("/health")
def health():
"""Unauthenticated liveness check - used by docker-compose healthcheck