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
+34 -2
View File
@@ -86,7 +86,12 @@
<span class="fw-bold text-dark">Yaris OBD</span>
<span class="badge bg-warning text-dark ms-1" style="font-size:.6rem;">Remote</span>
</a>
<div class="ms-auto d-flex align-items-center gap-2">
<div class="d-flex align-items-center gap-2">
<select class="form-select form-select-sm" id="portal-device-selector" style="width:auto;min-width:10rem;" aria-label="Select device">
<option value="">Loading…</option>
</select>
</div>
<div class="d-flex align-items-center gap-2">
<span class="text-muted small d-none d-sm-inline">{{ current_user.username }}</span>
<a href="{{ url_for('logout') }}" class="btn btn-outline-secondary btn-sm" title="Logout">
<i class="fas fa-sign-out-alt fa-fw"></i>
@@ -136,7 +141,34 @@
<script src="{{ url_for('static', filename='js/bs-init.js') }}"></script>
<script src="{{ url_for('static', filename='js/theme.js') }}"></script>
{# ── Shared confirmation helper ────────────────────────────────────────── #}
{# ── Device selector ─────────────────────────────────────────────────── #}
<script>
(function () {
const sel = document.getElementById("portal-device-selector");
if (!sel) return;
async function load() {
const r = await fetch("/api/v1/portal/select-device");
if (!r.ok) return;
const d = await r.json();
sel.innerHTML = d.devices.map(dev =>
'<option value="' + dev.id + '"' + (dev.id === d.selected_device_id ? " selected" : "") + ">" +
(dev.device_name || dev.name) + (dev.portal_default ? " (default)" : "") +
"</option>"
).join("");
if (!d.devices.length) sel.innerHTML = '<option value="">No devices</option>';
}
load();
sel.addEventListener("change", async function () {
const id = sel.value ? parseInt(sel.value) : null;
await fetch("/api/v1/portal/select-device", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({device_id: id}),
});
location.reload();
});
})();
</script>
<script>
window.confirmDelete = function(message) {
return Promise.resolve(window.confirm(message));