Исправляет ложные «Ошибки проверок»: GET /v1/ready отдаёт строки ok/memory, а не boolean. Уплотнён ReUI Frame/donut layout на вкладке Система. Co-authored-by: Cursor <[email protected]>
145 lines
4.5 KiB
TypeScript
145 lines
4.5 KiB
TypeScript
import { ColumnDef } from '@tanstack/react-table'
|
||
import { Database, HardDrive, HeartPulse, ListTodo, ShieldCheck } from 'lucide-react'
|
||
import { useMemo } from 'react'
|
||
|
||
import { DataGridPrimaryCell } from '@/components/data-grid-cell'
|
||
import { DataGridSection } from '@/components/data-grid-shell'
|
||
import { StatusBadge } from '@/components/status-badge'
|
||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||
import { useClientDataGrid } from '@/hooks/use-client-data-grid'
|
||
import {
|
||
isReadyCheckOk,
|
||
isSystemReady,
|
||
readyCheckStatusLabel,
|
||
} from '@/lib/metrics'
|
||
import { readyCheckRu } from '@/lib/ui-labels'
|
||
import type { ReadyStatus } from '@/queries/monitoring'
|
||
import { Item, ItemMedia } from '@evobgp/ui/components/item'
|
||
import { cn } from '@evobgp/ui/lib/utils'
|
||
|
||
const READY_CHECK_ICONS: Record<string, typeof Database> = {
|
||
postgres: Database,
|
||
store: HardDrive,
|
||
store_backend: HardDrive,
|
||
jobs: ListTodo,
|
||
}
|
||
|
||
interface ReadyCheckRow {
|
||
id: string
|
||
label: string
|
||
subtitle?: string
|
||
icon: typeof Database
|
||
iconClassName?: string
|
||
status: string
|
||
statusLabel: string
|
||
}
|
||
|
||
export function MonitoringReadyGrid({
|
||
health,
|
||
ready,
|
||
}: {
|
||
health?: { ok?: boolean; status?: string; error?: string } | null
|
||
ready: ReadyStatus
|
||
}) {
|
||
const data = useMemo<ReadyCheckRow[]>(() => {
|
||
const checks = ready.checks ?? {}
|
||
const systemReady = isSystemReady(ready.status)
|
||
const rows: ReadyCheckRow[] = [
|
||
{
|
||
id: 'liveness',
|
||
label: 'Живучесть',
|
||
subtitle: '/v1/health',
|
||
icon: HeartPulse,
|
||
iconClassName: health?.ok ? 'text-success' : 'text-destructive',
|
||
status: health?.ok ? 'ok' : 'error',
|
||
statusLabel: health?.ok ? 'В норме' : 'Ошибка',
|
||
},
|
||
{
|
||
id: 'readiness',
|
||
label: 'Готовность',
|
||
subtitle: '/v1/ready',
|
||
icon: ShieldCheck,
|
||
iconClassName: systemReady ? 'text-success' : 'text-warning',
|
||
status: systemReady ? 'ok' : 'warning',
|
||
statusLabel: systemReady ? 'Готов' : 'Не готов',
|
||
},
|
||
]
|
||
for (const key of Object.keys(checks)) {
|
||
const value = checks[key]
|
||
const ok = isReadyCheckOk(value)
|
||
rows.push({
|
||
id: key,
|
||
label: readyCheckRu(key),
|
||
icon: READY_CHECK_ICONS[key] ?? ListTodo,
|
||
iconClassName: ok ? 'text-success' : 'text-destructive',
|
||
status: ok ? 'ok' : 'error',
|
||
statusLabel: readyCheckStatusLabel(value, ok),
|
||
})
|
||
}
|
||
return rows
|
||
}, [health?.ok, ready.checks, ready.status])
|
||
|
||
const columns = useMemo<ColumnDef<ReadyCheckRow>[]>(
|
||
() => [
|
||
{
|
||
accessorKey: 'label',
|
||
header: ({ column }) => <DataGridColumnHeader column={column} title="Проверка" />,
|
||
cell: ({ row }) => {
|
||
const Icon = row.original.icon
|
||
return (
|
||
<div className="flex min-w-0 items-center gap-2.5 py-0.5">
|
||
<Item
|
||
className={cn(
|
||
'border-background bg-muted flex size-8 shrink-0 items-center justify-center border-2 p-0 shadow-[0_1px_3px_0_rgba(0,0,0,0.14)] dark:border [&_svg]:size-3.5',
|
||
row.original.iconClassName,
|
||
)}
|
||
>
|
||
<ItemMedia variant="icon" className="size-auto">
|
||
<Icon />
|
||
</ItemMedia>
|
||
</Item>
|
||
<DataGridPrimaryCell
|
||
title={row.original.label}
|
||
subtitle={row.original.subtitle}
|
||
/>
|
||
</div>
|
||
)
|
||
},
|
||
meta: { headerTitle: 'Проверка' },
|
||
},
|
||
{
|
||
id: 'status',
|
||
enableSorting: false,
|
||
header: 'Статус',
|
||
cell: ({ row }) => (
|
||
<div className="flex justify-start">
|
||
<StatusBadge status={row.original.status} label={row.original.statusLabel} />
|
||
</div>
|
||
),
|
||
meta: { headerTitle: 'Статус' },
|
||
},
|
||
],
|
||
[],
|
||
)
|
||
|
||
const { table, globalFilter, setGlobalFilter, filteredCount } = useClientDataGrid({
|
||
data,
|
||
columns,
|
||
getSearchText: (row) => `${row.label} ${row.subtitle ?? ''} ${row.statusLabel}`,
|
||
getRowId: (row) => row.id,
|
||
pageSize: 20,
|
||
})
|
||
|
||
return (
|
||
<DataGridSection
|
||
table={table}
|
||
recordCount={filteredCount}
|
||
showPagination={false}
|
||
emptyMessage="Нет проверок"
|
||
searchValue={globalFilter}
|
||
onSearchChange={setGlobalFilter}
|
||
searchPlaceholder="Поиск проверок…"
|
||
/>
|
||
)
|
||
}
|