import React, { useEffect, useState, useRef } from 'react'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import LineChartComponent from './Components/LineChartComponent'; import { io } from 'socket.io-client'; const MAX_POINTS = 20; const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; const TIME_RANGES = [ { label: '1 минута', value: 60, interval: 3000 }, { label: '5 минут', value: 300, interval: 15000 }, { label: '30 минут', value: 1800, interval: 90000 }, { label: '1 час', value: 3600, interval: 180000 }, { label: '3 часа', value: 10800, interval: 540000 }, { label: '6 часов', value: 21600, interval: 1080000 }, { label: '12 часов', value: 43200, interval: 2160000 }, { label: '24 часа', value: 86400, interval: 4320000 }, { label: '2 дня', value: 172800, interval: 8640000 }, { label: '7 дней', value: 604800, interval: 30240000 }, { label: '30 дней', value: 2592000, interval: 129600000 }, { label: '90 дней', value: 7776000, interval: 388800000 }, { label: '6 месяцев', value: 15552000, interval: 777600000 }, { label: '9 месяцев', value: 23328000, interval: 1166400000 }, { label: '1 год', value: 31536000, interval: 1576800000 }, ]; const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState({}); const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]); const [startDate, setStartDate] = useState(new Date()); const [endDate, setEndDate] = useState(new Date()); const [useCustomRange, setUseCustomRange] = useState(false); const [selectedGraphRange, setSelectedGraphRange] = useState(null); const [filteredData, setFilteredData] = useState(null); const [connectionStatus, setConnectionStatus] = useState('disconnected'); const intervalRef = useRef(null); const socketRef = useRef(null); const setupWebSocket = () => { const socket = io('http://192.168.2.39:3000/metrics-ws', { path: '/socket.io', transports: ['websocket'], reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, }); socketRef.current = socket; socket.on('connect', () => { setConnectionStatus('connected'); fetchData(); }); socket.on('disconnect', () => { setConnectionStatus('disconnected'); }); socket.on('connect_error', (err) => { setConnectionStatus('error'); }); socket.on('metrics-data', (response) => { processMetricsData(response); }); return socket; }; const calculateStep = (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 = () => { try { const now = Math.floor(Date.now() / 1000); let start = useCustomRange ? Math.floor(startDate.getTime() / 1000) : now - selectedRange.value; let end = useCustomRange ? Math.floor(endDate.getTime() / 1000) : now; // Проверка на корректность диапазона if (start >= end) { console.error('Invalid time range: start >= end'); return; } const step = calculateStep(start, end); if (socketRef.current?.connected) { socketRef.current.emit('get-metrics', { metric: metricName, start, end, step }); } else { console.error('WebSocket is not connected'); } } catch (error) { console.error('Error in fetchData:', error); } }; const processMetricsData = (response) => { const { metric, data } = response; if (metric !== metricName) return; let metrics = Array.isArray(data) ? data : []; let start, end; if (metrics.length === 0) { console.warn('Received empty metrics data'); return; } if (useCustomRange) { start = Math.floor(startDate.getTime() / 1000); end = Math.floor(endDate.getTime() / 1000); } else { end = Math.floor(Date.now() / 1000); start = end - selectedRange.value; } const step = calculateStep(start, end); const range = end - start; // 1. Генерируем ВСЕ ожидаемые временные точки const timePoints = []; for (let t = start; t <= end; t += step) { const date = new Date(t * 1000); const formattedTime = range > 86400 ? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); timePoints.push(formattedTime); } // 2. Создаем карту "время -> значение" для каждого инстанса const timeValueMap = {}; metrics.forEach(m => { const date = new Date(m.timestamp); const formattedTime = range > 86400 ? date.toLocaleString([], { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); const key = m.instance; if (!timeValueMap[key]) timeValueMap[key] = {}; timeValueMap[key][formattedTime] = m.value; }); // 3. Строим финальные данные, гарантируя все временные точки const newChartData = {}; Object.keys(timeValueMap).forEach(instance => { newChartData[instance] = timePoints.map(time => ({ time, value: timeValueMap[instance][time] ?? null // null если данных нет })); }); setChartData({ ...newChartData }); // Форсируем обновление }; const interpolateData = (data, minPoints = 15) => { if (data.length >= minPoints) return data; const interpolatedData = []; for (let i = 0; i < data.length - 1; i++) { interpolatedData.push(data[i]); const currentPoint = data[i]; const nextPoint = data[i + 1]; const currentTime = new Date(currentPoint.time).getTime(); const nextTime = new Date(nextPoint.time).getTime(); const timeDiff = nextTime - currentTime; const steps = Math.ceil((minPoints - data.length) / (data.length - 1)); for (let j = 1; j <= steps; j++) { const interpolatedTime = new Date(currentTime + (timeDiff * j) / (steps + 1)).toLocaleString(); const interpolatedPoint = { time: interpolatedTime }; Object.keys(currentPoint).forEach(key => { if (key !== 'time') { const currentValue = currentPoint[key]; const nextValue = nextPoint[key]; interpolatedPoint[key] = currentValue + ((nextValue - currentValue) * j) / (steps + 1); } }); interpolatedData.push(interpolatedPoint); } } interpolatedData.push(data[data.length - 1]); return interpolatedData.slice(0, minPoints); }; const handleRangeChange = (event) => { const selectedValue = event.target.value; const range = TIME_RANGES.find(r => r.value === parseInt(selectedValue, 10)); // Сбрасываем данные и состояние setChartData({}); setFilteredData(null); setSelectedGraphRange(null); // Обновляем диапазон и даты setSelectedRange(range); setUseCustomRange(false); const now = new Date(); setEndDate(now); setStartDate(new Date(now.getTime() - range.value * 1000)); // Принудительно запрашиваем новые данные // Используем setTimeout для гарантированного обновления состояния перед запросом setTimeout(() => { fetchData(); }, 0); }; const handleCustomRangeChange = () => { // Сбрасываем данные и состояние setChartData({}); setFilteredData(null); setSelectedGraphRange(null); setUseCustomRange(true); // Принудительно запрашиваем новые данные setTimeout(() => { fetchData(); }, 0); }; const handleResetZoom = () => { setSelectedGraphRange(null); setFilteredData(null); fetchData(); }; useEffect(() => { const socket = setupWebSocket(); // Первоначальная загрузка данных fetchData(); // Настраиваем интервал обновления const intervalId = setInterval(() => { fetchData(); }, selectedRange.interval); intervalRef.current = intervalId; return () => { clearInterval(intervalRef.current); socket.disconnect(); }; }, [metricName, selectedRange.value]); useEffect(() => { // При изменении диапазона или дат перезапускаем интервал if (socketRef.current?.connected) { clearInterval(intervalRef.current); fetchData(); intervalRef.current = setInterval(() => { fetchData(); }, selectedRange.interval); } }, [selectedRange, useCustomRange, startDate, endDate, metricName]); useEffect(() => { if (selectedGraphRange) { const { startIndex, endIndex } = selectedGraphRange; const allTimes = Object.values(chartData) .flat() .map(point => point.time) .filter((time, index, self) => self.indexOf(time) === index); 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; }); const filtered = data.slice(startIndex, endIndex + 1); const interpolated = interpolateData(filtered, 15); setFilteredData(interpolated); } else { setFilteredData(null); } }, [selectedGraphRange, chartData]); if (!Object.keys(chartData).length) return
Loading...
; const allTimes = Object.values(chartData) .flat() .map(point => point.time) .filter((time, index, self) => self.indexOf(time) === index); 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; }); return (