Compare commits

..

3 Commits

Author SHA1 Message Date
DmitriyA dabdda4afe Merge branch 'redisign' of http://git.enode/deployer3000/trust-module-frontend into redisign
test-org/trust-module-frontend/pipeline/pr-rc This commit looks good Details
2025-07-16 09:59:52 -04:00
DmitriyA 6a73bd8104 Merge branch 'rc' of http://git.enode/deployer3000/trust-module-frontend into redisign 2025-07-16 09:59:33 -04:00
DmitriyA 87a79f98d7 adding roles 2025-07-16 09:50:19 -04:00
8 changed files with 237 additions and 77 deletions

View File

@ -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 (

View File

@ -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}
/>
{/* Основной контент */}

View File

@ -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}
/>
</Box>
{!collapsed && (

View File

@ -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 (
<>
<FooterList>
@ -56,26 +66,29 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu
</FooterListItem>
)}
<FooterListItem>
{!collapsed && (
<Button
onClick={handleSettingsOpen}
sx={{
color: 'custom.sidebarText',
textTransform: 'none',
minWidth: 0,
padding: 0,
marginRight: 'auto'
}}
>
<ListItemText
primary="Настройки"
primaryTypographyProps={{
{/* кнопка настроек */}
<RoleBasedRender user={user} allowedRoles={['admin']}>
{!collapsed && (
<Button
onClick={handleSettingsOpen}
sx={{
color: 'custom.sidebarText',
variant: 'body2'
textTransform: 'none',
minWidth: 0,
padding: 0,
marginRight: 'auto'
}}
/>
</Button>
)}
>
<ListItemText
primary="Настройки"
primaryTypographyProps={{
color: 'custom.sidebarText',
variant: 'body2'
}}
/>
</Button>
)}
</RoleBasedRender>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Tooltip title="Переключить тему">
@ -98,9 +111,14 @@ const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu
</FooterListItem>
</FooterList>
<SettingsModal open={settingsOpen}
onClose={handleSettingsClose}
onMenuUpdate={forceRefreshMenu} />
{/* Используем RoleBasedRender для модального окна */}
<RoleBasedRender user={user} allowedRoles={['admin']}>
<SettingsModal
open={settingsOpen}
onClose={handleSettingsClose}
onMenuUpdate={forceRefreshMenu}
/>
</RoleBasedRender>
</>
);
};

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 }) => {
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}
/>
);
};

View File

@ -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"
/>
<TextField
@ -64,12 +87,45 @@ const LoginModal = ({ onLogin, onClose }) => {
margin="normal"
required
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
size="normal"
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>
)
}}
/>
{error && <p className="error">{error}</p>}
<button type="submit">Войти</button>
{error && (
<Typography color="error" sx={{ mt: 1 }}>
{error}
</Typography>
)}
<Button
type="submit"
variant="contained"
fullWidth
sx={{
mt: 2,
py: 1.5,
fontSize: '1rem'
}}
>
Войти
</Button>
</form>
</Modal>
);

View File

@ -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;
};

View File

@ -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 };