fixed data transmission via a web socket and left data transmission via http requests for historical data
parent
a24b89220c
commit
64401cadbc
|
|
@ -5,6 +5,7 @@ import { TimeRangeSelector } from './Components/TimeRangeSelector';
|
||||||
import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicator';
|
import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicator';
|
||||||
import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay';
|
import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay';
|
||||||
import { TIME_RANGES, COLORS } from './Components/constants';
|
import { TIME_RANGES, COLORS } from './Components/constants';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
const PrometheusChart = ({ metricName }) => {
|
const PrometheusChart = ({ metricName }) => {
|
||||||
const [chartData, setChartData] = useState(null);
|
const [chartData, setChartData] = useState(null);
|
||||||
|
|
@ -15,6 +16,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
const [connectionStatus, setConnectionStatus] = useState('disconnected');
|
const [connectionStatus, setConnectionStatus] = useState('disconnected');
|
||||||
const [selectedGraphRange, setSelectedGraphRange] = useState(null);
|
const [selectedGraphRange, setSelectedGraphRange] = useState(null);
|
||||||
const [filteredData, setFilteredData] = useState(null);
|
const [filteredData, setFilteredData] = useState(null);
|
||||||
|
const [isSelectingRange, setIsSelectingRange] = useState(false);
|
||||||
const intervalRef = useRef(null);
|
const intervalRef = useRef(null);
|
||||||
const socketRef = 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(() => {
|
const setupWebSocket = useCallback(() => {
|
||||||
if (socketRef.current) {
|
if (socketRef.current) {
|
||||||
// Если соединение уже существует, возвращаем его
|
// Если соединение уже существует, возвращаем его
|
||||||
|
|
@ -91,80 +175,35 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
return socket;
|
return socket;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const calculateStep = useCallback((start, end) => {
|
const fetchCustomRangeData = useCallback(async () => {
|
||||||
const range = end - start;
|
const start = Math.floor(startDate.getTime() / 1000);
|
||||||
if (range <= 3600) return 5;
|
const end = Math.floor(endDate.getTime() / 1000);
|
||||||
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 step = calculateStep(start, end);
|
const step = calculateStep(start, end);
|
||||||
|
|
||||||
if (socketRef.current?.connected) {
|
try {
|
||||||
socketRef.current.emit('get-metrics', {
|
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, {
|
||||||
|
params: {
|
||||||
metric: metricName,
|
metric: metricName,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
step,
|
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;
|
if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую
|
||||||
};
|
processMetricsData({
|
||||||
|
metric: metricName,
|
||||||
const processMetricsData = useCallback((response) => {
|
data: response.data.map(item => ({
|
||||||
if (response.metric !== metricName || !Array.isArray(response.data)) return;
|
...item,
|
||||||
|
timestamp: item.timestamp / 1000, // или item.timestamp если уже в секундах
|
||||||
setChartData(prev => {
|
value: item.value.toString() // преобразуем в строку, как ожидает processMetricsData
|
||||||
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
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении кастомных данных:', error);
|
||||||
// Группировка и ограничение
|
}
|
||||||
Object.keys(newData).forEach(instance => {
|
}, [metricName, startDate, endDate, calculateStep, processMetricsData]);
|
||||||
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 handleRangeChange = useCallback((event) => {
|
const handleRangeChange = useCallback((event) => {
|
||||||
|
|
@ -180,6 +219,11 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
setEndDate(now);
|
setEndDate(now);
|
||||||
setStartDate(new Date(now.getTime() - range.value * 1000));
|
setStartDate(new Date(now.getTime() - range.value * 1000));
|
||||||
|
|
||||||
|
// Переподключение сокета при возврате к стандартным диапазонам
|
||||||
|
if (!socketRef.current?.connected) {
|
||||||
|
socketRef.current?.connect();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCustomRangeChange = useCallback(() => {
|
const handleCustomRangeChange = useCallback(() => {
|
||||||
|
|
@ -192,9 +236,35 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
const handleResetZoom = useCallback(() => {
|
const handleResetZoom = useCallback(() => {
|
||||||
setSelectedGraphRange(null);
|
setSelectedGraphRange(null);
|
||||||
setFilteredData(null);
|
setFilteredData(null);
|
||||||
|
setIsSelectingRange(false);
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [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(() => {
|
useEffect(() => {
|
||||||
const socket = setupWebSocket();
|
const socket = setupWebSocket();
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -204,7 +274,15 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
}, [setupWebSocket]);
|
}, [setupWebSocket]);
|
||||||
|
|
||||||
useEffect(() => {
|
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 = () => {
|
const fetchDataWrapper = () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -214,10 +292,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Сразу запросить данные
|
|
||||||
fetchDataWrapper();
|
fetchDataWrapper();
|
||||||
|
|
||||||
// Установить интервал
|
|
||||||
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
|
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
@ -226,7 +301,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
intervalRef.current = null;
|
intervalRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [fetchData, selectedRange.interval]);
|
}, [fetchData, fetchCustomRangeData, selectedRange.interval, useCustomRange, isSelectingRange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chartData || !selectedGraphRange) {
|
if (!chartData || !selectedGraphRange) {
|
||||||
|
|
@ -298,7 +373,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
chartData={chartData}
|
chartData={chartData}
|
||||||
metricName={metricName}
|
metricName={metricName}
|
||||||
colors={COLORS}
|
colors={COLORS}
|
||||||
onRangeSelect={setSelectedGraphRange}
|
onRangeSelect={handleRangeSelect} // Используем модифицированный обработчик
|
||||||
filteredData={filteredData}
|
filteredData={filteredData}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue