added complex variables
parent
421d95565c
commit
205ddc71e0
|
|
@ -16,13 +16,13 @@ const formatXAxis = (tickItem) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTooltip = (value, name, props) => {
|
const formatTooltip = (value, name, props) => {
|
||||||
return [`${value.toFixed(2)}`, ` ${name}`];
|
return [`${value.toFixed(2)}`, `Устройство ${name}`];
|
||||||
};
|
};
|
||||||
|
|
||||||
const LineChartComponent = ({
|
const LineChartComponent = ({
|
||||||
data = [],
|
data = [],
|
||||||
multipleLines = false,
|
multipleLines = true, // По умолчанию включаем множественные линии
|
||||||
lineKey = 'device',
|
lineKey = 'device', // Ключ для разделения линий
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
height = 400,
|
height = 400,
|
||||||
|
|
@ -30,27 +30,25 @@ const LineChartComponent = ({
|
||||||
}) => {
|
}) => {
|
||||||
if (!data || data.length === 0) return <div>Нет данных для отображения</div>;
|
if (!data || data.length === 0) return <div>Нет данных для отображения</div>;
|
||||||
|
|
||||||
// Создаем массив уникальных линий
|
// Создаем массив уникальных устройств
|
||||||
const lineKeys = [...new Set(data.map(item => item[lineKey] || 'default'))];
|
const devices = [...new Set(data.map(item => item.device))];
|
||||||
|
|
||||||
// Преобразуем данные в формат, удобный для Recharts
|
// Группируем данные по timestamp для правильного отображения
|
||||||
const chartData = data.reduce((acc, item) => {
|
const timestamps = [...new Set(data.map(item => item.timestamp))].sort();
|
||||||
const timestamp = item.timestamp;
|
|
||||||
const existingPoint = acc.find(p => p.timestamp === timestamp);
|
|
||||||
|
|
||||||
if (existingPoint) {
|
const chartData = timestamps.map(timestamp => {
|
||||||
return acc.map(p =>
|
const point = { timestamp };
|
||||||
p.timestamp === timestamp
|
|
||||||
? { ...p, [item[lineKey] || 'default']: item.value }
|
// Для каждого устройства находим значение в этот timestamp
|
||||||
: p
|
devices.forEach(device => {
|
||||||
|
const deviceData = data.find(item =>
|
||||||
|
item.timestamp === timestamp && item.device === device
|
||||||
);
|
);
|
||||||
}
|
point[`device_${device}`] = deviceData ? deviceData.value : null;
|
||||||
|
});
|
||||||
|
|
||||||
return [...acc, {
|
return point;
|
||||||
timestamp,
|
});
|
||||||
[item[lineKey] || 'default']: item.value
|
|
||||||
}];
|
|
||||||
}, []).sort((a, b) => a.timestamp - b.timestamp);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100%', height: `${height}px` }}>
|
<div style={{ width: '100%', height: `${height}px` }}>
|
||||||
|
|
@ -63,39 +61,27 @@ const LineChartComponent = ({
|
||||||
dataKey="timestamp"
|
dataKey="timestamp"
|
||||||
tickFormatter={formatXAxis}
|
tickFormatter={formatXAxis}
|
||||||
/>
|
/>
|
||||||
<YAxis domain={[0, 25]} />
|
<YAxis />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
formatter={formatTooltip}
|
formatter={formatTooltip}
|
||||||
labelFormatter={(label) => format(new Date(label), 'yyyy-MM-dd HH:mm:ss')}
|
labelFormatter={(label) => format(new Date(label), 'yyyy-MM-dd HH:mm:ss')}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
|
|
||||||
{multipleLines ? (
|
{devices.map(device => (
|
||||||
lineKeys.map(key => (
|
|
||||||
<Line
|
<Line
|
||||||
key={`line-${key}`}
|
key={`line-${device}`}
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey={key}
|
dataKey={`device_${device}`}
|
||||||
name={` ${key}`}
|
name={`Устройство ${device}`}
|
||||||
stroke={lineColors[key] || lineColors.default}
|
stroke={lineColors[device] || lineColors.default}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
dot={false}
|
dot={false}
|
||||||
activeDot={{ r: 6 }}
|
activeDot={{ r: 6 }}
|
||||||
isAnimationActive={false}
|
isAnimationActive={false}
|
||||||
|
connectNulls={true}
|
||||||
/>
|
/>
|
||||||
))
|
))}
|
||||||
) : (
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey={lineKeys[0] || 'value'}
|
|
||||||
name={title}
|
|
||||||
stroke={lineColors.default}
|
|
||||||
strokeWidth={2}
|
|
||||||
dot={false}
|
|
||||||
activeDot={{ r: 6 }}
|
|
||||||
isAnimationActive={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Добавляем диапазоны если они есть */}
|
{/* Добавляем диапазоны если они есть */}
|
||||||
{ranges.map((range, idx) => (
|
{ranges.map((range, idx) => (
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import LineChartComponent from './LineChartComponent';
|
import LineChartComponent from './LineChartComponent';
|
||||||
import DateRangeSelector from '../Charts2/Components/DateRangeSelector';
|
import DateRangeSelector from '../Charts2/Components/DateRangeSelector';
|
||||||
import metricsService from '../Charts2/Components/metricsService';
|
import metricsService from '../Charts2/Components/metricsService';
|
||||||
import { Button, Radio, message, Tag, Spin } from 'antd';
|
import { Button, Radio, message, Tag } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import StatusLogTable from '../Charts2/Components/StatusLogTable';
|
import StatusLogTable from '../Charts2/Components/StatusLogTable';
|
||||||
import { Box, IconButton, Tooltip as MuiTooltip } from '@mui/material';
|
import { Box, IconButton, Tooltip as MuiTooltip } from '@mui/material';
|
||||||
|
|
@ -15,14 +15,12 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
title = metricName,
|
title = metricName,
|
||||||
description,
|
description,
|
||||||
context = {},
|
context = {},
|
||||||
ranges = [],
|
ranges = []
|
||||||
multipleLines = false,
|
|
||||||
lineKey = 'device'
|
|
||||||
} = metricInfo || {};
|
} = metricInfo || {};
|
||||||
|
|
||||||
const { device, source_id } = context;
|
const { device, source_id } = context;
|
||||||
|
|
||||||
const [rawData, setRawData] = useState([]);
|
const [chartData, setChartData] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [metricMeta, setMetricMeta] = useState({});
|
const [metricMeta, setMetricMeta] = useState({});
|
||||||
|
|
@ -32,43 +30,96 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
const [isLiveUpdating, setIsLiveUpdating] = useState(false);
|
const [isLiveUpdating, setIsLiveUpdating] = useState(false);
|
||||||
const [showLogs, setShowLogs] = useState(false);
|
const [showLogs, setShowLogs] = useState(false);
|
||||||
const [statusLogs, setStatusLogs] = useState([]);
|
const [statusLogs, setStatusLogs] = useState([]);
|
||||||
|
const MAX_POINTS = 50;
|
||||||
const MAX_POINTS = 1000;
|
|
||||||
const TIME_WINDOW_MS = 3600 * 1000;
|
const TIME_WINDOW_MS = 3600 * 1000;
|
||||||
|
|
||||||
const subscriptionKey = useMemo(() => {
|
|
||||||
|
// Эта функция может больше не понадобиться, так как
|
||||||
|
// сервис сам генерирует ключи, но оставьте для совместимости
|
||||||
|
const getSubscriptionKey = () => {
|
||||||
const filterParts = [];
|
const filterParts = [];
|
||||||
if (device) filterParts.push(`device=${encodeURIComponent(device)}`);
|
if (device) filterParts.push(`device=${encodeURIComponent(device)}`);
|
||||||
if (source_id) filterParts.push(`source_id=${encodeURIComponent(source_id)}`);
|
if (source_id) filterParts.push(`source_id=${encodeURIComponent(source_id)}`);
|
||||||
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
||||||
}, [metricName, device, source_id]);
|
};
|
||||||
|
|
||||||
const formatMetricData = (responseData) => {
|
const getStatusFromRanges = (value, ranges) => {
|
||||||
const dataArray = Array.isArray(responseData) ? responseData : responseData.data;
|
if (!ranges || ranges.length === 0) return 1;
|
||||||
|
for (const r of ranges) {
|
||||||
|
if (value >= r.min && value <= r.max) {
|
||||||
|
return r.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatMetricData = (dataArray) => {
|
||||||
if (!Array.isArray(dataArray)) {
|
if (!Array.isArray(dataArray)) {
|
||||||
console.error('Expected array but got:', responseData);
|
console.error('Expected array in formatMetricData, got:', typeof dataArray);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataArray.map(item => ({
|
return dataArray.map(item => {
|
||||||
|
if (item.timestamp === undefined || item.value === undefined) {
|
||||||
|
console.warn('Invalid metric item:', item);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
...item,
|
...item,
|
||||||
timestamp: item.timestamp,
|
timestamp: Number(item.timestamp),
|
||||||
value: parseFloat(item.value),
|
value: parseFloat(item.value),
|
||||||
|
status: getStatusFromRanges(parseFloat(item.value), ranges),
|
||||||
name: item.__name__ || metricName,
|
name: item.__name__ || metricName,
|
||||||
status: parseInt(item.status) || 0,
|
|
||||||
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,
|
description: item.description || description
|
||||||
lineId: item[lineKey] || 'default'
|
};
|
||||||
}));
|
}).filter(Boolean)
|
||||||
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateStep = (start, end) => {
|
const calculateStep = (startTime, endTime, maxPoints = 10000) => {
|
||||||
const duration = end.getTime() - start.getTime();
|
const durationSeconds = (endTime.getTime() - startTime.getTime()) / 1000;
|
||||||
return Math.max(Math.floor(duration / (MAX_POINTS * 1000)), 1);
|
return Math.max(Math.ceil(durationSeconds / maxPoints), 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const downsampleData = (data, maxPoints = MAX_POINTS) => {
|
||||||
|
if (data.length <= maxPoints) return [...data];
|
||||||
|
|
||||||
|
const sortedData = [...data].sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
const step = Math.max(1, Math.floor(sortedData.length / maxPoints));
|
||||||
|
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < sortedData.length; i += step) {
|
||||||
|
if (result.length >= maxPoints) break;
|
||||||
|
result.push(sortedData[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
const lastOriginalPoint = sortedData[sortedData.length - 1];
|
||||||
|
if (result[result.length - 1].timestamp !== lastOriginalPoint.timestamp) {
|
||||||
|
result[result.length - 1] = lastOriginalPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
@ -82,19 +133,23 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
|
|
||||||
const step = calculateStep(start, end);
|
const step = calculateStep(start, end);
|
||||||
|
|
||||||
|
// Используем новый метод для исторических данных
|
||||||
const data = await metricsService.fetchMetricsRange(
|
const data = await metricsService.fetchMetricsRange(
|
||||||
metricName,
|
metricName,
|
||||||
Math.floor(start.getTime() / 1000),
|
start.getTime(), // Теперь передаем timestamp в миллисекундах
|
||||||
Math.floor(end.getTime() / 1000),
|
end.getTime(),
|
||||||
step,
|
step,
|
||||||
extendedFilters
|
extendedFilters
|
||||||
);
|
);
|
||||||
|
|
||||||
const responseData = Array.isArray(data) ? data : data.data;
|
const formattedData = formatMetricData(data)
|
||||||
const formattedData = formatMetricData(responseData);
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
setRawData(formattedData);
|
|
||||||
|
|
||||||
if (formattedData.length > 0) {
|
const limitedData = formattedData.length > MAX_POINTS
|
||||||
|
? downsampleData(formattedData, MAX_POINTS)
|
||||||
|
: formattedData;
|
||||||
|
|
||||||
|
if (limitedData.length > 0) {
|
||||||
setMetricMeta({
|
setMetricMeta({
|
||||||
type: data[0]?.type,
|
type: data[0]?.type,
|
||||||
description: data[0]?.description || description,
|
description: data[0]?.description || description,
|
||||||
|
|
@ -102,6 +157,8 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
job: data[0]?.job
|
job: data[0]?.job
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setChartData(limitedData);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Error loading historical data for ${metricName}:`, err);
|
console.error(`Error loading historical data for ${metricName}:`, err);
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
|
|
@ -117,42 +174,55 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
|
|
||||||
const end = new Date();
|
const end = new Date();
|
||||||
const start = new Date(end.getTime() - TIME_WINDOW_MS);
|
const start = new Date(end.getTime() - TIME_WINDOW_MS);
|
||||||
const cutoffTime = Date.now() - TIME_WINDOW_MS;
|
|
||||||
fetchHistoricalData(start, end).finally(() => setIsLoading(false));
|
fetchHistoricalData(start, end).finally(() => setIsLoading(false));
|
||||||
|
|
||||||
|
// Изменяем параметры подписки
|
||||||
return metricsService.subscribeToMetric(
|
return metricsService.subscribeToMetric(
|
||||||
subscriptionKey,
|
metricName, // Теперь передаем просто имя метрики
|
||||||
(newData) => {
|
{ ...filters, device, source_id }, // Фильры отдельным параметром
|
||||||
setRawData(prev => {
|
(update) => { // Колбэк получает объект с данными
|
||||||
const actualData = Array.isArray(newData) ? newData : newData.data;
|
console.log('Received WS update:', update);
|
||||||
const formattedNewData = formatMetricData(actualData)
|
|
||||||
|
if (!update || !Array.isArray(update.data)) {
|
||||||
|
console.error('Invalid update format:', update);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setChartData(prev => {
|
||||||
|
const now = Date.now();
|
||||||
|
const cutoffTime = now - TIME_WINDOW_MS;
|
||||||
|
|
||||||
|
const formattedNew = formatMetricData(update.data)
|
||||||
.filter(point => point.timestamp >= cutoffTime);
|
.filter(point => point.timestamp >= cutoffTime);
|
||||||
|
|
||||||
const filteredPrev = prev.filter(point => point.timestamp >= cutoffTime);
|
const filteredPrev = prev.filter(point =>
|
||||||
|
point.timestamp >= cutoffTime
|
||||||
const merged = [...filteredPrev, ...formattedNewData]
|
|
||||||
.filter((v, i, a) =>
|
|
||||||
a.findIndex(t =>
|
|
||||||
t.timestamp === v.timestamp &&
|
|
||||||
t[lineKey] === v[lineKey]
|
|
||||||
) === i
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return merged;
|
const merged = [...filteredPrev, ...formattedNew]
|
||||||
|
.filter((v, i, a) =>
|
||||||
|
a.findIndex(t => t.timestamp === v.timestamp) === i
|
||||||
|
)
|
||||||
|
.sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
|
return merged.length > MAX_POINTS
|
||||||
|
? merged.slice(-MAX_POINTS)
|
||||||
|
: merged;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
5000,
|
5000 // Интервал обновления (можно настроить)
|
||||||
{
|
|
||||||
...filters,
|
|
||||||
...(device && { device }),
|
|
||||||
...(source_id && { source_id })
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const stopRealtimeUpdates = () => {
|
const stopRealtimeUpdates = () => {
|
||||||
setIsLiveUpdating(false);
|
setIsLiveUpdating(false);
|
||||||
metricsService.unsubscribeFromMetric(subscriptionKey);
|
// Теперь отписываемся по метрике и фильтрам
|
||||||
|
metricsService.unsubscribeFromMetric(
|
||||||
|
metricName,
|
||||||
|
{ ...filters, device, source_id }
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCustomRangeApply = () => {
|
const handleCustomRangeApply = () => {
|
||||||
|
|
@ -162,45 +232,29 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (rawData.length > 0) {
|
console.log('Metric changed:', { metricName, device, source_id, filters });
|
||||||
const logs = [];
|
|
||||||
const devices = [...new Set(rawData.map(item => item[lineKey]))];
|
|
||||||
|
|
||||||
devices.forEach(dev => {
|
|
||||||
const deviceData = rawData
|
|
||||||
.filter(item => item[lineKey] === dev)
|
|
||||||
.sort((a, b) => a.timestamp - b.timestamp);
|
|
||||||
|
|
||||||
if (deviceData.length > 0) {
|
|
||||||
logs.push(deviceData[0]); // Первая точка
|
|
||||||
|
|
||||||
for (let i = 1; i < deviceData.length; i++) {
|
|
||||||
if (deviceData[i].status !== deviceData[i - 1].status) {
|
|
||||||
logs.push(deviceData[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setStatusLogs(logs.sort((a, b) => b.timestamp - a.timestamp));
|
|
||||||
}
|
|
||||||
}, [rawData, lineKey]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let unsubscribe;
|
let unsubscribe;
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
if (mode === 'realtime') {
|
if (mode === 'realtime') {
|
||||||
unsubscribe = startRealtimeUpdates();
|
unsubscribe = startRealtimeUpdates();
|
||||||
} else {
|
} else {
|
||||||
stopRealtimeUpdates();
|
await fetchHistoricalData(startDate, endDate);
|
||||||
fetchHistoricalData(startDate, endDate);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (unsubscribe) unsubscribe();
|
if (unsubscribe) {
|
||||||
stopRealtimeUpdates();
|
unsubscribe(); // Вызываем функцию отписки
|
||||||
|
}
|
||||||
|
if (mode === 'realtime') {
|
||||||
|
stopRealtimeUpdates(); // Дополнительная очистка
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, [mode, metricName, device, source_id]);
|
}, [mode, metricName, device, source_id, JSON.stringify(filters)]); // Добавляем JSON.stringify для фильтров
|
||||||
|
|
||||||
const metaInfo = [
|
const metaInfo = [
|
||||||
metricMeta.instance && `Instance: ${metricMeta.instance}`,
|
metricMeta.instance && `Instance: ${metricMeta.instance}`,
|
||||||
|
|
@ -209,7 +263,7 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
].filter(Boolean).join(' | ');
|
].filter(Boolean).join(' | ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ position: 'relative' }}>
|
<div>
|
||||||
<div style={{ marginBottom: 16 }}>
|
<div style={{ marginBottom: 16 }}>
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
value={mode}
|
value={mode}
|
||||||
|
|
@ -231,10 +285,15 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{mode === 'realtime' && (
|
{mode === 'realtime' && isLiveUpdating && (
|
||||||
<Tag color={isLiveUpdating ? 'green' : 'red'}>
|
<Button
|
||||||
{isLiveUpdating ? 'Обновление в реальном времени' : 'Режим реального времени остановлен'}
|
type="primary"
|
||||||
</Tag>
|
danger
|
||||||
|
onClick={() => setMode('historical')}
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
>
|
||||||
|
Остановить обновление
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -259,26 +318,28 @@ const SystemChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
</MuiTooltip>
|
</MuiTooltip>
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div style={{ height: chartHeight, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
<div>Загрузка графика...</div>
|
||||||
<Spin size="large" tip="Загрузка данных..." />
|
|
||||||
</div>
|
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<div style={{ color: 'red', padding: 20 }}>Ошибка: {error}</div>
|
<div>Ошибка: {error}</div>
|
||||||
) : rawData.length === 0 ? (
|
) : chartData.length === 0 ? (
|
||||||
<div style={{ padding: 20 }}>Нет данных для метрики: {metricName}</div>
|
<div>Нет данных для метрики: {metricName}</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<LineChartComponent
|
<LineChartComponent
|
||||||
data={rawData}
|
data={chartData}
|
||||||
title={title}
|
title={title}
|
||||||
description={description}
|
description={description}
|
||||||
multipleLines={multipleLines}
|
|
||||||
lineKey={lineKey}
|
|
||||||
metaInfo={metaInfo}
|
metaInfo={metaInfo}
|
||||||
height={chartHeight}
|
height={chartHeight}
|
||||||
|
additionalFilters={{
|
||||||
|
device,
|
||||||
|
source_id
|
||||||
|
}}
|
||||||
ranges={ranges}
|
ranges={ranges}
|
||||||
/>
|
/>
|
||||||
{showLogs && <StatusLogTable logs={statusLogs} />}
|
{showLogs && (
|
||||||
|
<StatusLogTable logs={statusLogs} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,16 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
return `${metricName}${filterParts.length ? `?${filterParts.join('&')}` : ''}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getStatusFromRanges = (value, ranges) => {
|
||||||
|
if (!ranges || ranges.length === 0) return 1;
|
||||||
|
for (const r of ranges) {
|
||||||
|
if (value >= r.min && value <= r.max) {
|
||||||
|
return r.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
const formatMetricData = (dataArray) => {
|
const formatMetricData = (dataArray) => {
|
||||||
if (!Array.isArray(dataArray)) {
|
if (!Array.isArray(dataArray)) {
|
||||||
console.error('Expected array in formatMetricData, got:', typeof dataArray);
|
console.error('Expected array in formatMetricData, got:', typeof dataArray);
|
||||||
|
|
@ -59,7 +69,7 @@ const PrometheusChart = ({ metricInfo, chartHeight = 580 }) => {
|
||||||
...item,
|
...item,
|
||||||
timestamp: Number(item.timestamp),
|
timestamp: Number(item.timestamp),
|
||||||
value: parseFloat(item.value),
|
value: parseFloat(item.value),
|
||||||
status: parseInt(item.status || '0'),
|
status: getStatusFromRanges(parseFloat(item.value), ranges),
|
||||||
name: item.__name__ || metricName,
|
name: item.__name__ || metricName,
|
||||||
device: item.device?.trim() || null,
|
device: item.device?.trim() || null,
|
||||||
source_id: item.source_id || null,
|
source_id: item.source_id || null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue