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
- 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.
170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
/** 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' };
|
||
}
|