diff --git a/src/Charts2/Components/DateRangeSelector.jsx b/src/Charts2/Components/DateRangeSelector.jsx new file mode 100644 index 0000000..4cec48e --- /dev/null +++ b/src/Charts2/Components/DateRangeSelector.jsx @@ -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 ( +
+
+ Укажите диапазон дат: +
+
+
+ + } + /> +
+
+ + } + /> +
+ +
+
+ ); +}; + +export default DateRangeSelector; \ No newline at end of file diff --git a/src/Charts2/Components/LineChartComponent.jsx b/src/Charts2/Components/LineChartComponent.jsx new file mode 100644 index 0000000..4a5cea5 --- /dev/null +++ b/src/Charts2/Components/LineChartComponent.jsx @@ -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 ( +
+ {title &&

{title}

} + {description && ( +

{description}

+ )} + {metaInfo && ( +
+ {metaInfo} +
+ )} + + + + {showGrid && } + new Date(timestamp).toLocaleTimeString())} + /> + {customYAxis || } + new Date(timestamp).toLocaleString()} + /> + {showLegend && } + + {additionalLines.map((lineProps, index) => ( + + ))} + + +
+ ); +}; + +export default LineChartComponent; \ No newline at end of file diff --git a/src/Charts2/Components/metricsService.jsx b/src/Charts2/Components/metricsService.jsx new file mode 100644 index 0000000..644c14a --- /dev/null +++ b/src/Charts2/Components/metricsService.jsx @@ -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); \ No newline at end of file diff --git a/src/Charts2/PrometheusChart.jsx b/src/Charts2/PrometheusChart.jsx new file mode 100644 index 0000000..24c37a7 --- /dev/null +++ b/src/Charts2/PrometheusChart.jsx @@ -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 ( +
+
+ setMode(e.target.value)} + buttonStyle="solid" + style={{ marginBottom: 10 }} + > + Режим реального времени + Исторические данные + + + {mode === 'historical' && ( + + )} + + {mode === 'realtime' && isLiveUpdating && ( + + )} +
+ + {isLoading ? ( +
Loading chart data...
+ ) : error ? ( +
Error loading metric: {error}
+ ) : chartData.length === 0 ? ( +
No data available for {metricName}
+ ) : ( + + )} +
+ ); +}; + +export default PrometheusChart; \ No newline at end of file diff --git a/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx b/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx index 6d0eba6..636a973 100644 --- a/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx +++ b/src/Components/Layout/SidebarMenuComponents/MenuItem.jsx @@ -15,7 +15,7 @@ import { getStatusColor } from "../../TreeChart/dataUtils"; const StyledListItem = styled(ListItem)(({ theme, level }) => ({ cursor: "pointer", paddingLeft: theme.spacing(2 + level * 2), - position: 'relative', // Добавляем для позиционирования индикатора + position: 'relative', '&:hover': { backgroundColor: theme.palette.action.hover, }, diff --git a/src/Components/TreeChart/FlowChart.jsx b/src/Components/TreeChart/FlowChart.jsx index bf8fe4a..0b0dd09 100644 --- a/src/Components/TreeChart/FlowChart.jsx +++ b/src/Components/TreeChart/FlowChart.jsx @@ -45,12 +45,10 @@ const FlowChart = ({ data }) => { const findAndCollapseLastLevelParents = (items) => { items.forEach(item => { if (item.items && item.items.length > 0) { - // Проверяем, есть ли у детей свои дети const hasGrandchildren = item.items.some(child => child.items && child.items.length > 0 ); - // Если у детей нет своих детей - это родители последнего уровня if (!hasGrandchildren) { toggleNodeCollapse(item.id); } else { diff --git a/src/Components/TreeChart/FlowChartComponents/DataParser.jsx b/src/Components/TreeChart/FlowChartComponents/DataParser.jsx index 916bc97..92717f6 100644 --- a/src/Components/TreeChart/FlowChartComponents/DataParser.jsx +++ b/src/Components/TreeChart/FlowChartComponents/DataParser.jsx @@ -39,7 +39,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => { const baseLevelRadius = 150; 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 items = item.items || []; @@ -58,7 +58,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => { data: { ...item, label: item.title, - style: getNodeStyle(item, isLeaf), // Переносим стили в data + style: getNodeStyle(item, isLeaf), hasChildren: items.length > 0, collapsed: collapsedNodes[nodeId] } @@ -88,7 +88,7 @@ export const useDataParser = (nodePositions, collapsedNodes) => { const centerNode = { id: data.id, - type: 'customNode', // Добавляем тип узла + type: 'customNode', position: nodePositions[data.id] || { x: centerX, y: centerY }, style: getCenterNodeStyle(data), data: { label: data.title, hasChildren: data.items.length > 0, collapsed: collapsedNodes[data.id] } diff --git a/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx b/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx index d64d391..52ccd94 100644 --- a/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx +++ b/src/Components/TreeChart/FlowChartComponents/NodeWrapper.jsx @@ -10,13 +10,13 @@ const NodeWrapper = memo(({ id, data, selected }) => { display: 'flex', alignItems: 'center', justifyContent: 'center', - overflow: 'hidden', // Чтобы текст не выходил за границы - textOverflow: 'ellipsis', // Добавляем многоточие если текст не помещается - whiteSpace: 'nowrap', // Запрещаем перенос строк - padding: '0 8px', // Горизонтальный padding для текста - boxSizing: 'border-box' // Учитываем padding в общей ширине + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + padding: '0 8px', + boxSizing: 'border-box' }} - title={data.label} // Простой tooltip при наведении + title={data.label} > {/* Хендл для входящих соединений */} { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [nodePositions, setNodePositions] = useState({}); - const [collapsedNodes, setCollapsedNodes] = useState({}); // Добавили + const [collapsedNodes, setCollapsedNodes] = useState({}); const toggleNodeCollapse = useCallback((nodeId) => { setCollapsedNodes((prev) => ({ diff --git a/src/Components/TreeChart/FlowChartComponents/useNodeHandlers.jsx b/src/Components/TreeChart/FlowChartComponents/useNodeHandlers.jsx index 80e4fd0..efc6480 100644 --- a/src/Components/TreeChart/FlowChartComponents/useNodeHandlers.jsx +++ b/src/Components/TreeChart/FlowChartComponents/useNodeHandlers.jsx @@ -2,7 +2,6 @@ import { useCallback } from 'react'; export const useNodeHandlers = (debouncedSetNodePositions) => { const onNodeDrag = useCallback((event, node) => { - // Фиксируем позицию сразу при перемещении node.position = { x: Math.round(node.position.x), y: Math.round(node.position.y) diff --git a/src/Components/TreeChart/dataUtils.jsx b/src/Components/TreeChart/dataUtils.jsx index 1a1c70d..b9cd7e0 100755 --- a/src/Components/TreeChart/dataUtils.jsx +++ b/src/Components/TreeChart/dataUtils.jsx @@ -1,48 +1,43 @@ const StatusManager = () => { const getRandomStatus = () => { const statuses = [ - ...Array(90).fill("green"), // 90% шанс - ...Array(6).fill("yellow"), // 6% шанс - ...Array(3).fill("orange"), // 3% шанс - ...Array(1).fill("red"), // 1% шанс + ...Array(90).fill("green"), + ...Array(6).fill("yellow"), + ...Array(3).fill("orange"), + ...Array(1).fill("red"), ]; return statuses[Math.floor(Math.random() * statuses.length)]; }; const getStatusWeight = (status) => { switch (status) { - case "green": return 1; // 100% здоровья + case "green": return 1; case "yellow": return 0.75; case "orange": return 0.5; - case "red": return 0.25; // 25% здоровья - default: return 1; // По умолчанию "green" + case "red": return 0.25; + default: return 1; } }; const updateStatuses = (data) => { if (!data.items || data.items.length === 0) { - // Если это элемент нижнего уровня, генерируем случайный статус data.status = getRandomStatus(); return getStatusWeight(data.status); } - // Рекурсивно обновляем статусы для всех дочерних элементов let childStatusWeights = data.items.map((child) => updateStatuses(child)); - // Проверяем, есть ли дочерние элементы (избегаем деления на 0) if (childStatusWeights.length === 0) { data.status = "green"; return 1; } - // Вычисляем среднее арифметическое значение весов статусов const averageStatusWeight = childStatusWeights.reduce((sum, weight) => sum + weight, 0) / childStatusWeights.length; - // Определяем статус текущего элемента data.status = getStatusFromWeight(averageStatusWeight); - return Math.max(0, averageStatusWeight); // Гарантия, что не будет отрицательных значений + return Math.max(0, averageStatusWeight); }; const getStatusFromWeight = (weight) => { @@ -69,16 +64,13 @@ const StatusManager = () => { }; }; -// Создаем два независимых менеджера статусов export const statusManager1 = StatusManager(); export const statusManager2 = StatusManager(); -// Функция для расчета процентов здоровья системы export const calculateStatusPercentage = (averageStatusValue) => { return Math.max(0, Math.min(100, averageStatusValue * 100)); }; -// Экспортируем getStatusColor отдельно export const getStatusColor = (status) => { switch (status) { case "green": diff --git a/src/Components/TreeChart/menuData.json b/src/Components/TreeChart/menuData.json index 1d4c364..fcbdd2f 100755 --- a/src/Components/TreeChart/menuData.json +++ b/src/Components/TreeChart/menuData.json @@ -400,182 +400,136 @@ "items": [ { "id": "182", - "title": "Graviton S2082I (device$18)", + "title": "Graviton S2082I (device$19)", "items": [ { "id": "42", - "title": "OS Linux (module$4) АО", + "title": "OS Linux (module$6) АО", "items": [ { - "id": "1902", + "id": "371", "title": "Загрузка процессора за 1 минуту" }, { - "id": "1912", + "id": "372", "title": "Загрузка процессора за 5 минут" }, { - "id": "1922", + "id": "373", "title": "Загрузка процессора за 15 минут" }, { - "id": "1972", + "id": "378", "title": "Общий объем SWAP-файла" }, { - "id": "1982", + "id": "379", "title": "Используемый объем SWAP-файла" }, { - "id": "1992", + "id": "380", "title": "Общий объем физической оперативной памяти" }, { - "id": "2002", + "id": "381", "title": "Доступный объем физической оперативной памяти" }, { - "id": "2012", + "id": "382", "title": "Свободный объем физической и виртуальной оперативной памяти" }, { - "id": "2022", + "id": "383", "title": "Буферизованный объем оперативной памяти" }, { - "id": "2032", + "id": "384", "title": "Кэшированый объем оперативной памяти" }, { - "id": "2742", - "title": "Используемый объем SWAP-файла" - }, - { - "id": "2752", + "id": "375", "title": "Время затраченное процессором на процессы с пониженным приоритетом" }, { - "id": "2762", + "id": "376", "title": "Время затраченное процессором на процессы ядра ОС" }, { - "id": "2772", + "id": "377", "title": "Время простоя процессора" }, { - "id": "2782", + "id": "385", "title": "Общая емкость жестких дисков" }, { - "id": "2792", + "id": "386", "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", "title": "Сетевой адаптер №1 (port$261) Eth_1", "items": [ { - "id": "2072", + "id": "388", "title": "Скорость порта Eth_1" }, { - "id": "2092", + "id": "390", "title": "Административное состояние порта Eth_1" }, { - "id": "2102", + "id": "391", "title": "Оперативное состояние порта Eth_1" }, { - "id": "2112", + "id": "392", "title": "Общее количество отправленных октетов Eth_1" }, { - "id": "2122", + "id": "393", "title": "Количество входящих Multicast пакетов Eth_1" }, { - "id": "2132", + "id": "394", "title": "Количество иcходящих Multiicast пакетов Eth_1" }, { - "id": "2142", + "id": "395", "title": "Количество входящих Broadcast пакетов Eth_1" }, { - "id": "2152", + "id": "396", "title": "Количество иcходящих Broadcast пакетов Eth_1" }, { - "id": "2162", + "id": "397", "title": "Количество входящих Unicast пакетов Eth_1" }, { - "id": "2172", + "id": "398", "title": "Количество иcходящих Unicast пакетов Eth_1" }, { - "id": "2182", + "id": "399", "title": "Количество входящих пакетов помеченные как отброшенные Eth_1" }, { - "id": "2192", + "id": "400", "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_1" }, { - "id": "2202", + "id": "401", "title": "Количество входящих пакетов с ошибкой Eth_1" }, { - "id": "2212", + "id": "402", "title": "Количество исходящих пакетов с ошибкой Eth_1" }, { - "id": "2222", + "id": "403", "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_1" } ] @@ -585,63 +539,63 @@ "title": "Сетевой адаптер №2 (port$262) Eth_2", "items": [ { - "id": "2242", + "id": "405", "title": "Скорость порта Eth_2" }, { - "id": "2262", + "id": "407", "title": "Административное состояние порта Eth_2" }, { - "id": "2272", + "id": "408", "title": "Оперативное состояние порта Eth_2" }, { - "id": "2282", + "id": "409", "title": "Общее количество отправленных октетов Eth_2" }, { - "id": "2292", + "id": "410", "title": "Количество входящих Multicast пакетов Eth_2" }, { - "id": "2302", + "id": "411", "title": "Количество иcходящих Multiicast пакетов Eth_2" }, { - "id": "2312", + "id": "412", "title": "Количество входящих Broadcast пакетов Eth_2" }, { - "id": "2322", + "id": "413", "title": "Количество иcходящих Broadcast пакетов Eth_2" }, { - "id": "2332", + "id": "414", "title": "Количество входящих Unicast пакетов Eth_2" }, { - "id": "2342", + "id": "415", "title": "Количество иcходящих Unicast пакетов Eth_2" }, { - "id": "2352", + "id": "416", "title": "Количество входящих пакетов помеченные как отброшенные Eth_2" }, { - "id": "2362", + "id": "417", "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_2" }, { - "id": "2372", + "id": "418", "title": "Количество входящих пакетов с ошибкой Eth_2" }, { - "id": "2382", + "id": "419", "title": "Количество исходящих пакетов с ошибкой Eth_2" }, { - "id": "2392", + "id": "420", "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_2" } ] @@ -651,63 +605,63 @@ "title": "Сетевой адаптер №3 (port$263) Eth_3", "items": [ { - "id": "2412", + "id": "422", "title": "Скорость порта Eth_3" }, { - "id": "2432", + "id": "424", "title": "Административное состояние порта Eth_3" }, { - "id": "2442", + "id": "425", "title": "Оперативное состояние порта Eth_3" }, { - "id": "2452", + "id": "426", "title": "Общее количество отправленных октетов Eth_3" }, { - "id": "2462", + "id": "427", "title": "Количество входящих Multicast пакетов Eth_3" }, { - "id": "2472", + "id": "428", "title": "Количество иcходящих Multiicast пакетов Eth_3" }, { - "id": "2482", + "id": "429", "title": "Количество входящих Broadcast пакетов Eth_3" }, { - "id": "2492", + "id": "430", "title": "Количество иcходящих Broadcast пакетов Eth_3" }, { - "id": "2502", + "id": "431", "title": "Количество входящих Unicast пакетов Eth_3" }, { - "id": "2512", + "id": "432", "title": "Количество иcходящих Unicast пакетов Eth_3" }, { - "id": "2522", + "id": "433", "title": "Количество входящих пакетов помеченные как отброшенные Eth_3" }, { - "id": "2532", + "id": "434", "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_3" }, { - "id": "2542", + "id": "435", "title": "Количество входящих пакетов с ошибкой Eth_3" }, { - "id": "2552", + "id": "436", "title": "Количество исходящих пакетов с ошибкой Eth_3" }, { - "id": "2562", + "id": "437", "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_3" } ] @@ -717,63 +671,63 @@ "title": "Сетевой адаптер №4 (port$264) Eth_4", "items": [ { - "id": "2582", + "id": "439", "title": "Скорость порта Eth_4" }, { - "id": "2602", + "id": "441", "title": "Административное состояние порта Eth_4" }, { - "id": "2612", + "id": "442", "title": "Оперативное состояние порта Eth_4" }, { - "id": "2622", + "id": "443", "title": "Общее количество отправленных октетов Eth_4" }, { - "id": "2632", + "id": "444", "title": "Количество входящих Multicast пакетов Eth_4" }, { - "id": "2642", + "id": "445", "title": "Количество иcходящих Multiicast пакетов Eth_4" }, { - "id": "2652", + "id": "446", "title": "Количество входящих Broadcast пакетов Eth_4" }, { - "id": "2662", + "id": "447", "title": "Количество иcходящих Broadcast пакетов Eth_4" }, { - "id": "2672", + "id": "448", "title": "Количество входящих Unicast пакетов Eth_4" }, { - "id": "2682", + "id": "449", "title": "Количество иcходящих Unicast пакетов Eth_4" }, { - "id": "2692", + "id": "450", "title": "Количество входящих пакетов помеченные как отброшенные Eth_4" }, { - "id": "2702", + "id": "451", "title": "Количество иcходящих пакетов помеченные как отброшенные Eth_4" }, { - "id": "2712", + "id": "452", "title": "Количество входящих пакетов с ошибкой Eth_4" }, { - "id": "2722", + "id": "453", "title": "Количество исходящих пакетов с ошибкой Eth_4" }, { - "id": "2732", + "id": "454", "title": "Количество входящих пакетов с неизвестным или неподдерживаемым протоколом Eth_4" } ] @@ -889,15 +843,15 @@ "title": "Общее количество конференций" }, { - "id": "373", + "id": "373000", "title": "Общее количество активных конференций" }, { - "id": "383", + "id": "38300", "title": "Статус записи" }, { - "id": "393", + "id": "39300", "title": "Общее количество сохранённых записей" } ] @@ -1281,11 +1235,11 @@ "title": "Общее количество активных конференций" }, { - "id": "384", + "id": "38400", "title": "Статус записи" }, { - "id": "394", + "id": "39400", "title": "Общее количество сохранённых записей" } ] @@ -1671,7 +1625,7 @@ "title": "Общее количество конференций" }, { - "id": "379", + "id": "37900", "title": "Общее количество активных конференций" }, { @@ -1679,7 +1633,7 @@ "title": "Статус записи" }, { - "id": "399", + "id": "39900", "title": "Общее количество сохранённых записей" } ] @@ -2447,15 +2401,15 @@ "title": "Общее количество конференций" }, { - "id": "378", + "id": "37800", "title": "Общее количество активных конференций" }, { - "id": "388", + "id": "38800", "title": "Статус записи" }, { - "id": "398", + "id": "39800", "title": "Общее количество сохранённых записей" } ] @@ -2841,15 +2795,15 @@ "title": "Общее количество конференций" }, { - "id": "375", + "id": "37500", "title": "Общее количество активных конференций" }, { - "id": "385", + "id": "38500", "title": "Статус записи" }, { - "id": "395", + "id": "39500", "title": "Общее количество сохранённых записей" } ] @@ -3229,15 +3183,15 @@ "title": "Общее количество конференций" }, { - "id": "376", + "id": "37600", "title": "Общее количество активных конференций" }, { - "id": "386", + "id": "38600", "title": "Статус записи" }, { - "id": "396", + "id": "39600", "title": "Общее количество сохранённых записей" } ] @@ -3617,7 +3571,7 @@ "title": "Общее количество конференций" }, { - "id": "377", + "id": "37700", "title": "Общее количество активных конференций" }, { @@ -3625,7 +3579,7 @@ "title": "Статус записи" }, { - "id": "397", + "id": "39700", "title": "Общее количество сохранённых записей" } ] diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 15596a8..8b6ce63 100755 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -2,9 +2,10 @@ import React, { lazy, Suspense } from "react"; import Skeleton from '@mui/material/Skeleton'; import Box from '@mui/material/Box'; -const PrometheusChart = lazy(() => import('../../Charts/PrometheusChart')); +const PrometheusChart = lazy(() => import('../../Charts2/PrometheusChart')); import LazyChartBatchRenderer from "../hooks/LazyChartBatchRender"; +// Функция для генерации названия метрики на основе id const getMetricName = (id) => { return `zvks_apiforsnmp_measure_${id}`; }; @@ -12,28 +13,29 @@ const getMetricName = (id) => { const getAllChildIds = (node) => { let ids = []; if (node.id) { - ids.push(node.id); + ids.push(node.id); } if (node.items && node.items.length > 0) { node.items.forEach((child) => { - ids = ids.concat(getAllChildIds(child)); + ids = ids.concat(getAllChildIds(child)); }); } return ids; }; +// Компонент Skeleton для графика const ChartSkeleton = () => ( - {/* Заголовок */} - {/* График */} + + ); +// Компонент Skeleton для родительского контейнера const ContainerSkeleton = () => ( {/* Заголовок */} - {/* Описание */} - {/* Место для дочерних элементов */} + {[...Array(3)].map((_, i) => ( @@ -42,24 +44,22 @@ const ContainerSkeleton = () => ( ); -const tabContent = (data, existingContent = {}) => { - const tabContent = { ...existingContent }; +const tabContent = (data) => { + const tabContent = {}; + // Функция для рекурсивного обхода и сбора данных const generateContent = (nodes) => { nodes.forEach((node) => { - if (tabContent[node.id]) return; - if (node.items && node.items.length > 0) { - generateContent(node.items); + const childrenContent = generateContent(node.items); const content = ( -
+

{node.title}

}> - tabContent[child.id]?.content) || } - /> + tabContent[child.id].content)} /> +

Контент для {node.title}.

); @@ -77,17 +77,26 @@ const tabContent = (data, existingContent = {}) => {
); - tabContent[node.id] = { title: node.title, content: content, }; } }); + + return ( +
+ {nodes.map((node) => ( +
{tabContent[node.id].content}
+ ))} +
+ ); }; if (data.items && data.items.length > 0) { generateContent(data.items); + } else { + console.warn("Данные отсутствуют или массив items пуст"); } return tabContent; diff --git a/src/main.jsx b/src/main.jsx index e373476..b9a1a6d 100755 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,8 +2,6 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.jsx' -//import './Style/light-theme.css'; // Подключаем светлую тему по умолчанию -//import './Style/dark-theme.css'; // Подключаем темную тему createRoot(document.getElementById('root')).render(