feat(system): add Worker 006 telemetry and network profile
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
fetchWorkerTelemetry,
|
||||
type WorkerTelemetry,
|
||||
} from "./workerTelemetry";
|
||||
|
||||
export interface WorkerTelemetryState {
|
||||
telemetry: WorkerTelemetry | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
export function useWorkerTelemetry(pollMilliseconds = 10_000): WorkerTelemetryState {
|
||||
const [telemetry, setTelemetry] = useState<WorkerTelemetry | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [generation, setGeneration] = useState(0);
|
||||
const refresh = useCallback(() => setGeneration((value) => value + 1), []);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
setLoading(true);
|
||||
void fetchWorkerTelemetry(controller.signal)
|
||||
.then((document) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setTelemetry(document);
|
||||
setError(null);
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setError(reason instanceof Error ? reason.message : "Worker 006 не ответил.");
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) setLoading(false);
|
||||
});
|
||||
return () => controller.abort();
|
||||
}, [generation]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(refresh, pollMilliseconds);
|
||||
return () => window.clearInterval(timer);
|
||||
}, [pollMilliseconds, refresh]);
|
||||
|
||||
return { telemetry, loading, error, refresh };
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
export interface WorkerConnectionProfile {
|
||||
schema_version: "missioncore.worker-connection-profile/v1";
|
||||
profile_id: string;
|
||||
display_name: string;
|
||||
expected_node_id: string;
|
||||
ssh_host_alias: string;
|
||||
address: string;
|
||||
port: number;
|
||||
revision: number;
|
||||
updated_at_utc: string | null;
|
||||
}
|
||||
|
||||
export interface WorkerProbe {
|
||||
schema_version: "missioncore.worker-probe/v1";
|
||||
reachable: boolean;
|
||||
identity_matches: boolean;
|
||||
node_id: string | null;
|
||||
latency_ms: number | null;
|
||||
observed_at_utc: string;
|
||||
error_code: string | null;
|
||||
}
|
||||
|
||||
export interface WorkerProfileDocument {
|
||||
schema_version: "missioncore.worker-connection-profile/v1";
|
||||
profile: WorkerConnectionProfile;
|
||||
security: {
|
||||
transport: "ssh";
|
||||
host_key_policy: "strict-pinned";
|
||||
credentials_managed_by_ui: false;
|
||||
ssh_host_alias: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WorkerRuntime {
|
||||
name: string;
|
||||
role: string;
|
||||
owner: "mission-core" | "external";
|
||||
external: boolean;
|
||||
image: string | null;
|
||||
state: string;
|
||||
health: string | null;
|
||||
cpu_percent: number | null;
|
||||
memory_percent: number | null;
|
||||
memory_usage: string | null;
|
||||
network_io: string | null;
|
||||
block_io: string | null;
|
||||
pids: number | null;
|
||||
}
|
||||
|
||||
export interface WorkerPipelineStage {
|
||||
id: string;
|
||||
label: string;
|
||||
state: "active" | "waiting" | "ready" | "unavailable";
|
||||
}
|
||||
|
||||
export interface WorkerTelemetryHistoryRow {
|
||||
observed_at_utc: string;
|
||||
cpu_percent: number | null;
|
||||
memory_percent: number | null;
|
||||
gpu_percent: number | null;
|
||||
gpu_memory_percent: number | null;
|
||||
network_receive_bytes_per_second: number | null;
|
||||
network_send_bytes_per_second: number | null;
|
||||
}
|
||||
|
||||
export interface WorkerNetworkInterface {
|
||||
name: string;
|
||||
description: string | null;
|
||||
status: string | null;
|
||||
link_speed_bps: number | null;
|
||||
mac_address: string | null;
|
||||
addresses: string[];
|
||||
received_bytes: number | null;
|
||||
sent_bytes: number | null;
|
||||
}
|
||||
|
||||
export interface WorkerTelemetry {
|
||||
schema_version: "missioncore.worker-telemetry/v1";
|
||||
profile: WorkerConnectionProfile;
|
||||
connection: WorkerProbe;
|
||||
node: {
|
||||
node_id: string;
|
||||
observed_at_utc: string;
|
||||
os: {
|
||||
caption?: string;
|
||||
version?: string;
|
||||
uptime_seconds?: number;
|
||||
};
|
||||
cpu: {
|
||||
name?: string;
|
||||
logical_processors?: number;
|
||||
load_percent?: number;
|
||||
};
|
||||
memory: {
|
||||
total_bytes: number | null;
|
||||
used_bytes: number | null;
|
||||
free_bytes: number | null;
|
||||
used_percent: number | null;
|
||||
};
|
||||
disks: Array<{
|
||||
name?: string;
|
||||
size_bytes?: number;
|
||||
free_bytes?: number;
|
||||
}>;
|
||||
gpu: {
|
||||
name?: string;
|
||||
utilization_percent?: number;
|
||||
memory_used_mib?: number;
|
||||
memory_total_mib?: number;
|
||||
power_watts?: number;
|
||||
temperature_celsius?: number;
|
||||
memory_used_percent?: number;
|
||||
} | null;
|
||||
triton: {
|
||||
ready: boolean;
|
||||
requests_succeeded: number | null;
|
||||
requests_failed: number | null;
|
||||
inferences: number | null;
|
||||
};
|
||||
} | null;
|
||||
runtimes: WorkerRuntime[];
|
||||
pipeline: {
|
||||
service_state: string;
|
||||
current_stage: string | null;
|
||||
active_request_id: string | null;
|
||||
active_frame_index: number | null;
|
||||
completed_runs: number | null;
|
||||
failed_runs: number | null;
|
||||
model_load_seconds: number | null;
|
||||
stages: WorkerPipelineStage[];
|
||||
};
|
||||
network: {
|
||||
interfaces: WorkerNetworkInterface[];
|
||||
aggregate: {
|
||||
received_bytes: number;
|
||||
sent_bytes: number;
|
||||
receive_bytes_per_second: number | null;
|
||||
send_bytes_per_second: number | null;
|
||||
} | null;
|
||||
};
|
||||
history: WorkerTelemetryHistoryRow[];
|
||||
}
|
||||
|
||||
export interface WorkerProfileMutation {
|
||||
revision: number;
|
||||
address: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function requireJsonRecord(value: unknown, schema: string): Record<string, unknown> {
|
||||
if (!isRecord(value) || value.schema_version !== schema) {
|
||||
throw new Error("Ответ системной телеметрии не соответствует контракту.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
async function requestJson(
|
||||
url: string,
|
||||
init: RequestInit,
|
||||
): Promise<unknown> {
|
||||
const response = await fetch(url, {
|
||||
...init,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
...(init.body ? { "Content-Type": "application/json" } : {}),
|
||||
...init.headers,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Системный API вернул HTTP ${response.status}.`);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function fetchWorkerTelemetry(
|
||||
signal?: AbortSignal,
|
||||
): Promise<WorkerTelemetry> {
|
||||
const document = requireJsonRecord(
|
||||
await requestJson("/api/v1/system/worker-telemetry?history=90", {
|
||||
method: "GET",
|
||||
signal,
|
||||
}),
|
||||
"missioncore.worker-telemetry/v1",
|
||||
);
|
||||
if (
|
||||
!isRecord(document.connection)
|
||||
|| !isRecord(document.profile)
|
||||
|| !Array.isArray(document.runtimes)
|
||||
|| !isRecord(document.pipeline)
|
||||
|| !isRecord(document.network)
|
||||
|| !Array.isArray(document.history)
|
||||
) {
|
||||
throw new Error("Срез Worker 006 неполон.");
|
||||
}
|
||||
return document as unknown as WorkerTelemetry;
|
||||
}
|
||||
|
||||
export async function fetchWorkerProfile(
|
||||
signal?: AbortSignal,
|
||||
): Promise<WorkerProfileDocument> {
|
||||
const document = requireJsonRecord(
|
||||
await requestJson("/api/v1/system/worker-profile", {
|
||||
method: "GET",
|
||||
signal,
|
||||
}),
|
||||
"missioncore.worker-connection-profile/v1",
|
||||
);
|
||||
if (!isRecord(document.profile) || !isRecord(document.security)) {
|
||||
throw new Error("Профиль Worker 006 неполон.");
|
||||
}
|
||||
return document as unknown as WorkerProfileDocument;
|
||||
}
|
||||
|
||||
export async function testWorkerProfile(
|
||||
request: WorkerProfileMutation,
|
||||
signal?: AbortSignal,
|
||||
): Promise<WorkerProbe> {
|
||||
const document = requireJsonRecord(
|
||||
await requestJson("/api/v1/system/worker-profile/test", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(request),
|
||||
signal,
|
||||
}),
|
||||
"missioncore.worker-probe/v1",
|
||||
);
|
||||
return document as unknown as WorkerProbe;
|
||||
}
|
||||
|
||||
export async function saveWorkerProfile(
|
||||
request: WorkerProfileMutation,
|
||||
signal?: AbortSignal,
|
||||
): Promise<WorkerProfileDocument & { verification: WorkerProbe }> {
|
||||
const document = requireJsonRecord(
|
||||
await requestJson("/api/v1/system/worker-profile", {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(request),
|
||||
signal,
|
||||
}),
|
||||
"missioncore.worker-connection-profile/v1",
|
||||
);
|
||||
if (!isRecord(document.profile) || !isRecord(document.verification)) {
|
||||
throw new Error("Сохранённый профиль Worker 006 неполон.");
|
||||
}
|
||||
return document as unknown as WorkerProfileDocument & { verification: WorkerProbe };
|
||||
}
|
||||
Reference in New Issue
Block a user