feat(system): add Worker 006 telemetry and network profile

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 21:06:51 +03:00
parent 438196cd6d
commit 67e12a47a4
17 changed files with 2723 additions and 8 deletions
@@ -46,6 +46,8 @@ import type { WorkspaceRendererProps } from "./contracts";
import { DatasetGatewayWorkspace } from "./DatasetGatewayWorkspace";
import { ContourHealthWorkspace } from "./ContourHealthWorkspace";
import { LaboratoryArchiveWorkspace } from "./laboratory/LaboratoryArchiveWorkspace";
import { ComputeModulesWorkspace } from "./system/ComputeModulesWorkspace";
import { NetworkWorkspace } from "./system/NetworkWorkspace";
function statusTone(status: CapabilityStatus): "success" | "accent" | "warning" | "neutral" {
if (status === "active") return "success";
@@ -1177,6 +1179,10 @@ export function WorkspaceRenderer(props: WorkspaceRendererProps) {
state={props.state}
/>
);
case "compute-modules":
return <ComputeModulesWorkspace />;
case "network-monitor":
return <NetworkWorkspace />;
case "datasets":
return <DatasetGatewayWorkspace />;
case "lab-archive":
@@ -0,0 +1,226 @@
import {
Button,
GlassSurface,
Icon,
StatusBadge,
} from "@nodedc/ui-react";
import { TelemetrySeries } from "../../components/system/TelemetrySeries";
import { WorkerRuntimeCard } from "../../components/system/WorkerRuntimeCard";
import {
formatBytes,
formatDuration,
formatOptionalPercent,
} from "../../components/system/systemFormat";
import { useWorkerTelemetry } from "../../core/system/useWorkerTelemetry";
function pipelineStateLabel(state: string): string {
if (state === "busy") return "Выполняет задачу";
if (state === "ready") return "Готов к задаче";
return "Нет live-состояния";
}
export function ComputeModulesWorkspace() {
const { telemetry, loading, error, refresh } = useWorkerTelemetry();
const node = telemetry?.node ?? null;
const missionCoreRuntimes = telemetry?.runtimes.filter((runtime) => !runtime.external) ?? [];
const externalRuntimes = telemetry?.runtimes.filter((runtime) => runtime.external) ?? [];
const connected = Boolean(
telemetry?.connection.reachable && telemetry.connection.identity_matches && node,
);
const history = telemetry?.history ?? [];
const cpuPercent = node?.cpu.load_percent;
const memoryPercent = node?.memory.used_percent;
const gpuPercent = node?.gpu?.utilization_percent;
const gpuMemoryPercent = node?.gpu?.memory_used_percent;
return (
<div className="system-workspace compute-modules-workspace">
<section className="system-workspace__lead">
<div>
<span className="section-eyebrow">СИСТЕМА / ВЫЧИСЛИТЕЛЬНЫЕ МОДУЛИ</span>
<h2>Worker 006</h2>
<p>
Живой аппаратный и процессинговый срез выделенного узла. Нагрузка внешних
сервисов отделена от Mission Core и не входит в оценку наших runtime.
</p>
</div>
<div className="system-workspace__actions">
<StatusBadge tone={connected ? "success" : "danger"}>
{connected ? "Узел доступен" : "Нет связи с узлом"}
</StatusBadge>
<Button
size="compact"
variant="secondary"
disabled={loading}
onClick={refresh}
icon={<Icon name="refresh" size={14} />}
>
{loading ? "Читаем" : "Обновить"}
</Button>
</div>
</section>
{error ? (
<GlassSurface className="system-workspace__notice" padding="md" tone="soft">
<StatusBadge tone="warning">{error}</StatusBadge>
</GlassSurface>
) : null}
<section className="system-telemetry-grid" aria-label="Аппаратная телеметрия">
<TelemetrySeries
label="CPU"
value={formatOptionalPercent(cpuPercent)}
ceiling={100}
values={history.map((item) => item.cpu_percent)}
/>
<TelemetrySeries
label="RAM"
value={formatOptionalPercent(memoryPercent)}
ceiling={100}
values={history.map((item) => item.memory_percent)}
/>
<TelemetrySeries
label="GPU"
value={formatOptionalPercent(gpuPercent)}
ceiling={100}
values={history.map((item) => item.gpu_percent)}
/>
<TelemetrySeries
label="VRAM"
value={formatOptionalPercent(gpuMemoryPercent)}
ceiling={100}
values={history.map((item) => item.gpu_memory_percent)}
/>
</section>
<GlassSurface className="worker-hardware" padding="lg">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">HARDWARE</span>
<h3>{node?.node_id ?? telemetry?.profile.expected_node_id ?? "Worker 006"}</h3>
<p>{node?.os.caption ?? "Аппаратный профиль недоступен"}</p>
</div>
<StatusBadge tone={node?.node_id ? "success" : "danger"}>
{node?.node_id ? telemetry?.profile.display_name : "Нет данных"}
</StatusBadge>
</header>
<div className="worker-hardware__facts">
<dl>
<div><dt>Процессор</dt><dd>{node?.cpu.name ?? "—"}</dd></div>
<div><dt>Логические ядра</dt><dd>{node?.cpu.logical_processors ?? "—"}</dd></div>
<div><dt>Память занята</dt><dd>{formatBytes(node?.memory.used_bytes)}</dd></div>
<div><dt>Uptime</dt><dd>{formatDuration(node?.os.uptime_seconds)}</dd></div>
</dl>
<dl>
<div><dt>GPU</dt><dd>{node?.gpu?.name ?? "—"}</dd></div>
<div><dt>VRAM занята</dt><dd>{formatBytes(
typeof node?.gpu?.memory_used_mib === "number"
? node.gpu.memory_used_mib * 1024 * 1024
: null,
)}</dd></div>
<div><dt>Температура</dt><dd>{
typeof node?.gpu?.temperature_celsius === "number"
? `${node.gpu.temperature_celsius} °C`
: "—"
}</dd></div>
<div><dt>Мощность</dt><dd>{
typeof node?.gpu?.power_watts === "number"
? `${node.gpu.power_watts.toFixed(1)} Вт`
: "—"
}</dd></div>
</dl>
</div>
<div className="worker-disk-list">
{(node?.disks ?? []).map((disk) => {
const size = typeof disk.size_bytes === "number" ? disk.size_bytes : null;
const free = typeof disk.free_bytes === "number" ? disk.free_bytes : null;
const used = size !== null && free !== null ? size - free : null;
return (
<div key={disk.name ?? "disk"}>
<span>Диск {disk.name ?? "—"}</span>
<strong>{formatBytes(used)} / {formatBytes(size)}</strong>
</div>
);
})}
</div>
</GlassSurface>
<section className="system-runtime-section">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">PROCESSING RUNTIME</span>
<h3>Контейнеры Mission Core</h3>
<p>Два ограниченных runtime: inference server и прикладной perception pipeline.</p>
</div>
<StatusBadge tone={node?.triton.ready ? "success" : "danger"}>
{node?.triton.ready ? "Triton ready" : "Triton недоступен"}
</StatusBadge>
</header>
<div className="worker-runtime-grid">
{missionCoreRuntimes.map((runtime) => (
<WorkerRuntimeCard key={runtime.name} runtime={runtime} />
))}
</div>
</section>
<GlassSurface className="worker-pipeline" padding="lg">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">ТЕКУЩАЯ ЗАДАЧА</span>
<h3>{pipelineStateLabel(telemetry?.pipeline.service_state ?? "unavailable")}</h3>
<p>
{telemetry?.pipeline.active_request_id
? `Run ${telemetry.pipeline.active_request_id}`
: "Очередь свободна; модели остаются загруженными в persistent worker."}
</p>
</div>
<StatusBadge tone={
telemetry?.pipeline.service_state === "busy" ? "warning"
: telemetry?.pipeline.service_state === "ready" ? "success"
: "danger"
}>
{telemetry?.pipeline.service_state ?? "unavailable"}
</StatusBadge>
</header>
<div className="worker-pipeline__summary">
<div><span>Завершено запусков</span><strong>{telemetry?.pipeline.completed_runs ?? "—"}</strong></div>
<div><span>Ошибок запусков</span><strong>{telemetry?.pipeline.failed_runs ?? "—"}</strong></div>
<div><span>Inference success</span><strong>{node?.triton.requests_succeeded ?? "—"}</strong></div>
<div><span>Inference failed</span><strong>{node?.triton.requests_failed ?? "—"}</strong></div>
</div>
<ol className="worker-stage-list">
{(telemetry?.pipeline.stages ?? []).map((stage, index) => (
<li key={stage.id} data-state={stage.state}>
<span>{String(index + 1).padStart(2, "0")}</span>
<strong>{stage.label}</strong>
<small>{
stage.state === "active" ? "выполняется"
: stage.state === "waiting" ? "в очереди"
: stage.state === "ready" ? "готов"
: "нет данных"
}</small>
</li>
))}
</ol>
</GlassSurface>
<section className="system-runtime-section system-runtime-section--external">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">CO-TENANTS / НЕ MISSION CORE</span>
<h3>Внешняя нагрузка узла</h3>
<p>
Эти процессы влияют на общую плату и VRAM, но не считаются расходом Mission Core.
</p>
</div>
</header>
<div className="worker-runtime-grid">
{externalRuntimes.map((runtime) => (
<WorkerRuntimeCard key={runtime.name} runtime={runtime} />
))}
</div>
</section>
</div>
);
}
@@ -0,0 +1,311 @@
import { useEffect, useMemo, useState } from "react";
import {
Button,
GlassSurface,
Icon,
StatusBadge,
TextField,
} from "@nodedc/ui-react";
import { TelemetrySeries } from "../../components/system/TelemetrySeries";
import {
formatBitRate,
formatBytes,
formatLatency,
formatRate,
} from "../../components/system/systemFormat";
import {
fetchWorkerProfile,
saveWorkerProfile,
testWorkerProfile,
type WorkerConnectionProfile,
type WorkerProbe,
} from "../../core/system/workerTelemetry";
import { useWorkerTelemetry } from "../../core/system/useWorkerTelemetry";
type ProfileAction = "idle" | "testing" | "saving";
function probeLabel(probe: WorkerProbe | null): string {
if (!probe) return "Изменения не проверены";
if (!probe.reachable) return "Адрес не отвечает";
if (!probe.identity_matches) return "Ответил другой узел";
return "Worker 006 подтверждён";
}
export function NetworkWorkspace() {
const { telemetry, loading, error, refresh } = useWorkerTelemetry();
const [profile, setProfile] = useState<WorkerConnectionProfile | null>(null);
const [address, setAddress] = useState("");
const [port, setPort] = useState("22");
const [profileAction, setProfileAction] = useState<ProfileAction>("idle");
const [probe, setProbe] = useState<WorkerProbe | null>(null);
const [profileError, setProfileError] = useState<string | null>(null);
const aggregate = telemetry?.network.aggregate ?? null;
const connected = Boolean(
telemetry?.connection.reachable && telemetry.connection.identity_matches,
);
useEffect(() => {
const controller = new AbortController();
void fetchWorkerProfile(controller.signal)
.then((document) => {
if (controller.signal.aborted) return;
setProfile(document.profile);
setAddress(document.profile.address);
setPort(String(document.profile.port));
setProfileError(null);
})
.catch((reason: unknown) => {
if (controller.signal.aborted) return;
setProfileError(reason instanceof Error ? reason.message : "Профиль не прочитан.");
});
return () => controller.abort();
}, []);
const mutation = useMemo(() => {
const numericPort = Number(port);
if (!profile || !Number.isInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
return null;
}
return {
revision: profile.revision,
address: address.trim(),
port: numericPort,
};
}, [address, port, profile]);
const runTest = () => {
if (!mutation) return;
const controller = new AbortController();
setProfileAction("testing");
setProfileError(null);
setProbe(null);
void testWorkerProfile(mutation, controller.signal)
.then(setProbe)
.catch((reason: unknown) => {
setProfileError(reason instanceof Error ? reason.message : "Проверка не выполнена.");
})
.finally(() => setProfileAction("idle"));
};
const saveProfile = () => {
if (!mutation) return;
const controller = new AbortController();
setProfileAction("saving");
setProfileError(null);
void saveWorkerProfile(mutation, controller.signal)
.then((document) => {
setProfile(document.profile);
setAddress(document.profile.address);
setPort(String(document.profile.port));
setProbe(document.verification);
refresh();
})
.catch((reason: unknown) => {
setProfileError(reason instanceof Error ? reason.message : "Профиль не сохранён.");
})
.finally(() => setProfileAction("idle"));
};
return (
<div className="system-workspace network-workspace">
<section className="system-workspace__lead">
<div>
<span className="section-eyebrow">СИСТЕМА / СЕТЬ</span>
<h2>Локальный вычислительный контур</h2>
<p>
Переносимый профиль связи между Mission Core и Worker 006. Адрес можно заменить
при переходе в другую локальную сеть; идентичность узла проверяется до сохранения.
</p>
</div>
<div className="system-workspace__actions">
<StatusBadge tone={connected ? "success" : "danger"}>
{connected ? "Маршрут доступен" : "Маршрут недоступен"}
</StatusBadge>
<Button
size="compact"
variant="secondary"
disabled={loading}
onClick={refresh}
icon={<Icon name="refresh" size={14} />}
>
{loading ? "Читаем" : "Обновить"}
</Button>
</div>
</section>
{(error || profileError) ? (
<GlassSurface className="system-workspace__notice" padding="md" tone="soft">
<StatusBadge tone="warning">{profileError ?? error}</StatusBadge>
</GlassSurface>
) : null}
<section className="network-overview-grid" aria-label="Сетевая телеметрия">
<TelemetrySeries
label="Приём"
value={formatRate(aggregate?.receive_bytes_per_second)}
values={(telemetry?.history ?? []).map(
(item) => item.network_receive_bytes_per_second,
)}
/>
<TelemetrySeries
label="Передача"
value={formatRate(aggregate?.send_bytes_per_second)}
values={(telemetry?.history ?? []).map(
(item) => item.network_send_bytes_per_second,
)}
/>
<div className="network-stat-card">
<span>Сбор телеметрии</span>
<strong>{formatLatency(telemetry?.connection.latency_ms)}</strong>
<small>полный SSH probe Worker 006</small>
</div>
<div className="network-stat-card">
<span>Активные интерфейсы</span>
<strong>{telemetry?.network.interfaces.length ?? "—"}</strong>
<small>по Windows network counters</small>
</div>
</section>
<GlassSurface className="network-profile" padding="lg">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">ПРОФИЛЬ ПОДКЛЮЧЕНИЯ</span>
<h3>Worker 006</h3>
<p>
Реальный Node ID: <code>{profile?.expected_node_id ?? "DESKTOP-OPJ8J04"}</code>.
Пустой адрес использует закреплённый локальный SSH-профиль.
</p>
</div>
<StatusBadge tone={
probe?.reachable && probe.identity_matches ? "success"
: probe ? "danger"
: connected ? "success" : "neutral"
}>
{probe ? probeLabel(probe) : connected ? "Текущий профиль работает" : "Не проверено"}
</StatusBadge>
</header>
<div className="network-profile__form">
<TextField
label="Адрес Worker 006"
hint="IP или hostname"
value={address}
placeholder="из SSH-профиля mission-gpu"
autoComplete="off"
spellCheck={false}
onChange={(event) => {
setAddress(event.target.value);
setProbe(null);
}}
/>
<TextField
label="SSH-порт"
hint="165535"
type="number"
min={1}
max={65535}
value={port}
onChange={(event) => {
setPort(event.target.value);
setProbe(null);
}}
/>
<div className="network-profile__buttons">
<Button
variant="secondary"
disabled={!mutation || profileAction !== "idle"}
onClick={runTest}
>
{profileAction === "testing" ? "Проверяем" : "Проверить"}
</Button>
<Button
variant="primary"
disabled={!mutation || profileAction !== "idle"}
onClick={saveProfile}
>
{profileAction === "saving" ? "Сохраняем" : "Проверить и применить"}
</Button>
</div>
</div>
<dl className="network-profile__security">
<div><dt>Транспорт</dt><dd>SSH · key-only</dd></div>
<div><dt>Host key</dt><dd>strict · pinned</dd></div>
<div><dt>Профиль</dt><dd>{profile?.ssh_host_alias ?? "mission-gpu"}</dd></div>
<div><dt>Учётные данные</dt><dd>не доступны интерфейсу</dd></div>
</dl>
</GlassSurface>
<GlassSurface className="network-topology" padding="lg">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">ФАКТИЧЕСКИЙ МАРШРУТ</span>
<h3>Поток управления и обработки</h3>
<p>Схема собрана из текущего профиля и live health runtime, без demo-узлов.</p>
</div>
</header>
<div className="network-route">
<div data-state="online">
<span>Операторский UI</span>
<strong>Browser</strong>
<small>127.0.0.1:8000</small>
</div>
<i aria-hidden="true" data-state="online" />
<div data-state="online">
<span>Control Plane</span>
<strong>Mission Core</strong>
<small>локальный API</small>
</div>
<i aria-hidden="true" data-state={connected ? "online" : "offline"} />
<div data-state={connected ? "online" : "offline"}>
<span>Вычислительный узел</span>
<strong>Worker 006</strong>
<small>{profile?.address || profile?.ssh_host_alias || "mission-gpu"}:{profile?.port ?? 22}</small>
</div>
<i aria-hidden="true" data-state={
telemetry?.runtimes.some((runtime) => !runtime.external && runtime.state === "running")
? "online" : "offline"
} />
<div data-state={
telemetry?.runtimes.some((runtime) => !runtime.external && runtime.state === "running")
? "online" : "offline"
}>
<span>Runtime</span>
<strong>Triton + Pipeline</strong>
<small>внутренний Docker-контур</small>
</div>
</div>
</GlassSurface>
<section className="network-interface-section">
<header className="system-section-heading">
<div>
<span className="section-eyebrow">ИНТЕРФЕЙСЫ WORKER 006</span>
<h3>Адаптеры и счётчики</h3>
<p>Только активные интерфейсы, которые вернул сам узел.</p>
</div>
</header>
<div className="network-interface-list">
{(telemetry?.network.interfaces ?? []).map((adapter) => (
<GlassSurface className="network-interface-card" padding="md" key={adapter.name}>
<header>
<div>
<strong>{adapter.name}</strong>
<small>{adapter.description ?? "Описание недоступно"}</small>
</div>
<StatusBadge tone={adapter.status === "Up" ? "success" : "warning"}>
{adapter.status ?? "unknown"}
</StatusBadge>
</header>
<dl>
<div><dt>Адреса</dt><dd>{adapter.addresses.join(", ") || "—"}</dd></div>
<div><dt>Link</dt><dd>{formatBitRate(adapter.link_speed_bps)}</dd></div>
<div><dt>Получено</dt><dd>{formatBytes(adapter.received_bytes)}</dd></div>
<div><dt>Отправлено</dt><dd>{formatBytes(adapter.sent_bytes)}</dd></div>
</dl>
</GlassSurface>
))}
</div>
</section>
</div>
);
}