diff --git a/src/Charts2/Components/metricsService.jsx b/src/Charts2/Components/metricsService.jsx index 644c14a..4a0dd48 100644 --- a/src/Charts2/Components/metricsService.jsx +++ b/src/Charts2/Components/metricsService.jsx @@ -2,125 +2,102 @@ 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(); + this.pendingRequests = 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 методы - остаются без изменений + // Инициализация WebSocket соединения connectWebSocket() { if (this.socket && this.socket.connected) return; - console.trace('connectWebSocket called'); - this.socket = io(`${this.baseUrl}/api/metrics-ws`, { + + this.socket = io(`${this.baseUrl.replace('http', 'ws')}/api/metrics-ws`, { transports: ['websocket'], withCredentials: true, }); - + this.socket.on('connect', () => { - console.log('Socket.IO connected'); - // Подписаться заново на все метрики - for (const [metric, callbacks] of this.subscriptions.entries()) { + console.log('WebSocket connected'); + // Восстанавливаем подписки при переподключении + this.subscriptions.forEach((_, metric) => { this.socket.emit('subscribe-metric', { metric }); - } + }); }); - + this.socket.on('disconnect', () => { - console.log('Socket.IO disconnected'); + console.log('WebSocket disconnected'); }); - - this.socket.on('metrics-data', ({ metric, data }) => { + + this.socket.on('metrics-data', ({ metric, data, requestId }) => { + // Обработка исторических данных + if (requestId && this.pendingRequests.has(requestId)) { + const { resolve } = this.pendingRequests.get(requestId); + resolve(data); + this.pendingRequests.delete(requestId); + return; + } + + // Обработка реального времени const callbacks = this.subscriptions.get(metric) || []; callbacks.forEach(cb => cb(data)); }); - - this.socket.on('metrics-error', payload => { - console.error('Metrics error:', payload); + + this.socket.on('metrics-error', ({ error, requestId }) => { + if (requestId && this.pendingRequests.has(requestId)) { + const { reject } = this.pendingRequests.get(requestId); + reject(new Error(error)); + this.pendingRequests.delete(requestId); + } }); } + // Запрос исторических данных через WebSocket + async fetchMetricsRange(metric, start, end, step = 15) { + return new Promise((resolve, reject) => { + this.connectWebSocket(); + + const requestId = `range-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + this.pendingRequests.set(requestId, { resolve, reject }); + + this.socket.emit('get-metrics', { + metric, + start, + end, + step, + isRangeQuery: true, + requestId + }); + + // Таймаут запроса + setTimeout(() => { + if (this.pendingRequests.has(requestId)) { + this.pendingRequests.delete(requestId); + reject(new Error('Request timeout')); + } + }, 30000); + }); + } + + // Подписка на обновления в реальном времени 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) { @@ -130,8 +107,8 @@ class MetricsService { this.subscriptions.set(metric, filtered); } } - + // Закрытие соединения disconnectWebSocket() { if (this.socket) { this.socket.close(); diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx index f0ba272..0735dda 100644 --- a/src/Components/Layout/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -19,6 +19,7 @@ import { import MenuItem from "./SidebarMenuComponents/MenuItem"; import SidebarFooter from "./SidebarMenuComponents/SidebarFooter"; import { statusManager1 } from "../TreeChart/dataUtils"; +import tabContent from "../TreeChart/tabContent"; const SidebarResizer = styled('div')(({ theme }) => ({ diff --git a/src/Components/TreeChart/tabContent.jsx b/src/Components/TreeChart/tabContent.jsx index 8b6ce63..6021f3a 100755 --- a/src/Components/TreeChart/tabContent.jsx +++ b/src/Components/TreeChart/tabContent.jsx @@ -5,7 +5,6 @@ import Box from '@mui/material/Box'; const PrometheusChart = lazy(() => import('../../Charts2/PrometheusChart')); import LazyChartBatchRenderer from "../hooks/LazyChartBatchRender"; -// Функция для генерации названия метрики на основе id const getMetricName = (id) => { return `zvks_apiforsnmp_measure_${id}`; }; @@ -13,11 +12,11 @@ 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; @@ -26,8 +25,8 @@ const getAllChildIds = (node) => { // Компонент Skeleton для графика const ChartSkeleton = () => ( - - + + ); @@ -35,7 +34,7 @@ const ChartSkeleton = () => ( const ContainerSkeleton = () => ( {/* Заголовок */} - + {[...Array(3)].map((_, i) => (