{locked ? "Подтвердите доступ в системном окне Ubuntu." : "Не удалось связаться со службой. Повторите подключение."}
}
}
diff --git a/apps/node-agent/ui/src/nodeModel.ts b/apps/node-agent/ui/src/nodeModel.ts
index 5c7ffa8..1332546 100644
--- a/apps/node-agent/ui/src/nodeModel.ts
+++ b/apps/node-agent/ui/src/nodeModel.ts
@@ -1,17 +1,15 @@
import type { IconName } from "@nodedc/ui-react";
-export type RootId = "system" | "devices" | "remote";
-export type ViewId = "overview" | "network" | "setup" | "diagnostics" | "usb" | "tailscale" | "ssh";
-export const roots: { id: RootId; label: string; first: ViewId }[] = [
- { id: "system", label: "Состояние системы", first: "overview" },
- { id: "devices", label: "Устройства", first: "usb" },
- { id: "remote", label: "Удалённый контроль", first: "tailscale" },
+export type RootId = "system" | "devices";
+export type ViewId = "overview" | "network" | "diagnostics" | "usb" | "tailscale" | "ssh";
+export const roots: { id: RootId; label: string; first: ViewId | null }[] = [
+ { id: "system", label: "Система", first: "overview" },
+ { id: "devices", label: "Устройства", first: null },
];
export const views: { id: ViewId; root: RootId; label: string; icon: IconName }[] = [
- { id: "overview", root: "system", label: "Обзор компьютера", icon: "activity" },
+ { id: "overview", root: "system", label: "Обзор БК", icon: "activity" },
{ id: "network", root: "system", label: "Сеть", icon: "network" },
- { id: "setup", root: "system", label: "Конфигурация системы", icon: "settings" },
+ { id: "usb", root: "system", label: "USB-устройства", icon: "camera" },
+ { id: "tailscale", root: "system", label: "Tailscale", icon: "globe" },
+ { id: "ssh", root: "system", label: "SSH · доверенные устройства", icon: "key" },
{ id: "diagnostics", root: "system", label: "Диагностика", icon: "clipboard" },
- { id: "usb", root: "devices", label: "USB-устройства", icon: "camera" },
- { id: "tailscale", root: "remote", label: "Tailscale", icon: "globe" },
- { id: "ssh", root: "remote", label: "SSH · доверенные устройства", icon: "key" },
];
diff --git a/apps/node-agent/ui/src/useTailscaleStatus.ts b/apps/node-agent/ui/src/useTailscaleStatus.ts
new file mode 100644
index 0000000..b03206b
--- /dev/null
+++ b/apps/node-agent/ui/src/useTailscaleStatus.ts
@@ -0,0 +1,36 @@
+import { useEffect, useState } from "react";
+import { APIError, request } from "./api";
+
+export interface NetworkStatus { installed: boolean; state: string; online: boolean; addresses: string[] }
+const states: Record = {
+ not_installed: "Не установлен", unavailable: "Служба недоступна", NeedsLogin: "Требуется вход",
+ NeedsMachineAuth: "Ожидаем разрешения администратора сети", Stopped: "Отключён", Starting: "Подключаемся", NoState: "Запускается",
+};
+export function tailscaleLabel(value: NetworkStatus | null, checked: boolean): string {
+ return !checked ? "Проверяем подключение" : !value ? "Состояние недоступно" : value.state === "Running" ? value.online ? "В сети" : "Нет связи с координатором" : states[value.state] ?? "Состояние неизвестно";
+}
+
+// Shared by the overview and provider page so session expiry and network states
+// retain the same meaning. Only the mounted view owns a polling loop.
+export function useTailscaleStatus(failure: (error: unknown) => void, hostRevision: string, revision = 0) {
+ const [checked, setChecked] = useState(false);
+ const [value, setValue] = useState(null);
+ useEffect(() => {
+ let active = true;
+ let timer: ReturnType;
+ async function update() {
+ try {
+ const next = await request("/api/network/tailscale");
+ if (active) setValue(next);
+ } catch (error) {
+ if (active) {
+ setValue(null);
+ if (error instanceof APIError && error.status === 401) failure(error);
+ }
+ } finally { if (active) { setChecked(true); timer = setTimeout(() => void update(), 5000); } }
+ }
+ void update();
+ return () => { active = false; clearTimeout(timer); };
+ }, [revision, hostRevision, failure]);
+ return { value, checked };
+}
diff --git a/docs/node/02_NODE_DESKTOP_SURFACE.md b/docs/node/02_NODE_DESKTOP_SURFACE.md
index 2149d91..65e7926 100644
--- a/docs/node/02_NODE_DESKTOP_SURFACE.md
+++ b/docs/node/02_NODE_DESKTOP_SURFACE.md
@@ -1,5 +1,8 @@
# Node 0.3 — структура настольного приложения
+История композиции 0.3.0. Последующее уточнение владельца, перенос системных
+функций в 0.3.1 и план сопряжения: [03_SYSTEM_AND_VEHICLE_PAIRING_SURFACE.md](03_SYSTEM_AND_VEHICLE_PAIRING_SURFACE.md).
+
Решение владельца от 2026-09-05: привести доступные функции Node к визуальной
системе действующего Mission Core, повторно использовать шапку, навигацию и
длинные строки AI Inference. Это согласование состава вкладок и композиции.
diff --git a/docs/node/03_SYSTEM_AND_VEHICLE_PAIRING_SURFACE.md b/docs/node/03_SYSTEM_AND_VEHICLE_PAIRING_SURFACE.md
new file mode 100644
index 0000000..83f516e
--- /dev/null
+++ b/docs/node/03_SYSTEM_AND_VEHICLE_PAIRING_SURFACE.md
@@ -0,0 +1,179 @@
+# Система БК и добавление аппарата в Mission Core
+
+Уточнение владельца от 2026-09-05 к MISSIONCOR-76. Базовое тело Ops не
+переписывается. Этот документ фиксирует согласованную композицию следующего
+приращения и порядок реализации; наличие описанного действия не означает,
+что его backend уже реализован. История 0.3.0 остаётся в документе 02.
+
+## Задача и границы сущностей
+
+Оператор настраивает бортовой компьютер (БК), видит фактическое состояние его
+системы и связывает его с аппаратом в Core. Это настройка при первом запуске
+и обслуживание, а не лабораторный отчёт. После сопряжения оператор открывает
+аппарат в парке и работает с его реально зарегистрированными устройствами.
+
+БК — вычислительный корень подключений этого аппарата. USB-контроллер, hub,
+клавиатура и найденная камера относятся к системной инвентаризации. Запись USB
+не является регистрацией сенсора, доказательством его identity, набором
+capabilities или готовностью к съёмке. Устройства — объекты драйверов,
+подключённые и подтверждённые через принятый контракт Plugin SDK v0alpha2.
+
+## Node: согласованное размещение
+
+- Верхний раздел «Система» вместо «Состояние системы».
+- «Обзор БК»: ОС, архитектура, CPU/RAM, имя, сводка фактических подключений.
+ Название редактируется в блоке «Название БК». Самоподтверждающий зелёный
+ статус «Node работает» удаляется. Сведения имеют время получения.
+- «Сеть»: интерфейсы и адреса БК.
+- «USB-устройства»: вся системная USB-инвентаризация перенесена сюда.
+- «Tailscale» и «SSH · доверенные устройства» принадлежат «Системе».
+ Отдельного верхнего «Удалённого контроля» больше нет.
+- «Mission Core»: выбранное место для создания приглашения и обслуживания
+ привязки к Core. Подключение Core отделено от настройки сетевого транспорта.
+- «Диагностика»: существующий очищенный отчёт и замечания инвентаризации.
+ Это ограниченная диагностика, не полный support bundle всех будущих модулей.
+- Отдельная «Конфигурация системы» удаляется. Состояния SSH/Tailscale и
+ оборудования входят в сводку; операции остаются в своих подразделах.
+- Верхние «Устройства» предназначены для драйверных K1, D455 и следующих
+ сенсоров. Системный USB-список там не дублируется. В этом приращении
+ вкладка недоступна до появления рабочего подключения через драйвер;
+ пустой экран и фиктивные зарегистрированные устройства не создаются.
+
+Отдельный раздел телеметрии и VPN-профили сейчас не вводятся: владелец оставил
+их дальнейшую композицию на следующий этап. Текущий обзор показывает
+измеренные сведения, не имитирует будущую телеметрию аппарата.
+
+Выбрана системная композиция вместо трёх прежних верхних разделов: установка
+сети и доступ относятся к одному БК, а сенсоры имеют отдельный жизненный цикл.
+Альтернатива «всё внутри Сети» отклонена для сопряжения: частная достижимость
+и доверенная привязка к Core являются разными фактами. Альтернатива переноса
+всех операций в обзор также отклонена: обзор остаётся компактной сводкой и
+ведёт к соответствующей настройке. Это изменение композиции класса B,
+прямо согласованное владельцем в текущем сообщении, без нового общего контрола.
+
+## Обзор БК: состояния и доказательства
+
+Обзор сохраняет сведения об ОС, архитектуре, CPU/RAM и отдельную форму имени.
+Сводка содержит сеть (включённые интерфейсы с адресами, без loopback), число
+обнаруженных USB-устройств, фактическое состояние Tailscale, локальный ответ
+SSH и количество разрешённых ключей. Действия открывают нужный подраздел.
+Количество ключей не является числом online операторов. Ответ локального
+SSH не обещает удалённый вход. Наличие сетевого адреса не обещает связь с Core.
+
+Во время запроса — загрузка, при ошибке — отсутствие актуальных сведений,
+а не зелёная готовность. Tailscale использует один общий hook статуса с той же
+проверкой истёкшего сеанса и polling, что и его страница. Старый сеанс
+возвращает на штатный вход Node. Диагностические сведения остаются snapshot
+с временем получения и ручным обновлением; новая телеметрия не объявляется.
+
+## Core: Парк → Аппараты
+
+Название «Аппараты» сохраняется. Текущий catalog в productModel.ts с текстами
+о готовности/контрактах заменяется настоящим реестром вместе с backend.
+«Состояние контура» не поглощает реестр. Прежнее «Подключение» K1 остаётся
+отдельным legacy-путём и не используется скрыто для устройств удалённого БК.
+
+В шапке реестра плюс «Добавить аппарат». Канонический modal Window содержит
+выбор варианта, от которого зависит состав формы:
+
+1. С бортовым компьютером — название аппарата и код приглашения, созданный
+ в Node. Успешное подтверждение связывает аппарат с устойчивой identity БК.
+2. Прямое подключение / управление с пульта — регистрация через отдельный
+ реально поддерживаемый адаптер и его поля. Приглашение Node для этого
+ варианта не требуется; вымышленный БК не создаётся. Телеметрия и позиция
+ появляются только если их предоставляет данный адаптер.
+
+Упомянутые владельцем «автономный» и «с пульта» фиксируют разные сценарии
+подключения. Само наличие БК не доказывает автономное движение: аппарат с БК
+может управляться дистанционно. Наземный беспилотный аппарат (UGV), воздушный
+аппарат и другие классы описывают платформу; способ подключения и возможности
+автономности не подменяют этот класс. Первая реализуемая форма — «С бортовым
+компьютером»; неподдерживаемая прямая интеграция не выдаётся за рабочую.
+
+После успешного сопряжения аппарат появляется длинной строкой ResourceRow.
+Глазик с подписью «Открыть аппарат» открывает постоянную конфигурацию выбранного
+аппарата в ApplicationPanel, а не длинную модальную форму. Детали показывают
+название/класс, привязанный БК, актуальность связи, состав устройств и доступные
+действия. При потере сети строка не исчезает: конфигурация сохранена,
+последние данные помечены временем, недоступные команды заблокированы.
+
+Дерево первой интеграции: аппарат → БК → реально зарегистрированные D455/K1.
+Конкретные устройства отображаются по факту появления и регистрации, а не
+заранее как два обязательных online-объекта. Открытие сенсора ведёт в
+предметную конфигурацию и принятые слои данных, не выполняет команду съёмки.
+
+## Сопряжение: обязательный полный сценарий P1
+
+Node: Система → Mission Core → создать приглашение → скопировать код через UI.
+Показываются срок действия и возможность отменить приглашение. Уже привязанный
+БК показывает соответствующий Core и состояние связи; повторная привязка
+проходит явный сценарий. Секрет не сохраняется в истории URL, логах и отчёте.
+
+Core: Парк → Аппараты → плюс → с БК → вставить код → подтвердить сведения
+выбранного БК → связать. Запись в реестре и подтверждение доверия должны
+согласованно переживать обрыв, повторный запрос, рестарт и отмену.
+
+Сохраняются базовые одноразовость, expiry, pin публичной identity, проверка
+версии, ограничение окна сопряжения, один владелец и подтверждённая привязка.
+Ни IP/hostname, ни членство в Tailscale не являются identity или authority.
+Код не является адресом для произвольного сетевого запроса: private endpoint
+и перенаправления проверяются до соединения. После bootstrap Node использует
+исходящий аутентифицированный канал с mTLS; UI не передаёт команды через SSH.
+
+Обязательны replay/expiry/conflict, отмена, partial binding recovery,
+revoke/re-pair, ротация сертификатов и запрет потери identity при ремонте.
+Жизненные циклы enrollment/connectivity/acquisition, revisions, operations,
+capabilities, streams и evidence используют семантику Plugin SDK v0alpha2;
+параллельная runtime-онтология для нового экрана не вводится.
+
+## Устройства и authority
+
+Управление K1 из Core идёт через runtime выбранного БК, его Linux BLE/network
+adapter и K1 Bridge в общей с БК локальной сети. Близость к операторскому Mac
+не нужна. Включённый K1 должен быть достижим для БК и подтвердить identity и
+совместимый профиль. Состояние «рядом и включён» само по себе не даёт права
+команды. Не происходит скрытого fallback в legacy runtime Mac.
+
+Локальный драйвер владеет command session и сырой записью. Открытие глазика
+или reconnect не повторяют START/STOP. Unknown result сохраняется как
+неизвестный результат до разрешённого восстановления. Локальная запись
+продолжается независимо от окна, Core и WAN. Preview/WebRTC не заменяет raw.
+
+## Порядок реализации и приёмка
+
+1. Текущее приращение: перенос существующих страниц в «Систему», обзор БК,
+ переименование полей, устранение отдельной конфигурации и self-status.
+ Сборка .deb и GUI-проверка на физическом Mini.
+2. Закрыть оставшиеся системные проверки P1 по предыдущему отчёту:
+ чистая установка, GUI upgrade/repair, reboot и SSH lifecycle без консоли.
+3. Реализовать backend сопряжения и реестр Core; одновременно выпустить
+ рабочие Node «Mission Core», форму добавления и детали аппарата. Проверить
+ позитивные и негативные сценарии через два интерфейса.
+4. P2: драйвер D455, фактические profiles/options/streams, raw recording,
+ live-слои и открываемая в Core запись. Затем P3: K1 Linux Bridge.
+5. Далее совместный P4 и полный P5 восстановления. Вариант прямого аппарата
+ включается после появления конкретного адаптера и собственной приёмки.
+
+Используемые exports: AppHeader/HeaderNavigation, ApplicationShell/Panel,
+AdminNavigationPanel, SettingsCard, ResourceRow/List, Button/IconButton, Icon,
+Select/TextField/TextAreaField, Window/ConfirmationModal, StatusBadge,
+ActivityIndicator, ToastStack. Новых визуальных примитивов и копий DG нет.
+
+Для текущей приёмки: USB доступен из «Системы»; прежних remote/setup маршрутов
+нет; имя БК сохраняется; сводка не объявляет подключение камеры по USB готовым
+драйвером; Tailscale/SSH открываются из сводки; плюс SSH работает после переноса;
+темы/узкое окно и recovery сеанса не нарушены. Для будущего сопряжения:
+добавленный аппарат сохраняется после reload/restart и открывает именно
+свой БК/устройства, без дубликатов и зависимости от legacy-коннектора.
+
+## Проверка приращения 0.3.1
+
+Через интерфейс изолированной QA-ноды с production API проверены сохранение
+нового имени БК и его отображение в shell; USB в «Системе»; переходы из сводки
+в USB/Tailscale; недоступность Tailscale и восстановление «В сети»; SSH и его
+плюс; открытие формы и Escape с возвратом фокуса. Проверены светлая/тёмная темы,
+сводка и её действия в окне 720×800. После рестарта QA-службы именно из обзора
+приложение возвращается на штатный вход, без ложного статуса Tailscale.
+Typecheck, production build и существующий UI boundary прошли. Это GUI-проверка
+перестановки функций, не закрытие приёмки чистой установки или сопряжения.
+Аппаратная установка и её результат фиксируются отдельно в отчёте Ops.