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:
+82
-1
@@ -17,7 +17,7 @@ from flask_login import login_user, logout_user, current_user
|
||||
from modules import db
|
||||
from modules.auth import require_device_auth
|
||||
from modules.user_auth import login_manager, User, check_password, require_login
|
||||
from modules import device_config
|
||||
from modules import device_config, device_registry
|
||||
|
||||
# ── Geocode cache ──────────────────────────────────────────────────────────
|
||||
try:
|
||||
@@ -630,6 +630,75 @@ def portal_trip_compat(trip_id):
|
||||
|
||||
# ── Phase 25.5a: Device configuration sync ──────────────────────────────────
|
||||
|
||||
@app.get("/api/v1/portal/devices")
|
||||
@require_login
|
||||
def portal_devices():
|
||||
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
|
||||
return jsonify(
|
||||
devices=devices,
|
||||
configured_device_name=configured_name,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/v1/portal/devices")
|
||||
@require_login
|
||||
def portal_device_create():
|
||||
body = request.get_json(silent=True) or {}
|
||||
result, errors = device_registry.create_device(body.get("name"))
|
||||
if errors:
|
||||
return jsonify(error="Invalid device.", fields=errors), 400
|
||||
return jsonify(result), 201
|
||||
|
||||
|
||||
@app.get("/api/v1/portal/devices/<int:device_id>/config")
|
||||
@require_login
|
||||
def portal_device_config_detail(device_id):
|
||||
config = device_config.get_device_config(device_id)
|
||||
if not config:
|
||||
return jsonify(error="device not found"), 404
|
||||
device = db.get_device_by_id(device_id)
|
||||
return jsonify(
|
||||
device=device,
|
||||
config=config,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/v1/portal/devices/<int:device_id>/config")
|
||||
@require_login
|
||||
def portal_device_config_update(device_id):
|
||||
success, errors = device_config.update_device_config(
|
||||
device_id,
|
||||
request.get_json(silent=True) or {},
|
||||
)
|
||||
if not success:
|
||||
return jsonify(error="Invalid device config.", fields=errors), 400
|
||||
return jsonify(
|
||||
device=db.get_device_by_id(device_id),
|
||||
config=device_config.get_device_config(device_id),
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/v1/portal/devices/<int:device_id>/rotate-key")
|
||||
@require_login
|
||||
def portal_device_rotate_key(device_id):
|
||||
result, errors = device_registry.rotate_device_key(device_id)
|
||||
if errors:
|
||||
return jsonify(error=errors.get("error", "Could not rotate key.")), 404
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.post("/api/v1/portal/devices/<int:device_id>/request-sync")
|
||||
@require_login
|
||||
def portal_device_request_sync(device_id):
|
||||
result = device_registry.request_sync(device_id)
|
||||
if result is None:
|
||||
return jsonify(error="device not found"), 404
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.get("/api/v1/portal/device-config")
|
||||
@require_login
|
||||
def portal_device_config_get():
|
||||
@@ -660,6 +729,18 @@ def api_device_config_get():
|
||||
return jsonify(config)
|
||||
|
||||
|
||||
@app.post("/api/v1/device-config/ack")
|
||||
@require_device_auth
|
||||
def api_device_config_ack():
|
||||
body = request.get_json(silent=True) or {}
|
||||
try:
|
||||
config_version = int(body.get("config_version"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify(error="config_version is required"), 400
|
||||
device_registry.acknowledge_sync(g.device_id, config_version)
|
||||
return jsonify(status="ok")
|
||||
|
||||
|
||||
@app.post("/api/device-config")
|
||||
@require_login
|
||||
def api_device_config_update():
|
||||
|
||||
Reference in New Issue
Block a user