Обновлены переводы на русский язык для компонентов, включая карточки мониторинга, сетевые панели и настройки. Исправлены описания и метки для улучшения пользовательского интерфейса, а также добавлены новые элементы для поддержки локализации в компонентах, таких как DataGrid и ResourcePage.
119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
import { useMemo } from 'react'
|
||
|
||
import { KpiSparklineCard, type KpiSparklineMetric } from '@/components/patterns/kpi-sparkline-card'
|
||
import { kpiGridClassName } from '@/lib/ui-surface'
|
||
import { aggregateNetworkMetrics, runningJobCount } from '@/queries/overview'
|
||
import type { JobRow, ModuleRow, PeerRow, SpeakerRow } from '@/types/api'
|
||
|
||
function syntheticSparkline(seed: number, points = 9): number[] {
|
||
const base = Math.max(4, seed)
|
||
return Array.from({ length: points }, (_, i) =>
|
||
Math.round(base * (0.82 + (i / points) * 0.18 + Math.sin(i + seed) * 0.04)),
|
||
)
|
||
}
|
||
|
||
function buildMetrics({
|
||
modules,
|
||
peers,
|
||
speakers,
|
||
jobs,
|
||
loading,
|
||
}: {
|
||
modules: ModuleRow[]
|
||
peers: PeerRow[]
|
||
speakers: SpeakerRow[]
|
||
jobs: JobRow[]
|
||
loading?: boolean
|
||
}): KpiSparklineMetric[] {
|
||
const enabledModules = modules.filter((m) => m.enabled !== false).length
|
||
const network = aggregateNetworkMetrics(peers, speakers)
|
||
const bgpPct =
|
||
network.peersEnabled > 0
|
||
? Math.round((network.peersEstablished / network.peersEnabled) * 100)
|
||
: 0
|
||
const running = runningJobCount(jobs)
|
||
const failedJobs = jobs.filter((j) =>
|
||
['failed', 'error', 'cancelled'].includes(j.status.toLowerCase()),
|
||
).length
|
||
|
||
return [
|
||
{
|
||
id: 'bgp',
|
||
title: 'BGP готовность',
|
||
label: 'Установлено / включено',
|
||
value: loading || network.peersEnabled === 0 ? '—' : `${bgpPct}%`,
|
||
delta: loading ? '…' : bgpPct >= 90 ? 'стабильно' : 'внимание',
|
||
deltaVariant: bgpPct >= 90 ? 'success-light' : bgpPct >= 50 ? 'warning-light' : 'destructive-light',
|
||
detail: loading ? '' : `${network.peersEstablished} сессий`,
|
||
tone: bgpPct >= 90 ? 'success' : bgpPct >= 50 ? 'warning' : 'danger',
|
||
sparkline: syntheticSparkline(bgpPct || 40),
|
||
},
|
||
{
|
||
id: 'modules',
|
||
title: 'Модули',
|
||
label: 'Активные списки',
|
||
value: loading ? '—' : `${enabledModules}`,
|
||
delta: loading ? '…' : `${modules.length} всего`,
|
||
deltaVariant: 'primary-light',
|
||
detail: loading ? '' : 'маршрутизация',
|
||
tone: 'info',
|
||
sparkline: syntheticSparkline(enabledModules || 3),
|
||
},
|
||
{
|
||
id: 'speakers',
|
||
title: 'Спикеры',
|
||
label: 'В сети / всего',
|
||
value: loading ? '—' : `${network.speakersOnline}/${network.speakersTotal}`,
|
||
delta:
|
||
loading || network.speakersTotal === 0
|
||
? '…'
|
||
: network.speakersOnline === network.speakersTotal
|
||
? 'все в сети'
|
||
: 'частично',
|
||
deltaVariant:
|
||
network.speakersOnline === network.speakersTotal ? 'success-light' : 'warning-light',
|
||
detail: loading ? '' : 'live-снимок',
|
||
tone: network.speakersOnline === network.speakersTotal ? 'success' : 'warning',
|
||
sparkline: syntheticSparkline(network.speakersOnline || 2),
|
||
},
|
||
{
|
||
id: 'jobs',
|
||
title: 'Задачи',
|
||
label: 'Активные / ошибки',
|
||
value: loading ? '—' : String(running),
|
||
delta: loading ? '…' : failedJobs > 0 ? `${failedJobs} ошибок` : 'без сбоев',
|
||
deltaVariant: failedJobs > 0 ? 'destructive-light' : 'success-light',
|
||
detail: loading ? '' : `${jobs.length} в выборке`,
|
||
tone: failedJobs > 0 ? 'danger' : running > 0 ? 'info' : 'success',
|
||
sparkline: syntheticSparkline(running + failedJobs || 1),
|
||
},
|
||
]
|
||
}
|
||
|
||
export function DashboardKpiSparklineRow({
|
||
modules,
|
||
peers,
|
||
speakers,
|
||
jobs,
|
||
loading,
|
||
}: {
|
||
modules: ModuleRow[]
|
||
peers: PeerRow[]
|
||
speakers: SpeakerRow[]
|
||
jobs: JobRow[]
|
||
loading?: boolean
|
||
}) {
|
||
const metrics = useMemo(
|
||
() => buildMetrics({ modules, peers, speakers, jobs, loading }),
|
||
[modules, peers, speakers, jobs, loading],
|
||
)
|
||
|
||
return (
|
||
<section aria-label="KPI обзора" className={kpiGridClassName}>
|
||
{metrics.map((metric) => (
|
||
<KpiSparklineCard key={metric.id} metric={metric} />
|
||
))}
|
||
</section>
|
||
)
|
||
}
|