From 5613fcf205ab7945c1052550dffafba4da77e533 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Fri, 7 Mar 2025 09:02:32 -0500 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B3=D1=80=D0=B0=D1=84=D0=B8=D0=BA,=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BE=D0=BD=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=D0=B7=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=20=D0=BA=D0=BE?= =?UTF-8?q?=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B7=D0=B0=20=D0=B4=D0=B8=D0=B0?= =?UTF-8?q?=D0=BF=D0=B0=D0=B7=D0=BE=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.css | 30 -- src/Charts/Components/LineChartComponent.jsx | 28 +- src/Charts/PrometheusChart.jsx | 301 ++++++------------- src/Components/TreeChart/tabContent.jsx | 2 + 4 files changed, 105 insertions(+), 256 deletions(-) diff --git a/src/App.css b/src/App.css index 40842f6..9c7629a 100755 --- a/src/App.css +++ b/src/App.css @@ -42,34 +42,4 @@ .read-the-docs { color: #888; -} - -/* Глобальный стиль для WebKit-браузеров (Chrome, Edge, Safari) */ -::-webkit-scrollbar { - width: 10px; /* Толщина вертикального скролла */ - height: 10px; /* Толщина горизонтального скролла */ -} - -/* Фон скроллбара */ -::-webkit-scrollbar-track { - background: #f1f1f1; /* Цвет фона */ - border-radius: 10px; /* Скругление углов */ -} - -/* Ползунок */ -::-webkit-scrollbar-thumb { - background: #3d74c7; /* Основной цвет */ - border-radius: 10px; /* Скругляем края */ - border: 2px solid #f1f1f1; /* Белая обводка */ -} - -/* Эффект при наведении */ -::-webkit-scrollbar-thumb:hover { - background: #2b5aa5; /* Чуть темнее при наведении */ -} - -/* Глобальный стиль для Firefox */ -* { - scrollbar-width: thin; /* Делаем тонким */ - scrollbar-color: #3d74c7 #f1f1f1; /* Ползунок + фон */ } \ No newline at end of file diff --git a/src/Charts/Components/LineChartComponent.jsx b/src/Charts/Components/LineChartComponent.jsx index 23ae6da..f607307 100644 --- a/src/Charts/Components/LineChartComponent.jsx +++ b/src/Charts/Components/LineChartComponent.jsx @@ -1,12 +1,12 @@ import React from 'react'; -import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer, Brush } from 'recharts'; +import { LineChart, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Line, ResponsiveContainer } from 'recharts'; -const LineChartComponent = ({ chartData, metricName, metricType, colors, description, isLongRange }) => { +const LineChartComponent = ({ chartData, metricName, metricType, colors, description }) => { // Создаем массив уникальных временных меток const allTimes = Object.values(chartData) .flat() .map(point => point.time) - .filter((time, index, self) => self.indexOf(time) === index); // Убираем дубликаты + .filter((time, index, self) => self.indexOf(time) === index); // Формируем данные для графика const data = allTimes.map(time => { @@ -18,14 +18,17 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip return point; }); - console.log('Processed Data:', data); // Логируем данные для графика - // Кастомный Tooltip для отображения значения const CustomTooltip = ({ active, payload, label }) => { if (active && payload && payload.length) { return ( -
-

{`${payload[0].value}`}

+
+

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

{/* Время из label */} + {payload.map((entry, index) => ( +

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

+ ))}
); } @@ -41,7 +44,7 @@ const LineChartComponent = ({ chartData, metricName, metricType, colors, descrip - } /> + } /> {/* Подключаем кастомный Tooltip */} {Object.keys(chartData).map((key, index) => ( ))} - {isLongRange && ( // Добавляем Brush только для длительных периодов - - )}
diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index f6ce9be..159afba 100644 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -1,258 +1,141 @@ import React, { useEffect, useState, useRef } from 'react'; import axios from 'axios'; import LineChartComponent from './Components/LineChartComponent'; -import BarChartComponent from './Components/BarChartComponent'; -import ScatterChartComponent from './Components/ScatterChartComponent'; -import DatePicker from 'react-datepicker'; -import '../Style/DatePicker.css'; const MAX_POINTS = 20; // Ограничение точек на графике const COLORS = ['#3e95cd', '#8e5ea2', '#3cba9f', '#e8c3b9', '#c45850']; // Фиксированные цвета для линий -// Компонент для выбора временного диапазона -const TimeRangeSelector = ({ onRangeChange }) => { - return ( -
- - - -
- ); -}; - -// Компонент для выбора произвольного диапазона дат -const DateRangeSelector = ({ onDateChange }) => { - const [startDate, setStartDate] = useState(new Date()); - const [endDate, setEndDate] = useState(new Date()); - - const handleDateChange = (dates) => { - const [start, end] = dates; - setStartDate(start); - setEndDate(end); - onDateChange({ start, end }); - }; - - return ( -
- -
- ); -}; +// Список временных диапазонов и интервалов обновления +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 [metricType, setMetricType] = useState(''); const [metricDescription, setMetricDescription] = useState(''); - const [timeRange, setTimeRange] = useState('24h'); // По умолчанию выбран диапазон "24 часа" - const [customRange, setCustomRange] = useState({ start: null, end: null }); + const [selectedRange, setSelectedRange] = useState(TIME_RANGES[0]); // По умолчанию 1 минута const intervalRef = useRef(null); - const isLongRange = (range) => { - return range === '2w' || range === 'custom'; // "2w" — две недели, "custom" — кастомный диапазон - }; - - const fetchData = async (range, customStart, customEnd) => { + const fetchData = async () => { try { - const end = customEnd ? Math.floor(customEnd.getTime() / 1000) : Math.floor(Date.now() / 1000); - let start; + const end = Math.floor(Date.now() / 1000); + const start = end - selectedRange.value; - if (customStart) { - start = Math.floor(customStart.getTime() / 1000); - } else { - switch (range) { - case '1h': - start = end - 60 * 60; // 1 час назад - break; - case '24h': - start = end - 24 * 60 * 60; // 24 часа назад - break; - case '2w': - start = end - 14 * 24 * 60 * 60; // 2 недели назад - break; - default: - start = end - 24 * 60 * 60; // По умолчанию 24 часа - } - } + // Динамический шаг (чем больше диапазон, тем больше шаг) + let step; + if (selectedRange.value <= 3600) step = 5; // 1 час и меньше → 5 сек + else if (selectedRange.value <= 21600) step = 30; // 1-6 часов → 30 сек + else if (selectedRange.value <= 86400) step = 120; // 6-24 часа → 2 минуты + else step = 300; // > 24 часов → 5 минут - const step = range === '2w' ? 3600 : 60; // Для двух недель увеличиваем шаг до 1 часа + console.log(`Запрашиваем данные с шагом ${step} сек`); const response = await axios.get(`http://192.168.2.39:3000/metrics`, { - params: { - metric: metricName, - start, - end, - step, - }, + params: { metric: metricName, start, end, step }, }); const result = response.data; - - // Проверяем структуру данных - let metrics; - if (Array.isArray(result)) { - metrics = result; - } else if (result.data && Array.isArray(result.data)) { - metrics = result.data; - } else { - throw new Error('Invalid data format'); - } + let metrics = Array.isArray(result) ? result : result.data || []; if (!Array.isArray(metrics) || metrics.length === 0) { - throw new Error('No metrics data available'); + console.warn('No metrics data available, filling with empty values.'); + metrics = []; } - const type = metrics[0].type; - setMetricType(type); + // 1. Генерация временных точек с учетом диапазона + const timePoints = []; + for (let t = start; t <= end; t += step) { + const date = new Date(t * 1000); + const formattedTime = selectedRange.value > 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' }); - // Устанавливаем описание метрики - setMetricDescription(metrics[0].description); - - // Очищаем предыдущие данные - setChartData({}); - - if (type === 'summary') { - // Обработка данных для summary - const newData = metrics.map(m => ({ - instance: m.instance, - quantile: m.quantile, - value: m.value - })); - - // Группируем данные по instance - const groupedData = newData.reduce((acc, point) => { - if (!acc[point.instance]) { - acc[point.instance] = []; - } - acc[point.instance].push(point); - return acc; - }, {}); - - setChartData(groupedData); - } else { - // Обработка данных для counter, gauge, unknown - const newDataPoints = metrics.map(m => ({ - time: new Date(m.timestamp).toLocaleTimeString(), - value: m.value, - instance: m.instance, - device: m.device || m.scrape_job, // Используем device или scrape_job - })); - - // Группируем данные по instance и device/scrape_job - const updatedData = {}; - - newDataPoints.forEach(point => { - const key = `${point.instance}-${point.device}`; // Уникальный ключ - if (!updatedData[key]) { - updatedData[key] = []; - } - updatedData[key].push({ - time: point.time, - value: point.value, - }); - - // Ограничиваем количество точек до MAX_POINTS - if (updatedData[key].length > MAX_POINTS) { - updatedData[key] = updatedData[key].slice(-MAX_POINTS); // Оставляем последние MAX_POINTS точек - } - }); - - setChartData(updatedData); + timePoints.push(formattedTime); } + + // 2. Обработка данных + const updatedData = {}; + metrics.forEach(m => { + const date = new Date(m.timestamp); + const formattedTime = selectedRange.value > 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}-${m.device || m.scrape_job}`; + if (!updatedData[key]) updatedData[key] = {}; + updatedData[key][formattedTime] = m.value; + }); + + // 3. Заполнение пропусков + const chartData = {}; + Object.keys(updatedData).forEach(key => { + chartData[key] = timePoints.map(time => ({ + time, + value: updatedData[key][time] ?? null, + })); + }); + + setChartData(chartData); } catch (error) { - console.error('Error fetching metrics:', error); + console.error('Ошибка при загрузке метрик:', error); } }; - useEffect(() => { - fetchData(timeRange, customRange.start, customRange.end); // Первоначальная загрузка данных - if (!isLongRange(timeRange)) { // Обновляем только для коротких диапазонов - intervalRef.current = setInterval(() => { - fetchData(timeRange, customRange.start, customRange.end); - }, 5000); // Обновляем каждые 5 секунд - } + useEffect(() => { + fetchData(); // Первоначальная загрузка данных + + intervalRef.current = setInterval(() => { + fetchData(); + }, selectedRange.interval); // Обновляем с выбранным интервалом return () => { if (intervalRef.current) { clearInterval(intervalRef.current); // Очищаем интервал при размонтировании } }; - }, [metricName, timeRange, customRange]); + }, [metricName, selectedRange]); // Зависимость от metricName и selectedRange - const handleRangeChange = (range) => { - setTimeRange(range); - setCustomRange({ start: null, end: null }); - - if (!isLongRange(range)) { - clearInterval(intervalRef.current); // Останавливаем обновление - } - }; - - const handleDateChange = ({ start, end }) => { - setCustomRange({ start, end }); - setTimeRange('custom'); // Устанавливаем кастомный диапазон + const handleRangeChange = (event) => { + const selectedValue = event.target.value; + const range = TIME_RANGES.find(range => range.value === parseInt(selectedValue, 10)); + setSelectedRange(range); }; if (!Object.keys(chartData).length) return

Loading...

; - const renderChart = () => { - switch (metricType) { - case 'counter': - case 'gauge': - return ( - - ); - case 'summary': - return ( - - ); - case 'unknown': - return ( - - ); - default: - return

Unsupported metric type

; - } - }; - return (
- - - {renderChart()} +
+ + +
+
); }; diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 26ab955..9c55909 100644 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -7,6 +7,8 @@ const getMetricName = (id) => { return `zvks_apiforsnmp_measure_${id}`; }; +//!!!!!!!!!!Пофиксить вкладуи с eth4, во всех eth 1-4 открывается именно 4 !!!!!!!!!!!!! + // Функция для рекурсивного сбора всех id потомков const getAllChildIds = (node) => { let ids = [];