Add local settings management

Add cellular-first ntfy delivery with WiFi fallback and safe SIM868 HTTP header framing.

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-09 21:26:15 -04:00
co-authored by Copilot
parent b77ed838bd
commit 3a6f12ba9d
15 changed files with 1118 additions and 103 deletions
+5
View File
@@ -41,6 +41,11 @@
<i class="fas fa-exclamation-triangle"></i><span>DTC</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link{% if request.endpoint == 'settings' %} active{% endif %}" href="{{ url_for('settings') }}">
<i class="fas fa-cog"></i><span>Settings</span>
</a>
</li>
</ul>
<div class="text-center d-none d-md-inline">
<button class="btn rounded-circle border-0" id="sidebarToggle" type="button" aria-label="Toggle sidebar"></button>
-21
View File
@@ -97,27 +97,6 @@
) }}
</div>
</div>
<div class="card shadow">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Remote server upload</p>
</div>
<div class="card-body">
<p id="upload-status">Checking upload status...</p>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="dashcam-upload-toggle" disabled>
<label class="form-check-label" for="dashcam-upload-toggle">
Upload dashcam videos to remote server
</label>
</div>
<p class="text-muted small mt-2 mb-0">
Trip/GPS log uploads always stay on. Turning this off only pauses
the (larger) dashcam video uploads - useful on a slow or metered
connection. If configured, small trip-log batches can also fall
back to the SIM868's cellular data when WiFi/Ethernet is
unreachable - dashcam video never uses cellular.
</p>
</div>
</div>
{% endblock %}
{% block scripts %}
+132
View File
@@ -0,0 +1,132 @@
{% extends "base.html" %}
{% block title %}Settings - Yaris OBD{% endblock %}
{% block content %}
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
<div>
<h3 class="text-dark mb-1">Settings</h3>
<p class="text-muted mb-0">Configuration stored locally on this Pi.</p>
</div>
<button class="btn btn-primary d-none d-md-inline-block settings-save" type="submit" form="settings-form" disabled>
<i class="fas fa-save me-1"></i>Save settings
</button>
</div>
<div id="settings-message" class="alert" role="status" hidden></div>
<form id="settings-form">
<div class="card shadow mb-4">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Uploads and local video</p>
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="dashcam-upload-enabled">
<label class="form-check-label" for="dashcam-upload-enabled">Upload dashcam videos to the remote server</label>
</div>
<p class="text-muted small">Trip/GPS uploads remain enabled. Dashcam video only uploads over WiFi/Ethernet.</p>
<p id="upload-health" class="small mb-3">Checking remote upload status...</p>
<label class="form-label" for="retention-days">Delete uploaded local videos after</label>
<div class="input-group settings-number">
<input class="form-control" id="retention-days" type="number" min="0" max="3650" step="1" required>
<span class="input-group-text">days</span>
</div>
<div class="form-text">Use 0 to keep uploaded local copies until manually deleted.</div>
<div class="invalid-feedback" data-error-for="local_video_retention_days"></div>
</div>
</div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Cellular trip-log fallback</p>
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="cellular-enabled">
<label class="form-check-label" for="cellular-enabled">Enable SIM868 cellular fallback</label>
</div>
<label class="form-label" for="cellular-apn">Carrier APN</label>
<input class="form-control" id="cellular-apn" maxlength="100" autocomplete="off" placeholder="example.apn">
<div class="invalid-feedback" data-error-for="cellular_apn"></div>
<div class="form-text">Used only for small trip-log batches when WiFi/Ethernet is unavailable. Dashcam video never uses cellular.</div>
</div>
</div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Optional ntfy notifications</p>
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="ntfy-enabled">
<label class="form-check-label" for="ntfy-enabled">Send alerts through ntfy</label>
</div>
<label class="form-label" for="ntfy-url">ntfy topic URL</label>
<input class="form-control" id="ntfy-url" type="url" maxlength="2048" autocomplete="off" placeholder="https://ntfy.sh/private-topic">
<div class="invalid-feedback" data-error-for="ntfy_url"></div>
<div class="form-text" id="ntfy-url-state">Leave blank to keep the saved URL unchanged.</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="clear-ntfy-url">
<label class="form-check-label" for="clear-ntfy-url">Remove saved ntfy URL</label>
</div>
<label class="form-label mt-3" for="ntfy-token">Access token</label>
<input class="form-control" id="ntfy-token" type="password" maxlength="512" autocomplete="new-password" placeholder="Optional">
<div class="invalid-feedback" data-error-for="ntfy_token"></div>
<div class="form-text" id="ntfy-token-state">Leave blank to keep the saved token unchanged.</div>
<div class="form-check mt-2">
<input class="form-check-input" type="checkbox" id="clear-ntfy-token">
<label class="form-check-label" for="clear-ntfy-token">Remove saved ntfy token</label>
</div>
</div>
</div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Alert thresholds and repeat interval</p>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="temperature-c">Pi temperature</label>
<div class="input-group"><input class="form-control" id="temperature-c" type="number" min="40" max="100" step="0.1" required><span class="input-group-text">°C</span></div>
<div class="invalid-feedback" data-error-for="temperature_c"></div>
</div>
<div class="col-md-6">
<label class="form-label" for="disk-percent">Pi/dashcam disk usage</label>
<div class="input-group"><input class="form-control" id="disk-percent" type="number" min="50" max="100" step="0.1" required><span class="input-group-text">%</span></div>
<div class="invalid-feedback" data-error-for="disk_percent"></div>
</div>
<div class="col-md-6">
<label class="form-label" for="tracking-backlog">Pending tracking rows</label>
<input class="form-control" id="tracking-backlog" type="number" min="1" max="1000000" step="1" required>
<div class="invalid-feedback" data-error-for="tracking_backlog"></div>
</div>
<div class="col-md-6">
<label class="form-label" for="video-backlog">Pending videos</label>
<input class="form-control" id="video-backlog" type="number" min="1" max="100000" step="1" required>
<div class="invalid-feedback" data-error-for="video_backlog"></div>
</div>
<div class="col-md-6">
<label class="form-label" for="gps-loss-seconds">GPS loss while moving</label>
<div class="input-group"><input class="form-control" id="gps-loss-seconds" type="number" min="10" max="3600" step="1" required><span class="input-group-text">seconds</span></div>
<div class="invalid-feedback" data-error-for="gps_loss_seconds"></div>
</div>
<div class="col-md-6">
<label class="form-label" for="repeat-seconds">Repeat active notifications</label>
<div class="input-group"><input class="form-control" id="repeat-seconds" type="number" min="60" max="604800" step="1" required><span class="input-group-text">seconds</span></div>
<div class="invalid-feedback" data-error-for="repeat_seconds"></div>
</div>
</div>
<p class="text-muted small mt-3 mb-0">Local Web UI alerts remain available even when ntfy is disabled.</p>
</div>
</div>
<button class="btn btn-primary w-100 d-md-none settings-save mb-4" type="submit" disabled>
<i class="fas fa-save me-1"></i>Save settings
</button>
</form>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='js/settings.js') }}"></script>
{% endblock %}