diff --git a/frontend/src/pages/HomeObjectDetail.tsx b/frontend/src/pages/HomeObjectDetail.tsx index 0535423..f306c34 100644 --- a/frontend/src/pages/HomeObjectDetail.tsx +++ b/frontend/src/pages/HomeObjectDetail.tsx @@ -1,6 +1,6 @@ import { ArrowLeftOutlined } from '@ant-design/icons' import { useQueryClient } from '@tanstack/react-query' -import { Alert, Button, Card, Col, Empty, Row, Select, Space, Table, Tag, Typography } from 'antd' +import { Alert, Button, Card, Col, Collapse, Empty, Row, Select, Space, Table, Tag, Typography } from 'antd' import type { ColumnsType } from 'antd/es/table' import dayjs from 'dayjs' import { useMemo, useState } from 'react' @@ -56,13 +56,6 @@ function healthColor(score: number | null | undefined): string { return 'red' } -function formatAge(seconds: number | null | undefined): string { - if (seconds == null) return '-' - if (seconds < 60) return `${seconds}с` - if (seconds < 3600) return `${Math.floor(seconds / 60)}м` - return `${Math.floor(seconds / 3600)}ч ${Math.floor((seconds % 3600) / 60)}м` -} - function uptimeColor(point: DashboardAvailabilityPoint): string { if (point.samples === 0) return '#d9d9d9' if (point.offline > 0) return '#ff4d4f' @@ -92,21 +85,6 @@ function UptimeBars({ points, compact = false }: { points: DashboardAvailability /> ))} - - - {dayjs(points[0].bucket_start).format('DD.MM HH:mm')} - - - {dayjs(points[points.length - 1].bucket_end).format('DD.MM HH:mm')} - - - {!compact ? ( - - online - offline - нет данных - - ) : null} ) } @@ -148,15 +126,14 @@ export function HomeObjectDetailPage() { const grouped = useMemo(() => { const rows = devices?.devices ?? [] let online = 0 - let warn = 0 + let offline = 0 + let unknown = 0 const byRack = new Map() for (const row of rows) { if (row.ping_status === 'online') online += 1 - - if (row.disk_status === 'warn' || row.disk_status === 'failed') { - warn += 1 - } + if (row.ping_status === 'offline') offline += 1 + if (row.ping_status === 'unknown') unknown += 1 const rackName = row.rack_name && row.rack_name.trim() ? row.rack_name : 'Без стойки' if (!byRack.has(rackName)) byRack.set(rackName, []) @@ -165,35 +142,35 @@ export function HomeObjectDetailPage() { const rackGroups = Array.from(byRack.entries()) .sort((a, b) => { - if (a[0] === 'Без стойки') return 1 - if (b[0] === 'Без стойки') return -1 + if (a[0] === 'Без стойки') return -1 + if (b[0] === 'Без стойки') return 1 return a[0].localeCompare(b[0]) }) .map(([rack, rowsInRack]) => ({ rack, rows: [...rowsInRack].sort((a, b) => { - const aProblem = a.has_problem ? 0 : 1 - const bProblem = b.has_problem ? 0 : 1 - if (aProblem !== bProblem) return aProblem - bProblem if (a.ping_status !== b.ping_status) { if (a.ping_status === 'offline') return -1 if (b.ping_status === 'offline') return 1 + if (a.ping_status === 'unknown') return -1 + if (b.ping_status === 'unknown') return 1 } return a.ip.localeCompare(b.ip) }), })) - return { online, warn, rackGroups } + return { online, offline, unknown, rackGroups } }, [devices]) const columns: ColumnsType = [ { title: 'IP / Hostname', key: 'identity', + width: 190, render: (_, row) => ( - + {row.ip} - + {row.hostname ?? '—'} @@ -203,18 +180,24 @@ export function HomeObjectDetailPage() { title: 'Категория', dataIndex: 'category', key: 'category', - width: 130, + width: 100, + ellipsis: true, + }, + { + title: 'Роль', + key: 'role', + width: 90, + render: (_, row) => row.role ?? '—', }, { title: 'Ping', key: 'ping', - width: 190, + width: 140, render: (_, row) => ( {row.ping_status} - {row.ping_checked_at ? dayjs(row.ping_checked_at).format('DD.MM HH:mm:ss') : '—'} - {row.last_ping_ms != null ? ` • ${row.last_ping_ms}ms` : ''} + {row.last_ping_ms != null ? `${row.last_ping_ms}ms` : '—'} ), @@ -222,7 +205,7 @@ export function HomeObjectDetailPage() { { title: 'Uptime', key: 'uptime', - width: 280, + width: 220, render: (_, row) => { const availability = availabilityByDevice.get(Number(row.device_id)) @@ -243,32 +226,6 @@ export function HomeObjectDetailPage() { ) }, }, - { - title: 'Свежесть', - key: 'freshness', - width: 110, - render: (_, row) => {formatAge(row.ping_age_sec)}, - }, - { - title: 'Disk', - key: 'disk', - width: 140, - render: (_, row) => - row.disk_status ? ( - - {row.disk_status} - {row.disk_max_pct != null ? ` (${row.disk_max_pct}%)` : ''} - - ) : ( - '—' - ), - }, - { - title: 'Проблема', - key: 'problem', - width: 100, - render: (_, row) => (row.has_problem ? Да : Нет), - }, ] return ( @@ -319,9 +276,17 @@ export function HomeObjectDetailPage() { - Проблемных + Unknown - {summary?.totals.problem_devices ?? 0} + {grouped.unknown} + + + + + + Offline + + {grouped.offline} @@ -333,14 +298,6 @@ export function HomeObjectDetailPage() { - - - Disk warn/failed - - {grouped.warn} - - - ) : ( - - {grouped.rackGroups.map((rackGroup) => ( - + ({ + key: rackGroup.rack, + label: `${rackGroup.rack} (${rackGroup.rows.length})`, + children: ( loading={devicesLoading} dataSource={rackGroup.rows} rowKey="device_id" size="small" pagination={false} - scroll={{ x: 1300 }} columns={columns} /> - - ))} - + ), + }))} + /> )} ) } -