From 46da90fbb6873911e5d081a0434d792b1d261955 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Tue, 8 Apr 2025 01:42:47 -0400 Subject: [PATCH 1/2] Fixed the date and time display --- src/Charts/Components/LineChartComponent.jsx | 58 +++++++++---- src/Charts/PrometheusChart.jsx | 91 +++++++++++--------- 2 files changed, 92 insertions(+), 57 deletions(-) diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 53d859f..1973bac 100755 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -13,24 +13,33 @@ const LineChartComponent = ({ const chartRef = useRef(null); const containerRef = useRef(null); - const allTimes = Object.values(chartData) + const allTimestamps = Object.values(chartData) .flat() - .map(point => point.time) - .filter((time, index, self) => self.indexOf(time) === index); + .map(point => point.timestamp) + .filter((timestamp, index, self) => self.indexOf(timestamp) === index) + .sort((a, b) => a - b); + + const data = allTimestamps.map(timestamp => { + const point = { timestamp }; + + const firstPoint = Object.values(chartData) + .flat() + .find(p => p.timestamp === timestamp); + + if (firstPoint) { + point.time = firstPoint.time; + point.fullTime = firstPoint.fullTime; + } - const data = allTimes.map(time => { - const point = { time }; Object.keys(chartData).forEach(key => { - const instanceData = chartData[key].find(p => p.time === time); + const instanceData = chartData[key].find(p => p.timestamp === timestamp); 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 displayData = filteredData || data; useEffect(() => { @@ -47,7 +56,7 @@ const LineChartComponent = ({ const handleMouseDown = (e) => { if (!e || !e.activeLabel) return; setIsSelecting(true); - setSelectionArea({ start: e.activeLabel, end: null }); + setSelectionArea({ start: e.activeLabel, end: null }); // activeLabel — это timestamp }; const handleMouseMove = (e) => { @@ -63,8 +72,8 @@ const LineChartComponent = ({ return; } - const startIndex = data.findIndex(point => point.time === selectionArea.start); - const endIndex = data.findIndex(point => point.time === selectionArea.end); + const startIndex = data.findIndex(point => point.timestamp === selectionArea.start); + const endIndex = data.findIndex(point => point.timestamp === selectionArea.end); if (startIndex >= 0 && endIndex >= 0) { onRangeSelect({ @@ -78,6 +87,9 @@ const LineChartComponent = ({ const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { + const currentPoint = data.find(point => point.timestamp === label); + const displayTime = currentPoint?.fullTime || new Date(label).toLocaleString(); + return (
-

{`${label}`}

+

{displayTime}

{payload.map((item, index) => (

{`Значение: ${item.value}`} @@ -98,6 +110,7 @@ const LineChartComponent = ({ return null; }; + if (!data.length) { return

Нет данных для отображения
; } @@ -128,9 +141,21 @@ const LineChartComponent = ({ > { + 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' + }); + }} /> + } /> {Object.keys(chartData).map((instance, index) => ( @@ -152,6 +177,7 @@ const LineChartComponent = ({ fill="#4a6baf" /> )} +
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index ab30858..dce480d 100755 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -22,20 +22,21 @@ const PrometheusChart = ({ metricName }) => { const formatTime = useCallback((timestamp, rangeSeconds) => { const date = new Date(timestamp); - if (rangeSeconds > 86400) { - return { - display: date.toLocaleString([], { - day: '2-digit', - month: '2-digit', - year: '2-digit', - hour: '2-digit', - minute: '2-digit' - }), - timestamp: timestamp - }; - } + const showDate = rangeSeconds > 86400; // Показывать дату если диапазон > 24 часов + return { - display: date.toLocaleTimeString([], { + 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([], { + + month: '2-digit', + day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' @@ -53,9 +54,9 @@ const PrometheusChart = ({ metricName }) => { }, []); const fetchData = useCallback(() => { - + if (isSelectingRange) return; - + const now = Math.floor(Date.now() / 1000); const start = now - selectedRange.value; const end = now; @@ -70,7 +71,7 @@ const PrometheusChart = ({ metricName }) => { _t: Date.now() }); } - }, [metricName, selectedRange.value, isSelectingRange]); + }, [metricName, selectedRange.value, isSelectingRange]); const groupBySecond = (points) => { const grouped = []; @@ -106,10 +107,21 @@ const PrometheusChart = ({ metricName }) => { const timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000; const value = parseFloat(item.value); + const formattedTime = formatTime(timestamp, selectedRange.value); - if (!newData[instance].some(p => p.timestamp === timestamp)) { + 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 + }; + } else { newData[instance].push({ - time: formatTime(timestamp, selectedRange.value).display, + time: formattedTime.display, + fullTime: formattedTime.fullDisplay, value: value, timestamp: timestamp }); @@ -117,7 +129,7 @@ const PrometheusChart = ({ metricName }) => { }); Object.keys(newData).forEach(instance => { - newData[instance] = groupBySecond(newData[instance]) + newData[instance] = newData[instance] .sort((a, b) => a.timestamp - b.timestamp) .slice(-1000); }); @@ -179,7 +191,7 @@ const PrometheusChart = ({ metricName }) => { const start = Math.floor(startDate.getTime() / 1000); const end = Math.floor(endDate.getTime() / 1000); const step = calculateStep(start, end); - + try { const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, { params: { @@ -189,7 +201,7 @@ const PrometheusChart = ({ metricName }) => { step } }); - + if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую processMetricsData({ metric: metricName, @@ -209,17 +221,17 @@ const PrometheusChart = ({ metricName }) => { const handleRangeChange = useCallback((event) => { const selectedValue = event.target.value; const range = TIME_RANGES.find(r => r.value === parseInt(selectedValue, 10)); - + setSelectedRange(range); setUseCustomRange(false); setChartData(null); setSelectedGraphRange(null); setFilteredData(null); - + const now = new Date(); setEndDate(now); setStartDate(new Date(now.getTime() - range.value * 1000)); - + // Переподключение сокета при возврате к стандартным диапазонам if (!socketRef.current?.connected) { socketRef.current?.connect(); @@ -236,7 +248,7 @@ const PrometheusChart = ({ metricName }) => { const handleResetZoom = useCallback(() => { setSelectedGraphRange(null); setFilteredData(null); - setIsSelectingRange(false); + setIsSelectingRange(false); fetchData(); }, [fetchData]); @@ -245,7 +257,7 @@ const PrometheusChart = ({ metricName }) => { // Начало выделения - останавливаем обновления setIsSelectingRange(true); setSelectedGraphRange(range); - + // Отключаем сокет if (socketRef.current?.connected) { socketRef.current.disconnect(); @@ -258,7 +270,7 @@ const PrometheusChart = ({ metricName }) => { } else { // Окончание выделения - возобновляем соединение setIsSelectingRange(false); - + if (!useCustomRange && socketRef.current && !socketRef.current.connected) { socketRef.current.connect(); } @@ -274,16 +286,8 @@ const PrometheusChart = ({ metricName }) => { }, [setupWebSocket]); useEffect(() => { - if (useCustomRange) { - if (socketRef.current?.connected) { - socketRef.current.disconnect(); - } - fetchCustomRangeData(); - return; - } - - if (!socketRef.current?.connected || isSelectingRange) return; - + if (useCustomRange || isSelectingRange) return; + const fetchDataWrapper = () => { try { fetchData(); @@ -291,17 +295,22 @@ 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); - intervalRef.current = null; } }; - }, [fetchData, fetchCustomRangeData, selectedRange.interval, useCustomRange, isSelectingRange]); + }, [fetchData, selectedRange.interval, useCustomRange, isSelectingRange]); useEffect(() => { if (!chartData || !selectedGraphRange) { -- 2.40.1 From b6b3b36f5a6f2c549c11ed218f861fd28391af04 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 9 Apr 2025 07:47:51 -0400 Subject: [PATCH 2/2] fixed data interpolation and range allocation --- src/Charts/Components/LineChartComponent.jsx | 132 ++++--- src/Charts/PrometheusChart.jsx | 347 ++++++++++++------- src/Components/TreeChart/tabContent.jsx | 2 - src/Components/hooks/TabContent.jsx | 1 - 4 files changed, 312 insertions(+), 170 deletions(-) diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 1973bac..caca080 100755 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -11,7 +11,6 @@ const LineChartComponent = ({ const [selectionArea, setSelectionArea] = useState(null); const [isSelecting, setIsSelecting] = useState(false); const chartRef = useRef(null); - const containerRef = useRef(null); const allTimestamps = Object.values(chartData) .flat() @@ -39,9 +38,32 @@ const LineChartComponent = ({ return point; }); - const displayData = filteredData || data; + const instanceKeys = displayData.length + ? Object.keys(displayData[0]).filter(k => !['timestamp', 'time', 'fullTime'].includes(k)) + : []; + + // Функция для определения оптимального формата времени в зависимости от диапазона + const getTimeFormat = () => { + if (!data.length) return 'HH:mm:ss'; + + const first = data[0].timestamp; + const last = data[data.length - 1].timestamp; + const range = last - first; + + // Если диапазон больше 24 часов - показываем дату + if (range > 86400000) { + return 'dd.MM HH:mm'; + } + // Если больше 1 часа - показываем часы и минуты + if (range > 3600000) { + return 'HH:mm'; + } + // Для коротких диапазонов - показываем время с секундами + return 'HH:mm:ss'; + }; + useEffect(() => { const handleSelectStart = (e) => { if (isSelecting) { @@ -49,47 +71,66 @@ const LineChartComponent = ({ } }; + document.addEventListener('selectstart', handleSelectStart); return () => document.removeEventListener('selectstart', handleSelectStart); }, [isSelecting]); const handleMouseDown = (e) => { - if (!e || !e.activeLabel) return; + if (!e) return; + + // Получаем индекс точки по координатам + const activeIndex = e.activeTooltipIndex; + if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return; + setIsSelecting(true); - setSelectionArea({ start: e.activeLabel, end: null }); // activeLabel — это timestamp + setSelectionArea({ + start: data[activeIndex].timestamp, + end: null, + startIndex: activeIndex, + endIndex: null + }); }; const handleMouseMove = (e) => { - if (!selectionArea?.start || !e?.activeLabel) return; - setSelectionArea(prev => ({ ...prev, end: e.activeLabel })); + if (!isSelecting || !selectionArea?.start || !e) return; + + const activeIndex = e.activeTooltipIndex; + if (activeIndex === undefined || activeIndex < 0 || activeIndex >= data.length) return; + + setSelectionArea(prev => ({ + ...prev, + end: data[activeIndex].timestamp, + endIndex: activeIndex + })); }; const handleMouseUp = () => { - setIsSelecting(false); - - if (!selectionArea?.start || !selectionArea?.end) { + if (!isSelecting || !selectionArea?.start || !selectionArea?.end) { + setIsSelecting(false); setSelectionArea(null); return; } - const startIndex = data.findIndex(point => point.timestamp === selectionArea.start); - const endIndex = data.findIndex(point => point.timestamp === selectionArea.end); + const startIndex = Math.min(selectionArea.startIndex, selectionArea.endIndex); + const endIndex = Math.max(selectionArea.startIndex, selectionArea.endIndex); - if (startIndex >= 0 && endIndex >= 0) { - onRangeSelect({ - startIndex: Math.min(startIndex, endIndex), - endIndex: Math.max(startIndex, endIndex) - }); - } + // Нормализуем индексы к диапазону [0, 1] для родительского компонента + const normalizedStart = startIndex / (data.length - 1); + const normalizedEnd = endIndex / (data.length - 1); + onRangeSelect({ + startIndex: normalizedStart, + endIndex: normalizedEnd + }); + + setIsSelecting(false); setSelectionArea(null); }; const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { const currentPoint = data.find(point => point.timestamp === label); - const displayTime = currentPoint?.fullTime || new Date(label).toLocaleString(); - return (
-

{displayTime}

+

+ {currentPoint?.fullTime || new Date(label).toLocaleString('ru-RU')} +

{payload.map((item, index) => (

{`Значение: ${item.value}`} @@ -110,26 +153,12 @@ const LineChartComponent = ({ return null; }; - if (!data.length) { return

Нет данных для отображения
; } 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 }) => { -- 2.40.1