graph refactor
parent
b9a2be4860
commit
4dfd972615
|
|
@ -0,0 +1,97 @@
|
||||||
|
import React from 'react';
|
||||||
|
import DatePicker from 'react-datepicker';
|
||||||
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
|
||||||
|
const DateRangeSelector = ({
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
onStartDateChange,
|
||||||
|
onEndDateChange,
|
||||||
|
onApply
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
marginTop: 10,
|
||||||
|
backgroundColor: '#f5f5f5',
|
||||||
|
padding: '15px',
|
||||||
|
borderRadius: '4px'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
marginBottom: '10px',
|
||||||
|
fontWeight: '500',
|
||||||
|
color: '#555'
|
||||||
|
}}>
|
||||||
|
Укажите диапазон дат:
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '10px',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
alignItems: 'flex-end'
|
||||||
|
}}>
|
||||||
|
<div style={{ flex: '1 1 200px' }}>
|
||||||
|
<DatePicker
|
||||||
|
selected={startDate}
|
||||||
|
onChange={onStartDateChange}
|
||||||
|
showTimeSelect
|
||||||
|
timeFormat="HH:mm"
|
||||||
|
timeIntervals={15}
|
||||||
|
dateFormat="yyyy-MM-dd HH:mm"
|
||||||
|
placeholderText="Начальная дата"
|
||||||
|
customInput={
|
||||||
|
<input style={{
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
color: "#555",
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #ddd'
|
||||||
|
}} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: '1 1 200px' }}>
|
||||||
|
<DatePicker
|
||||||
|
selected={endDate}
|
||||||
|
onChange={onEndDateChange}
|
||||||
|
showTimeSelect
|
||||||
|
timeFormat="HH:mm"
|
||||||
|
timeIntervals={15}
|
||||||
|
dateFormat="yyyy-MM-dd HH:mm"
|
||||||
|
placeholderText="Конечная дата"
|
||||||
|
customInput={
|
||||||
|
<input style={{
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
color: "#555",
|
||||||
|
width: '100%',
|
||||||
|
padding: '8px 12px',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #ddd'
|
||||||
|
}} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onApply}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
backgroundColor: '#4a6baf',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'background-color 0.2s',
|
||||||
|
flex: '0 0 auto',
|
||||||
|
height: '36px'
|
||||||
|
}}
|
||||||
|
onMouseOver={(e) => e.target.style.backgroundColor = '#3a5a9f'}
|
||||||
|
onMouseOut={(e) => e.target.style.backgroundColor = '#4a6baf'}
|
||||||
|
>
|
||||||
|
Применить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateRangeSelector;
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
|
|
||||||
|
const LineChartComponent = ({
|
||||||
|
data,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
metaInfo,
|
||||||
|
dataKey = 'value',
|
||||||
|
lineColor = '#8884d8',
|
||||||
|
height = 400,
|
||||||
|
showLegend = true,
|
||||||
|
showGrid = true,
|
||||||
|
customTooltip,
|
||||||
|
customXAxisFormatter,
|
||||||
|
customYAxis,
|
||||||
|
additionalLines = []
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%', height: '560' }}>
|
||||||
|
{title && <h3>{title}</h3>}
|
||||||
|
{description && (
|
||||||
|
<p style={{ marginTop: -10, color: '#666' }}>{description}</p>
|
||||||
|
)}
|
||||||
|
{metaInfo && (
|
||||||
|
<div style={{ fontSize: 12, color: '#888', marginBottom: 10 }}>
|
||||||
|
{metaInfo}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ResponsiveContainer width="100%" height="80%">
|
||||||
|
<LineChart
|
||||||
|
data={data}
|
||||||
|
margin={{
|
||||||
|
top: 5,
|
||||||
|
right: 30,
|
||||||
|
left: 20,
|
||||||
|
bottom: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showGrid && <CartesianGrid strokeDasharray="3 3" />}
|
||||||
|
<XAxis
|
||||||
|
dataKey="timestamp"
|
||||||
|
tickFormatter={customXAxisFormatter || ((timestamp) => new Date(timestamp).toLocaleTimeString())}
|
||||||
|
/>
|
||||||
|
{customYAxis || <YAxis />}
|
||||||
|
<Tooltip
|
||||||
|
content={customTooltip}
|
||||||
|
labelFormatter={(timestamp) => new Date(timestamp).toLocaleString()}
|
||||||
|
/>
|
||||||
|
{showLegend && <Legend />}
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dataKey={dataKey}
|
||||||
|
stroke={lineColor}
|
||||||
|
activeDot={{ r: 8 }}
|
||||||
|
name={title}
|
||||||
|
/>
|
||||||
|
{additionalLines.map((lineProps, index) => (
|
||||||
|
<Line key={index} {...lineProps} />
|
||||||
|
))}
|
||||||
|
</LineChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LineChartComponent;
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
import { io } from 'socket.io-client';
|
||||||
|
|
||||||
|
class MetricsService {
|
||||||
|
constructor(baseUrl) {
|
||||||
|
console.log('MetricsService constructor');
|
||||||
|
this.baseUrl = baseUrl || window.location.origin;
|
||||||
|
this.socket = null;
|
||||||
|
this.subscriptions = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP методы - адаптированы под ваш бэкенд
|
||||||
|
async fetchMetricsRange(metric, start, end, step = 15) {
|
||||||
|
try {
|
||||||
|
// Формируем URL согласно вашему API
|
||||||
|
const url = new URL(`${this.baseUrl}/api/metrics`);
|
||||||
|
url.searchParams.append('metric', metric);
|
||||||
|
url.searchParams.append('start', start);
|
||||||
|
url.searchParams.append('end', end);
|
||||||
|
url.searchParams.append('step', step);
|
||||||
|
|
||||||
|
console.log('Fetching metrics range from:', url.toString());
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Проверяем формат данных
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
console.error('Unexpected data format:', data);
|
||||||
|
throw new Error('Invalid data format: expected array');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchMetricsRange:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchMetrics(metric) {
|
||||||
|
try {
|
||||||
|
// Формируем URL для текущих метрик
|
||||||
|
const url = new URL(`${this.baseUrl}/api/metrics`);
|
||||||
|
url.searchParams.append('metric', metric);
|
||||||
|
|
||||||
|
console.log('Fetching current metrics from:', url.toString());
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Проверяем формат данных
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
console.error('Unexpected data format:', data);
|
||||||
|
throw new Error('Invalid data format: expected array');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in fetchMetrics:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocket методы - остаются без изменений
|
||||||
|
connectWebSocket() {
|
||||||
|
if (this.socket && this.socket.connected) return;
|
||||||
|
console.trace('connectWebSocket called');
|
||||||
|
this.socket = io(`${this.baseUrl}/api/metrics-ws`, {
|
||||||
|
transports: ['websocket'],
|
||||||
|
withCredentials: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('connect', () => {
|
||||||
|
console.log('Socket.IO connected');
|
||||||
|
// Подписаться заново на все метрики
|
||||||
|
for (const [metric, callbacks] of this.subscriptions.entries()) {
|
||||||
|
this.socket.emit('subscribe-metric', { metric });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('disconnect', () => {
|
||||||
|
console.log('Socket.IO disconnected');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('metrics-data', ({ metric, data }) => {
|
||||||
|
const callbacks = this.subscriptions.get(metric) || [];
|
||||||
|
callbacks.forEach(cb => cb(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on('metrics-error', payload => {
|
||||||
|
console.error('Metrics error:', payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeToMetric(metric, callback, interval = 5000) {
|
||||||
|
this.connectWebSocket();
|
||||||
|
|
||||||
|
if (!this.subscriptions.has(metric)) {
|
||||||
|
this.subscriptions.set(metric, []);
|
||||||
|
this.socket.emit('subscribe-metric', { metric, interval });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subscriptions.get(metric).push(callback);
|
||||||
|
|
||||||
|
return () => this.unsubscribeFromMetric(metric, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsubscribeFromMetric(metric, callback) {
|
||||||
|
const callbacks = this.subscriptions.get(metric) || [];
|
||||||
|
const filtered = callbacks.filter(cb => cb !== callback);
|
||||||
|
|
||||||
|
if (filtered.length === 0) {
|
||||||
|
this.subscriptions.delete(metric);
|
||||||
|
if (this.socket && this.socket.connected) {
|
||||||
|
this.socket.emit('unsubscribe-metric', { metric });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.subscriptions.set(metric, filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
disconnectWebSocket() {
|
||||||
|
if (this.socket) {
|
||||||
|
this.socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metricsService = new MetricsService(import.meta.env.VITE_BACK_URL);
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import LineChartComponent from './Components/LineChartComponent';
|
||||||
|
import DateRangeSelector from './Components/DateRangeSelector';
|
||||||
|
import { metricsService } from './Components/metricsService';
|
||||||
|
import { Button, Radio, message } from 'antd';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const PrometheusChart = ({ metricName, chartHeight = 560 }) => {
|
||||||
|
const [chartData, setChartData] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
const [metricInfo, setMetricInfo] = useState({});
|
||||||
|
const [mode, setMode] = useState('realtime');
|
||||||
|
const [startDate, setStartDate] = useState(moment().subtract(1, 'hour').toDate());
|
||||||
|
const [endDate, setEndDate] = useState(moment().toDate());
|
||||||
|
const [isLiveUpdating, setIsLiveUpdating] = useState(false);
|
||||||
|
|
||||||
|
const fetchHistoricalData = async (start, end) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const startUnix = Math.floor(new Date(start).getTime() / 1000);
|
||||||
|
const endUnix = Math.floor(new Date(end).getTime() / 1000);
|
||||||
|
|
||||||
|
const data = await metricsService.fetchMetricsRange(metricName, startUnix, endUnix, 15);
|
||||||
|
|
||||||
|
const dataArray = Array.isArray(data) ? data : [data];
|
||||||
|
const formattedData = dataArray.map(item => ({
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
value: parseFloat(item.value),
|
||||||
|
name: item.__name__ || metricName,
|
||||||
|
status: item.status
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (dataArray.length > 0) {
|
||||||
|
setMetricInfo({
|
||||||
|
type: dataArray[0].type,
|
||||||
|
description: dataArray[0].description,
|
||||||
|
instance: dataArray[0].instance,
|
||||||
|
job: dataArray[0].job
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setChartData(formattedData);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error loading historical data for ${metricName}:`, err);
|
||||||
|
setError(err.message);
|
||||||
|
message.error(`Failed to load historical data: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const startRealtimeUpdates = () => {
|
||||||
|
setIsLiveUpdating(true);
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const end = new Date();
|
||||||
|
const start = new Date(end.getTime() - 3600 * 1000);
|
||||||
|
fetchHistoricalData(start, end).finally(() => {
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return metricsService.subscribeToMetric(
|
||||||
|
metricName,
|
||||||
|
(newData) => {
|
||||||
|
const newDataArray = Array.isArray(newData) ? newData : [newData];
|
||||||
|
const formattedNewData = newDataArray.map(item => ({
|
||||||
|
timestamp: item.timestamp,
|
||||||
|
value: parseFloat(item.value),
|
||||||
|
name: item.__name__ || metricName,
|
||||||
|
status: item.status
|
||||||
|
}));
|
||||||
|
|
||||||
|
setChartData(prevData => [...prevData, ...formattedNewData].slice(-200));
|
||||||
|
},
|
||||||
|
5000
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopRealtimeUpdates = () => {
|
||||||
|
setIsLiveUpdating(false);
|
||||||
|
metricsService.unsubscribeFromMetric(metricName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCustomRangeApply = () => {
|
||||||
|
if (startDate && endDate) {
|
||||||
|
fetchHistoricalData(startDate, endDate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let unsubscribe;
|
||||||
|
|
||||||
|
if (mode === 'realtime') {
|
||||||
|
unsubscribe = startRealtimeUpdates();
|
||||||
|
} else {
|
||||||
|
stopRealtimeUpdates();
|
||||||
|
fetchHistoricalData(startDate, endDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (unsubscribe) unsubscribe();
|
||||||
|
stopRealtimeUpdates();
|
||||||
|
};
|
||||||
|
}, [mode, metricName]);
|
||||||
|
|
||||||
|
const metaInfo = [
|
||||||
|
metricInfo.instance && `Instance: ${metricInfo.instance}`,
|
||||||
|
metricInfo.job && `Job: ${metricInfo.job}`,
|
||||||
|
metricInfo.type && `Type: ${metricInfo.type}`
|
||||||
|
].filter(Boolean).join(' | ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Radio.Group
|
||||||
|
value={mode}
|
||||||
|
onChange={(e) => setMode(e.target.value)}
|
||||||
|
buttonStyle="solid"
|
||||||
|
style={{ marginBottom: 10 }}
|
||||||
|
>
|
||||||
|
<Radio.Button value="realtime">Режим реального времени</Radio.Button>
|
||||||
|
<Radio.Button value="historical">Исторические данные</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
{mode === 'historical' && (
|
||||||
|
<DateRangeSelector
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
|
onStartDateChange={setStartDate}
|
||||||
|
onEndDateChange={setEndDate}
|
||||||
|
onApply={handleCustomRangeApply}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{mode === 'realtime' && isLiveUpdating && (
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={() => setMode('historical')}
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
>
|
||||||
|
Остановить обновление
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading ? (
|
||||||
|
<div>Loading chart data...</div>
|
||||||
|
) : error ? (
|
||||||
|
<div>Error loading metric: {error}</div>
|
||||||
|
) : chartData.length === 0 ? (
|
||||||
|
<div>No data available for {metricName}</div>
|
||||||
|
) : (
|
||||||
|
<LineChartComponent
|
||||||
|
data={chartData}
|
||||||
|
title={metricName}
|
||||||
|
description={metricInfo.description}
|
||||||
|
metaInfo={metaInfo}
|
||||||
|
height={chartHeight}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrometheusChart;
|
||||||
|
|
@ -15,7 +15,7 @@ import { getStatusColor } from "../../TreeChart/dataUtils";
|
||||||
const StyledListItem = styled(ListItem)(({ theme, level }) => ({
|
const StyledListItem = styled(ListItem)(({ theme, level }) => ({
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
paddingLeft: theme.spacing(2 + level * 2),
|
paddingLeft: theme.spacing(2 + level * 2),
|
||||||
position: 'relative', // Добавляем для позиционирования индикатора
|
position: 'relative',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: theme.palette.action.hover,
|
backgroundColor: theme.palette.action.hover,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -45,12 +45,10 @@ const FlowChart = ({ data }) => {
|
||||||
const findAndCollapseLastLevelParents = (items) => {
|
const findAndCollapseLastLevelParents = (items) => {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
if (item.items && item.items.length > 0) {
|
if (item.items && item.items.length > 0) {
|
||||||
// Проверяем, есть ли у детей свои дети
|
|
||||||
const hasGrandchildren = item.items.some(child =>
|
const hasGrandchildren = item.items.some(child =>
|
||||||
child.items && child.items.length > 0
|
child.items && child.items.length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// Если у детей нет своих детей - это родители последнего уровня
|
|
||||||
if (!hasGrandchildren) {
|
if (!hasGrandchildren) {
|
||||||
toggleNodeCollapse(item.id);
|
toggleNodeCollapse(item.id);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => {
|
||||||
const baseLevelRadius = 150;
|
const baseLevelRadius = 150;
|
||||||
|
|
||||||
const traverse = (item, parentId = null, level = 0, angleStart = 0, angleEnd = 2 * Math.PI, parentRadius = 0) => {
|
const traverse = (item, parentId = null, level = 0, angleStart = 0, angleEnd = 2 * Math.PI, parentRadius = 0) => {
|
||||||
if (!item || collapsedNodes[parentId]) return; // Пропускаем свёрнутые узлы
|
if (!item || collapsedNodes[parentId]) return;
|
||||||
|
|
||||||
const nodeId = item.id;
|
const nodeId = item.id;
|
||||||
const items = item.items || [];
|
const items = item.items || [];
|
||||||
|
|
@ -58,7 +58,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => {
|
||||||
data: {
|
data: {
|
||||||
...item,
|
...item,
|
||||||
label: item.title,
|
label: item.title,
|
||||||
style: getNodeStyle(item, isLeaf), // Переносим стили в data
|
style: getNodeStyle(item, isLeaf),
|
||||||
hasChildren: items.length > 0,
|
hasChildren: items.length > 0,
|
||||||
collapsed: collapsedNodes[nodeId]
|
collapsed: collapsedNodes[nodeId]
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +88,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => {
|
||||||
|
|
||||||
const centerNode = {
|
const centerNode = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
type: 'customNode', // Добавляем тип узла
|
type: 'customNode',
|
||||||
position: nodePositions[data.id] || { x: centerX, y: centerY },
|
position: nodePositions[data.id] || { x: centerX, y: centerY },
|
||||||
style: getCenterNodeStyle(data),
|
style: getCenterNodeStyle(data),
|
||||||
data: { label: data.title, hasChildren: data.items.length > 0, collapsed: collapsedNodes[data.id] }
|
data: { label: data.title, hasChildren: data.items.length > 0, collapsed: collapsedNodes[data.id] }
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ const NodeWrapper = memo(({ id, data, selected }) => {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
overflow: 'hidden', // Чтобы текст не выходил за границы
|
overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis', // Добавляем многоточие если текст не помещается
|
textOverflow: 'ellipsis',
|
||||||
whiteSpace: 'nowrap', // Запрещаем перенос строк
|
whiteSpace: 'nowrap',
|
||||||
padding: '0 8px', // Горизонтальный padding для текста
|
padding: '0 8px',
|
||||||
boxSizing: 'border-box' // Учитываем padding в общей ширине
|
boxSizing: 'border-box'
|
||||||
}}
|
}}
|
||||||
title={data.label} // Простой tooltip при наведении
|
title={data.label}
|
||||||
>
|
>
|
||||||
{/* Хендл для входящих соединений */}
|
{/* Хендл для входящих соединений */}
|
||||||
<Handle
|
<Handle
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export const useFlowChart = (initialData) => {
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||||
const [nodePositions, setNodePositions] = useState({});
|
const [nodePositions, setNodePositions] = useState({});
|
||||||
const [collapsedNodes, setCollapsedNodes] = useState({}); // Добавили
|
const [collapsedNodes, setCollapsedNodes] = useState({});
|
||||||
|
|
||||||
const toggleNodeCollapse = useCallback((nodeId) => {
|
const toggleNodeCollapse = useCallback((nodeId) => {
|
||||||
setCollapsedNodes((prev) => ({
|
setCollapsedNodes((prev) => ({
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { useCallback } from 'react';
|
||||||
|
|
||||||
export const useNodeHandlers = (debouncedSetNodePositions) => {
|
export const useNodeHandlers = (debouncedSetNodePositions) => {
|
||||||
const onNodeDrag = useCallback((event, node) => {
|
const onNodeDrag = useCallback((event, node) => {
|
||||||
// Фиксируем позицию сразу при перемещении
|
|
||||||
node.position = {
|
node.position = {
|
||||||
x: Math.round(node.position.x),
|
x: Math.round(node.position.x),
|
||||||
y: Math.round(node.position.y)
|
y: Math.round(node.position.y)
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,43 @@
|
||||||
const StatusManager = () => {
|
const StatusManager = () => {
|
||||||
const getRandomStatus = () => {
|
const getRandomStatus = () => {
|
||||||
const statuses = [
|
const statuses = [
|
||||||
...Array(90).fill("green"), // 90% шанс
|
...Array(90).fill("green"),
|
||||||
...Array(6).fill("yellow"), // 6% шанс
|
...Array(6).fill("yellow"),
|
||||||
...Array(3).fill("orange"), // 3% шанс
|
...Array(3).fill("orange"),
|
||||||
...Array(1).fill("red"), // 1% шанс
|
...Array(1).fill("red"),
|
||||||
];
|
];
|
||||||
return statuses[Math.floor(Math.random() * statuses.length)];
|
return statuses[Math.floor(Math.random() * statuses.length)];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusWeight = (status) => {
|
const getStatusWeight = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "green": return 1; // 100% здоровья
|
case "green": return 1;
|
||||||
case "yellow": return 0.75;
|
case "yellow": return 0.75;
|
||||||
case "orange": return 0.5;
|
case "orange": return 0.5;
|
||||||
case "red": return 0.25; // 25% здоровья
|
case "red": return 0.25;
|
||||||
default: return 1; // По умолчанию "green"
|
default: return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStatuses = (data) => {
|
const updateStatuses = (data) => {
|
||||||
if (!data.items || data.items.length === 0) {
|
if (!data.items || data.items.length === 0) {
|
||||||
// Если это элемент нижнего уровня, генерируем случайный статус
|
|
||||||
data.status = getRandomStatus();
|
data.status = getRandomStatus();
|
||||||
return getStatusWeight(data.status);
|
return getStatusWeight(data.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Рекурсивно обновляем статусы для всех дочерних элементов
|
|
||||||
let childStatusWeights = data.items.map((child) => updateStatuses(child));
|
let childStatusWeights = data.items.map((child) => updateStatuses(child));
|
||||||
|
|
||||||
// Проверяем, есть ли дочерние элементы (избегаем деления на 0)
|
|
||||||
if (childStatusWeights.length === 0) {
|
if (childStatusWeights.length === 0) {
|
||||||
data.status = "green";
|
data.status = "green";
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вычисляем среднее арифметическое значение весов статусов
|
|
||||||
const averageStatusWeight =
|
const averageStatusWeight =
|
||||||
childStatusWeights.reduce((sum, weight) => sum + weight, 0) / childStatusWeights.length;
|
childStatusWeights.reduce((sum, weight) => sum + weight, 0) / childStatusWeights.length;
|
||||||
|
|
||||||
// Определяем статус текущего элемента
|
|
||||||
data.status = getStatusFromWeight(averageStatusWeight);
|
data.status = getStatusFromWeight(averageStatusWeight);
|
||||||
|
|
||||||
return Math.max(0, averageStatusWeight); // Гарантия, что не будет отрицательных значений
|
return Math.max(0, averageStatusWeight);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusFromWeight = (weight) => {
|
const getStatusFromWeight = (weight) => {
|
||||||
|
|
@ -69,16 +64,13 @@ const StatusManager = () => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Создаем два независимых менеджера статусов
|
|
||||||
export const statusManager1 = StatusManager();
|
export const statusManager1 = StatusManager();
|
||||||
export const statusManager2 = StatusManager();
|
export const statusManager2 = StatusManager();
|
||||||
|
|
||||||
// Функция для расчета процентов здоровья системы
|
|
||||||
export const calculateStatusPercentage = (averageStatusValue) => {
|
export const calculateStatusPercentage = (averageStatusValue) => {
|
||||||
return Math.max(0, Math.min(100, averageStatusValue * 100));
|
return Math.max(0, Math.min(100, averageStatusValue * 100));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Экспортируем getStatusColor отдельно
|
|
||||||
export const getStatusColor = (status) => {
|
export const getStatusColor = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "green":
|
case "green":
|
||||||
|
|
|
||||||
|
|
@ -400,182 +400,136 @@
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "182",
|
"id": "182",
|
||||||
"title": "Graviton S2082I (device$18)",
|
"title": "Graviton S2082I (device$19)",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "42",
|
"id": "42",
|
||||||
"title": "OS Linux (module$4) АО",
|
"title": "OS Linux (module$6) АО",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "1902",
|
"id": "371",
|
||||||
"title": "Загрузка процессора за 1 минуту"
|
"title": "Загрузка процессора за 1 минуту"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1912",
|
"id": "372",
|
||||||
"title": "Загрузка процессора за 5 минут"
|
"title": "Загрузка процессора за 5 минут"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1922",
|
"id": "373",
|
||||||
"title": "Загрузка процессора за 15 минут"
|
"title": "Загрузка процессора за 15 минут"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1972",
|
"id": "378",
|
||||||
"title": "Общий объем SWAP-файла"
|
"title": "Общий объем SWAP-файла"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1982",
|
"id": "379",
|
||||||
"title": "Используемый объем SWAP-файла"
|
"title": "Используемый объем SWAP-файла"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "1992",
|
"id": "380",
|
||||||
"title": "Общий объем физической оперативной памяти"
|
"title": "Общий объем физической оперативной памяти"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2002",
|
"id": "381",
|
||||||
"title": "Доступный объем физической оперативной памяти"
|
"title": "Доступный объем физической оперативной памяти"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2012",
|
"id": "382",
|
||||||
"title": "Свободный объем физической и виртуальной оперативной памяти"
|
"title": "Свободный объем физической и виртуальной оперативной памяти"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2022",
|
"id": "383",
|
||||||
"title": "Буферизованный объем оперативной памяти"
|
"title": "Буферизованный объем оперативной памяти"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2032",
|
"id": "384",
|
||||||
"title": "Кэшированый объем оперативной памяти"
|
"title": "Кэшированый объем оперативной памяти"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2742",
|
"id": "375",
|
||||||
"title": "Используемый объем SWAP-файла"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2752",
|
|
||||||
"title": "Время затраченное процессором на процессы с пониженным приоритетом"
|
"title": "Время затраченное процессором на процессы с пониженным приоритетом"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2762",
|
"id": "376",
|
||||||
"title": "Время затраченное процессором на процессы ядра ОС"
|
"title": "Время затраченное процессором на процессы ядра ОС"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2772",
|
"id": "377",
|
||||||
"title": "Время простоя процессора"
|
"title": "Время простоя процессора"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2782",
|
"id": "385",
|
||||||
"title": "Общая емкость жестких дисков"
|
"title": "Общая емкость жестких дисков"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2792",
|
"id": "386",
|
||||||
"title": "Доступная емкость жестких дисков"
|
"title": "Доступная емкость жестких дисков"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "52",
|
|
||||||
"title": "Vinteo (module$5) ПО",
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "312",
|
|
||||||
"title": "Общее количество участников"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "322",
|
|
||||||
"title": "Ожидание соединения"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "332",
|
|
||||||
"title": "Зарегистрированные абоненты"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "342",
|
|
||||||
"title": "Количество пользоватей HLS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "352",
|
|
||||||
"title": "Общее количество P2P комнат"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "362",
|
|
||||||
"title": "Общее количество конференций"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "372",
|
|
||||||
"title": "Общее количество активных конференций"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "382",
|
|
||||||
"title": "Статус записи"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "392",
|
|
||||||
"title": "Общее количество сохранённых записей"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "2802",
|
"id": "2802",
|
||||||
"title": "Сетевой адаптер №1 (port$261) Eth_1",
|
"title": "Сетевой адаптер №1 (port$261) Eth_1",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "2072",
|
"id": "388",
|
||||||
"title": "Скорость порта Eth_1"
|
"title": "Скорость порта Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2092",
|
"id": "390",
|
||||||
"title": "Административное состояние порта Eth_1"
|
"title": "Административное состояние порта Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2102",
|
"id": "391",
|
||||||
"title": "Оперативное состояние порта Eth_1"
|
"title": "Оперативное состояние порта Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2112",
|
"id": "392",
|
||||||
"title": "Общее количество отправленных октетов Eth_1"
|
"title": "Общее количество отправленных октетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2122",
|
"id": "393",
|
||||||
"title": "Количество входящих Multicast пакетов Eth_1"
|
"title": "Количество входящих Multicast пакетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2132",
|
"id": "394",
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_1"
|
"title": "Количество иcходящих Multiicast пакетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2142",
|
"id": "395",
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_1"
|
"title": "Количество входящих Broadcast пакетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2152",
|
"id": "396",
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_1"
|
"title": "Количество иcходящих Broadcast пакетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2162",
|
"id": "397",
|
||||||
"title": "Количество входящих Unicast пакетов Eth_1"
|
"title": "Количество входящих Unicast пакетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2172",
|
"id": "398",
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_1"
|
"title": "Количество иcходящих Unicast пакетов Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2182",
|
"id": "399",
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_1"
|
"title": "Количество входящих пакетов помеченные как отброшенные Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2192",
|
"id": "400",
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_1"
|
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2202",
|
"id": "401",
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_1"
|
"title": "Количество входящих пакетов с ошибкой Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2212",
|
"id": "402",
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_1"
|
"title": "Количество исходящих пакетов с ошибкой Eth_1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2222",
|
"id": "403",
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_1"
|
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -585,63 +539,63 @@
|
||||||
"title": "Сетевой адаптер №2 (port$262) Eth_2",
|
"title": "Сетевой адаптер №2 (port$262) Eth_2",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "2242",
|
"id": "405",
|
||||||
"title": "Скорость порта Eth_2"
|
"title": "Скорость порта Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2262",
|
"id": "407",
|
||||||
"title": "Административное состояние порта Eth_2"
|
"title": "Административное состояние порта Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2272",
|
"id": "408",
|
||||||
"title": "Оперативное состояние порта Eth_2"
|
"title": "Оперативное состояние порта Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2282",
|
"id": "409",
|
||||||
"title": "Общее количество отправленных октетов Eth_2"
|
"title": "Общее количество отправленных октетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2292",
|
"id": "410",
|
||||||
"title": "Количество входящих Multicast пакетов Eth_2"
|
"title": "Количество входящих Multicast пакетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2302",
|
"id": "411",
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_2"
|
"title": "Количество иcходящих Multiicast пакетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2312",
|
"id": "412",
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_2"
|
"title": "Количество входящих Broadcast пакетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2322",
|
"id": "413",
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_2"
|
"title": "Количество иcходящих Broadcast пакетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2332",
|
"id": "414",
|
||||||
"title": "Количество входящих Unicast пакетов Eth_2"
|
"title": "Количество входящих Unicast пакетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2342",
|
"id": "415",
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_2"
|
"title": "Количество иcходящих Unicast пакетов Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2352",
|
"id": "416",
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_2"
|
"title": "Количество входящих пакетов помеченные как отброшенные Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2362",
|
"id": "417",
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_2"
|
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2372",
|
"id": "418",
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_2"
|
"title": "Количество входящих пакетов с ошибкой Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2382",
|
"id": "419",
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_2"
|
"title": "Количество исходящих пакетов с ошибкой Eth_2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2392",
|
"id": "420",
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_2"
|
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -651,63 +605,63 @@
|
||||||
"title": "Сетевой адаптер №3 (port$263) Eth_3",
|
"title": "Сетевой адаптер №3 (port$263) Eth_3",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "2412",
|
"id": "422",
|
||||||
"title": "Скорость порта Eth_3"
|
"title": "Скорость порта Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2432",
|
"id": "424",
|
||||||
"title": "Административное состояние порта Eth_3"
|
"title": "Административное состояние порта Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2442",
|
"id": "425",
|
||||||
"title": "Оперативное состояние порта Eth_3"
|
"title": "Оперативное состояние порта Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2452",
|
"id": "426",
|
||||||
"title": "Общее количество отправленных октетов Eth_3"
|
"title": "Общее количество отправленных октетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2462",
|
"id": "427",
|
||||||
"title": "Количество входящих Multicast пакетов Eth_3"
|
"title": "Количество входящих Multicast пакетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2472",
|
"id": "428",
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_3"
|
"title": "Количество иcходящих Multiicast пакетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2482",
|
"id": "429",
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_3"
|
"title": "Количество входящих Broadcast пакетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2492",
|
"id": "430",
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_3"
|
"title": "Количество иcходящих Broadcast пакетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2502",
|
"id": "431",
|
||||||
"title": "Количество входящих Unicast пакетов Eth_3"
|
"title": "Количество входящих Unicast пакетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2512",
|
"id": "432",
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_3"
|
"title": "Количество иcходящих Unicast пакетов Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2522",
|
"id": "433",
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_3"
|
"title": "Количество входящих пакетов помеченные как отброшенные Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2532",
|
"id": "434",
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_3"
|
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2542",
|
"id": "435",
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_3"
|
"title": "Количество входящих пакетов с ошибкой Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2552",
|
"id": "436",
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_3"
|
"title": "Количество исходящих пакетов с ошибкой Eth_3"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2562",
|
"id": "437",
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_3"
|
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_3"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -717,63 +671,63 @@
|
||||||
"title": "Сетевой адаптер №4 (port$264) Eth_4",
|
"title": "Сетевой адаптер №4 (port$264) Eth_4",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"id": "2582",
|
"id": "439",
|
||||||
"title": "Скорость порта Eth_4"
|
"title": "Скорость порта Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2602",
|
"id": "441",
|
||||||
"title": "Административное состояние порта Eth_4"
|
"title": "Административное состояние порта Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2612",
|
"id": "442",
|
||||||
"title": "Оперативное состояние порта Eth_4"
|
"title": "Оперативное состояние порта Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2622",
|
"id": "443",
|
||||||
"title": "Общее количество отправленных октетов Eth_4"
|
"title": "Общее количество отправленных октетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2632",
|
"id": "444",
|
||||||
"title": "Количество входящих Multicast пакетов Eth_4"
|
"title": "Количество входящих Multicast пакетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2642",
|
"id": "445",
|
||||||
"title": "Количество иcходящих Multiicast пакетов Eth_4"
|
"title": "Количество иcходящих Multiicast пакетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2652",
|
"id": "446",
|
||||||
"title": "Количество входящих Broadcast пакетов Eth_4"
|
"title": "Количество входящих Broadcast пакетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2662",
|
"id": "447",
|
||||||
"title": "Количество иcходящих Broadcast пакетов Eth_4"
|
"title": "Количество иcходящих Broadcast пакетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2672",
|
"id": "448",
|
||||||
"title": "Количество входящих Unicast пакетов Eth_4"
|
"title": "Количество входящих Unicast пакетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2682",
|
"id": "449",
|
||||||
"title": "Количество иcходящих Unicast пакетов Eth_4"
|
"title": "Количество иcходящих Unicast пакетов Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2692",
|
"id": "450",
|
||||||
"title": "Количество входящих пакетов помеченные как отброшенные Eth_4"
|
"title": "Количество входящих пакетов помеченные как отброшенные Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2702",
|
"id": "451",
|
||||||
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_4"
|
"title": "Количество иcходящих пакетов помеченные как отброшенные Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2712",
|
"id": "452",
|
||||||
"title": "Количество входящих пакетов с ошибкой Eth_4"
|
"title": "Количество входящих пакетов с ошибкой Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2722",
|
"id": "453",
|
||||||
"title": "Количество исходящих пакетов с ошибкой Eth_4"
|
"title": "Количество исходящих пакетов с ошибкой Eth_4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2732",
|
"id": "454",
|
||||||
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_4"
|
"title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_4"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -889,15 +843,15 @@
|
||||||
"title": "Общее количество конференций"
|
"title": "Общее количество конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "373",
|
"id": "373000",
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "383",
|
"id": "38300",
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "393",
|
"id": "39300",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1281,11 +1235,11 @@
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "384",
|
"id": "38400",
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "394",
|
"id": "39400",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1671,7 +1625,7 @@
|
||||||
"title": "Общее количество конференций"
|
"title": "Общее количество конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "379",
|
"id": "37900",
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1679,7 +1633,7 @@
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "399",
|
"id": "39900",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -2447,15 +2401,15 @@
|
||||||
"title": "Общее количество конференций"
|
"title": "Общее количество конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "378",
|
"id": "37800",
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "388",
|
"id": "38800",
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "398",
|
"id": "39800",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -2841,15 +2795,15 @@
|
||||||
"title": "Общее количество конференций"
|
"title": "Общее количество конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "375",
|
"id": "37500",
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "385",
|
"id": "38500",
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "395",
|
"id": "39500",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -3229,15 +3183,15 @@
|
||||||
"title": "Общее количество конференций"
|
"title": "Общее количество конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "376",
|
"id": "37600",
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "386",
|
"id": "38600",
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "396",
|
"id": "39600",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -3617,7 +3571,7 @@
|
||||||
"title": "Общее количество конференций"
|
"title": "Общее количество конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "377",
|
"id": "37700",
|
||||||
"title": "Общее количество активных конференций"
|
"title": "Общее количество активных конференций"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -3625,7 +3579,7 @@
|
||||||
"title": "Статус записи"
|
"title": "Статус записи"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "397",
|
"id": "39700",
|
||||||
"title": "Общее количество сохранённых записей"
|
"title": "Общее количество сохранённых записей"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ import React, { lazy, Suspense } from "react";
|
||||||
import Skeleton from '@mui/material/Skeleton';
|
import Skeleton from '@mui/material/Skeleton';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart'));
|
const PrometheusChart = lazy(() => import('../../Charts2/PrometheusChart'));
|
||||||
import LazyChartBatchRenderer from "../hooks/LazyChartBatchRender";
|
import LazyChartBatchRenderer from "../hooks/LazyChartBatchRender";
|
||||||
|
|
||||||
|
// Функция для генерации названия метрики на основе id
|
||||||
const getMetricName = (id) => {
|
const getMetricName = (id) => {
|
||||||
return `zvks_apiforsnmp_measure_${id}`;
|
return `zvks_apiforsnmp_measure_${id}`;
|
||||||
};
|
};
|
||||||
|
|
@ -22,18 +23,19 @@ const getAllChildIds = (node) => {
|
||||||
return ids;
|
return ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Компонент Skeleton для графика
|
||||||
const ChartSkeleton = () => (
|
const ChartSkeleton = () => (
|
||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: '100%' }}>
|
||||||
<Skeleton variant="text" width="60%" height={30} /> {/* Заголовок */}
|
<Skeleton variant="text" width="60%" height={30} />
|
||||||
<Skeleton variant="rectangular" width="100%" height={300} sx={{ mt: 2 }} /> {/* График */}
|
<Skeleton variant="rectangular" width="100%" height={300} sx={{ mt: 2 }} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Компонент Skeleton для родительского контейнера
|
||||||
const ContainerSkeleton = () => (
|
const ContainerSkeleton = () => (
|
||||||
<Box sx={{ width: '100%' }}>
|
<Box sx={{ width: '100%' }}>
|
||||||
<Skeleton variant="text" width="40%" height={40} /> {/* Заголовок */}
|
<Skeleton variant="text" width="40%" height={40} /> {/* Заголовок */}
|
||||||
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} /> {/* Описание */}
|
<Skeleton variant="text" width="80%" height={20} sx={{ mt: 1 }} />
|
||||||
{/* Место для дочерних элементов */}
|
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
{[...Array(3)].map((_, i) => (
|
{[...Array(3)].map((_, i) => (
|
||||||
<ChartSkeleton key={i} />
|
<ChartSkeleton key={i} />
|
||||||
|
|
@ -42,24 +44,22 @@ const ContainerSkeleton = () => (
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const tabContent = (data, existingContent = {}) => {
|
const tabContent = (data) => {
|
||||||
const tabContent = { ...existingContent };
|
const tabContent = {};
|
||||||
|
|
||||||
|
// Функция для рекурсивного обхода и сбора данных
|
||||||
const generateContent = (nodes) => {
|
const generateContent = (nodes) => {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
if (tabContent[node.id]) return;
|
|
||||||
|
|
||||||
if (node.items && node.items.length > 0) {
|
if (node.items && node.items.length > 0) {
|
||||||
generateContent(node.items);
|
const childrenContent = generateContent(node.items);
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<div key={node.id}>
|
<div>
|
||||||
<h2>{node.title}</h2>
|
<h2>{node.title}</h2>
|
||||||
<Suspense fallback={<ContainerSkeleton />}>
|
<Suspense fallback={<ContainerSkeleton />}>
|
||||||
<LazyChartBatchRenderer
|
<LazyChartBatchRenderer charts={node.items.map((child) => tabContent[child.id].content)} />
|
||||||
charts={node.items.map(child => tabContent[child.id]?.content) || <ChartSkeleton />}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
<p>Контент для {node.title}.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -77,17 +77,26 @@ const tabContent = (data, existingContent = {}) => {
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
tabContent[node.id] = {
|
tabContent[node.id] = {
|
||||||
title: node.title,
|
title: node.title,
|
||||||
content: content,
|
content: content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{nodes.map((node) => (
|
||||||
|
<div key={node.id}>{tabContent[node.id].content}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.items && data.items.length > 0) {
|
if (data.items && data.items.length > 0) {
|
||||||
generateContent(data.items);
|
generateContent(data.items);
|
||||||
|
} else {
|
||||||
|
console.warn("Данные отсутствуют или массив items пуст");
|
||||||
}
|
}
|
||||||
|
|
||||||
return tabContent;
|
return tabContent;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
//import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию
|
|
||||||
//import './Style/dark-theme.css'; // Подключаем темную тему
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue