import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { Activity, AlertTriangle, Cpu, Database, Layers, RefreshCw, Settings2, ListTree, } from 'lucide-react'; import { adminApi } from '../../api/client'; import { ProgressBar } from '../../components/ProgressBar'; 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 { id: number; source_id: number; status: string; stage: string; target: number; fetched: number; processed: number; added: number; duplicates: number; skipped: number; failed: number; error: string | null; percent: number; started_at: string; heartbeat_at: string | null; } interface DebugData { generated_at: string; celery: { workers: Worker[]; error?: string }; queues: Record | { error: string }; corpus: { documents: number; documents_embedded: number; documents_without_embedding: number; fingerprints_estimate: number; elasticsearch_documents: number | null; }; sources: { by_status: Record; active_runs: Run[]; stale_run_ids: number[]; recent_problem_runs: Run[]; }; tasks_24h: Record; recent_failed_tasks: { public_id: string; type: string; error: string; created_at: string }[]; config: Record; } const STAGE_LABELS: Record = { queued: 'в очереди', fetch: 'выборка', index: 'индексация', finished: 'завершено', }; function fmtNum(n: number | null | undefined): string { return n == null ? '—' : n.toLocaleString('ru-RU'); } export function Debug() { const { data, isFetching, error } = useQuery({ queryKey: ['admin-debug'], queryFn: () => adminApi.debug().then((r) => r.data as DebugData), refetchInterval: 5000, }); if (error) { return
Не удалось получить срез состояния: {String(error)}
; } if (!data) { return
Сбор данных…
; } const queuesErr = 'error' in data.queues ? (data.queues as { error: string }).error : null; const queues = queuesErr ? {} : (data.queues as Record); const embedPercent = data.corpus.documents ? (data.corpus.documents_embedded / data.corpus.documents) * 100 : 0; return (

Отладка {isFetching && }

срез от {new Date(data.generated_at).toLocaleTimeString('ru-RU')}
{/* Активные прогоны заливки */} {data.sources.stale_run_ids.length > 0 && (
Прогоны без признаков жизни больше 10 минут: {data.sources.stale_run_ids.join(', ')}. Обычно это упавший или перезапущенный worker-indexer — проверьте его логи и очередь queue.index.
)} {data.sources.active_runs.length ? (
{data.sources.active_runs.map((r) => (
#{r.id} · источник {r.source_id}
))}
) : (

Сейчас ничего не заливается.

)}
{Object.entries(data.sources.by_status).map(([s, c]) => ( {s}: {c} ))}
{/* Воркеры */} {data.celery.error &&

{data.celery.error}

} {data.celery.workers.length ? (
{data.celery.workers.map((w) => (
{w.name} параллельно: {w.concurrency ?? '—'} · в работе: {w.active.length} · зарезервировано: {w.reserved}
{w.active.length ? (
{w.active.map((t) => (
{t.started_ago_s != null ? `${t.started_ago_s}с` : '—'} {t.name} {t.args}
))}
) :

простаивает

}
))}
) :

Воркеры не отвечают на ping.

}
{/* Очереди */} {queuesErr ? (

{queuesErr}

) : ( {Object.entries(queues).map(([name, q]) => ( ))}
ОчередьЖдутВ работеПотребителей
{name} {fmtNum(q.messages)} {fmtNum(q.unacked)} {q.consumers}
)}
{/* Корпус */}
0 ? 'partial' : 'done'} label={`покрытие эмбеддингами (L3): без вектора ${fmtNum(data.corpus.documents_without_embedding)} документов`} />
{/* Проблемные прогоны */} {data.sources.recent_problem_runs.length > 0 && (
{data.sources.recent_problem_runs.map((r) => (
#{r.id} источник {r.source_id} {r.status} получено {r.fetched}/{r.target}, добавлено {r.added}, ошибок {r.failed} {r.error && {r.error}}
))}
)} {/* Задачи пользователей */}
{Object.entries(data.tasks_24h).map(([s, c]) => ( {s}: {c} ))} {!Object.keys(data.tasks_24h).length && задач не было}
{data.recent_failed_tasks.length > 0 && (
Последние упавшие:
{data.recent_failed_tasks.map((t) => (
{new Date(t.created_at).toLocaleString('ru-RU')} {t.type} {t.error || '—'}
))}
)}
{/* Конфигурация */}
{Object.entries(data.config).map(([k, v]) => (
{k} {v}
))}
); } function Card({ icon: Icon, title, children }: { icon: React.ElementType; title: string; children: React.ReactNode }) { return (

{title}

{children}
); } function Metric({ label, value }: { label: string; value: string }) { return (
{value}
{label}
); }