feat: add multi-device provisioning and local remote-config UI

- add multi-device portal device management, key rotation, and sync request APIs
- add Pi settings UI for remote server URL, device name, and API key
- add local test-connection and sync-now actions
- make upload and config sync reuse dynamic shared credentials
- add Phase 25.5c migration and docs updates

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 18:02:09 -04:00
co-authored by Copilot
parent fbc41a5db5
commit ba682fbf3a
23 changed files with 1357 additions and 167 deletions
+32 -1
View File
@@ -1,5 +1,5 @@
from flask import Flask, render_template, make_response, json, jsonify, request, send_file, Response
from modules import app_gps, app_events, app_car, app_account, app_obd, app_sql, app_dash, app_upload, app_video, app_health, app_alerts, app_settings, app_network, app_device_config, app_device_sync
from modules import app_gps, app_events, app_car, app_account, app_obd, app_sql, app_dash, app_upload, app_video, app_health, app_alerts, app_settings, app_network, app_device_config, app_device_sync, app_remote_config
import os, time
from gevent.pywsgi import WSGIServer
@@ -64,6 +64,37 @@ def api_device_config():
_reload_runtime_device_config()
return jsonify(app_device_config.get_device_config())
@app.route('/api/remote-config', methods=['GET', 'POST'])
def api_remote_config():
if request.method == 'GET':
return jsonify(app_remote_config.get_remote_config())
errors = {}
app_remote_config.update_remote_config(request.get_json(silent=True), errors)
if errors:
return jsonify(error="Invalid remote config.", fields=errors), 400
app_upload.start_uploading()
app_device_sync.start_sync()
return jsonify(app_remote_config.get_remote_config())
@app.route('/api/remote-config/test', methods=['POST'])
def api_remote_config_test():
result = app_remote_config.test_connection()
return jsonify(result), 200 if result.get("ok") else 400
@app.route('/api/remote-config/sync-now', methods=['POST'])
def api_remote_config_sync_now():
try:
result = app_device_sync.sync_now()
except Exception as error:
return jsonify(status="error", message=str(error)), 502
status_codes = {
"synced": 200,
"no_change": 200,
"not_configured": 400,
"offline": 503,
}
return jsonify(result), status_codes.get(result["status"], 500)
@app.route('/api/uplink/status')
def uplink_status():
try: