Nodes: Update All Nodes — fleet-wide update request for every approved node

POST /nodes/request-update-all (admin) does what Update Node does for each
approved node: flags update_requested_at for the HTTPS updater and nudges it
over MQTT. Pending and rejected nodes are skipped. Admin-only button on the
Nodes page reports which nodes weren't reachable over MQTT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Logan Cusano
2026-10-05 00:29:30 -04:00
co-authored by Claude Opus 5.5
parent 16a609e35b
commit f18f7a49f6
4 changed files with 132 additions and 2 deletions
+31 -1
View File
@@ -97,10 +97,40 @@ async def request_node_update(node_id: str, _: dict = Depends(require_admin_toke
if not node:
raise HTTPException(404, f"Node '{node_id}' not found.")
requested_at = datetime.now(timezone.utc).isoformat()
mqtt_sent = await _flag_update(node_id, requested_at)
return {"ok": True, "update_requested_at": requested_at, "mqtt_sent": mqtt_sent}
async def _flag_update(node_id: str, requested_at: str) -> bool:
await fstore.doc_update("nodes", node_id, {"update_requested_at": requested_at})
mqtt_sent = mqtt_handler.send_command(node_id, {"action": "node_update"})
logger.info(f"Update requested for {node_id!r} at {requested_at} (mqtt_sent={mqtt_sent})")
return {"ok": True, "update_requested_at": requested_at, "mqtt_sent": mqtt_sent}
return mqtt_sent
@router.post("/request-update-all")
async def request_update_all_nodes(_: dict = Depends(require_admin_token)):
"""Fleet-wide "Update Node": the same two-channel request as
request_node_update, for every approved node. Pending/rejected nodes are
skipped — a rejected node must not be nudged (server-26#195), and a pending
one has no key to check in with."""
requested_at = datetime.now(timezone.utc).isoformat()
nodes = await fstore.collection_list("nodes")
requested, mqtt_sent, skipped = [], [], []
for n in nodes:
nid = n.get("node_id")
if not nid:
continue
if n.get("approval_status") != "approved":
skipped.append(nid)
continue
requested.append(nid)
if await _flag_update(nid, requested_at):
mqtt_sent.append(nid)
logger.info(f"Fleet update requested at {requested_at}: {len(requested)} nodes, "
f"{len(mqtt_sent)} via MQTT, {len(skipped)} skipped")
return {"ok": True, "update_requested_at": requested_at, "requested": requested,
"mqtt_sent": mqtt_sent, "skipped": skipped}
@router.delete("/{node_id}", status_code=204)