import React, { useEffect, useMemo, useState } from 'react'; import { Card, Col, Row, Select, Table, Tabs } from 'antd'; import dayjs from 'dayjs'; import { useMetricsStore, NodeMetric } from '../../store/metricsStore'; import { monitoringApi } from '../../api/monitoringApi'; import AreaTrendChart from '../../components/AreaTrendChart'; const NODE_COLORS = ['#1677ff', '#52c41a', '#fa8c16', '#eb2f96', '#722ed1', '#13c2c2']; const TIME_RANGES = [ { label: '5 мин', value: 5 }, { label: '15 мин', value: 15 }, { label: '30 мин', value: 30 }, { label: '1 час', value: 60 }, ]; const bytesToMb = (value?: number) => (value ?? 0) / 1048576; const formatTime = (ts: string) => dayjs(ts).format('HH:mm:ss'); const buildChartData = (metrics: NodeMetric[]) => metrics.map((m) => ({ time: formatTime(m.timestamp), cpu: m.cpu_utilization ?? 0, memoryMb: bytesToMb(m.memory_total), memoryProcessesMb: bytesToMb(m.memory_processes), memoryAtomMb: bytesToMb(m.memory_atom), memoryBinaryMb: bytesToMb(m.memory_binary), memoryEtsMb: bytesToMb(m.memory_ets), memoryAvailableMb: bytesToMb(m.memory_available), ws: m.ws_connections ?? 0, runQueue: m.run_queue ?? 0, processCount: m.process_count ?? 0, activeSessions: m.active_sessions ?? 0, ioIn: m.io_in ?? 0, ioOut: m.io_out ?? 0, mnesiaCommits: m.mnesia_commits ?? 0, mnesiaFailures: m.mnesia_failures ?? 0, tableSizes: m.table_sizes ?? {}, })); const MetricChart: React.FC<{ data: ReturnType; lines: Array<{ key: string; color: string; name: string }>; }> = ({ data, lines }) => ( ); const MonitoringPage: React.FC = () => { const allHistory = useMetricsStore((s) => s.allHistory); const current = useMetricsStore((s) => s.current); const setAllHistory = useMetricsStore((s) => s.setAllHistory); const filterVisibleHistory = useMetricsStore((s) => s.filterVisibleHistory); const visibleHistory = useMetricsStore((s) => s.visibleHistory); const [minutes, setMinutes] = useState(15); const [loadingHistory, setLoadingHistory] = useState(false); useEffect(() => { let cancelled = false; const loadHistory = async () => { setLoadingHistory(true); try { const to = dayjs().toISOString(); const from = dayjs().subtract(minutes, 'minute').toISOString(); const metrics = await monitoringApi.getNodeMetrics(from, to); if (!cancelled && metrics.length > 0) { setAllHistory(metrics); } } catch { // live WS дополняет историю; REST недоступен — не блокируем страницу } finally { if (!cancelled) { setLoadingHistory(false); } } }; loadHistory(); return () => { cancelled = true; }; }, [minutes, setAllHistory]); useEffect(() => { filterVisibleHistory(minutes); }, [minutes, allHistory.length, filterVisibleHistory]); const allNodes = useMemo(() => { const nodes = new Set(); [...allHistory, ...(current ? [current] : [])].forEach((m) => nodes.add(m.node)); return Array.from(nodes).sort(); }, [allHistory, current]); const metricsByNode = useMemo(() => { const source = visibleHistory.length > 0 ? visibleHistory : allHistory; const map = new Map(); source.forEach((m) => { const list = map.get(m.node) || []; list.push(m); map.set(m.node, list); }); return map; }, [visibleHistory, allHistory]); const tabItems = allNodes.map((node, idx) => { const nodeMetrics = metricsByNode.get(node) || []; const chartData = buildChartData(nodeMetrics); const latestTableSizes = chartData.at(-1)?.tableSizes ?? {}; const color = NODE_COLORS[idx % NODE_COLORS.length]; return { key: node, label: {node}, children: ( ({ table, size, }))} columns={[ { title: 'Таблица', dataIndex: 'table', key: 'table' }, { title: 'Записей', dataIndex: 'size', key: 'size' }, ]} /> ), }; }); return (

Мониторинг нод