From 5c3c8acb468044a752b0b8e2ad0beda5d97432f9 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Mon, 10 Mar 2025 06:17:37 -0400 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B4=D0=B8?= =?UTF-8?q?=D0=B0=D0=BF=D0=B0=D0=B7=D0=BE=D0=BD=D0=B0=20=D0=BF=D1=80=D1=8F?= =?UTF-8?q?=D0=BC=D0=BE=20=D0=BD=D0=B0=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Charts/Components/LineChartComponent.jsx | 49 ++++++--- src/Charts/PrometheusChart.jsx | 105 +++++++++++-------- src/Components/Layout/Dashboard.jsx | 1 - 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 37d76da..d684d1c 100644 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -1,7 +1,10 @@ -import React from 'react'; -import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer, Brush } from 'recharts'; +import React, { useState } from 'react'; +import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts'; + +const LineChartComponent = ({ chartData, metricName, colors, description, onRangeSelect, filteredData }) => { + const [selectionStart, setSelectionStart] = useState(null); + const [selectionEnd, setSelectionEnd] = useState(null); -const LineChartComponent = ({ chartData, metricName, metricType, colors, description }) => { // Создаем массив уникальных временных меток const allTimes = Object.values(chartData) .flat() @@ -18,15 +21,39 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip return point; }); + // Используем отфильтрованные данные, если они есть + const displayData = filteredData || data; + + // Обработчик клика на графике + const handleClick = (e) => { + if (!e || !e.activeLabel) return; + + const clickedTime = e.activeLabel; + + if (!selectionStart) { + setSelectionStart(clickedTime); + } else if (!selectionEnd) { + setSelectionEnd(clickedTime); + + const startIndex = data.findIndex(point => point.time === selectionStart); + const endIndex = data.findIndex(point => point.time === clickedTime); + + onRangeSelect({ startIndex, endIndex }); + + setSelectionStart(null); + setSelectionEnd(null); + } + }; + // Кастомный Tooltip для отображения значения const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return (
-

{`Время: ${label}`}

{/* Время из label */} +

{`Время: ${label}`}

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

- {`Значение: ${entry.value}`} {/* Имя и значение из payload */} + {`Значение: ${entry.value}`}

))}
@@ -38,13 +65,12 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip return (
-

{description}

- + - } /> {/* Подключаем кастомный Tooltip */} + } /> {Object.keys(chartData).map((key, index) => ( ))} -
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index d91f9cc..1a0d741 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -4,10 +4,8 @@ import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; import LineChartComponent from './Components/LineChartComponent'; -const MAX_POINTS = 20; // Ограничение точек на графике -const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий - -// Список временных диапазонов и интервалов обновления +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 }, @@ -28,13 +26,12 @@ const TIME_RANGES = [ const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState({}); - const [metricType, setMetricType] = useState(''); - const [metricDescription, setMetricDescription] = useState(''); - const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]); // По умолчанию 1 минута - const [startDate, setStartDate] = useState(new Date()); // Начальная дата для кастомного диапазона - const [endDate, setEndDate] = useState(new Date()); // Конечная дата для кастомного диапазона - const [useCustomRange, setUseCustomRange] = useState(false); // Флаг для выбора кастомного диапазона - const [brushRange, setBrushRange] = useState({ startIndex: 0, endIndex: 0 }); // Состояние Brush + 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 intervalRef = useRef(null); const fetchData = async () => { @@ -42,24 +39,19 @@ const PrometheusChart = ({ metricName }) => { let start, end; 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; } - // Динамический шаг (чем больше диапазон, тем больше шаг) let step; const range = end - start; - if (range <= 3600) step = 5; // 1 час и меньше → 5 сек - else if (range <= 21600) step = 30; // 1-6 часов → 30 сек - else if (range <= 86400) step = 120; // 6-24 часа → 2 минуты - else step = 300; // > 24 часов → 5 минут - - console.log(`Запрашиваем данные с шагом ${step} сек`); + if (range <= 3600) step = 5; + else if (range <= 21600) step = 30; + else if (range <= 86400) step = 120; + else step = 300; const response = await axios.get('http://192.168.2.39:3000/metrics', { params: { metric: metricName, start, end, step }, @@ -68,12 +60,10 @@ const PrometheusChart = ({ metricName }) => { const result = response.data; let metrics = Array.isArray(result) ? result : result.data || []; - if (!Array.isArray(metrics) || metrics.length === 0) { - console.warn('No metrics data available, filling with empty values.'); + if (!Array.isArray(metrics)) { metrics = []; } - // 1. Генерация временных точек с учетом диапазона const timePoints = []; for (let t = start; t <= end; t += step) { const date = new Date(t * 1000); @@ -84,7 +74,6 @@ const PrometheusChart = ({ metricName }) => { timePoints.push(formattedTime); } - // 2. Обработка данных const updatedData = {}; metrics.forEach(m => { const date = new Date(m.timestamp); @@ -97,7 +86,6 @@ const PrometheusChart = ({ metricName }) => { updatedData[key][formattedTime] = m.value; }); - // 3. Заполнение пропусков const chartData = {}; Object.keys(updatedData).forEach(key => { chartData[key] = timePoints.map(time => ({ @@ -107,44 +95,80 @@ const PrometheusChart = ({ metricName }) => { }); setChartData(chartData); - - // Устанавливаем Brush на весь диапазон - setBrushRange({ - startIndex: 0, - endIndex: timePoints.length - 1, - }); } catch (error) { console.error('Ошибка при загрузке метрик:', error); } }; useEffect(() => { - fetchData(); // Первоначальная загрузка данных + fetchData(); intervalRef.current = setInterval(() => { fetchData(); - }, selectedRange.interval); // Обновляем с выбранным интервалом + }, selectedRange.interval); return () => { if (intervalRef.current) { - clearInterval(intervalRef.current); // Очищаем интервал при размонтировании + clearInterval(intervalRef.current); } }; - }, [metricName, selectedRange, useCustomRange, startDate, endDate]); // Зависимость от metricName, selectedRange, useCustomRange, startDate и endDate + }, [metricName, selectedRange, useCustomRange, startDate, endDate]); const handleRangeChange = (event) => { const selectedValue = event.target.value; const range = TIME_RANGES.find(range => range.value === parseInt(selectedValue, 10)); setSelectedRange(range); - setUseCustomRange(false); // Переключаемся на предустановленный диапазон + setUseCustomRange(false); + setSelectedGraphRange(null); // Сбрасываем выбранный диапазон + setFilteredData(null); // Сбрасываем отфильтрованные данные }; const handleCustomRangeChange = () => { - setUseCustomRange(true); // Переключаемся на кастомный диапазон + setUseCustomRange(true); + setSelectedGraphRange(null); // Сбрасываем выбранный диапазон + setFilteredData(null); // Сбрасываем отфильтрованные данные }; + 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); + setFilteredData(filtered); // Сохраняем отфильтрованные данные + } 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 (
@@ -184,11 +208,10 @@ const PrometheusChart = ({ metricName }) => {
); diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index ce9c706..040dff0 100644 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -94,7 +94,6 @@ const Dashboard = () => { return (

Общий мониторинг

- {/* Используем актуальные данные */}
);