prepared the graph for refactoring and added an indicator

pull/40/head
DmitriyA 2025-04-28 09:27:07 -04:00
parent d5aa312104
commit b9a2be4860
7 changed files with 129 additions and 71 deletions

View File

@ -1,34 +1,26 @@
import React from 'react'; const ChartSkeleton = () => (
import { Box, Skeleton } from '@mui/material'; <Box sx={{
const ChartSkeleton = ({ count = 1 }) => {
return (
<>
{Array.from({ length: count }).map((_, index) => (
<Box
key={index}
sx={{
backgroundColor: '#fff', backgroundColor: '#fff',
borderRadius: '8px', borderRadius: '8px',
padding: '20px', padding: '20px',
mb: 3, marginBottom: '20px',
height: '400px' position: 'relative'
}} }}>
> <Box sx={{ position: 'absolute', right: '20px', top: '20px' }}>
<Skeleton variant="circular" width={16} height={16} />
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Skeleton variant="text" width="40%" height={30} /> <Skeleton variant="text" width="40%" height={30} />
<Skeleton variant="text" width="20%" height={30} /> <Skeleton variant="text" width="30%" height={30} />
</Box> </Box>
<Skeleton variant="rectangular" width="100%" height="80%" />
<Skeleton variant="rectangular" width="100%" height={300} />
<Box sx={{ display: 'flex', justifyContent: 'center', mt: 2, gap: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'center', mt: 2, gap: 2 }}>
{[1, 2, 3, 4].map((i) => ( {[1, 2, 3, 4].map((_, i) => (
<Skeleton key={i} variant="rounded" width={80} height={36} /> <Skeleton key={i} variant="rounded" width={80} height={36} />
))} ))}
</Box> </Box>
</Box> </Box>
))}
</>
); );
};
export default ChartSkeleton;

View File

