feat: add remote device settings portal and Pi hot reload

- add /settings/device portal page and portal device-config GET endpoint
- add portal navigation and client-side device settings editor UI
- hot-reload Account/Car display config after local saves and server syncs
- document portal workflow and Phase 25.5b completion
- add runtime reload tests for account, car, and sync callback

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 17:34:22 -04:00
co-authored by Copilot
parent c4ccc3efb9
commit 585da18225
12 changed files with 383 additions and 22 deletions
+87
View File
@@ -0,0 +1,87 @@
{% extends "base.html" %}
{% block title %}Device settings – Yaris OBD Remote{% endblock %}
{% block content %}
<div class="d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2 mb-3">
<div>
<h3 class="text-dark mb-1 fw-bold">Device settings</h3>
<p class="text-muted mb-0">
Edit the identity synced to the Pi for <code>{{ configured_device_name }}</code>.
</p>
</div>
<button class="btn btn-primary" id="device-settings-save" type="submit" form="device-settings-form" disabled>
<i class="fas fa-save me-1"></i>Save device settings
</button>
</div>
<div id="device-settings-message" class="alert" role="status" hidden></div>
<div class="row g-3">
<div class="col-12 col-xl-8">
<div class="card shadow-sm">
<div class="card-header py-2">
<h6 class="text-primary fw-bold m-0">Synced identity</h6>
</div>
<div class="card-body">
<form id="device-settings-form" novalidate>
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label" for="owner_name">Owner name</label>
<input class="form-control" id="owner_name" name="owner_name" maxlength="100" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="car_model">Car model</label>
<input class="form-control" id="car_model" name="car_model" maxlength="100" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="device_name">Device name</label>
<input class="form-control" id="device_name" name="device_name" maxlength="100">
<div class="form-text">Used by the remote portal and future multi-device views.</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="avatar_path">Avatar path</label>
<input class="form-control" id="avatar_path" name="avatar_path" maxlength="255" placeholder="img/yaris.png">
<div class="form-text">Relative to the shared <code>static/</code> folder.</div>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="col-12 col-xl-4">
<div class="card shadow-sm mb-3">
<div class="card-header py-2">
<h6 class="text-primary fw-bold m-0">Sync behavior</h6>
</div>
<div class="card-body small text-muted">
<p class="mb-2">
The Pi picks these values up on its next successful internet-backed sync.
</p>
<ul class="mb-0 ps-3">
<li>Expected propagation delay: up to ~5 minutes while online.</li>
<li>The Pi must have <strong>Sync with server</strong> enabled locally.</li>
<li>Offline cars keep the last synced config until connectivity returns.</li>
</ul>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header py-2">
<h6 class="text-primary fw-bold m-0">Preview</h6>
</div>
<div class="card-body text-center">
<img id="avatar-preview" alt="Avatar preview" class="rounded-circle border mb-3" src="{{ url_for('static', filename='img/yaris.png') }}" style="width:96px;height:96px;object-fit:cover;">
<div class="small text-muted mb-1">Config version</div>
<div class="fw-bold fs-5" id="config-version">—</div>
<div class="small text-muted mt-3" id="preview-caption">Preview updates as you type.</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/portal-device-settings.js') }}"></script>
{% endblock %}