/** Pure-helpers для страницы /monitoring. */ export type OverallStatus = 'ok' | 'warn' | 'error' | 'unknown'; export type JobsKpi = { running: number; failed: number; total: number }; export type ReadyStatus = { status: string; checks?: Record }; export type HealthStatus = { ok: boolean; status?: string; error?: string }; export type CheckBadge = { label: string; variant: 'default' | 'secondary' | 'destructive' | 'outline'; class?: string; hint?: string; }; export function toErrorMessage(error: unknown): string { if (error instanceof Error && error.message.trim() !== '') return error.message; return 'Ошибка запроса'; } export function summarizeJobsStatuses(items: Array<{ status?: string }>): JobsKpi { let running = 0; let failed = 0; for (const row of items) { const status = String(row.status ?? '').toLowerCase(); if (status === 'queued' || status === 'running' || status === 'cancel_requested') { running += 1; } if (status === 'failed' || status === 'error' || status === 'canceled') { failed += 1; } } return { running, failed, total: items.length }; } export function deriveOverallStatus(input: { health: HealthStatus | null; ready: ReadyStatus | null; jobs: JobsKpi | null; birdConfigured: boolean; birdHealthy: boolean | null; }): OverallStatus { const { health, ready, jobs, birdConfigured, birdHealthy } = input; if (health === null && ready === null && jobs === null) return 'unknown'; if (!health?.ok) return 'error'; if (ready !== null && ready.status !== 'ready') return 'error'; if (jobs !== null && jobs.failed > 0) return 'warn'; if (birdConfigured && birdHealthy === false) return 'warn'; return 'ok'; } export function overallStatusLabel(status: OverallStatus): string { switch (status) { case 'ok': return 'В норме'; case 'warn': return 'Внимание'; case 'error': return 'Ошибка'; default: return 'Нет данных'; } } export function overallStatusHint( status: OverallStatus, input: { healthOk: boolean | undefined; jobsFailed: number; } ): string { if (status === 'unknown') return 'Нет данных. Запустите обновление.'; if (status === 'error') { if (!input.healthOk) return 'Проверьте доступность API и логи сервиса.'; return 'Readiness не в норме: проверьте postgres/store/jobs.'; } if (status === 'warn') { if (input.jobsFailed > 0) return 'Есть ошибки в задачах: откройте операции и последние jobs.'; return 'Проверьте BGP-сессии и вывод birdc.'; } return 'Критичных отклонений не обнаружено.'; } export function overallBadgeVariant(status: OverallStatus): CheckBadge['variant'] { if (status === 'ok') return 'default'; if (status === 'warn') return 'secondary'; if (status === 'error') return 'destructive'; return 'outline'; } export function overallBadgeClass(status: OverallStatus): string | undefined { if (status === 'ok') return 'border-success/30 bg-success/15 text-success'; if (status === 'warn') return 'border-warning/30 bg-warning/15 text-warning'; return undefined; } /** Нормализация значений ready.checks и health/readiness в badge. */ export function checkStatusBadge(value: unknown): CheckBadge { const raw = String(value ?? '').trim(); const lower = raw.toLowerCase(); if (lower === 'ok' || lower === 'ready' || lower === 'true' || lower === 'up') { return { label: 'OK', variant: 'default', class: 'border-success/30 bg-success/15 text-success' }; } if (lower === 'memory') { return { label: 'In-memory', variant: 'secondary', hint: 'Очередь задач в памяти процесса, не shared между воркерами.' }; } if ( lower === 'failed' || lower === 'false' || lower === 'error' || lower === 'down' || lower === 'unavailable' ) { return { label: 'Ошибка', variant: 'destructive' }; } if (raw === '') { return { label: '—', variant: 'outline' }; } return { label: raw, variant: 'outline' }; } /** Человекочитаемое имя проверки readiness. */ export function checkDisplayName(key: string): string { switch (key) { case 'postgres': return 'PostgreSQL'; case 'store': return 'Хранилище'; case 'jobs': return 'Очередь задач'; default: return key; } } export function livenessBadge(health: HealthStatus | null): CheckBadge { if (health === null) return { label: '—', variant: 'outline' }; if (health.ok) { return { label: 'В норме', variant: 'default', class: 'border-success/30 bg-success/15 text-success' }; } return { label: 'Недоступен', variant: 'destructive' }; } export function readinessBadge(ready: ReadyStatus | null): CheckBadge { if (ready === null) return { label: '—', variant: 'outline' }; if (ready.status === 'ready') { return { label: 'Готов', variant: 'default', class: 'border-success/30 bg-success/15 text-success' }; } return { label: ready.status || 'Не готов', variant: 'destructive' }; }