feat(node): pair onboard computers with the Core fleet through UI

This commit is contained in:
DCCONSTRUCTIONS
2026-09-05 21:16:13 +03:00
parent fc545f8440
commit e82d012907
29 changed files with 2442 additions and 19 deletions
@@ -0,0 +1,50 @@
import { useCallback, useEffect, useState } from "react";
export interface BoardHost {
hostname: string; os: string; architecture: string; cpus: number;
memory_kib: number | null; collected_at: string;
networks: { name: string; up: boolean; addresses: string[] }[];
usb: { port: string; product: string }[];
}
export interface Vehicle {
id: string; node_id: string; name: string; platform: string;
enrollment: "pending" | "paired" | "revoked" | "failed";
connectivity: "online" | "offline"; last_seen: number | null;
host: BoardHost | null; notice: string; revision: number;
}
export interface FleetPreview {
preview_id: string; node_id: string; name: string; host: BoardHost;
endpoint: string; expires_at: number;
}
export async function fleetRequest<T>(path = "", method = "GET", body?: unknown): Promise<T> {
const response = await fetch(`/api/v1/fleet${path}`, {
method, credentials: "same-origin", cache: "no-store",
headers: body === undefined ? {} : { "Content-Type": "application/json" },
body: body === undefined ? undefined : JSON.stringify(body), signal: AbortSignal.timeout(12000),
});
if (!response.ok) {
const data = await response.json().catch(() => ({}));
throw new Error(typeof data.detail === "string" ? data.detail : "Не удалось выполнить действие с аппаратом.");
}
return response.json();
}
export function useFleet() {
const [items, setItems] = useState<Vehicle[] | null>(null);
const [error, setError] = useState("");
const refresh = useCallback(async () => {
const value = await fleetRequest<{ items: Vehicle[] }>();
setItems(value.items); setError(""); return value.items;
}, []);
useEffect(() => {
let active = true;
let timer: ReturnType<typeof setTimeout>;
async function poll() {
try { const value = await fleetRequest<{ items: Vehicle[] }>(); if (active) { setItems(value.items); setError(""); } }
catch { if (active) setError("Реестр недоступен. Показаны последние полученные сведения; связь сейчас не подтверждена."); }
finally { if (active) timer = setTimeout(poll, 5000); }
}
void poll();
return () => { active = false; clearTimeout(timer); };
}, []);
return { items, error, refresh };
}