Files
EvoBGP/web/src/lib/monitoring/status.ts
T
Denozordec e558570967
CI / changes (push) Successful in 8s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 28s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 3m36s
feat(web): enhance monitoring page with new status tracking and UI components
- Refactored monitoring page to utilize new UI components from the core library.
- Added detailed job status tracking and error handling features.
- Improved overall layout and responsiveness of the monitoring interface.
- Introduced new derived states for better management of health and job statuses.
- Updated imports to streamline component usage and enhance maintainability.
2026-05-20 11:41:58 +07:00

170 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** 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<string, unknown> };
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' };
}