133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import type { StatusTone } from "@nodedc/ui-react";
|
|
|
|
import type { BackendStatus } from "@mission-core/plugin-sdk";
|
|
import type { XgridsK1Metrics } from "./api";
|
|
|
|
const phaseLabels: Record<string, string> = {
|
|
idle: "Ожидание",
|
|
scanning: "Поиск Bluetooth",
|
|
device_selected: "Устройство выбрано",
|
|
provisioning: "Передача настроек Wi‑Fi",
|
|
connecting: "Подключение",
|
|
connected: "Сетевой адрес устройства получен",
|
|
starting_live: "Запуск потока",
|
|
live: "Поток в реальном времени",
|
|
replay: "Повтор записи",
|
|
stopping: "Остановка",
|
|
error: "Ошибка",
|
|
};
|
|
|
|
export function phaseLabel(phase: string | null | undefined): string {
|
|
if (!phase) return "Нет состояния";
|
|
return phaseLabels[phase] ?? "Неизвестное состояние";
|
|
}
|
|
|
|
export function phaseTone(phase: string | null | undefined): StatusTone {
|
|
if (!phase) return "neutral";
|
|
if (phase === "error") return "danger";
|
|
if (["live", "replay"].includes(phase)) return "success";
|
|
if (phase === "connected") return "warning";
|
|
if (["scanning", "provisioning", "connecting", "starting_live", "stopping"].includes(phase)) return "accent";
|
|
return "neutral";
|
|
}
|
|
|
|
export function backendLabel(status: BackendStatus): string {
|
|
return {
|
|
unconfigured: "Модель не выбрана",
|
|
checking: "Проверка контура",
|
|
online: "Контур доступен",
|
|
degraded: "Контур ограничен",
|
|
offline: "Контур недоступен",
|
|
}[status];
|
|
}
|
|
|
|
export function backendTone(status: BackendStatus): StatusTone {
|
|
if (status === "online") return "success";
|
|
if (status === "degraded" || status === "checking") return "warning";
|
|
if (status === "unconfigured") return "neutral";
|
|
return "danger";
|
|
}
|
|
|
|
export function eventStatusLabel(status: string): string {
|
|
return {
|
|
connecting: "подключение",
|
|
open: "подключён",
|
|
closed: "закрыт",
|
|
error: "ошибка",
|
|
}[status] ?? "неизвестно";
|
|
}
|
|
|
|
export function finiteMetric(value: number | null | undefined): number | null {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
}
|
|
|
|
export function pipelineLatency(metrics: XgridsK1Metrics | undefined): number | null {
|
|
if (!metrics) return null;
|
|
const direct = finiteMetric(metrics.pipeline_ms ?? metrics.end_to_end_ms);
|
|
if (direct !== null) return direct;
|
|
const segments = [finiteMetric(metrics.mqtt_to_decode_ms), finiteMetric(metrics.publish_ms)]
|
|
.filter((value): value is number => value !== null);
|
|
return segments.length === 2 ? segments.reduce((total, value) => total + value, 0) : null;
|
|
}
|
|
|
|
function nonNegativeMetric(value: number | null | undefined): number | null {
|
|
const finite = finiteMetric(value);
|
|
return finite !== null && finite >= 0 ? finite : null;
|
|
}
|
|
|
|
export interface K1DeviceTelemetry {
|
|
elapsedSeconds: number | null;
|
|
routeDistanceMeters: number | null;
|
|
speedMetersPerSecond: number | null;
|
|
}
|
|
|
|
export interface SpatialActionFailure {
|
|
title: string;
|
|
detail: string;
|
|
}
|
|
|
|
export function spatialActionFailure(
|
|
error: string | null | undefined,
|
|
): SpatialActionFailure | null {
|
|
const detail = error?.trim();
|
|
return detail
|
|
? {
|
|
title: "Действие K1 не выполнено",
|
|
detail,
|
|
}
|
|
: null;
|
|
}
|
|
|
|
export function deviceTelemetry(
|
|
metrics: XgridsK1Metrics | null | undefined,
|
|
): K1DeviceTelemetry {
|
|
return {
|
|
elapsedSeconds: nonNegativeMetric(
|
|
metrics?.device_elapsed_seconds ?? metrics?.elapsed_seconds,
|
|
),
|
|
routeDistanceMeters: nonNegativeMetric(
|
|
metrics?.device_route_distance_meters ?? metrics?.route_distance_meters,
|
|
),
|
|
speedMetersPerSecond: nonNegativeMetric(
|
|
metrics?.device_speed_meters_per_second ??
|
|
metrics?.device_speed_mps ??
|
|
metrics?.speed_meters_per_second,
|
|
),
|
|
};
|
|
}
|
|
|
|
export function formatNumber(value: number | null, digits = 1): string {
|
|
if (value === null) return "—";
|
|
return value.toLocaleString("ru-RU", {
|
|
maximumFractionDigits: digits,
|
|
minimumFractionDigits: digits,
|
|
});
|
|
}
|
|
|
|
export function sourceModeLabel(mode: string | null | undefined): string {
|
|
if (mode === "live") return "Реальное время";
|
|
if (mode === "replay") return "Повтор записи";
|
|
if (mode === "idle") return "Ожидание";
|
|
return "Неизвестно";
|
|
}
|