@ -101,15 +101,14 @@ const PrometheusChart = ({ metricName }) => {
}, []); }, []);
const processMetricsData = useCallback((response) => { const processMetricsData = useCallback((response, replace = false) => {
console.log('Processing metrics data:', response); console.log('Processing metrics data:', response);
if (response.metric !== metricName) return; if (response.metric !== metricName) return;
const dataArray = Array.isArray(response.data) ? response.data : [response.data]; const dataArray = Array.isArray(response.data) ? response.data : [response.data];
if (!dataArray.length) return; if (!dataArray.length) return;
setChartData(prev => { const newData = {};
const newData = { ...(prev || {}) };
const rangeSeconds = useCustomRange const rangeSeconds = useCustomRange
? (endDate.getTime() - startDate.getTime()) / 1000 ? (endDate.getTime() - startDate.getTime()) / 1000
: selectedRange.value; : selectedRange.value;
@ -118,10 +117,8 @@ const PrometheusChart = ({ metricName }) => {
const instance = item.instance || 'default'; const instance = item.instance || 'default';
if (!newData[instance]) newData[instance] = []; if (!newData[instance]) newData[instance] = [];
// Унифицированная конвертация timestamp
let timestamp; let timestamp;
if (typeof item.timestamp === 'number') { if (typeof item.timestamp === 'number') {
// Определяем, в секундах или миллисекундах пришел timestamp
timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000; timestamp = item.timestamp > 1e12 ? item.timestamp : item.timestamp * 1000;
} else { } else {
timestamp = Date.now(); timestamp = Date.now();
@ -133,21 +130,34 @@ const PrometheusChart = ({ metricName }) => {
newData[instance].push({ newData[instance].push({
time: formattedTime.display, time: formattedTime.display,
fullTime: formattedTime.fullDisplay, fullTime: formattedTime.fullDisplay,
value: value, value,
timestamp: timestamp timestamp
}); });
}); });
// Сортируем и ограничиваем данные
Object.keys(newData).forEach(instance => { Object.keys(newData).forEach(instance => {
newData[instance] = newData[instance] newData[instance] = newData[instance]
.sort((a, b) => a.timestamp - b.timestamp) .sort((a, b) => a.timestamp - b.timestamp)
.slice(-1000); .slice(-1000);
}); });
return newData;
if (replace) {
setChartData(newData); // Заменяем полностью
} else {
setChartData(prev => {
const merged = { ...(prev || {}) };
Object.keys(newData).forEach(instance => {
if (!merged[instance]) merged[instance] = [];
merged[instance] = [...merged[instance], ...newData[instance]]
.sort((a, b) => a.timestamp - b.timestamp)
.slice(-1000);
}); });
return merged;
});
}
}, [metricName, selectedRange.value, formatTime, useCustomRange, startDate, endDate]); }, [metricName, selectedRange.value, formatTime, useCustomRange, startDate, endDate]);
const fetchData = useCallback(() => { const fetchData = useCallback(() => {
if (isSelectingRange) return; if (isSelectingRange) return;
@ -197,7 +207,7 @@ const PrometheusChart = ({ metricName }) => {
processMetricsData({ processMetricsData({
metric: metricName, metric: metricName,
data: processedData data: processedData
}); }, true);
} }
} catch (error) { } catch (error) {
console.error('Ошибка при получении кастомных данных:', error); console.error('Ошибка при получении кастомных данных:', error);
@ -340,7 +350,9 @@ const PrometheusChart = ({ metricName }) => {
useEffect(() => { useEffect(() => {
// Обработчик данных с сервера // Обработчик данных с сервера
const handleMetricsData = (data) => { const handleMetricsData = (data) => {
if (!useCustomRange) {
processMetricsData({ metric: metricName, data }); processMetricsData({ metric: metricName, data });
}
}; };
// Подписываемся на обновления метрики // Подписываемся на обновления метрики
@ -359,7 +371,8 @@ const PrometheusChart = ({ metricName }) => {
clearInterval(intervalRef.current); clearInterval(intervalRef.current);
} }
}; };
}, [metricName, processMetricsData]); }, [metricName, useCustomRange, processMetricsData]);
useEffect(() => { useEffect(() => {
if (useCustomRange && !isSelectingRange) { if (useCustomRange && !isSelectingRange) {

View File

@ -1,4 +1,3 @@
// src/services/WebSocketManager.js
import { io } from 'socket.io-client'; import { io } from 'socket.io-client';
class WebSocketManager { class WebSocketManager {
@ -7,13 +6,16 @@ class WebSocketManager {
this.subscribers = new Map(); this.subscribers = new Map();
this.connectionStatus = 'disconnected'; this.connectionStatus = 'disconnected';
this.connectionCallbacks = new Set(); this.connectionCallbacks = new Set();
this.connecting = false;
} }
connect() { connect() {
if (this.socket && (this.socket.connected || this.socket.reconnecting)) { if (this.socket?.connected || this.connecting) {
return this.socket; return this.socket;
} }
this.connecting = true;
this.socket = io(`${import.meta.env.VITE_BACK_WS_URL}/api/metrics-ws`, { this.socket = io(`${import.meta.env.VITE_BACK_WS_URL}/api/metrics-ws`, {
transports: ['websocket'], transports: ['websocket'],
reconnection: true, reconnection: true,
@ -24,11 +26,13 @@ class WebSocketManager {
this.socket.on('connect', () => { this.socket.on('connect', () => {
this.connectionStatus = 'connected'; this.connectionStatus = 'connected';
this.connecting = false;
this.notifyConnectionStatus(); this.notifyConnectionStatus();
}); });
this.socket.on('disconnect', (reason) => { this.socket.on('disconnect', (reason) => {
this.connectionStatus = 'disconnected'; this.connectionStatus = 'disconnected';
this.connecting = false;
this.notifyConnectionStatus(); this.notifyConnectionStatus();
if (reason === 'io server disconnect') this.socket.connect(); if (reason === 'io server disconnect') this.socket.connect();
}); });
@ -50,7 +54,9 @@ class WebSocketManager {
} }
subscribe(metricName, callback) { subscribe(metricName, callback) {
if (!this.socket?.connected) {
this.connect(); this.connect();
}
if (!this.subscribers.has(metricName)) { if (!this.subscribers.has(metricName)) {
this.subscribers.set(metricName, new Set()); this.subscribers.set(metricName, new Set());

View File

@ -1,9 +1,30 @@
import SystemStatusChart from "../../Charts/SystemStatusChart"; import SystemStatusChart from "../../Charts/SystemStatusChart";
import TreeTable from "../UI/TreeTable"; import TreeTable from "../UI/TreeTable";
import FlowChart from "../TreeChart/FlowChart"; import FlowChart from "../TreeChart/FlowChart";
import { getStatusColor } from "../TreeChart/dataUtils";
const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => { const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleOpenTab }) => {
// Функция для подсчета количества элементов каждого статуса
const countStatuses = (data) => {
const counts = { green: 0, yellow: 0, orange: 0, red: 0 };
const countRecursive = (node) => {
if (node.status) {
counts[node.status]++;
}
if (node.items && node.items.length > 0) {
node.items.forEach(child => countRecursive(child));
}
};
countRecursive(data);
return counts;
};
if (activeTab === "Главная") { if (activeTab === "Главная") {
const statusCounts = treeData1 ? countStatuses(treeData1) : { green: 0, yellow: 0, orange: 0, red: 0 };
return ( return (
<div> <div>
<h2 style={{ textAlign: 'center' }}>Общий мониторинг состояния системы</h2> <h2 style={{ textAlign: 'center' }}>Общий мониторинг состояния системы</h2>
@ -17,6 +38,32 @@ const TabContent = ({ activeTab, statusHistories, treeData1, tabContent, handleO
<SystemStatusChart data={statusHistories.history2} /> <SystemStatusChart data={statusHistories.history2} />
</div> </div>
</div> </div>
{/* Контейнер для индикаторов статусов */}
<div style={{
display: 'flex',
justifyContent: 'flex-end',
marginTop: '20px',
gap: '10px'
}}>
{Object.entries(statusCounts).map(([status, count]) => (
<div key={status} style={{
width: '30px',
height: '30px',
backgroundColor: getStatusColor(status),
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
fontWeight: 'bold',
borderRadius: '5px',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
}}>
{count}
</div>
))}
</div>
<label>Статус компонентов системы</label> <label>Статус компонентов системы</label>
<TreeTable data={treeData1} /> <TreeTable data={treeData1} />
</div> </div>