Compare commits

..

No commits in common. "dabdda4afe9581ab42155def33abc482e0b142b2" and "15c20f1352ac8bbf6c494bb4a23d194efd304ea3" have entirely different histories.

8 changed files with 74 additions and 234 deletions

View File

@ -22,75 +22,23 @@ 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();
handleAuthResponse(authStatus);
setAuthState({
isAuthenticated: authStatus.isAuthenticated,
isLoading: false,
user: authStatus.user || null
});
setShowLoginModal(!authStatus.isAuthenticated);
} catch (error) {
console.error('Auth verification error:', error);
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,
isAuthenticated: false,
isLoading: false,
user: userToSave
user: null
});
setShowLoginModal(false);
} else {
handleAuthFailure();
setShowLoginModal(true);
}
};
const handleAuthFailure = () => {
localStorage.removeItem('user');
localStorage.removeItem('access_token');
setAuthState({
isAuthenticated: false,
isLoading: false,
user: null
});
setShowLoginModal(true);
};
verifyAuth();
}, []);
@ -98,33 +46,29 @@ function App() {
setAuthState({
isAuthenticated: true,
isLoading: false,
user: {
id: userData.id,
login: userData.login,
role: userData.role
}
user: userData
});
setShowLoginModal(false);
};
const handleLogout = async () => {
try {
await axios.post(`${import.meta.env.VITE_BACK_URL}/api/auth/logout`, null, {
withCredentials: true,
});
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);
}
};
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 (

View File

@ -37,7 +37,7 @@ const Content = styled(Box)(({ theme }) => ({
color: theme.palette.custom.modalText,
}));
const Dashboard = ({ isDarkMode, setIsDarkMode, user }) => {
const Dashboard = ({ isDarkMode, setIsDarkMode }) => {
const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная");
const [tabContent, setTabContent] = useState({});
const [treeData1, setTreeData1] = useState(menuData);
@ -91,11 +91,11 @@ const Dashboard = ({ isDarkMode, setIsDarkMode, user }) => {
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, user }) => {
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,7 +138,6 @@ const Dashboard = ({ isDarkMode, setIsDarkMode, user }) => {
isDarkMode={isDarkMode}
setIsDarkMode={setIsDarkMode}
onMenuSelect={handleMenuSelect}
user={user}
/>
{/* Основной контент */}

View File

@ -21,8 +21,7 @@ const SidebarMenu = ({
isDarkMode,
setIsDarkMode,
onSelectItem,
forceRefreshMenu,
user
forceRefreshMenu
}) => {
const [collapsed, setCollapsed] = useState(false);
const { sidebarWidth, startResizing } = useSidebarResize(290);
@ -149,7 +148,6 @@ const SidebarMenu = ({
isDarkMode={isDarkMode}
setIsDarkMode={setIsDarkMode}
forceRefreshMenu={forceRefreshMenu}
user={user}
/>
</Box>
{!collapsed && (

View File

@ -1,3 +1,4 @@
// components/SidebarMenuComponents/SidebarFooter.jsx
import React, { useState } from "react";
import { Brightness4, Brightness7 } from "@mui/icons-material";
import { IconButton, Tooltip } from "@mui/material";
@ -11,7 +12,6 @@ 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,13 +30,7 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({
alignItems: 'center'
}));
const SidebarFooter = ({
collapsed,
isDarkMode,
setIsDarkMode,
forceRefreshMenu,
user
}) => {
const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu }) => {
const [settingsOpen, setSettingsOpen] = useState(false);
const handleSettingsOpen = () => {
@ -46,11 +40,7 @@ const SidebarFooter = ({
const handleSettingsClose = () => {
setSettingsOpen(false);
};
console.log('SidebarFooter user with role:', {
...user,
hasRole: 'role' in user,
roleValue: user?.role
});
return (
<>
<FooterList>
@ -66,29 +56,26 @@ const SidebarFooter = ({
</FooterListItem>
)}
<FooterListItem>
{/* кнопка настроек */}
<RoleBasedRender user={user} allowedRoles={['admin']}>
{!collapsed && (
<Button
onClick={handleSettingsOpen}
sx={{
{!collapsed && (
<Button
onClick={handleSettingsOpen}
sx={{
color: 'custom.sidebarText',
textTransform: 'none',
minWidth: 0,
padding: 0,
marginRight: 'auto'
}}
>
<ListItemText
primary="Настройки"
primaryTypographyProps={{
color: 'custom.sidebarText',
textTransform: 'none',
minWidth: 0,
padding: 0,
marginRight: 'auto'
variant: 'body2'
}}
>
<ListItemText
primary="Настройки"
primaryTypographyProps={{
color: 'custom.sidebarText',
variant: 'body2'
}}
/>
</Button>
)}
</RoleBasedRender>
/>
</Button>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Tooltip title="Переключить тему">
@ -111,14 +98,9 @@ const SidebarFooter = ({
</FooterListItem>
</FooterList>
{/* Используем RoleBasedRender для модального окна */}
<RoleBasedRender user={user} allowedRoles={['admin']}>
<SettingsModal
open={settingsOpen}
onClose={handleSettingsClose}
onMenuUpdate={forceRefreshMenu}
/>
</RoleBasedRender>
<SettingsModal open={settingsOpen}
onClose={handleSettingsClose}
onMenuUpdate={forceRefreshMenu} />
</>
);
};

View File

@ -3,7 +3,7 @@ import SidebarMenu from './SidebarMenu';
import { Box, CircularProgress, Typography } from '@mui/material';
import axios from 'axios';
const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) => {
const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
const [menuData, setMenuData] = useState(null);
const [lastModified, setLastModified] = useState(null);
const [loading, setLoading] = useState(true);
@ -177,7 +177,6 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) =
onCloseEditModal={() => setEditModalOpen(false)}
onSaveChanges={handleSaveChanges}
forceRefreshMenu={forceRefreshMenu}
user={user}
/>
);
};

View File

@ -1,27 +1,21 @@
import React, { useState } from "react";
import Modal from "./Modal";
import "../../Style/LoginModal.css";
import {
TextField,
IconButton,
Button,
Typography,
InputAdornment
} from "@mui/material";
import {
Visibility,
VisibilityOff
} from "@mui/icons-material";
import Logo from '../../assets/images/logo.svg?react';
import TextField from '@mui/material/TextField';
import axios from 'axios';
const LoginModal = ({ onLogin, onClose }) => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [showPassword, setShowPassword] = React.useState(false);
const handleClickShowPassword = () => setShowPassword((show) => !show);
const handleSubmit = async (e) => {
e.preventDefault();
try {
const { data } = await axios.post(
`${import.meta.env.VITE_BACK_URL}/api/auth/login`,
@ -34,33 +28,16 @@ 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);
localStorage.setItem('user', JSON.stringify(userData));
console.log('User data saved:', userData);
onLogin(userData);
onLogin(data.user);
onClose();
} else {
setError(data.message || 'Ошибка авторизации');
setError(data.message || "Неверный логин или пароль");
}
} catch (err) {
console.error('Login error:', err);
setError(err.response?.data?.message || err.message || 'Ошибка при входе');
console.error('Ошибка при отправке запроса:', err);
setError(err.response?.data?.message || "Ошибка при подключении к серверу");
}
};
@ -75,8 +52,8 @@ const LoginModal = ({ onLogin, onClose }) => {
variant="filled"
margin="normal"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
size="normal"
/>
<TextField
@ -87,45 +64,12 @@ const LoginModal = ({ onLogin, onClose }) => {
margin="normal"
required
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword(!showPassword)}
edge="end"
sx={{
marginRight: '-12px',
alignSelf: 'flex-end'
}}
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
)
}}
size="normal"
/>
{error && (
<Typography color="error" sx={{ mt: 1 }}>
{error}
</Typography>
)}
<Button
type="submit"
variant="contained"
fullWidth
sx={{
mt: 2,
py: 1.5,
fontSize: '1rem'
}}
>
Войти
</Button>
{error && <p className="error">{error}</p>}
<button type="submit">Войти</button>
</form>
</Modal>
);

View File

@ -1,15 +0,0 @@
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;
};

View File

@ -1,3 +1,5 @@
import axios from 'axios';
export const checkAuth = async () => {
try {
const { data } = await axios.get(
@ -10,20 +12,7 @@ export const checkAuth = async () => {
}
);
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
}
};
return data;
} catch (err) {
console.error('Auth check failed:', err);
return { isAuthenticated: false };