Compare commits
No commits in common. "350f3750150c8e7df7245d86f012e42ceae80fd9" and "c55806d180443a1543da85559c738bcf2d4fe7d7" have entirely different histories.
350f375015
...
c55806d180
|
|
@ -1,209 +1,68 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
ResponsiveContainer,
|
|
||||||
ReferenceArea
|
|
||||||
} from 'recharts';
|
|
||||||
|
|
||||||
// ====== Вспомогательные функции ======
|
|
||||||
const getStatusColor = (status) => {
|
|
||||||
switch(status) {
|
|
||||||
case 0: return '#757575'; // тёмно-серый
|
|
||||||
case 1: return '#00C853'; // ярко-зелёный
|
|
||||||
case 2: return '#FF6D00'; // ярко-оранжевый
|
|
||||||
case 3: return '#D50000'; // ярко-красный
|
|
||||||
default: return '#BDBDBD'; // fallback-серый
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusText = (status) => {
|
|
||||||
return {
|
|
||||||
0: 'Нет соединения',
|
|
||||||
1: 'Норма',
|
|
||||||
2: 'Отклонение',
|
|
||||||
3: 'Критично'
|
|
||||||
}[status] || 'Неизвестно';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusDescription = (status) => {
|
|
||||||
return {
|
|
||||||
0: 'Устройство не отвечает',
|
|
||||||
1: 'Параметры в норме',
|
|
||||||
2: 'Обнаружены отклонения от нормы',
|
|
||||||
3: 'Критическое состояние системы'
|
|
||||||
}[status] || 'Статус неизвестен';
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatusIndicator = ({ cx, cy, payload }) => {
|
|
||||||
const status = payload?.status ?? 0;
|
|
||||||
return (
|
|
||||||
<circle
|
|
||||||
cx={cx}
|
|
||||||
cy={cy}
|
|
||||||
r={6}
|
|
||||||
fill={getStatusColor(status)}
|
|
||||||
stroke="#fff"
|
|
||||||
strokeWidth={2}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CustomTooltip = ({ active, payload, label }) => {
|
|
||||||
if (!active || !payload || !payload.length) return null;
|
|
||||||
|
|
||||||
const status = payload[0].payload.status;
|
|
||||||
const statusColor = getStatusColor(status);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{
|
|
||||||
background: '#fff',
|
|
||||||
padding: '10px',
|
|
||||||
border: '1px solid #ccc',
|
|
||||||
borderRadius: '4px',
|
|
||||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
|
||||||
}}>
|
|
||||||
<p><strong>{new Date(label).toLocaleString()}</strong></p>
|
|
||||||
<p style={{ color: payload[0].color }}>
|
|
||||||
Значение: <strong>{payload[0].value.toFixed(2)}</strong>
|
|
||||||
</p>
|
|
||||||
<div style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
padding: '4px 8px',
|
|
||||||
background: `${statusColor}20`,
|
|
||||||
borderLeft: `4px solid ${statusColor}`,
|
|
||||||
borderRadius: '4px'
|
|
||||||
}}>
|
|
||||||
<span style={{
|
|
||||||
width: 12,
|
|
||||||
height: 12,
|
|
||||||
backgroundColor: statusColor,
|
|
||||||
borderRadius: '50%',
|
|
||||||
marginRight: 8
|
|
||||||
}} />
|
|
||||||
<div>
|
|
||||||
<strong>{getStatusText(status)}</strong>
|
|
||||||
<div style={{ fontSize: '0.8em', color: '#666' }}>
|
|
||||||
{getStatusDescription(status)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ====== Основной компонент ======
|
|
||||||
const LineChartComponent = ({
|
const LineChartComponent = ({
|
||||||
data,
|
data,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
metaInfo,
|
metaInfo,
|
||||||
dataKey = 'value',
|
dataKey = 'value',
|
||||||
height = 400
|
lineColor = '#8884d8',
|
||||||
|
height = 400,
|
||||||
|
showLegend = true,
|
||||||
|
showGrid = true,
|
||||||
|
customTooltip,
|
||||||
|
customXAxisFormatter,
|
||||||
|
customYAxis,
|
||||||
|
additionalLines = []
|
||||||
}) => {
|
}) => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', height: `${height}px` }}>
|
||||||
|
{title && <h3>{title}</h3>}
|
||||||
|
{description && (
|
||||||
|
<p style={{ marginTop: -10, color: '#666' }}>{description}</p>
|
||||||
|
)}
|
||||||
|
{metaInfo && (
|
||||||
|
<div style={{ fontSize: 12, color: '#888', marginBottom: 10 }}>
|
||||||
|
{metaInfo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
const getStatusAreas = () => {
|
<ResponsiveContainer width="100%" height="80%">
|
||||||
if (!data || data.length === 0) return null;
|
<LineChart
|
||||||
|
data={data}
|
||||||
const areas = [];
|
margin={{
|
||||||
let currentStatus = data[0].status;
|
top: 5,
|
||||||
let start = data[0].timestamp;
|
right: 30,
|
||||||
|
left: 20,
|
||||||
for (let i = 1; i < data.length; i++) {
|
bottom: 5,
|
||||||
const current = data[i];
|
}}
|
||||||
if (current.status !== currentStatus) {
|
>
|
||||||
areas.push({ status: currentStatus, start, end: current.timestamp });
|
{showGrid && <CartesianGrid strokeDasharray="3 3" />}
|
||||||
currentStatus = current.status;
|
<XAxis
|
||||||
start = current.timestamp;
|
dataKey="timestamp"
|
||||||
}
|
tickFormatter={customXAxisFormatter || ((timestamp) => new Date(timestamp).toLocaleTimeString())}
|
||||||
}
|
/>
|
||||||
|
{customYAxis || <YAxis />}
|
||||||
areas.push({ status: currentStatus, start, end: data[data.length - 1].timestamp });
|
<Tooltip
|
||||||
|
content={customTooltip}
|
||||||
return areas.map((area, i) => (
|
labelFormatter={(timestamp) => new Date(timestamp).toLocaleString()}
|
||||||
<ReferenceArea
|
/>
|
||||||
key={`area-${i}`}
|
{showLegend && <Legend />}
|
||||||
x1={area.start}
|
<Line
|
||||||
x2={area.end}
|
type="monotone"
|
||||||
fill={getStatusColor(area.status)}
|
dataKey={dataKey}
|
||||||
fillOpacity={0.1}
|
stroke={lineColor}
|
||||||
/>
|
activeDot={{ r: 8 }}
|
||||||
));
|
name={title}
|
||||||
};
|
/>
|
||||||
|
{additionalLines.map((lineProps, index) => (
|
||||||
return (
|
<Line key={index} {...lineProps} />
|
||||||
<div style={{ width: '100%', height: `${height}px` }}>
|
))}
|
||||||
{title && <h3>{title}</h3>}
|
</LineChart>
|
||||||
{description && (
|
</ResponsiveContainer>
|
||||||
<p style={{ marginTop: -10, color: '#666' }}>{description}</p>
|
|
||||||
)}
|
|
||||||
{metaInfo && (
|
|
||||||
<div style={{ fontSize: 12, color: '#888', marginBottom: 10 }}>
|
|
||||||
{metaInfo}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
|
|
||||||
<ResponsiveContainer width="100%" height="80%">
|
|
||||||
<LineChart
|
|
||||||
data={data}
|
|
||||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
|
||||||
>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis
|
|
||||||
dataKey="timestamp"
|
|
||||||
tickFormatter={(ts) => new Date(ts).toLocaleTimeString()}
|
|
||||||
/>
|
|
||||||
<YAxis />
|
|
||||||
{getStatusAreas()}
|
|
||||||
<Tooltip content={<CustomTooltip />} />
|
|
||||||
<Legend />
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey={dataKey}
|
|
||||||
stroke="#8884d8"
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={<StatusIndicator />}
|
|
||||||
activeDot={{ r: 8 }}
|
|
||||||
isAnimationActive={false}
|
|
||||||
name={title}
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
|
|
||||||
{/* Легенда статусов */}
|
|
||||||
<div style={{
|
|
||||||
marginTop: 20,
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 20,
|
|
||||||
flexWrap: 'wrap'
|
|
||||||
}}>
|
|
||||||
{[
|
|
||||||
{ status: 1, label: '1 - Норма', color: '#4CAF50' },
|
|
||||||
{ status: 2, label: '2 - Отклонение', color: '#FF9800' },
|
|
||||||
{ status: 3, label: '3 - Критично', color: '#F44336' },
|
|
||||||
{ status: 0, label: '0 - Нет связи', color: '#888' }
|
|
||||||
].map(item => (
|
|
||||||
<div key={item.status} style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<div style={{
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
backgroundColor: item.color,
|
|
||||||
marginRight: 8,
|
|
||||||
borderRadius: '50%'
|
|
||||||
}}></div>
|
|
||||||
<span>{item.label}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LineChartComponent;
|
export default LineChartComponent;
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
// src/Components/StatusLogTable.jsx
|
|
||||||
import React from 'react';
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableContainer,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
Paper,
|
|
||||||
Chip,
|
|
||||||
Typography
|
|
||||||
} from '@mui/material';
|
|
||||||
|
|
||||||
const statusColors = {
|
|
||||||
'0': 'default',
|
|
||||||
'1': 'success',
|
|
||||||
'2': 'warning',
|
|
||||||
'3': 'error'
|
|
||||||
};
|
|
||||||
|
|
||||||
const StatusLogTable = ({ logs }) => {
|
|
||||||
return (
|
|
||||||
<TableContainer component={Paper} sx={{ mt: 2, maxHeight: 400 }}>
|
|
||||||
<Table stickyHeader size="small">
|
|
||||||
<TableHead>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell>Время</TableCell>
|
|
||||||
<TableCell>Устройство</TableCell>
|
|
||||||
<TableCell>Модуль</TableCell>
|
|
||||||
<TableCell>Статус</TableCell>
|
|
||||||
<TableCell>Значение</TableCell>
|
|
||||||
<TableCell>Описание</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableHead>
|
|
||||||
<TableBody>
|
|
||||||
{logs.map((log, index) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell>
|
|
||||||
{new Date(log.timestamp).toLocaleString()}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{log.device}</TableCell>
|
|
||||||
<TableCell>{log.source_id?.split('$')[1]}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Chip
|
|
||||||
label={getStatusText(log.status)}
|
|
||||||
color={statusColors[log.status] || 'default'}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{parseFloat(log.value).toFixed(2)}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Typography variant="body2">
|
|
||||||
{log.description || getStatusDescription(log.status)}
|
|
||||||
</Typography>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</TableContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Вспомогательные функции
|
|
||||||
const getStatusText = (status) => {
|
|
||||||
const statusMap = {
|
|
||||||
'0': 'Нет соединения',
|
|
||||||
'1': 'Норма',
|
|
||||||
'2': 'Отклонение',
|
|
||||||
'3': 'Критично'
|
|
||||||
};
|
|
||||||
return statusMap[status] || 'Неизвестно';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusDescription = (status) => {
|
|
||||||
const descriptions = {
|
|
||||||
'0': 'Устройство не отвечает',
|
|
||||||
'1': 'Параметры в норме',
|
|
||||||
'2': 'Обнаружены отклонения от нормы',
|
|
||||||
'3': 'Критическое состояние системы'
|
|
||||||
};
|
|
||||||
return descriptions[status] || 'Статус неизвестен';
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StatusLogTable;
|
|
||||||
|
|
@ -6,8 +6,6 @@ class MetricsService {
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.subscriptions = new Map();
|
this.subscriptions = new Map();
|
||||||
this.pendingRequests = new Map();
|
this.pendingRequests = new Map();
|
||||||
window.addEventListener('beforeunload', this.cleanupAll.bind(this));
|
|
||||||
window.addEventListener('pagehide', this.cleanupAll.bind(this));
|
|
||||||
|
|
||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
this.cleanupAll();
|
this.cleanupAll();
|
||||||
|
|
@ -42,7 +40,6 @@ class MetricsService {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('metrics-data', ({ metric, data, requestId }) => {
|
this.socket.on('metrics-data', ({ metric, data, requestId }) => {
|
||||||
console.log('Incoming metric update:', metric);
|
|
||||||
if (requestId && this.pendingRequests.has(requestId)) {
|
if (requestId && this.pendingRequests.has(requestId)) {
|
||||||
const { resolve } = this.pendingRequests.get(requestId);
|
const { resolve } = this.pendingRequests.get(requestId);
|
||||||
resolve(data);
|
resolve(data);
|
||||||
|
|
@ -92,8 +89,13 @@ class MetricsService {
|
||||||
subscribeToMetric(metricKey, callback, interval = 5000, filters = {}) {
|
subscribeToMetric(metricKey, callback, interval = 5000, filters = {}) {
|
||||||
this.connectWebSocket();
|
this.connectWebSocket();
|
||||||
|
|
||||||
if (!this.subscriptions.has(metricKey)) {
|
const alreadySubscribed = this.subscriptions.has(metricKey);
|
||||||
this.subscriptions.set(metricKey, []);
|
const callbacks = this.subscriptions.get(metricKey) || [];
|
||||||
|
callbacks.push(callback);
|
||||||
|
this.subscriptions.set(metricKey, callbacks);
|
||||||
|
|
||||||
|
if (!alreadySubscribed) {
|
||||||
|
// Разделяем metricKey на метрику и фильтры
|
||||||
const [metric] = metricKey.split('?');
|
const [metric] = metricKey.split('?');
|
||||||
this.socket.emit('subscribe-metric', {
|
this.socket.emit('subscribe-metric', {
|
||||||
metric,
|
metric,
|
||||||
|
|
@ -102,12 +104,7 @@ class MetricsService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const callbacks = this.subscriptions.get(metricKey);
|
return () => this.unsubscribeFromMetric(metricKey, callback);
|
||||||
callbacks.push(callback);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
this.unsubscribeFromMetric(metricKey, callback);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsubscribeFromMetric(metricKey, callback) {
|
unsubscribeFromMetric(metricKey, callback) {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@ import DateRangeSelector from './Components/DateRangeSelector';
|
||||||
import metricsService from './Components/metricsService';
|
import metricsService from './Components/metricsService';
|
||||||
import { Button, Radio, message, Tag } from 'antd';
|
import { Button, Radio, message, Tag } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import StatusLogTable from './Components/StatusLogTable';
|
|
||||||
import { Box, IconButton, Tooltip as MuiTooltip } from '@mui/material';
|
|
||||||
import { ListAlt } from '@mui/icons-material';
|
|
||||||
|
|
||||||
const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -17,7 +14,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
context = {}
|
context = {}
|
||||||
} = metricInfo || {};
|
} = metricInfo || {};
|
||||||
|
|
||||||
const { device, source_id } = context;
|
const { device, source_id: module } = context;
|
||||||
|
|
||||||
const [chartData, setChartData] = useState([]);
|
const [chartData, setChartData] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
@ -27,45 +24,27 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
const [startDate, setStartDate] = useState(moment().subtract(1, 'hour').toDate());
|
const [startDate, setStartDate] = useState(moment().subtract(1, 'hour').toDate());
|
||||||
const [endDate, setEndDate] = useState(moment().toDate());
|
const [endDate, setEndDate] = useState(moment().toDate());
|
||||||
const [isLiveUpdating, setIsLiveUpdating] = useState(false);
|
const [isLiveUpdating, setIsLiveUpdating] = useState(false);
|
||||||
const [showLogs, setShowLogs] = useState(false);
|
|
||||||
const [statusLogs, setStatusLogs] = useState([]);
|
|
||||||
|
|
||||||
const getSubscriptionKey = () => {
|
const getSubscriptionKey = () => {
|
||||||
const filterParts = [];
|
const filterParts = [];
|
||||||
if (device) filterParts.push(`device=${encodeURIComponent(device)}`);
|
if (device) filterParts.push(`device=${device}`);
|
||||||
if (source_id) filterParts.push(`source_id=${encodeURIComponent(source_id)}`);
|
if (module) filterParts.push(`source_id=${module}`);
|
||||||
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatMetricData = (dataArray) => {
|
const formatMetricData = (dataArray) => {
|
||||||
return dataArray
|
return dataArray
|
||||||
.map(item => ({
|
.map(item => ({
|
||||||
...item,
|
|
||||||
timestamp: item.timestamp,
|
timestamp: item.timestamp,
|
||||||
value: parseFloat(item.value),
|
value: parseFloat(item.value),
|
||||||
name: item.__name__ || metricName,
|
name: item.__name__ || metricName,
|
||||||
status: item.status?.toString() || '0',
|
status: item.status,
|
||||||
device: item.device?.trim() || null,
|
device: item.device?.trim() || null,
|
||||||
source_id: item.source_id || null,
|
source_id: item.source_id || null
|
||||||
description: item.description || description
|
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.timestamp - b.timestamp);
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обновляем логи при изменении данных
|
|
||||||
useEffect(() => {
|
|
||||||
if (chartData.length > 0) {
|
|
||||||
const newLogs = chartData.reduce((acc, point, index) => {
|
|
||||||
if (index === 0 || point.status !== chartData[index - 1].status) {
|
|
||||||
return [...acc, point];
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
setStatusLogs(newLogs);
|
|
||||||
}
|
|
||||||
}, [chartData]);
|
|
||||||
|
|
||||||
|
|
||||||
const fetchHistoricalData = async (start, end) => {
|
const fetchHistoricalData = async (start, end) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
@ -74,7 +53,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
const extendedFilters = {
|
const extendedFilters = {
|
||||||
...filters,
|
...filters,
|
||||||
...(device && { device: device.toString() }),
|
...(device && { device: device.toString() }),
|
||||||
...(source_id && { source_id: source_id.toString() })
|
...(module && { source_id: module.toString() })
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await metricsService.fetchMetricsRange(
|
const data = await metricsService.fetchMetricsRange(
|
||||||
|
|
@ -128,7 +107,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
{
|
{
|
||||||
...filters,
|
...filters,
|
||||||
...(device && { device }),
|
...(device && { device }),
|
||||||
...(source_id && { source_id })
|
...(module && { source_id: module })
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -145,7 +124,6 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Current metric context:', { device, source_id, metricName });
|
|
||||||
let unsubscribe;
|
let unsubscribe;
|
||||||
if (mode === 'realtime') {
|
if (mode === 'realtime') {
|
||||||
unsubscribe = startRealtimeUpdates();
|
unsubscribe = startRealtimeUpdates();
|
||||||
|
|
@ -158,7 +136,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
if (unsubscribe) unsubscribe();
|
if (unsubscribe) unsubscribe();
|
||||||
stopRealtimeUpdates();
|
stopRealtimeUpdates();
|
||||||
};
|
};
|
||||||
}, [mode, metricName, device, source_id]);
|
}, [mode, metricName, device, module]);
|
||||||
|
|
||||||
const metaInfo = [
|
const metaInfo = [
|
||||||
metricMeta.instance && `Instance: ${metricMeta.instance}`,
|
metricMeta.instance && `Instance: ${metricMeta.instance}`,
|
||||||
|
|
@ -202,50 +180,27 @@ const PrometheusChart = ({ metricInfo, chartHeight = 560 }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{device && <Tag color="geekblue">Устройство: {device}</Tag>}
|
{device && <Tag color="geekblue">Устройство: {device}</Tag>}
|
||||||
{source_id && <Tag color="purple">Модуль: {source_id.split('$')[1]}</Tag>}
|
{module && <Tag color="purple">Модуль: {module.split('$')[1]}</Tag>}
|
||||||
|
|
||||||
<Box position="relative">
|
{isLoading ? (
|
||||||
<MuiTooltip title={showLogs ? "Скрыть логи" : "Показать логи"}>
|
<div>Загрузка графика...</div>
|
||||||
<IconButton
|
) : error ? (
|
||||||
onClick={() => setShowLogs(!showLogs)}
|
<div>Ошибка: {error}</div>
|
||||||
sx={{
|
) : chartData.length === 0 ? (
|
||||||
position: 'absolute',
|
<div>Нет данных для метрики: {metricName}</div>
|
||||||
right: 16,
|
) : (
|
||||||
top: 16,
|
<LineChartComponent
|
||||||
zIndex: 1000,
|
data={chartData}
|
||||||
bgcolor: 'background.paper',
|
title={title}
|
||||||
boxShadow: 1
|
description={description}
|
||||||
}}
|
metaInfo={metaInfo}
|
||||||
>
|
height={chartHeight}
|
||||||
<ListAlt />
|
additionalFilters={{
|
||||||
</IconButton>
|
device,
|
||||||
</MuiTooltip>
|
module
|
||||||
|
}}
|
||||||
{isLoading ? (
|
/>
|
||||||
<div>Загрузка графика...</div>
|
)}
|
||||||
) : error ? (
|
|
||||||
<div>Ошибка: {error}</div>
|
|
||||||
) : chartData.length === 0 ? (
|
|
||||||
<div>Нет данных для метрики: {metricName}</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<LineChartComponent
|
|
||||||
data={chartData}
|
|
||||||
title={title}
|
|
||||||
description={description}
|
|
||||||
metaInfo={metaInfo}
|
|
||||||
height={chartHeight}
|
|
||||||
additionalFilters={{
|
|
||||||
device,
|
|
||||||
source_id
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{showLogs && (
|
|
||||||
<StatusLogTable logs={statusLogs} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => {
|
||||||
context: {
|
context: {
|
||||||
device: item.filters?.device,
|
device: item.filters?.device,
|
||||||
source_id: item.filters?.source_id,
|
source_id: item.filters?.source_id,
|
||||||
parent: item
|
parent: item // для построения пути
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue