220 lines
8.9 KiB
JavaScript
220 lines
8.9 KiB
JavaScript
import React, { useEffect, useState, useRef } from 'react';
|
|
import axios from 'axios';
|
|
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 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 intervalRef = useRef(null);
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
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;
|
|
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 },
|
|
});
|
|
|
|
const result = response.data;
|
|
let metrics = Array.isArray(result) ? result : result.data || [];
|
|
|
|
if (!Array.isArray(metrics)) {
|
|
metrics = [];
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
const updatedData = {};
|
|
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}-${m.device || m.scrape_job}`;
|
|
if (!updatedData[key]) updatedData[key] = {};
|
|
updatedData[key][formattedTime] = m.value;
|
|
});
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
|
|
intervalRef.current = setInterval(() => {
|
|
fetchData();
|
|
}, selectedRange.interval);
|
|
|
|
return () => {
|
|
if (intervalRef.current) {
|
|
clearInterval(intervalRef.current);
|
|
}
|
|
};
|
|
}, [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);
|
|
setSelectedGraphRange(null); // Сбрасываем выбранный диапазон
|
|
setFilteredData(null); // Сбрасываем отфильтрованные данные
|
|
};
|
|
|
|
const handleCustomRangeChange = () => {
|
|
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 <p>Loading...</p>;
|
|
|
|
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 (
|
|
<div>
|
|
<div>
|
|
<label htmlFor="time-range">Выберите временной диапазон: </label>
|
|
<select id="time-range" value={selectedRange.value} onChange={handleRangeChange}>
|
|
{TIME_RANGES.map(range => (
|
|
<option key={range.value} value={range.value}>{range.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label>Или выберите другой диапазон: </label>
|
|
<div>
|
|
<label>Начальная дата: </label>
|
|
<DatePicker
|
|
selected={startDate}
|
|
onChange={(date) => setStartDate(date)}
|
|
showTimeSelect
|
|
timeFormat="HH:mm"
|
|
timeIntervals={15}
|
|
dateFormat="yyyy-MM-dd HH:mm"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label>Конечная дата: </label>
|
|
<DatePicker
|
|
selected={endDate}
|
|
onChange={(date) => setEndDate(date)}
|
|
showTimeSelect
|
|
timeFormat="HH:mm"
|
|
timeIntervals={15}
|
|
dateFormat="yyyy-MM-dd HH:mm"
|
|
/>
|
|
</div>
|
|
<button onClick={handleCustomRangeChange}>Использовать кастомный диапазон</button>
|
|
</div>
|
|
<LineChartComponent
|
|
chartData={chartData}
|
|
metricName={metricName}
|
|
colors={COLORS}
|
|
description={metricName}
|
|
onRangeSelect={setSelectedGraphRange}
|
|
filteredData={filteredData}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PrometheusChart; |