From f38c8825feaaa4d09e3a87d94f10e9788cec444a Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Mon, 21 Apr 2025 03:28:14 -0400 Subject: [PATCH] removed unnecessary components --- src/Charts/Components/hooks.jsx | 153 --------------- src/Charts/MetricChart.jsx | 291 ---------------------------- src/Components/Layout/Dashboard.jsx | 4 +- 3 files changed, 2 insertions(+), 446 deletions(-) delete mode 100644 src/Charts/Components/hooks.jsx delete mode 100644 src/Charts/MetricChart.jsx diff --git a/src/Charts/Components/hooks.jsx b/src/Charts/Components/hooks.jsx deleted file mode 100644 index a9aabf7..0000000 --- a/src/Charts/Components/hooks.jsx +++ /dev/null @@ -1,153 +0,0 @@ -// src/utils/metricsUtils.js -import { MINUTE, HOUR, DAY } from './constants'; - -export function formatTime(timestamp, rangeSeconds) { - const ts = typeof timestamp === 'number' ? timestamp : Date.now(); - const date = new Date(ts); - - const timeOptions = { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: false - }; - - const dateOptions = rangeSeconds > 86400 ? { - month: '2-digit', - day: '2-digit', - ...timeOptions - } : timeOptions; - - return { - display: date.toLocaleString('ru-RU', dateOptions), - fullDisplay: date.toLocaleString('ru-RU', { - year: 'numeric', - month: '2-digit', - day: '2-digit', - ...timeOptions - }), - timestamp: ts - }; -} - -export function calculateStep(start, end) { - const rangeSeconds = end - start; - - if (rangeSeconds <= MINUTE) return 1; - if (rangeSeconds <= 5 * MINUTE) return 5; - if (rangeSeconds <= 15 * MINUTE) return 15; - if (rangeSeconds <= HOUR) return 30; - if (rangeSeconds <= 3 * HOUR) return 2 * MINUTE; - if (rangeSeconds <= 6 * HOUR) return 5 * MINUTE; - if (rangeSeconds <= 12 * HOUR) return 10 * MINUTE; - if (rangeSeconds <= DAY) return 15 * MINUTE; - if (rangeSeconds <= 3 * DAY) return HOUR; - return 2 * HOUR; -} - -export function processMetricsData(metricName, responseData, prevData, rangeSeconds) { - if (!responseData) { - console.error('No data received for processing'); - return prevData || {}; - } - - // Добавим обработку случая, когда данные приходят в формате {metric, data, metadata} - const rawData = responseData.data || (Array.isArray(responseData) ? responseData : [responseData]); - - const newData = { ...(prevData || {}) }; - - rawData.forEach(item => { - try { - const instance = item.instance || item.metric?.instance || 'default'; - if (!newData[instance]) newData[instance] = []; - - // Обработка timestamp - let timestamp = item.timestamp; - if (typeof timestamp !== 'number') { - timestamp = Date.now(); - } else if (timestamp < 1e12) { // Если timestamp в секундах - timestamp *= 1000; - } - - // Обработка value - let value = item.value; - if (value === undefined && item.metric?.value !== undefined) { - value = item.metric.value; - } - if (typeof value !== 'number') { - value = parseFloat(value); - if (isNaN(value)) { - console.warn('Invalid value, using 0 as fallback:', item); - value = 0; - } - } - - const formattedTime = formatTime(timestamp, rangeSeconds); - - newData[instance].push({ - time: formattedTime.display, - fullTime: formattedTime.fullDisplay, - value: value, - timestamp: timestamp, - meta: { - description: item.description || item.metric?.description, - type: item.type || item.metric?.type, - status: item.status || item.metric?.status - } - }); - } catch (error) { - console.error('Error processing metric item:', item, error); - } - }); - - - // Сортировка и ограничение данных - Object.keys(newData).forEach(instance => { - newData[instance] = newData[instance] - .sort((a, b) => a.timestamp - b.timestamp) - .slice(-1000); - }); - - return newData; -} - -export function interpolateData(data, targetPointCount, timeRangeSeconds) { - if (!data || data.length < 2) return data || []; - if (data.length >= targetPointCount) return data; - - const interpolated = []; - const step = (data.length - 1) / (targetPointCount - 1); - - for (let i = 0; i < targetPointCount; i++) { - const index = i * step; - const lowerIndex = Math.floor(index); - const upperIndex = Math.ceil(index); - - if (lowerIndex === upperIndex) { - interpolated.push(data[lowerIndex]); - continue; - } - - const fraction = index - lowerIndex; - const lower = data[lowerIndex]; - const upper = data[upperIndex]; - - const interpolatedPoint = { - time: '', - fullTime: '', - value: lower.value + fraction * (upper.value - lower.value), - timestamp: lower.timestamp + fraction * (upper.timestamp - lower.timestamp) - }; - - // Форматирование времени - const formatted = formatTime(interpolatedPoint.timestamp, timeRangeSeconds || DAY); - interpolatedPoint.time = formatted.display; - interpolatedPoint.fullTime = formatted.fullDisplay; - - interpolated.push(interpolatedPoint); - - console.log('Item:', item.value, timestamp, formattedTime.display); - } - - return interpolated; -} \ No newline at end of file diff --git a/src/Charts/MetricChart.jsx b/src/Charts/MetricChart.jsx deleted file mode 100644 index bf3fd7d..0000000 --- a/src/Charts/MetricChart.jsx +++ /dev/null @@ -1,291 +0,0 @@ -import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceArea } from 'recharts'; -import io from 'socket.io-client'; -import axios from 'axios'; -import { Select, Button, Space, DatePicker, Spin, Alert } from 'antd'; -import moment from 'moment'; - -const { Option } = Select; -const { RangePicker } = DatePicker; - -const timeRanges = [ - { label: '1 мин', value: 1 }, - { label: '5 мин', value: 5 }, - { label: '30 мин', value: 30 }, - { label: '1 час', value: 60 }, - { label: '3 часа', value: 180 }, - { label: '6 часов', value: 360 }, - { label: '12 часов', value: 720 }, - { label: '24 часа', value: 1440 }, -]; - -const getStatusColor = (status) => { - if (!status) return '#1890ff'; - switch (status.toUpperCase()) { - case 'OK': return '#52c41a'; - case 'WARNING': return '#faad14'; - case 'CRITICAL': return '#f5222d'; - default: return '#1890ff'; - } -}; - -const MetricChart = ({ metricName, title }) => { - const [data, setData] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [selectedRange, setSelectedRange] = useState(timeRanges[0]); - const [customRange, setCustomRange] = useState([]); // Используем массив вместо null для RangePicker - const [isLive, setIsLive] = useState(true); - const [refAreaLeft, setRefAreaLeft] = useState(null); - const [refAreaRight, setRefAreaRight] = useState(null); - const socketRef = useRef(null); - const dataRef = useRef([]); - - // Форматирование данных для графика - const formatData = useCallback((rawData) => { - if (!Array.isArray(rawData)) { - console.error('Expected array but received:', rawData); - return []; - } - return rawData.map(item => ({ - timestamp: item.timestamp, - time: moment(item.timestamp).format('HH:mm:ss'), - value: parseFloat(item.value) || 0, - status: item.status - })); - }, []); - - // Загрузка исторических данных - const fetchHistoricalData = useCallback(async (start, end) => { - setLoading(true); - setError(null); - try { - const duration = moment.duration(end.diff(start)).asMinutes(); - const step = Math.max(1, Math.floor(duration / 100)) + 's'; - - const response = await axios.get(`${import.meta.env.VITE_BACK_HTTP_URL}/metrics`, { - params: { - metric: metricName, - start: start.valueOf(), - end: end.valueOf(), - step: step - }, - headers: { - 'Accept': 'application/json' // Убедимся, что получаем JSON - } - }); - - if (response.headers['content-type'].includes('text/html')) { - throw new Error('Server returned HTML instead of JSON. Check your API endpoint.'); - } - - const formattedData = formatData(response.data); - dataRef.current = formattedData; - setData(formattedData); - setIsLive(false); - } catch (err) { - setError(err.response?.data?.message || err.message || 'Failed to fetch data'); - console.error('Error fetching historical data:', err); - } finally { - setLoading(false); - } - }, [metricName, formatData]); - - // Подключение к WebSocket и загрузка начальных данных - const connectWebSocket = useCallback(() => { - if (socketRef.current) { - socketRef.current.disconnect(); - } - - socketRef.current = io(`${import.meta.env.VITE_BACK_WS_URL}/api/metrics-ws`, { - transports: ['websocket'], - reconnectionAttempts: 5 - }); - - socketRef.current.on('connect', () => { - console.log('WebSocket connected'); - socketRef.current.emit('subscribe-metric', { - metric: metricName, - interval: 5000 - }); - }); - - socketRef.current.on('metrics-data', (response) => { - if (response.metric === metricName && response.data) { - try { - const newDataPoint = formatData([response.data])[0]; // Оборачиваем в массив - if (newDataPoint) { - dataRef.current = [...dataRef.current, newDataPoint].slice(-1000); - if (isLive) { - const now = moment(); - const cutoff = now.subtract(selectedRange.value, 'minutes'); - setData(dataRef.current.filter(item => moment(item.timestamp).isAfter(cutoff))); - } - } - } catch (e) { - console.error('Error processing WebSocket data:', e); - } - } - }); - - socketRef.current.on('error', (err) => { - setError(err.message || 'WebSocket error'); - }); - - return () => { - if (socketRef.current) { - socketRef.current.emit('unsubscribe-metric'); - socketRef.current.disconnect(); - } - }; - }, [metricName, formatData, isLive, selectedRange.value]); - - // Обработчики изменения диапазона - const handleRangeChange = (value) => { - const range = timeRanges.find(r => r.value === value); - if (!range) return; - - setSelectedRange(range); - setCustomRange([]); // Сбрасываем кастомный диапазон - setIsLive(true); - - const now = moment(); - const cutoff = now.subtract(range.value, 'minutes'); - setData(dataRef.current.filter(item => moment(item.timestamp).isAfter(cutoff))); - }; - - const handleCustomRange = (dates) => { - if (!dates || dates.length !== 2) { - setCustomRange([]); - setIsLive(true); - return; - } - - const [start, end] = dates; - setCustomRange(dates); - fetchHistoricalData(start, end); - }; - - // Эффекты - useEffect(() => { - if (isLive) { - const cleanup = connectWebSocket(); - // Загружаем начальные данные - const end = moment(); - const start = end.clone().subtract(selectedRange.value, 'minutes'); - fetchHistoricalData(start, end); - return cleanup; - } - }, [isLive, connectWebSocket, selectedRange.value, fetchHistoricalData]); - - // Обработчики для zoom на графике - const handleMouseDown = (e) => { - if (!e || !e.activeLabel) return; - setRefAreaLeft(e.activeLabel); - setRefAreaRight(e.activeLabel); - }; - - const handleMouseMove = (e) => { - if (!refAreaLeft || !e.activeLabel) return; - setRefAreaRight(e.activeLabel); - }; - - const handleMouseUp = () => { - if (!refAreaLeft || !refAreaRight) return; - - const leftIdx = data.findIndex(d => d.time === refAreaLeft); - const rightIdx = data.findIndex(d => d.time === refAreaRight); - - if (leftIdx !== -1 && rightIdx !== -1) { - const start = moment(Math.min(data[leftIdx].timestamp, data[rightIdx].timestamp)); - const end = moment(Math.max(data[leftIdx].timestamp, data[rightIdx].timestamp)); - fetchHistoricalData(start, end); - } - - setRefAreaLeft(null); - setRefAreaRight(null); - }; - - const handleBackToLive = () => { - setIsLive(true); - setCustomRange([]); - setSelectedRange(timeRanges[0]); - }; - - return ( -
-

{title}

- - - - - - - {!isLive && ( - - )} - - - {error && } - {loading && } - - - - - - - [ - `${value} (${props.payload?.status || 'N/A'})`, - name - ]} - labelFormatter={(label) => { - // Исправляем предупреждение Moment.js - if (!label) return ''; - return moment(label, 'HH:mm:ss').isValid() - ? moment(label, 'HH:mm:ss').format('YYYY-MM-DD HH:mm:ss') - : label; - }} - /> - - - {refAreaLeft && refAreaRight && ( - - )} - - -
- ); -}; - -export default React.memo(MetricChart); \ No newline at end of file diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index c979b4c..9b66e58 100755 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -48,9 +48,9 @@ const MainContent = styled(Box)(({ theme }) => ({ const Content = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.custom.modalBackground, - padding: theme.spacing(2.5), + //padding: theme.spacing(2.5), borderRadius: '10px', - boxShadow: theme.shadows[2], + //boxShadow: theme.shadows[2], maxWidth: '100%', overflow: 'auto', color: theme.palette.custom.modalText,