diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index f1be38e..bdc98c4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -221,7 +221,10 @@ Identity (Universal Auth) и генерирует `.env` заново (`infisica Кнопки «Запустить всё» / «Остановить всё» — массовый старт и кооперативная отмена (флаг `cancel_requested`, воркер останавливается сам на ближайшем тике; уже начатый прогон не рвём посреди записи в базу). -- **Панель отладки** (админка → «Отладка», `GET /api/admin/debug`): один срез — +- **Панель отладки** (админка → «Отладка», `GET /api/admin/debug` + `/health`): + один срез — доступность инфраструктуры (PostgreSQL/Redis/MinIO/ES/Ollama/ + брокер: этот блок показывается даже когда сам срез не собирается, потому что + при аварии первый вопрос — что именно отвалилось), живые воркеры Celery и что именно они крутят, глубина очередей RabbitMQ (в т.ч. `unacked` и число потребителей), покрытие корпуса эмбеддингами, активные и проблемные прогоны, зависшие прогоны (нет heartbeat >10 мин), diff --git a/services/frontend/src/pages/admin/Debug.tsx b/services/frontend/src/pages/admin/Debug.tsx index 321b38d..2a4789c 100644 --- a/services/frontend/src/pages/admin/Debug.tsx +++ b/services/frontend/src/pages/admin/Debug.tsx @@ -1,11 +1,13 @@ import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { - Activity, AlertTriangle, Cpu, Database, Layers, RefreshCw, Settings2, ListTree, + Activity, AlertTriangle, CheckCircle2, Cpu, Database, Layers, RefreshCw, + Server, Settings2, ListTree, XCircle, } from 'lucide-react'; import { adminApi } from '../../api/client'; import { ProgressBar } from '../../components/ProgressBar'; +interface Health { name: string; ok: boolean; detail?: string } interface ActiveTask { name: string; id: string; args: string; started_ago_s: number | null } interface Worker { name: string; concurrency: number | null; reserved: number; active: ActiveTask[] } interface Run { @@ -56,12 +58,23 @@ export function Debug() { queryFn: () => adminApi.debug().then((r) => r.data as DebugData), refetchInterval: 5000, }); + // Отдельным запросом: когда отваливается инфраструктура (брокер, Ollama), + // отладка должна первой показывать ЧТО именно недоступно, а не только следствия + const { data: health } = useQuery({ + queryKey: ['admin-health'], + queryFn: () => adminApi.health().then((r) => r.data as Health[]), + refetchInterval: 10000, + }); - if (error) { - return
Не удалось получить срез состояния: {String(error)}
; - } - if (!data) { - return
Сбор данных…
; + if (error || !data) { + return ( +
+ {error + ?
Не удалось получить срез состояния: {String(error)}
+ :
Сбор данных…
} + +
+ ); } const queuesErr = 'error' in data.queues ? (data.queues as { error: string }).error : null; @@ -80,6 +93,8 @@ export function Debug() { срез от {new Date(data.generated_at).toLocaleTimeString('ru-RU')} + + {/* Активные прогоны заливки */} {data.sources.stale_run_ids.length > 0 && ( @@ -244,6 +259,31 @@ export function Debug() { ); } +/** Что из инфраструктуры доступно прямо сейчас — первый вопрос при разборе аварии. */ +function HealthCard({ health }: { health?: Health[] }) { + return ( + + {health?.length ? ( +
+ {health.map((h) => ( +
+ + {h.ok + ? + : } + {h.name} + + + {h.detail || (h.ok ? 'OK' : 'недоступен')} + +
+ ))} +
+ ) :

Опрос сервисов…

} +
+ ); +} + function Card({ icon: Icon, title, children }: { icon: React.ElementType; title: string; children: React.ReactNode }) { return (