Compare commits
No commits in common. "534aeb9d50761972d2d23d7a80e01ad41ed7c91b" and "57290a8d03ba9037230ee205ad488c2a207b10e7" have entirely different histories.
534aeb9d50
...
57290a8d03
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@ -15,7 +15,6 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"material-react-table": "^3.2.1",
|
"material-react-table": "^3.2.1",
|
||||||
"raisa-fastapi-protected-web": "1.1.0",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-daisyui": "^5.0.5",
|
"react-daisyui": "^5.0.5",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@ -2776,9 +2775,6 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/raisa-fastapi-protected-web": {
|
|
||||||
"version": "1.1.0"
|
|
||||||
},
|
|
||||||
"node_modules/react": {
|
"node_modules/react": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"material-react-table": "^3.2.1",
|
"material-react-table": "^3.2.1",
|
||||||
"raisa-fastapi-protected-web": "1.1.0",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-daisyui": "^5.0.5",
|
"react-daisyui": "^5.0.5",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
@ -12,10 +12,6 @@ export const AuthApi = {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
async loginWithKeycloak(token) {
|
|
||||||
const { data } = await api.post('/auth/login', { token });
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
async refresh() {
|
async refresh() {
|
||||||
const refresh_token = localStorage.getItem('refresh_token');
|
const refresh_token = localStorage.getItem('refresh_token');
|
||||||
if (!refresh_token) return null;
|
if (!refresh_token) return null;
|
||||||
|
|||||||
@ -1,20 +1,12 @@
|
|||||||
import { CircularProgress } from '@mui/material';
|
import { CircularProgress } from '@mui/material';
|
||||||
import { TokensContext } from 'raisa-fastapi-protected-web';
|
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
import { AuthApi } from '../../api/auth';
|
import { AuthApi } from '../../api/auth';
|
||||||
import { UsersApi } from '../../api/users';
|
import { UsersApi } from '../../api/users';
|
||||||
import { clearAuthTokens, getAuthToken, isTokenExpired, saveAuthTokens } from '../../api/utils/token';
|
import { getAuthToken, isTokenExpired, saveAuthTokens } from '../../api/utils/token';
|
||||||
import { Config } from '../../conf/config';
|
|
||||||
|
|
||||||
const AuthContext = createContext(null);
|
const AuthContext = createContext(null);
|
||||||
|
|
||||||
const LoadingScreen = () => (
|
export const AuthProvider = ({ children }) => {
|
||||||
<div className='flex items-center justify-center min-h-screen'>
|
|
||||||
<CircularProgress />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const LocalAuthProvider = ({ children }) => {
|
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(() => {
|
const [isAuthenticated, setIsAuthenticated] = useState(() => {
|
||||||
@ -67,7 +59,7 @@ const LocalAuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = async () => {
|
||||||
try {
|
try {
|
||||||
AuthApi.logout();
|
AuthApi.logout();
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
@ -107,92 +99,18 @@ const LocalAuthProvider = ({ children }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ isAuthenticated, user, login, logout, loadUser, loading }}>
|
<AuthContext.Provider value={{ isAuthenticated, user, login, logout, loadUser }}>
|
||||||
{loading ? <LoadingScreen /> : children}
|
{loading ? (
|
||||||
|
<div className='flex items-center justify-center min-h-screen'>
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProductionAuthProvider = ({ children }) => {
|
|
||||||
const tokenInfo = useContext(TokensContext);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
||||||
const [user, setUser] = useState(null);
|
|
||||||
const [authError, setAuthError] = useState(null);
|
|
||||||
|
|
||||||
const loadUser = useCallback(async () => {
|
|
||||||
const userData = await UsersApi.me();
|
|
||||||
setUser(userData);
|
|
||||||
return userData;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let active = true;
|
|
||||||
const keycloakAccessToken = tokenInfo?.accessToken;
|
|
||||||
|
|
||||||
if (!keycloakAccessToken) {
|
|
||||||
clearAuthTokens();
|
|
||||||
setLoading(true);
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
setUser(null);
|
|
||||||
return () => {
|
|
||||||
active = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginWithKeycloak = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
setAuthError(null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const tokens = await AuthApi.loginWithKeycloak(keycloakAccessToken);
|
|
||||||
if (!active) return;
|
|
||||||
|
|
||||||
saveAuthTokens(tokens);
|
|
||||||
setIsAuthenticated(true);
|
|
||||||
try {
|
|
||||||
await loadUser();
|
|
||||||
} catch (userError) {
|
|
||||||
console.error('Ошибка загрузки пользователя:', userError);
|
|
||||||
if (active) setUser(null);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (!active) return;
|
|
||||||
|
|
||||||
AuthApi.logout();
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
setUser(null);
|
|
||||||
setAuthError(error?.response?.data?.detail || error?.response?.data?.message || 'Не удалось войти через Keycloak');
|
|
||||||
console.error('Ошибка авторизации через Keycloak:', error);
|
|
||||||
} finally {
|
|
||||||
if (active) setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loginWithKeycloak();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
active = false;
|
|
||||||
};
|
|
||||||
}, [loadUser, tokenInfo?.accessToken]);
|
|
||||||
|
|
||||||
if (loading) return <LoadingScreen />;
|
|
||||||
|
|
||||||
if (authError) {
|
|
||||||
return <div className='flex items-center justify-center min-h-screen'>{authError}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <AuthContext.Provider value={{ isAuthenticated, user, loadUser, loading }}>{children}</AuthContext.Provider>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AuthProvider = ({ children }) => {
|
|
||||||
if (Config.IS_PRODUCTION) {
|
|
||||||
return <ProductionAuthProvider>{children}</ProductionAuthProvider>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <LocalAuthProvider>{children}</LocalAuthProvider>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = () => {
|
||||||
const context = useContext(AuthContext);
|
const context = useContext(AuthContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
import { useAuth } from '../../app/context/AuthProvider';
|
||||||
import { Config } from '../../conf/config';
|
import { isTestMode } from '../../conf/config';
|
||||||
|
|
||||||
export function HeaderMenu() {
|
export function HeaderMenu() {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
@ -58,8 +58,8 @@ export function HeaderMenu() {
|
|||||||
</UserNameAndRole>
|
</UserNameAndRole>
|
||||||
|
|
||||||
{/* на стенде прода не показываем кнопку выхода */}
|
{/* на стенде прода не показываем кнопку выхода */}
|
||||||
{!Config.IS_PRODUCTION && <DownButton onClick={toggleMenu} isOpen={isMenuOpen} />}
|
{isTestMode && <DownButton onClick={toggleMenu} isOpen={isMenuOpen} />}
|
||||||
{!Config.IS_PRODUCTION && isMenuOpen && (
|
{isTestMode && isMenuOpen && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<LogoutButton onClick={handleLogout}>Выйти</LogoutButton>
|
<LogoutButton onClick={handleLogout}>Выйти</LogoutButton>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|||||||
@ -3,17 +3,14 @@ const environment = process.env;
|
|||||||
|
|
||||||
let env_var: string | undefined;
|
let env_var: string | undefined;
|
||||||
|
|
||||||
if (environment.APP_ENV === 'test') {
|
if (environment.APP_ENV == 'test') {
|
||||||
env_var = environment.OPENBAO__SETTINGS_TEST__REACT_APP_API_URL;
|
env_var = environment.OPENBAO__SETTINGS_TEST__REACT_APP_API_URL;
|
||||||
} else {
|
} else {
|
||||||
env_var = environment.OPENBAO__SETTINGS__REACT_APP_API_URL;
|
env_var = environment.OPENBAO__SETTINGS__REACT_APP_API_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/complexity/noStaticOnlyClass: существующий публичный интерфейс конфигурации приложения.
|
|
||||||
export class Config {
|
export class Config {
|
||||||
static API_URL: string = env_var ?? environment.REACT_APP_API_URL ?? environment.REACT_APP_ROOT_PATH ?? '';
|
static API_URL: string = env_var ?? environment.REACT_APP_API_URL ?? environment.REACT_APP_ROOT_PATH ?? '';
|
||||||
// TODO: заменить заглушку на признак production-сборки, эквивалентный !settings.DEBUG на бэкенде.
|
|
||||||
static IS_PRODUCTION = true;
|
|
||||||
static KEYCLOAK_URL: string = environment.REACT_APP_KEYCLOAK_URL ?? '';
|
static KEYCLOAK_URL: string = environment.REACT_APP_KEYCLOAK_URL ?? '';
|
||||||
static REALM: string = environment.REACT_APP_REALM ?? '';
|
static REALM: string = environment.REACT_APP_REALM ?? '';
|
||||||
static CLIENT_ID: string = environment.REACT_APP_CLIENT_ID ?? '';
|
static CLIENT_ID: string = environment.REACT_APP_CLIENT_ID ?? '';
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
|||||||
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
|
||||||
import { ruRU } from '@mui/x-date-pickers/locales';
|
import { ruRU } from '@mui/x-date-pickers/locales';
|
||||||
import ruLocale from 'date-fns/locale/ru';
|
import ruLocale from 'date-fns/locale/ru';
|
||||||
import { RaisaAuthContext, createAuthContext } from 'raisa-fastapi-protected-web';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
import { AuthProvider } from './app/context/AuthProvider';
|
import { AuthProvider } from './app/context/AuthProvider';
|
||||||
@ -11,12 +10,11 @@ import theme from './app/theme/theme';
|
|||||||
import './index.css';
|
import './index.css';
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import { Config } from './conf/config';
|
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
|
||||||
const baseName = (process.env.REACT_APP_ROOT_PATH || '/').replace(/\/$/, '');
|
const baseName = (process.env.REACT_APP_ROOT_PATH || '/').replace(/\/$/, '');
|
||||||
|
|
||||||
const application = (
|
createRoot(document.getElementById('root')).render(
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<LocalizationProvider
|
<LocalizationProvider
|
||||||
dateAdapter={AdapterDateFns}
|
dateAdapter={AdapterDateFns}
|
||||||
@ -34,17 +32,5 @@ const application = (
|
|||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</LocalizationProvider>
|
</LocalizationProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const root = Config.IS_PRODUCTION ? (
|
|
||||||
<RaisaAuthContext
|
|
||||||
authClient={createAuthContext(Config.KEYCLOAK_URL, Config.REALM, Config.CLIENT_ID)}
|
|
||||||
app={{ appName: Config.APP_NAME, namespace: Config.NAMESPACE }}>
|
|
||||||
{application}
|
|
||||||
</RaisaAuthContext>
|
|
||||||
) : (
|
|
||||||
application
|
|
||||||
);
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')).render(root);
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user