Fixed the date and time display
parent
64401cadbc
commit
46da90fbb6
|
|
@ -13,24 +13,33 @@ const LineChartComponent = ({
|
||||||
const chartRef = useRef(null);
|
const chartRef = useRef(null);
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
const allTimes = Object.values(chartData)
|
const allTimestamps = Object.values(chartData)
|
||||||
.flat()
|
.flat()
|
||||||
.map(point => point.time)
|
.map(point => point.timestamp)
|
||||||
.filter((time, index, self) => self.indexOf(time) === index);
|
.filter((timestamp, index, self) => self.indexOf(timestamp) === index)
|
||||||
|
.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const data = allTimestamps.map(timestamp => {
|
||||||
|
const point = { timestamp };
|
||||||
|
|
||||||
|
const firstPoint = Object.values(chartData)
|
||||||
|
.flat()
|
||||||
|
.find(p => p.timestamp === timestamp);
|
||||||
|
|
||||||
|
if (firstPoint) {
|
||||||
|
point.time = firstPoint.time;
|
||||||
|
point.fullTime = firstPoint.fullTime;
|
||||||
|
}
|
||||||
|
|
||||||
const data = allTimes.map(time => {
|
|
||||||
const point = { time };
|
|
||||||
Object.keys(chartData).forEach(key => {
|
Object.keys(chartData).forEach(key => {
|
||||||
const instanceData = chartData[key].find(p => p.time === time);
|
const instanceData = chartData[key].find(p => p.timestamp === timestamp);
|
||||||
point[key] = instanceData ? instanceData.value : null;
|
point[key] = instanceData ? instanceData.value : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
return point;
|
return point;
|
||||||
}).sort((a, b) => {
|
|
||||||
const timeA = chartData[Object.keys(chartData)[0]].find(d => d.time === a.time)?.timestamp;
|
|
||||||
const timeB = chartData[Object.keys(chartData)[0]].find(d => d.time === b.time)?.timestamp;
|
|
||||||
return timeA - timeB;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const displayData = filteredData || data;
|
const displayData = filteredData || data;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -47,7 +56,7 @@ const LineChartComponent = ({
|
||||||
const handleMouseDown = (e) => {
|
const handleMouseDown = (e) => {
|
||||||
if (!e || !e.activeLabel) return;
|
if (!e || !e.activeLabel) return;
|
||||||
setIsSelecting(true);
|
setIsSelecting(true);
|
||||||
setSelectionArea({ start: e.activeLabel, end: null });
|
setSelectionArea({ start: e.activeLabel, end: null }); // activeLabel — это timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (e) => {
|
const handleMouseMove = (e) => {
|
||||||
|
|
@ -63,8 +72,8 @@ const LineChartComponent = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const startIndex = data.findIndex(point => point.time === selectionArea.start);
|
const startIndex = data.findIndex(point => point.timestamp === selectionArea.start);
|
||||||
const endIndex = data.findIndex(point => point.time === selectionArea.end);
|
const endIndex = data.findIndex(point => point.timestamp === selectionArea.end);
|
||||||
|
|
||||||
if (startIndex >= 0 && endIndex >= 0) {
|
if (startIndex >= 0 && endIndex >= 0) {
|
||||||
onRangeSelect({
|
onRangeSelect({
|
||||||
|
|
@ -78,6 +87,9 @@ const LineChartComponent = ({
|
||||||
|
|
||||||
const CustomTooltip = ({ active, payload, label }) => {
|
const CustomTooltip = ({ active, payload, label }) => {
|
||||||
if (active && payload && payload.length) {
|
if (active && payload && payload.length) {
|
||||||
|
const currentPoint = data.find(point => point.timestamp === label);
|
||||||
|
const displayTime = currentPoint?.fullTime || new Date(label).toLocaleString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
|
|
@ -86,7 +98,7 @@ const LineChartComponent = ({
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
|
boxShadow: '0 2px 5px rgba(0,0,0,0.1)'
|
||||||
}}>
|
}}>
|
||||||
<p style={{ fontWeight: 'bold', marginBottom: '5px' }}>{`${label}`}</p>
|
<p style={{ fontWeight: 'bold', marginBottom: '5px' }}>{displayTime}</p>
|
||||||
{payload.map((item, index) => (
|
{payload.map((item, index) => (
|
||||||
<p key={index} style={{ color: item.color }}>
|
<p key={index} style={{ color: item.color }}>
|
||||||
{`Значение: ${item.value}`}
|
{`Значение: ${item.value}`}
|
||||||
|
|
@ -98,6 +110,7 @@ const LineChartComponent = ({
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (!data.length) {
|
if (!data.length) {
|
||||||
return <div style={{ padding: '20px', textAlign: 'center' }}>Нет данных для отображения</div>;
|
return <div style={{ padding: '20px', textAlign: 'center' }}>Нет данных для отображения</div>;
|
||||||
}
|
}
|
||||||
|
|
@ -128,9 +141,21 @@ const LineChartComponent = ({
|
||||||
>
|
>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="time"
|
dataKey="timestamp"
|
||||||
tick={{ fontSize: 12 }}
|
height={75}
|
||||||
|
tick={{ fontSize: 12, angle: -45, textAnchor: 'end' }}
|
||||||
|
interval={Math.max(1, Math.floor(data.length / 10))}
|
||||||
|
tickFormatter={(timestamp) => {
|
||||||
|
const point = data.find(p => p.timestamp === timestamp);
|
||||||
|
return point?.fullTime || new Date(timestamp).toLocaleString('ru-RU', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<YAxis tick={{ fontSize: 12 }} />
|
<YAxis tick={{ fontSize: 12 }} />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip />} />
|
||||||
{Object.keys(chartData).map((instance, index) => (
|
{Object.keys(chartData).map((instance, index) => (
|
||||||
|
|
@ -152,6 +177,7 @@ const LineChartComponent = ({
|
||||||
fill="#4a6baf"
|
fill="#4a6baf"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,20 +22,21 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
|
|
||||||
const formatTime = useCallback((timestamp, rangeSeconds) => {
|
const formatTime = useCallback((timestamp, rangeSeconds) => {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
if (rangeSeconds > 86400) {
|
const showDate = rangeSeconds > 86400; // Показывать дату если диапазон > 24 часов
|
||||||
return {
|
|
||||||
display: date.toLocaleString([], {
|
|
||||||
day: '2-digit',
|
|
||||||
month: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
}),
|
|
||||||
timestamp: timestamp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
display: date.toLocaleTimeString([], {
|
display: date.toLocaleString([], {
|
||||||
|
|
||||||
|
month: showDate ? '2-digit' : undefined,
|
||||||
|
day: showDate ? '2-digit' : undefined,
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
}),
|
||||||
|
fullDisplay: date.toLocaleString([], {
|
||||||
|
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
second: '2-digit'
|
second: '2-digit'
|
||||||
|
|
@ -53,9 +54,9 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchData = useCallback(() => {
|
const fetchData = useCallback(() => {
|
||||||
|
|
||||||
if (isSelectingRange) return;
|
if (isSelectingRange) return;
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const start = now - selectedRange.value;
|
const start = now - selectedRange.value;
|
||||||
const end = now;
|
const end = now;
|
||||||
|
|
@ -70,7 +71,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
_t: Date.now()
|
_t: Date.now()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [metricName, selectedRange.value, isSelectingRange]);
|
}, [metricName, selectedRange.value, isSelectingRange]);
|
||||||
|
|
||||||
const groupBySecond = (points) => {
|
const groupBySecond = (points) => {
|
||||||
const grouped = [];
|
const grouped = [];
|
||||||
|
|
@ -106,10 +107,21 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
|
|
||||||
const timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
|
const timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
|
||||||
const value = parseFloat(item.value);
|
const value = parseFloat(item.value);
|
||||||
|
const formattedTime = formatTime(timestamp, selectedRange.value);
|
||||||
|
|
||||||
if (!newData[instance].some(p => p.timestamp === timestamp)) {
|
const existingPointIndex = newData[instance].findIndex(p => p.timestamp === timestamp);
|
||||||
|
|
||||||
|
if (existingPointIndex >= 0) {
|
||||||
|
newData[instance][existingPointIndex] = {
|
||||||
|
time: formattedTime.display,
|
||||||
|
fullTime: formattedTime.fullDisplay,
|
||||||
|
value: value,
|
||||||
|
timestamp: timestamp
|
||||||
|
};
|
||||||
|
} else {
|
||||||
newData[instance].push({
|
newData[instance].push({
|
||||||
time: formatTime(timestamp, selectedRange.value).display,
|
time: formattedTime.display,
|
||||||
|
fullTime: formattedTime.fullDisplay,
|
||||||
value: value,
|
value: value,
|
||||||
timestamp: timestamp
|
timestamp: timestamp
|
||||||
});
|
});
|
||||||
|
|
@ -117,7 +129,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(newData).forEach(instance => {
|
Object.keys(newData).forEach(instance => {
|
||||||
newData[instance] = groupBySecond(newData[instance])
|
newData[instance] = newData[instance]
|
||||||
.sort((a, b) => a.timestamp - b.timestamp)
|
.sort((a, b) => a.timestamp - b.timestamp)
|
||||||
.slice(-1000);
|
.slice(-1000);
|
||||||
});
|
});
|
||||||
|
|
@ -179,7 +191,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
const start = Math.floor(startDate.getTime() / 1000);
|
const start = Math.floor(startDate.getTime() / 1000);
|
||||||
const end = Math.floor(endDate.getTime() / 1000);
|
const end = Math.floor(endDate.getTime() / 1000);
|
||||||
const step = calculateStep(start, end);
|
const step = calculateStep(start, end);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, {
|
const response = await axios.get(`${import.meta.env.VITE_BACK_URL}/metrics`, {
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -189,7 +201,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
step
|
step
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую
|
if (response.data) { // Изменили условие, так как бэкенд возвращает массив напрямую
|
||||||
processMetricsData({
|
processMetricsData({
|
||||||
metric: metricName,
|
metric: metricName,
|
||||||
|
|
@ -209,17 +221,17 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
const handleRangeChange = useCallback((event) => {
|
const handleRangeChange = useCallback((event) => {
|
||||||
const selectedValue = event.target.value;
|
const selectedValue = event.target.value;
|
||||||
const range = TIME_RANGES.find(r => r.value === parseInt(selectedValue, 10));
|
const range = TIME_RANGES.find(r => r.value === parseInt(selectedValue, 10));
|
||||||
|
|
||||||
setSelectedRange(range);
|
setSelectedRange(range);
|
||||||
setUseCustomRange(false);
|
setUseCustomRange(false);
|
||||||
setChartData(null);
|
setChartData(null);
|
||||||
setSelectedGraphRange(null);
|
setSelectedGraphRange(null);
|
||||||
setFilteredData(null);
|
setFilteredData(null);
|
||||||
|
|
||||||
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) {
|
if (!socketRef.current?.connected) {
|
||||||
socketRef.current?.connect();
|
socketRef.current?.connect();
|
||||||
|
|
@ -236,7 +248,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
const handleResetZoom = useCallback(() => {
|
const handleResetZoom = useCallback(() => {
|
||||||
setSelectedGraphRange(null);
|
setSelectedGraphRange(null);
|
||||||
setFilteredData(null);
|
setFilteredData(null);
|
||||||
setIsSelectingRange(false);
|
setIsSelectingRange(false);
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [fetchData]);
|
}, [fetchData]);
|
||||||
|
|
||||||
|
|
@ -245,7 +257,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
// Начало выделения - останавливаем обновления
|
// Начало выделения - останавливаем обновления
|
||||||
setIsSelectingRange(true);
|
setIsSelectingRange(true);
|
||||||
setSelectedGraphRange(range);
|
setSelectedGraphRange(range);
|
||||||
|
|
||||||
// Отключаем сокет
|
// Отключаем сокет
|
||||||
if (socketRef.current?.connected) {
|
if (socketRef.current?.connected) {
|
||||||
socketRef.current.disconnect();
|
socketRef.current.disconnect();
|
||||||
|
|
@ -258,7 +270,7 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
} else {
|
} else {
|
||||||
// Окончание выделения - возобновляем соединение
|
// Окончание выделения - возобновляем соединение
|
||||||
setIsSelectingRange(false);
|
setIsSelectingRange(false);
|
||||||
|
|
||||||
if (!useCustomRange && socketRef.current && !socketRef.current.connected) {
|
if (!useCustomRange && socketRef.current && !socketRef.current.connected) {
|
||||||
socketRef.current.connect();
|
socketRef.current.connect();
|
||||||
}
|
}
|
||||||
|
|
@ -274,16 +286,8 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
}, [setupWebSocket]);
|
}, [setupWebSocket]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (useCustomRange) {
|
if (useCustomRange || isSelectingRange) return;
|
||||||
if (socketRef.current?.connected) {
|
|
||||||
socketRef.current.disconnect();
|
|
||||||
}
|
|
||||||
fetchCustomRangeData();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!socketRef.current?.connected || isSelectingRange) return;
|
|
||||||
|
|
||||||
const fetchDataWrapper = () => {
|
const fetchDataWrapper = () => {
|
||||||
try {
|
try {
|
||||||
fetchData();
|
fetchData();
|
||||||
|
|
@ -291,17 +295,22 @@ const PrometheusChart = ({ metricName }) => {
|
||||||
console.error('Error in interval fetch:', error);
|
console.error('Error in interval fetch:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Очищаем предыдущий интервал
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запускаем сразу и затем по интервалу
|
||||||
fetchDataWrapper();
|
fetchDataWrapper();
|
||||||
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
|
intervalRef.current = setInterval(fetchDataWrapper, selectedRange.interval);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (intervalRef.current) {
|
if (intervalRef.current) {
|
||||||
clearInterval(intervalRef.current);
|
clearInterval(intervalRef.current);
|
||||||
intervalRef.current = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [fetchData, fetchCustomRangeData, selectedRange.interval, useCustomRange, isSelectingRange]);
|
}, [fetchData, selectedRange.interval, useCustomRange, isSelectingRange]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chartData || !selectedGraphRange) {
|
if (!chartData || !selectedGraphRange) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue