Восстановлен потерянный код: календари, мониторинг, refresh, stats на list-страницах.
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Card, Col, Row, Select, Tabs } from 'antd';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
import dayjs from 'dayjs';
|
||||
import { useMetricsStore, NodeMetric } from '../../store/metricsStore';
|
||||
|
||||
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 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: (m.memory_total ?? 0) / 1048576,
|
||||
ws: m.ws_connections ?? 0,
|
||||
runQueue: m.run_queue ?? 0,
|
||||
mnesiaCommits: m.mnesia_commits ?? 0,
|
||||
mnesiaFailures: m.mnesia_failures ?? 0,
|
||||
}));
|
||||
|
||||
const MetricChart: React.FC<{ data: ReturnType<typeof buildChartData>; lines: Array<{ key: string; color: string; name: string }> }> = ({
|
||||
data,
|
||||
lines,
|
||||
}) => (
|
||||
<ResponsiveContainer width="100%" height={280}>
|
||||
<LineChart data={data}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="time" minTickGap={30} />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
{lines.map((line) => (
|
||||
<Line key={line.key} type="monotone" dataKey={line.key} stroke={line.color} name={line.name} dot={false} />
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
|
||||
const MonitoringPage: React.FC = () => {
|
||||
const allHistory = useMetricsStore((s) => s.allHistory);
|
||||
const current = useMetricsStore((s) => s.current);
|
||||
const filterVisibleHistory = useMetricsStore((s) => s.filterVisibleHistory);
|
||||
const visibleHistory = useMetricsStore((s) => s.visibleHistory);
|
||||
const [minutes, setMinutes] = useState(15);
|
||||
|
||||
useEffect(() => {
|
||||
filterVisibleHistory(minutes);
|
||||
}, [minutes, allHistory.length, filterVisibleHistory]);
|
||||
|
||||
const allNodes = useMemo(() => {
|
||||
const nodes = new Set<string>();
|
||||
[...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<string, NodeMetric[]>();
|
||||
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 color = NODE_COLORS[idx % NODE_COLORS.length];
|
||||
|
||||
return {
|
||||
key: node,
|
||||
label: <span style={{ color }}>{node}</span>,
|
||||
children: (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Card title="CPU и память (МБ)">
|
||||
<MetricChart
|
||||
data={chartData}
|
||||
lines={[
|
||||
{ key: 'cpu', color, name: 'CPU %' },
|
||||
{ key: 'memoryMb', color: '#1890ff', name: 'Память МБ' },
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} lg={12}>
|
||||
<Card title="WebSocket и очередь">
|
||||
<MetricChart
|
||||
data={chartData}
|
||||
lines={[
|
||||
{ key: 'ws', color: '#52c41a', name: 'WS' },
|
||||
{ key: 'runQueue', color: '#fa8c16', name: 'Run queue' },
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={24} lg={12}>
|
||||
<Card title="Mnesia">
|
||||
<MetricChart
|
||||
data={chartData}
|
||||
lines={[
|
||||
{ key: 'mnesiaCommits', color: '#722ed1', name: 'Commits' },
|
||||
{ key: 'mnesiaFailures', color: '#ff4d4f', name: 'Failures' },
|
||||
]}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<h2 style={{ margin: 0 }}>Мониторинг нод</h2>
|
||||
<Select
|
||||
value={minutes}
|
||||
onChange={setMinutes}
|
||||
options={TIME_RANGES}
|
||||
style={{ width: 120 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{allNodes.length === 0 ? (
|
||||
<Card>Ожидание метрик по WebSocket…</Card>
|
||||
) : (
|
||||
<Tabs items={tabItems} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MonitoringPage;
|
||||
|
||||
Reference in New Issue
Block a user