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:
co-authored by
Claude Opus 5.5
parent
16a609e35b
commit
f18f7a49f6
@@ -7,6 +7,7 @@ import { useSystems } from "@/lib/useSystems";
|
||||
import { NodeCard } from "@/components/NodeCard";
|
||||
import { NodeConfigModal } from "@/components/NodeConfigModal";
|
||||
import { useAuth } from "@/components/AuthProvider";
|
||||
import { c2api } from "@/lib/c2api";
|
||||
import type { NodeRecord } from "@/lib/types";
|
||||
|
||||
export default function NodesPage() {
|
||||
@@ -20,6 +21,32 @@ export default function NodesPage() {
|
||||
}, [authLoading, isAdmin, isOperator, router]);
|
||||
|
||||
const [configNode, setConfigNode] = useState<NodeRecord | null>(null);
|
||||
const [updatingAll, setUpdatingAll] = useState(false);
|
||||
|
||||
async function handleUpdateAll() {
|
||||
if (
|
||||
!confirm(
|
||||
"Update every approved node? Each one pulls the latest release it tracks and recreates its " +
|
||||
"containers (radio capture pauses for about a minute per node). Online nodes start within " +
|
||||
"seconds; offline nodes pick it up on their next check-in (up to ~15 minutes).",
|
||||
)
|
||||
)
|
||||
return;
|
||||
setUpdatingAll(true);
|
||||
try {
|
||||
const r = await c2api.requestUpdateAllNodes();
|
||||
const offline = r.requested.filter((n) => !r.mqtt_sent.includes(n));
|
||||
alert(
|
||||
`Update requested for ${r.requested.length} node(s).` +
|
||||
(offline.length ? ` Not reachable over MQTT right now (will update on next check-in): ${offline.join(", ")}.` : "") +
|
||||
(r.skipped.length ? ` Skipped (not approved): ${r.skipped.join(", ")}.` : ""),
|
||||
);
|
||||
} catch (err) {
|
||||
alert(err instanceof Error ? err.message : "Update request failed.");
|
||||
} finally {
|
||||
setUpdatingAll(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Every hook must run before this guard. React tracks hooks by call order,
|
||||
// so returning early on the first render and then reaching a useState on the
|
||||
@@ -32,7 +59,18 @@ export default function NodesPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-xl font-bold text-white font-mono">Nodes</h1>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h1 className="text-xl font-bold text-white font-mono">Nodes</h1>
|
||||
{isAdmin && (
|
||||
<button
|
||||
onClick={handleUpdateAll}
|
||||
disabled={updatingAll}
|
||||
className="px-4 py-2 bg-gray-800 hover:bg-gray-700 text-gray-300 rounded-lg text-sm font-mono transition-colors disabled:opacity-50"
|
||||
>
|
||||
{updatingAll ? "Requesting…" : "Update All Nodes"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{pending.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user