From 87a79f98d777b4aca64d89854fa6607749e25a94 Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Wed, 16 Jul 2025 09:50:19 -0400 Subject: [PATCH] adding roles --- src/App.jsx | 114 +++++++++++++----- src/Components/Layout/Dashboard.jsx | 11 +- src/Components/Layout/SidebarMenu.jsx | 4 +- .../SidebarMenuComponents/SidebarFooter.jsx | 66 ++++++---- src/Components/Layout/SidebarMenuWrapper.jsx | 3 +- src/Components/UI/LoginModal.jsx | 84 ++++++++++--- src/Components/UI/RoleBasedRender.jsx | 15 +++ src/Components/UI/auth.jsx | 17 ++- 8 files changed, 237 insertions(+), 77 deletions(-) create mode 100644 src/Components/UI/RoleBasedRender.jsx diff --git a/src/App.jsx b/src/App.jsx index 798e186..2406af5 100755 --- a/src/App.jsx +++ b/src/App.jsx @@ -22,23 +22,75 @@ function App() { useEffect(() => { const verifyAuth = async () => { try { + const savedToken = localStorage.getItem('access_token'); + + // Если есть токен, но нет пользователя - делаем запрос к серверу + if (savedToken && !localStorage.getItem('user')) { + const authStatus = await checkAuth(); + handleAuthResponse(authStatus); + return; + } + + // Если есть сохраненный пользователь + const savedUser = JSON.parse(localStorage.getItem('user')); + if (savedUser && savedToken) { + // Если у сохраненного пользователя нет роли - запрашиваем свежие данные + if (!savedUser.role) { + const authStatus = await checkAuth(); + handleAuthResponse(authStatus); + } else { + setAuthState({ + isAuthenticated: true, + isLoading: false, + user: savedUser + }); + setShowLoginModal(false); + } + return; + } + + // Стандартная проверка авторизации const authStatus = await checkAuth(); - setAuthState({ - isAuthenticated: authStatus.isAuthenticated, - isLoading: false, - user: authStatus.user || null - }); - setShowLoginModal(!authStatus.isAuthenticated); + handleAuthResponse(authStatus); } catch (error) { console.error('Auth verification error:', error); - setAuthState({ - isAuthenticated: false, - isLoading: false, - user: null - }); - setShowLoginModal(true); + handleAuthFailure(); } }; + + + const handleAuthResponse = (authStatus) => { + if (authStatus.isAuthenticated && authStatus.user?.role) { + const userToSave = { + id: authStatus.user.id, + login: authStatus.user.login, + role: authStatus.user.role + }; + + console.log('Saving user:', userToSave); + localStorage.setItem('user', JSON.stringify(userToSave)); + + setAuthState({ + isAuthenticated: true, + isLoading: false, + user: userToSave + }); + setShowLoginModal(false); + } else { + handleAuthFailure(); + } + }; + const handleAuthFailure = () => { + localStorage.removeItem('user'); + localStorage.removeItem('access_token'); + setAuthState({ + isAuthenticated: false, + isLoading: false, + user: null + }); + setShowLoginModal(true); + }; + verifyAuth(); }, []); @@ -46,29 +98,33 @@ function App() { setAuthState({ isAuthenticated: true, isLoading: false, - user: userData + user: { + id: userData.id, + login: userData.login, + role: userData.role + } }); setShowLoginModal(false); }; const handleLogout = async () => { - try { - await axios.post(`${import.meta.env.VITE_BACK_URL}/api/auth/logout`, null, { - withCredentials: true, // чтобы отправлялись куки - }); - - localStorage.removeItem('access_token'); - setAuthState({ - isAuthenticated: false, - isLoading: false, - user: null, - }); - setShowLoginModal(true); - } catch (error) { - console.error('Logout failed:', error); - } -}; + try { + await axios.post(`${import.meta.env.VITE_BACK_URL}/api/auth/logout`, null, { + withCredentials: true, + }); + localStorage.removeItem('access_token'); + localStorage.removeItem('user'); + setAuthState({ + isAuthenticated: false, + isLoading: false, + user: null, + }); + setShowLoginModal(true); + } catch (error) { + console.error('Logout failed:', error); + } + }; // Полноэкранный лоадер во время проверки авторизации if (authState.isLoading) { return ( diff --git a/src/Components/Layout/Dashboard.jsx b/src/Components/Layout/Dashboard.jsx index e0c2c42..48ac303 100755 --- a/src/Components/Layout/Dashboard.jsx +++ b/src/Components/Layout/Dashboard.jsx @@ -37,7 +37,7 @@ const Content = styled(Box)(({ theme }) => ({ color: theme.palette.custom.modalText, })); -const Dashboard = ({ isDarkMode, setIsDarkMode }) => { +const Dashboard = ({ isDarkMode, setIsDarkMode, user }) => { const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная"); const [tabContent, setTabContent] = useState({}); const [treeData1, setTreeData1] = useState(menuData); @@ -91,11 +91,11 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => { filters: item.filters, title: item.title, description: item.description, - ranges: item.ranges, + ranges: item.ranges, context: { device: item.filters?.device, source_id: item.filters?.source_id, - parent: item + parent: item } }} /> @@ -111,13 +111,13 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => { type: item.metric ? 'metric' : 'menuItem', metric: item.metric, filters: item.filters, - ranges: item.ranges + ranges: item.ranges }; handleOpenTab(newTab); } else { setActiveTab(tabId); } -}; + }; // Вспомогательная функция для получения всех дочерних элементов const getAllChildren = (node) => { @@ -138,6 +138,7 @@ const Dashboard = ({ isDarkMode, setIsDarkMode }) => { isDarkMode={isDarkMode} setIsDarkMode={setIsDarkMode} onMenuSelect={handleMenuSelect} + user={user} /> {/* Основной контент */} diff --git a/src/Components/Layout/SidebarMenu.jsx b/src/Components/Layout/SidebarMenu.jsx index d592dc9..3c07130 100644 --- a/src/Components/Layout/SidebarMenu.jsx +++ b/src/Components/Layout/SidebarMenu.jsx @@ -21,7 +21,8 @@ const SidebarMenu = ({ isDarkMode, setIsDarkMode, onSelectItem, - forceRefreshMenu + forceRefreshMenu, + user }) => { const [collapsed, setCollapsed] = useState(false); const { sidebarWidth, startResizing } = useSidebarResize(290); @@ -148,6 +149,7 @@ const SidebarMenu = ({ isDarkMode={isDarkMode} setIsDarkMode={setIsDarkMode} forceRefreshMenu={forceRefreshMenu} + user={user} /> {!collapsed && ( diff --git a/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx b/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx index d9546ff..9268f9e 100644 --- a/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx +++ b/src/Components/Layout/SidebarMenuComponents/SidebarFooter.jsx @@ -1,4 +1,3 @@ -// components/SidebarMenuComponents/SidebarFooter.jsx import React, { useState } from "react"; import { Brightness4, Brightness7 } from "@mui/icons-material"; import { IconButton, Tooltip } from "@mui/material"; @@ -12,6 +11,7 @@ import { Button } from "@mui/material"; import SettingsModal from "../SettingsModal"; +import { RoleBasedRender } from "../../UI/RoleBasedRender"; const FooterList = styled(List)(({ theme }) => ({ backgroundColor: theme.palette.custom.sidebar, @@ -30,7 +30,13 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({ alignItems: 'center' })); -const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu }) => { +const SidebarFooter = ({ + collapsed, + isDarkMode, + setIsDarkMode, + forceRefreshMenu, + user +}) => { const [settingsOpen, setSettingsOpen] = useState(false); const handleSettingsOpen = () => { @@ -40,7 +46,11 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu const handleSettingsClose = () => { setSettingsOpen(false); }; - + console.log('SidebarFooter user with role:', { + ...user, + hasRole: 'role' in user, + roleValue: user?.role + }); return ( <> @@ -56,26 +66,29 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu )} - {!collapsed && ( - - )} + > + + + )} + @@ -98,9 +111,14 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu - + {/* Используем RoleBasedRender для модального окна */} + + + ); }; diff --git a/src/Components/Layout/SidebarMenuWrapper.jsx b/src/Components/Layout/SidebarMenuWrapper.jsx index ddce8fa..462c2ce 100644 --- a/src/Components/Layout/SidebarMenuWrapper.jsx +++ b/src/Components/Layout/SidebarMenuWrapper.jsx @@ -3,7 +3,7 @@ import SidebarMenu from './SidebarMenu'; import { Box, CircularProgress, Typography } from '@mui/material'; import axios from 'axios'; -const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => { +const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) => { const [menuData, setMenuData] = useState(null); const [lastModified, setLastModified] = useState(null); const [loading, setLoading] = useState(true); @@ -177,6 +177,7 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => { onCloseEditModal={() => setEditModalOpen(false)} onSaveChanges={handleSaveChanges} forceRefreshMenu={forceRefreshMenu} + user={user} /> ); }; diff --git a/src/Components/UI/LoginModal.jsx b/src/Components/UI/LoginModal.jsx index f76b7b3..0b308e3 100755 --- a/src/Components/UI/LoginModal.jsx +++ b/src/Components/UI/LoginModal.jsx @@ -1,21 +1,27 @@ import React, { useState } from "react"; import Modal from "./Modal"; import "../../Style/LoginModal.css"; -import Logo from '../../assets/images/logo.svg?react'; -import TextField from '@mui/material/TextField'; +import { + TextField, + IconButton, + Button, + Typography, + InputAdornment +} from "@mui/material"; +import { + Visibility, + VisibilityOff +} from "@mui/icons-material"; import axios from 'axios'; const LoginModal = ({ onLogin, onClose }) => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); - const [showPassword, setShowPassword] = React.useState(false); - - const handleClickShowPassword = () => setShowPassword((show) => !show); + const [showPassword, setShowPassword] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); - try { const { data } = await axios.post( `${import.meta.env.VITE_BACK_URL}/api/auth/login`, @@ -28,16 +34,33 @@ const LoginModal = ({ onLogin, onClose }) => { } ); + console.log('Login response:', data); + if (data.success) { + if (!data.user?.role) { + console.error('Role missing in response:', data); + throw new Error('Роль пользователя не получена'); + } + + const userData = { + id: data.user.id, + login: data.user.login, + role: data.user.role + }; + localStorage.setItem('access_token', data.access_token); - onLogin(data.user); + localStorage.setItem('user', JSON.stringify(userData)); + + console.log('User data saved:', userData); + + onLogin(userData); onClose(); } else { - setError(data.message || "Неверный логин или пароль"); + setError(data.message || 'Ошибка авторизации'); } } catch (err) { - console.error('Ошибка при отправке запроса:', err); - setError(err.response?.data?.message || "Ошибка при подключении к серверу"); + console.error('Login error:', err); + setError(err.response?.data?.message || err.message || 'Ошибка при входе'); } }; @@ -52,8 +75,8 @@ const LoginModal = ({ onLogin, onClose }) => { variant="filled" margin="normal" required + value={username} onChange={(e) => setUsername(e.target.value)} - size="normal" /> { margin="normal" required type={showPassword ? 'text' : 'password'} + value={password} onChange={(e) => setPassword(e.target.value)} - size="normal" + InputProps={{ + endAdornment: ( + + setShowPassword(!showPassword)} + edge="end" + sx={{ + marginRight: '-12px', + alignSelf: 'flex-end' + }} + > + {showPassword ? : } + + + ) + }} /> - {error &&

{error}

} - + {error && ( + + {error} + + )} + + ); diff --git a/src/Components/UI/RoleBasedRender.jsx b/src/Components/UI/RoleBasedRender.jsx new file mode 100644 index 0000000..afdec19 --- /dev/null +++ b/src/Components/UI/RoleBasedRender.jsx @@ -0,0 +1,15 @@ +import React from 'react'; + +export const RoleBasedRender = ({ user, allowedRoles, children }) => { + console.log('RoleBasedRender check:', { + user, + hasRole: user?.role, + allowedRoles, + hasAccess: user && allowedRoles.includes(user.role) + }); + + if (!user || !allowedRoles.includes(user.role)) { + return null; + } + return children; +}; \ No newline at end of file diff --git a/src/Components/UI/auth.jsx b/src/Components/UI/auth.jsx index c0beca8..76f0b4c 100644 --- a/src/Components/UI/auth.jsx +++ b/src/Components/UI/auth.jsx @@ -1,5 +1,3 @@ -import axios from 'axios'; - export const checkAuth = async () => { try { const { data } = await axios.get( @@ -12,7 +10,20 @@ export const checkAuth = async () => { } ); - return data; + console.log('Auth check response:', data); + + if (!data.user) { + return { isAuthenticated: false }; + } + + return { + isAuthenticated: data.isAuthenticated, + user: { + id: data.user.id, + login: data.user.login, + role: data.user.role + } + }; } catch (err) { console.error('Auth check failed:', err); return { isAuthenticated: false };