+ {currentPoint?.fullTime || new Date(label).toLocaleString('ru-RU')}
+
{`Значение: ${item.value}`}
@@ -110,26 +153,12 @@ const LineChartComponent = ({
return null;
};
-
if (!data.length) {
return
-
-
+
{
- const point = data.find(p => p.timestamp === timestamp);
- return point?.fullTime || new Date(timestamp).toLocaleString('ru-RU', {
- day: '2-digit',
- month: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
+ const date = new Date(timestamp);
+ const format = getTimeFormat();
+
+ if (format === 'dd.MM HH:mm') {
+ return date.toLocaleString('ru-RU', {
+ day: '2-digit',
+ month: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ } else if (format === 'HH:mm') {
+ return date.toLocaleString('ru-RU', {
+ hour: '2-digit',
+ minute: '2-digit'
+ });
+ } else {
+ return date.toLocaleString('ru-RU', {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ });
+ }
}}
/>
} />
- {Object.keys(chartData).map((instance, index) => (
+ {instanceKeys.map((instance, index) => (
)}
-
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx
index dce480d..991a606 100755
--- a/src/Charts/PrometheusChart.jsx
+++ b/src/Charts/PrometheusChart.jsx
@@ -17,40 +17,57 @@ const PrometheusChart = ({ metricName }) => {
const [selectedGraphRange, setSelectedGraphRange] = useState(null);
const [filteredData, setFilteredData] = useState(null);
const [isSelectingRange, setIsSelectingRange] = useState(false);
+ const [lastCustomRange, setLastCustomRange] = useState(null);
const intervalRef = useRef(null);
const socketRef = useRef(null);
+ const debounceRef = useRef(null);
const formatTime = useCallback((timestamp, rangeSeconds) => {
- const date = new Date(timestamp);
- const showDate = rangeSeconds > 86400; // Показывать дату если диапазон > 24 часов
-
+ const ts = typeof timestamp === 'number' ? timestamp : Date.now();
+ const date = new Date(ts);
+
+ // Определяем формат в зависимости от диапазона
+ const showFullDate = rangeSeconds > 86400; // больше суток
+
+ const timeOptions = {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false
+ };
+
+ const dateOptions = showFullDate ? {
+ month: '2-digit',
+ day: '2-digit',
+ ...timeOptions
+ } : timeOptions;
+
return {
- display: date.toLocaleString([], {
-
- month: showDate ? '2-digit' : undefined,
- day: showDate ? '2-digit' : undefined,
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- }),
- fullDisplay: date.toLocaleString([], {
-
+ display: date.toLocaleString('ru-RU', dateOptions),
+ fullDisplay: date.toLocaleString('ru-RU', {
+ year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
- second: '2-digit'
+ second: '2-digit',
+ hour12: false
}),
- timestamp: timestamp
+ timestamp: ts
};
}, []);
const calculateStep = useCallback((start, end) => {
const range = end - start;
- if (range <= 3600) return 5;
- if (range <= 21600) return 30;
- if (range <= 86400) return 120;
- return 300;
+ if (range <= 60) return 1; // 1 мин
+ if (range <= 300) return 5; // 5 мин
+ if (range <= 1800) return 15; // 30 мин
+ if (range <= 3600) return 30; // 1 час
+ if (range <= 10800) return 60; // 3 часа
+ if (range <= 21600) return 120; // 6 часов
+ if (range <= 43200) return 300; // 12 часов
+ if (range <= 86400) return 600; // 24 часа
+ return 1800; // > 24 часов
}, []);
const fetchData = useCallback(() => {
@@ -73,70 +90,52 @@ const PrometheusChart = ({ metricName }) => {
}
}, [metricName, selectedRange.value, isSelectingRange]);
- const groupBySecond = (points) => {
- const grouped = [];
- const timeMap = {};
-
- points.forEach(point => {
- const timeKey = Math.floor(point.timestamp / 1000);
- if (!timeMap[timeKey]) {
- timeMap[timeKey] = { ...point, count: 1 };
- grouped.push(timeMap[timeKey]);
- } else {
- timeMap[timeKey].value = (timeMap[timeKey].value * timeMap[timeKey].count + point.value) /
- (timeMap[timeKey].count + 1);
- timeMap[timeKey].count += 1;
- }
- });
-
- return grouped;
- };
-
const processMetricsData = useCallback((response) => {
+ console.log('Processing metrics data:', response);
if (response.metric !== metricName) return;
-
+
const dataArray = Array.isArray(response.data) ? response.data : [response.data];
if (!dataArray.length) return;
-
+
setChartData(prev => {
const newData = { ...(prev || {}) };
-
+ const rangeSeconds = useCustomRange
+ ? (endDate.getTime() - startDate.getTime()) / 1000
+ : selectedRange.value;
+
dataArray.forEach(item => {
const instance = item.instance || 'default';
if (!newData[instance]) newData[instance] = [];
-
- const timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
- const value = parseFloat(item.value);
- const formattedTime = formatTime(timestamp, selectedRange.value);
-
- const existingPointIndex = newData[instance].findIndex(p => p.timestamp === timestamp);
-
- if (existingPointIndex >= 0) {
- newData[instance][existingPointIndex] = {
- time: formattedTime.display,
- fullTime: formattedTime.fullDisplay,
- value: value,
- timestamp: timestamp
- };
+
+ // Унифицированная конвертация timestamp
+ let timestamp;
+ if (typeof item.timestamp === 'number') {
+ // Определяем, в секундах или миллисекундах пришел timestamp
+ timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
} else {
- newData[instance].push({
- time: formattedTime.display,
- fullTime: formattedTime.fullDisplay,
- value: value,
- timestamp: timestamp
- });
+ timestamp = Date.now();
}
+
+ const value = parseFloat(item.value);
+ const formattedTime = formatTime(timestamp, rangeSeconds);
+
+ newData[instance].push({
+ time: formattedTime.display,
+ fullTime: formattedTime.fullDisplay,
+ value: value,
+ timestamp: timestamp
+ });
});
-
+
+ // Сортируем и ограничиваем данные
Object.keys(newData).forEach(instance => {
newData[instance] = newData[instance]
.sort((a, b) => a.timestamp - b.timestamp)
.slice(-1000);
});
-
- return Object.keys(newData).length ? newData : prev;
+ return newData;
});
- }, [metricName, selectedRange.value, formatTime]);
+ }, [metricName, selectedRange.value, formatTime, useCustomRange, startDate, endDate]);
const setupWebSocket = useCallback(() => {
if (socketRef.current) {
@@ -190,7 +189,7 @@ const PrometheusChart = ({ metricName }) => {
const fetchCustomRangeData = useCallback(async () => {
const start = Math.floor(startDate.getTime() / 1000);
const end = Math.floor(endDate.getTime() / 1000);
- const step = calculateStep(start, end);
+ const rangeSeconds = end - start;
try {
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, {
@@ -198,18 +197,21 @@ const PrometheusChart = ({ metricName }) => {
metric: metricName,
start,
end,
- step
+ step: calculateStep(start, end)
}
});
- if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую
+ if (response.data?.length) {
+ // Преобразуем данные перед передачей в processMetricsData
+ const processedData = response.data.map(item => ({
+ ...item,
+ timestamp: item.timestamp, // оставляем в секундах - processMetricsData конвертирует
+ value: item.value.toString()
+ }));
+
processMetricsData({
metric: metricName,
- data: response.data.map(item => ({
- ...item,
- timestamp: item.timestamp / 1000, // или item.timestamp если уже в секундах
- value: item.value.toString() // преобразуем в строку, как ожидает processMetricsData
- }))
+ data: processedData
});
}
} catch (error) {
@@ -219,6 +221,12 @@ const PrometheusChart = ({ metricName }) => {
const handleRangeChange = useCallback((event) => {
+ // Очищаем текущий интервал
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+
const selectedValue = event.target.value;
const range = TIME_RANGES.find(r => r.value === parseInt(selectedValue, 10));
@@ -232,50 +240,131 @@ const PrometheusChart = ({ metricName }) => {
setEndDate(now);
setStartDate(new Date(now.getTime() - range.value * 1000));
- // Переподключение сокета при возврате к стандартным диапазонам
+ // Переподключение сокета
if (!socketRef.current?.connected) {
socketRef.current?.connect();
}
}, []);
const handleCustomRangeChange = useCallback(() => {
+ // Отключаем WebSocket соединение
+ if (socketRef.current?.connected) {
+ socketRef.current.disconnect();
+ setConnectionStatus('disconnected');
+ }
+
+ // Очищаем интервал обновления
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+
setUseCustomRange(true);
setChartData(null);
setSelectedGraphRange(null);
setFilteredData(null);
- }, []);
+ fetchCustomRangeData();
+ }, [fetchCustomRangeData]);
const handleResetZoom = useCallback(() => {
setSelectedGraphRange(null);
setFilteredData(null);
setIsSelectingRange(false);
- fetchData();
- }, [fetchData]);
+
+ if (useCustomRange) {
+ fetchCustomRangeData();
+ } else {
+ if (!socketRef.current?.connected) {
+ socketRef.current?.connect();
+ }
+ fetchData();
+ }
+
+ if (lastCustomRange) {
+ handleRangeSelect(lastCustomRange);
+ return;
+ }
+ }, [fetchData, fetchCustomRangeData, useCustomRange]);
+
+ const interpolateData = useCallback((data, targetPointCount) => {
+ if (!data || data.length < 2) return data;
+ if (data.length >= targetPointCount) return data;
+
+ const interpolated = [];
+ const step = (data.length - 1) / (targetPointCount - 1);
+
+ for (let i = 0; i < targetPointCount; i++) {
+ const index = i * step;
+ const lowerIndex = Math.floor(index);
+ const upperIndex = Math.ceil(index);
+
+ if (lowerIndex === upperIndex) {
+ interpolated.push(data[lowerIndex]);
+ continue;
+ }
+
+ const fraction = index - lowerIndex;
+ const interpolatedPoint = {};
+
+ Object.keys(data[lowerIndex]).forEach(key => {
+ if (key === 'timestamp') {
+ interpolatedPoint[key] = data[lowerIndex][key] +
+ fraction * (data[upperIndex][key] - data[lowerIndex][key]);
+
+ // Добавляем отображаемое время
+ const { display, fullDisplay } = formatTime(interpolatedPoint[key],
+ (endDate - startDate) / 1000);
+ interpolatedPoint.time = display;
+ interpolatedPoint.fullTime = fullDisplay;
+ } else if (typeof data[lowerIndex][key] === 'number') {
+ interpolatedPoint[key] = data[lowerIndex][key] +
+ fraction * (data[upperIndex][key] - data[lowerIndex][key]);
+ } else {
+ interpolatedPoint[key] = data[lowerIndex][key];
+ }
+ });
+
+ interpolated.push(interpolatedPoint);
+ }
+
+ return interpolated;
+ }, []);
const handleRangeSelect = useCallback((range) => {
- if (range) {
- // Начало выделения - останавливаем обновления
- setIsSelectingRange(true);
- setSelectedGraphRange(range);
+ setLastCustomRange(range);
+ if (!range || !chartData) return;
- // Отключаем сокет
- if (socketRef.current?.connected) {
- socketRef.current.disconnect();
- }
- // Очищаем интервал
- if (intervalRef.current) {
- clearInterval(intervalRef.current);
- intervalRef.current = null;
- }
- } else {
- // Окончание выделения - возобновляем соединение
- setIsSelectingRange(false);
+ setIsSelectingRange(true);
+ setSelectedGraphRange(range);
- if (!useCustomRange && socketRef.current && !socketRef.current.connected) {
- socketRef.current.connect();
- }
+ // Отключаем автоматические обновления
+ if (socketRef.current?.connected) {
+ socketRef.current.disconnect();
}
- }, [useCustomRange]);
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ intervalRef.current = null;
+ }
+
+ // Получаем все точки и сортируем по времени
+ const allPoints = Object.values(chartData).flat();
+ const sortedPoints = allPoints.sort((a, b) => a.timestamp - b.timestamp);
+
+ // Вычисляем абсолютные индексы
+ const startIndex = Math.floor(range.startIndex * (sortedPoints.length - 1));
+ const endIndex = Math.floor(range.endIndex * (sortedPoints.length - 1));
+
+ // Фильтруем точки по выбранному диапазону
+ const filtered = sortedPoints.slice(startIndex, endIndex + 1);
+
+ // Применяем интерполяцию только если точек меньше 100
+ const interpolated = filtered.length < 100 ?
+ interpolateData(filtered, Math.min(100, filtered.length * 3)) :
+ filtered;
+
+ setFilteredData(interpolated);
+ setIsSelectingRange(false);
+ }, [chartData, interpolateData, formatTime]);
useEffect(() => {
const socket = setupWebSocket();
@@ -285,9 +374,30 @@ const PrometheusChart = ({ metricName }) => {
};
}, [setupWebSocket]);
+ // Обновим useEffect для кастомного диапазона
+ useEffect(() => {
+ if (useCustomRange && !isSelectingRange) {
+ // Очищаем предыдущий таймер
+ if (debounceRef.current) {
+ clearTimeout(debounceRef.current);
+ }
+
+ // Устанавливаем новый таймер с задержкой 500 мс
+ debounceRef.current = setTimeout(() => {
+ fetchCustomRangeData();
+ }, 500);
+ }
+
+ return () => {
+ if (debounceRef.current) {
+ clearTimeout(debounceRef.current);
+ }
+ };
+ }, [useCustomRange, isSelectingRange, startDate, endDate, fetchCustomRangeData]);
+
useEffect(() => {
if (useCustomRange || isSelectingRange) return;
-
+
const fetchDataWrapper = () => {
try {
fetchData();
@@ -295,16 +405,16 @@ const PrometheusChart = ({ metricName }) => {
console.error('Error in interval fetch:', error);
}
};
-
+
// Очищаем предыдущий интервал
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
-
+
// Запускаем сразу и затем по интервалу
fetchDataWrapper();
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
-
+
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
@@ -313,33 +423,24 @@ const PrometheusChart = ({ metricName }) => {
}, [fetchData, selectedRange.interval, useCustomRange, isSelectingRange]);
useEffect(() => {
- if (!chartData || !selectedGraphRange) {
+ if (!selectedGraphRange || !chartData) {
setFilteredData(null);
return;
}
- const { startIndex, endIndex } = selectedGraphRange;
- const allTimes = Object.values(chartData)
- .flat()
- .map(point => point.time)
- .filter((time, index, self) => self.indexOf(time) === index);
+ const allPoints = Object.values(chartData).flat();
+ const sortedPoints = allPoints.sort((a, b) => a.timestamp - b.timestamp);
- const data = allTimes.map(time => {
- const point = { time };
- Object.keys(chartData).forEach(key => {
- const instanceData = chartData[key].find(p => p.time === time);
- point[key] = instanceData ? instanceData.value : null;
- });
- return point;
- }).sort((a, b) => {
- const timeA = chartData[Object.keys(chartData)[0]].find(d => d.time === a.time)?.timestamp;
- const timeB = chartData[Object.keys(chartData)[0]].find(d => d.time === b.time)?.timestamp;
- return timeA - timeB;
- });
+ const startIndex = Math.floor(selectedGraphRange.startIndex * (sortedPoints.length - 1));
+ const endIndex = Math.floor(selectedGraphRange.endIndex * (sortedPoints.length - 1));
- const filtered = data.slice(startIndex, endIndex + 1);
- setFilteredData(filtered);
- }, [selectedGraphRange, chartData]);
+ const filtered = sortedPoints.slice(startIndex, endIndex + 1);
+ const interpolated = filtered.length > 100 ?
+ interpolateData(filtered, 100) :
+ filtered;
+
+ setFilteredData(interpolated);
+ }, [selectedGraphRange, chartData, interpolateData]);
if (chartData === null) {
return
Loading data...
;
@@ -382,7 +483,7 @@ const PrometheusChart = ({ metricName }) => {
chartData={chartData}
metricName={metricName}
colors={COLORS}
- onRangeSelect={handleRangeSelect} // Используем модифицированный обработчик
+ onRangeSelect={handleRangeSelect}
filteredData={filteredData}
/>
diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx
index 58e6e49..c5353f5 100755
--- a/src/Components/TreeChart/tabContent.jsx
+++ b/src/Components/TreeChart/tabContent.jsx
@@ -7,8 +7,6 @@ const getMetricName = (id) => {
return `zvks_apiforsnmp_measure_${id}`;
};
-//!!!!!!!!!!Пофиксить вкладуи с eth4, во всех eth 1-4 открывается именно 4 !!!!!!!!!!!!!
-
// Функция для рекурсивного сбора всех id потомков
const getAllChildIds = (node) => {
let ids = [];
diff --git a/src/Components/hooks/TabContent.jsx b/src/Components/hooks/TabContent.jsx
index 10e58a0..bdb6226 100644
--- a/src/Components/hooks/TabContent.jsx
+++ b/src/Components/hooks/TabContent.jsx
@@ -1,6 +1,5 @@
import SystemStatusChart from "../../Charts/SystemStatusChart";
import TreeTable from "../UI/TreeTable";
-
import FlowChart from "../TreeChart/FlowChart";
const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => {