From e47161acd1097f035c5e10084923f0269702c523 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Tue, 22 Apr 2025 08:59:35 -0400 Subject: [PATCH] fixed a bug with collapsing the menu, improved the MUI skeleton, fixed several visual bugs --- src/App.jsx | 65 +++++++-------- src/Charts/PrometheusChart.jsx | 45 ++++++++++- src/Components/Layout/Dashboard.jsx | 7 +- src/Components/Layout/SidebarMenu.jsx | 9 +-- src/Components/UI/LoginModal.jsx | 5 +- src/Components/UI/TreeTable.jsx | 2 +- src/Components/UI/auth.jsx | 27 +++---- src/Components/hooks/LazyChartBatchRender.jsx | 79 +++++++++++++++---- src/Style/theme.jsx | 2 +- 9 files changed, 156 insertions(+), 85 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index c2372e5..dfe1af0 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,11 +1,10 @@ import React, { useState, useMemo, useEffect } from "react"; -import { ThemeProvider, CssBaseline, Switch, Box, CircularProgress } from "@mui/material"; +import { ThemeProvider, CssBaseline, Switch, Box, CircularProgress, Typography } from "@mui/material"; import Dashboard from "./Components/Layout/Dashboard"; import LoginModal from "./Components/UI/LoginModal"; import { lightTheme, darkTheme } from "./Style/theme"; import Logo from './assets/images/logo.svg?react'; -import { checkAuth } from "./Components/UI/auth"; -import axios from 'axios'; +import { checkAuth } from "./Components/UI/auth"; function App() { const [authState, setAuthState] = useState({ @@ -24,13 +23,11 @@ function App() { const verifyAuth = async () => { try { const authStatus = await checkAuth(); - setAuthState({ isAuthenticated: authStatus.isAuthenticated, isLoading: false, user: authStatus.user || null }); - setShowLoginModal(!authStatus.isAuthenticated); } catch (error) { console.error('Auth verification error:', error); @@ -42,7 +39,6 @@ function App() { setShowLoginModal(true); } }; - verifyAuth(); }, []); @@ -57,7 +53,7 @@ function App() { const handleLogout = async () => { try { - await axios.get(`${import.meta.env.VITE_BACK_URL}/api/metrics`, { + await fetch('http://192.168.2.39:3000/api/auth/logout', { method: 'POST', credentials: 'include' }); @@ -73,20 +69,25 @@ function App() { } }; + // Полноэкранный лоадер во время проверки авторизации if (authState.isLoading) { return ( -

Проверка авторизации...

+ + Проверка авторизации... +
); @@ -95,26 +96,20 @@ function App() { return ( - {!authState.isAuthenticated && showLoginModal ? ( + {!authState.isAuthenticated ? ( <> - + - setShowLoginModal(false)} /> @@ -124,19 +119,15 @@ function App() { display: "flex", height: "100vh", overflow: "hidden", - bgcolor: "background.default", - color: "text.primary" + bgcolor: "background.default" }}> - + + setIsDarkMode(!isDarkMode)} + sx={{ position: "absolute", top: 10, right: 10 }} /> - - setIsDarkMode((prev) => !prev)} - /> - )} diff --git a/src/Charts/PrometheusChart.jsx b/src/Charts/PrometheusChart.jsx index 420fd75..0b1ae11 100755 --- a/src/Charts/PrometheusChart.jsx +++ b/src/Charts/PrometheusChart.jsx @@ -6,6 +6,37 @@ import { ConnectionStatusIndicator } from './Components/ConnectionStatusIndicato import { CurrentRangeDisplay } from './Components/CurrentRangeDisplay'; import { TIME_RANGES, COLORS, SECOND, MINUTE, HOUR, DAY } from './Components/constants'; import axios from 'axios'; +import Skeleton from '@mui/material/Skeleton'; +import Box from '@mui/material/Box'; + + +// Компонент Skeleton для графика +const ChartSkeleton = () => ( + + + + + + + + + + + + + + {[1, 2, 3, 4].map((_, i) => ( + + ))} + + +); const PrometheusChart = ({ metricName }) => { const [chartData, setChartData] = useState(null); @@ -395,11 +426,21 @@ const PrometheusChart = ({ metricName }) => { }, [selectedGraphRange, chartData, interpolateData]); if (chartData === null) { - return
Loading data...
; + return ; } if (Object.keys(chartData).length === 0) { - return
No data available
; + return ( + + No data available + + ); } return ( diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index 9b66e58..a747672 100755 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -58,10 +58,11 @@ const Content = styled(Box)(({ theme }) => ({ const Dashboard = () => { const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная"); - const { sidebarWidth, startResizing } = useSidebarResize(250); + const { sidebarWidth, startResizing } = useSidebarResize(290); const [tabContent, setTabContent] = useState({}); const [treeData1, setTreeData1] = useState(menuData); const [treeData2, setTreeData2] = useState(menuData); + const [collapsed, setCollapsed] = useState(false); const [statusHistories, setStatusHistories] = useState({ history1: [], history2: [], @@ -103,12 +104,14 @@ const Dashboard = () => { return ( {/* Сайдбар */} - + diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx index 7efd192..98e7962 100644 --- a/src/Components/Layout/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -32,8 +32,7 @@ const SidebarResizer = styled('div')(({ theme }) => ({ zIndex: 2 })); -const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing }) => { - const [collapsed, setCollapsed] = useState(false); +const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing, collapsed, setCollapsed }) => { const [hovered, setHovered] = useState(false); const [menuData, setMenuData] = useState(data); @@ -105,11 +104,7 @@ const SidebarMenu = ({ data, onOpenTab, sidebarWidth, startResizing }) => { } }} > - {collapsed ? ( - hovered ? : - ) : ( - - )} + {collapsed ? : } diff --git a/src/Components/UI/LoginModal.jsx b/src/Components/UI/LoginModal.jsx index d38d7b7..22559d6 100755 --- a/src/Components/UI/LoginModal.jsx +++ b/src/Components/UI/LoginModal.jsx @@ -17,8 +17,7 @@ const LoginModal = ({ onLogin, onClose }) => { e.preventDefault(); try { - const response = await axios.post( - `${import.meta.env.VITE_BACK_URL}/api/auth/login`, { + const response = await fetch('http://192.168.2.39:3000/api/auth/login', { method: 'POST', credentials: 'include', headers: { @@ -31,7 +30,7 @@ const LoginModal = ({ onLogin, onClose }) => { if (data.success) { localStorage.setItem('access_token', data.access_token); - onLogin(data.user); // Передаем данные пользователя + onLogin(data.user); onClose(); } else { setError(data.message || "Неверный логин или пароль"); diff --git a/src/Components/UI/TreeTable.jsx b/src/Components/UI/TreeTable.jsx index bbc86b1..3087ddb 100755 --- a/src/Components/UI/TreeTable.jsx +++ b/src/Components/UI/TreeTable.jsx @@ -324,7 +324,7 @@ const TreeTable = ({ data }) => { diff --git a/src/Components/UI/auth.jsx b/src/Components/UI/auth.jsx index 7532538..6d3699c 100644 --- a/src/Components/UI/auth.jsx +++ b/src/Components/UI/auth.jsx @@ -2,24 +2,21 @@ import axios from 'axios'; export const checkAuth = async () => { try { - const response = await axios.get( - `${import.meta.env.VITE_BACK_URL}/api/auth/check`, - { - withCredentials: true, // аналог `credentials: 'include'` в fetch + const response = await fetch('http://192.168.2.39:3000/api/auth/check', { + method: 'GET', + credentials: 'include', // Важно для отправки cookies headers: { - Authorization: `Bearer ${localStorage.getItem('access_token') || ''}`, + 'Authorization': `Bearer ${localStorage.getItem('access_token') || ''}`, }, - } - ); + }); - // У axios нет свойства .ok, проверяем статус 200-299 - if (response.status >= 200 && response.status < 300) { - return response.data; // Данные уже в JSON, не нужно .json() - } else { + if (!response.ok) { throw new Error('Not authenticated'); - } - } catch (err) { - console.error('Auth check failed:', err); - return { isAuthenticated: false }; + } + + return await response.json(); +} catch (err) { + console.error('Auth check failed:', err); + return { isAuthenticated: false }; } }; \ No newline at end of file diff --git a/src/Components/hooks/LazyChartBatchRender.jsx b/src/Components/hooks/LazyChartBatchRender.jsx index a12f491..d9f1dca 100644 --- a/src/Components/hooks/LazyChartBatchRender.jsx +++ b/src/Components/hooks/LazyChartBatchRender.jsx @@ -1,28 +1,73 @@ -import { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from 'react'; +import Box from '@mui/material/Box'; +import Skeleton from '@mui/material/Skeleton'; -const LazyChartBatchRenderer = ({ charts, batchSize = 3, delay = 150 }) => { - const [visibleCharts, setVisibleCharts] = useState([]); +const LazyChartBatchRenderer = ({ charts }) => { + const [visibleIndices, setVisibleIndices] = useState(new Set()); + const placeholderRefs = useRef([]); + + const ChartSkeleton = () => ( + + + + + + + + + + + {[1, 2, 3, 4].map((_, i) => ( + + ))} + + + ); useEffect(() => { - let index = 0; - const timer = setInterval(() => { - setVisibleCharts((prev) => [ - ...prev, - ...charts.slice(index, index + batchSize), - ]); - index += batchSize; - if (index >= charts.length) clearInterval(timer); - }, delay); + const observer = new IntersectionObserver( + (entries) => { + setVisibleIndices((prev) => { + const updated = new Set(prev); + entries.forEach((entry) => { + const index = parseInt(entry.target.dataset.index, 10); + if (entry.isIntersecting) { + updated.add(index); + } else { + updated.delete(index); + } + }); + return updated; + }); + }, + { + root: null, + rootMargin: '200px', + threshold: 0.1, + } + ); - return () => clearInterval(timer); + placeholderRefs.current.forEach((ref) => { + if (ref) observer.observe(ref); + }); + + return () => { + observer.disconnect(); + }; }, [charts]); return ( - <> - {visibleCharts.map((chart, idx) => ( -
{chart}
+
+ {charts.map((chart, index) => ( +
(placeholderRefs.current[index] = el)} + data-index={index} + > + {visibleIndices.has(index) ? chart : } +
))} - +
); }; diff --git a/src/Style/theme.jsx b/src/Style/theme.jsx index 017bc36..6d33a29 100644 --- a/src/Style/theme.jsx +++ b/src/Style/theme.jsx @@ -139,7 +139,7 @@ export const darkTheme = createTheme({ // Фоновые цвета background: { - default: "#1E1E1E", // Основной фон приложения + default: "#2d2d2d", // Основной фон приложения paper: "#2d2d2d", // Фон "бумажных" поверхностей },