diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index cefc315..ab30858 100755 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -5,6 +5,7 @@ import { TimeRangeSelector } from './Components/TimeRangeSelector'; import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicator'; import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay'; import { TIME_RANGES, COLORS } from './Components/constants'; +import axios from 'axios'; const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState(null); @@ -15,6 +16,7 @@ const PrometheusChart = ({ metricName }) => { const [connectionStatus, setConnectionStatus] = useState('disconnected'); const [selectedGraphRange, setSelectedGraphRange] = useState(null); const [filteredData, setFilteredData] = useState(null); + const [isSelectingRange, setIsSelectingRange] = useState(false); const intervalRef = useRef(null); const socketRef = useRef(null); @@ -42,6 +44,88 @@ const PrometheusChart = ({ metricName }) => { }; }, []); + 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; + }, []); + + const fetchData = useCallback(() => { + + if (isSelectingRange) return; + + const now = Math.floor(Date.now() / 1000); + const start = now - selectedRange.value; + const end = now; + const step = calculateStep(start, end); + + if (socketRef.current?.connected) { + socketRef.current.emit('get-metrics', { + metric: metricName, + start, + end, + step, + _t: Date.now() + }); + } + }, [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) => { + if (response.metric !== metricName) return; + + const dataArray = Array.isArray(response.data) ? response.data : [response.data]; + if (!dataArray.length) return; + + setChartData(prev => { + const newData = { ...(prev || {}) }; + + 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); + + if (!newData[instance].some(p => p.timestamp === timestamp)) { + newData[instance].push({ + time: formatTime(timestamp, selectedRange.value).display, + value: value, + timestamp: timestamp + }); + } + }); + + Object.keys(newData).forEach(instance => { + newData[instance] = groupBySecond(newData[instance]) + .sort((a, b) => a.timestamp - b.timestamp) + .slice(-1000); + }); + + return Object.keys(newData).length ? newData : prev; + }); + }, [metricName, selectedRange.value, formatTime]); + const setupWebSocket = useCallback(() => { if (socketRef.current) { // Если соединение уже существует, возвращаем его @@ -91,95 +175,55 @@ const PrometheusChart = ({ metricName }) => { return socket; }, []); - 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; - }, []); - - const fetchData = useCallback(() => { - const now = Math.floor(Date.now() / 1000); - const start = now - selectedRange.value; - const end = now; + const fetchCustomRangeData = useCallback(async () => { + const start = Math.floor(startDate.getTime() / 1000); + const end = Math.floor(endDate.getTime() / 1000); const step = calculateStep(start, end); - - if (socketRef.current?.connected) { - socketRef.current.emit('get-metrics', { - metric: metricName, - start, - end, - step, - _t: Date.now() // Добавляем timestamp для уникальности - }); - } - }, [metricName, selectedRange.value]); // Только необходимые зависимости - - 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) => { - if (response.metric !== metricName || !Array.isArray(response.data)) return; - - setChartData(prev => { - const newData = { ...(prev || {}) }; - - // Добавление новых точек - response.data.forEach(item => { - const instance = item.instance || 'default'; - if (!newData[instance]) newData[instance] = []; - - if (!newData[instance].some(p => p.timestamp === item.timestamp)) { - newData[instance].push({ - time: formatTime(item.timestamp, selectedRange.value).display, - value: parseFloat(item.value), - timestamp: item.timestamp - }); + + try { + const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, { + params: { + metric: metricName, + start, + end, + step } }); - - // Группировка и ограничение - Object.keys(newData).forEach(instance => { - newData[instance] = groupBySecond(newData[instance]) - .sort((a, b) => a.timestamp - b.timestamp) - .slice(-1000); - }); - - return Object.keys(newData).length ? newData : prev; - }); - }, [metricName, selectedRange.value, formatTime]); + + if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую + processMetricsData({ + metric: metricName, + data: response.data.map(item => ({ + ...item, + timestamp: item.timestamp / 1000, // или item.timestamp если уже в секундах + value: item.value.toString() // преобразуем в строку, как ожидает processMetricsData + })) + }); + } + } catch (error) { + console.error('Ошибка при получении кастомных данных:', error); + } + }, [metricName, startDate, endDate, calculateStep, processMetricsData]); 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(); + } }, []); const handleCustomRangeChange = useCallback(() => { @@ -192,9 +236,35 @@ const PrometheusChart = ({ metricName }) => { const handleResetZoom = useCallback(() => { setSelectedGraphRange(null); setFilteredData(null); + setIsSelectingRange(false); fetchData(); }, [fetchData]); + const handleRangeSelect = useCallback((range) => { + if (range) { + // Начало выделения - останавливаем обновления + setIsSelectingRange(true); + setSelectedGraphRange(range); + + // Отключаем сокет + if (socketRef.current?.connected) { + socketRef.current.disconnect(); + } + // Очищаем интервал + if (intervalRef.current) { + clearInterval(intervalRef.current); + intervalRef.current = null; + } + } else { + // Окончание выделения - возобновляем соединение + setIsSelectingRange(false); + + if (!useCustomRange && socketRef.current && !socketRef.current.connected) { + socketRef.current.connect(); + } + } + }, [useCustomRange]); + useEffect(() => { const socket = setupWebSocket(); return () => { @@ -204,8 +274,16 @@ const PrometheusChart = ({ metricName }) => { }, [setupWebSocket]); useEffect(() => { - if (!socketRef.current?.connected) return; - + if (useCustomRange) { + if (socketRef.current?.connected) { + socketRef.current.disconnect(); + } + fetchCustomRangeData(); + return; + } + + if (!socketRef.current?.connected || isSelectingRange) return; + const fetchDataWrapper = () => { try { fetchData(); @@ -213,20 +291,17 @@ const PrometheusChart = ({ metricName }) => { console.error('Error in interval fetch:', error); } }; - - // Сразу запросить данные + fetchDataWrapper(); - - // Установить интервал intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval); - + return () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; - }, [fetchData, selectedRange.interval]); + }, [fetchData, fetchCustomRangeData, selectedRange.interval, useCustomRange, isSelectingRange]); useEffect(() => { if (!chartData || !selectedGraphRange) { @@ -298,7 +373,7 @@ const PrometheusChart = ({ metricName }) => { chartData={chartData} metricName={metricName} colors={COLORS} - onRangeSelect={setSelectedGraphRange} + onRangeSelect={handleRangeSelect} // Используем модифицированный обработчик filteredData={filteredData} />