mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
* F #3951: Update app layouts and router * F #3951: Fix footer component * F #3951: Add loading screen & update sidebar * F #3951: Add responsiveness to sidebar
This commit is contained in:
parent
f979efd2b5
commit
78d699561c
@ -36,7 +36,7 @@
|
||||
"axios": "^0.19.2",
|
||||
"body-parser": "^1.19.0",
|
||||
"btoa": "^1.2.1",
|
||||
"classnames": "^2.2.6",
|
||||
"clsx": "^1.1.1",
|
||||
"colors": "^1.4.0",
|
||||
"compression": "^1.7.4",
|
||||
"concurrently": "^5.2.0",
|
||||
|
@ -1,11 +1,13 @@
|
||||
const CHANGE_ZONE = 'CHANGE_ZONE';
|
||||
const DISPLAY_LOADING = 'DISPLAY_LOADING';
|
||||
const TOGGLE_MENU = 'TOGGLE_MENU';
|
||||
const FIX_MENU = 'FIX_MENU';
|
||||
|
||||
const Actions = {
|
||||
CHANGE_ZONE,
|
||||
DISPLAY_LOADING,
|
||||
TOGGLE_MENU
|
||||
TOGGLE_MENU,
|
||||
FIX_MENU
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
@ -21,5 +23,9 @@ module.exports = {
|
||||
openMenu: isOpen => ({
|
||||
type: TOGGLE_MENU,
|
||||
isOpen
|
||||
}),
|
||||
fixMenu: isFixed => ({
|
||||
type: FIX_MENU,
|
||||
isFixed
|
||||
})
|
||||
};
|
||||
|
@ -18,9 +18,9 @@ import { StaticRouter, BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { CssBaseline, ThemeProvider } from '@material-ui/core';
|
||||
import { CssBaseline, ThemeProvider, StylesProvider } from '@material-ui/core';
|
||||
|
||||
import theme from 'client/assets/theme';
|
||||
import theme, { generateClassName } from 'client/assets/theme';
|
||||
import { TranslateProvider } from 'client/components/HOC';
|
||||
import Router from 'client/router';
|
||||
|
||||
@ -35,21 +35,23 @@ const App = ({ location, context, store }) => {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Provider store={store}>
|
||||
<TranslateProvider>
|
||||
{location && context ? (
|
||||
// server build
|
||||
<StaticRouter location={location} context={context}>
|
||||
<Router />
|
||||
</StaticRouter>
|
||||
) : (
|
||||
// browser build
|
||||
<BrowserRouter>
|
||||
<Router />
|
||||
</BrowserRouter>
|
||||
)}
|
||||
</TranslateProvider>
|
||||
</Provider>
|
||||
<StylesProvider generateClassName={generateClassName}>
|
||||
<Provider store={store}>
|
||||
<TranslateProvider>
|
||||
{location && context ? (
|
||||
// server build
|
||||
<StaticRouter location={location} context={context}>
|
||||
<Router />
|
||||
</StaticRouter>
|
||||
) : (
|
||||
// browser build
|
||||
<BrowserRouter>
|
||||
<Router />
|
||||
</BrowserRouter>
|
||||
)}
|
||||
</TranslateProvider>
|
||||
</Provider>
|
||||
</StylesProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,14 @@
|
||||
import { createMuiTheme, responsiveFontSizes } from '@material-ui/core';
|
||||
import {
|
||||
createMuiTheme,
|
||||
responsiveFontSizes,
|
||||
createGenerateClassName
|
||||
} from '@material-ui/core';
|
||||
|
||||
const defaultBreakpoints = {
|
||||
export const generateClassName = createGenerateClassName({
|
||||
productionPrefix: 'one-'
|
||||
});
|
||||
|
||||
export const defaultBreakpoints = {
|
||||
xs: 0,
|
||||
sm: 600,
|
||||
md: 960,
|
||||
@ -12,99 +20,95 @@ const defaultBreakpoints = {
|
||||
desktop: 1280
|
||||
};
|
||||
|
||||
const theme = createMuiTheme({
|
||||
typography: {
|
||||
fontFamily: ['Ubuntu', 'Lato'].join(',')
|
||||
},
|
||||
overrides: {
|
||||
MuiDrawer: {
|
||||
paper: {
|
||||
width: 360,
|
||||
overflow: 'hidden',
|
||||
[`@media (max-width: ${defaultBreakpoints.tablet}px)`]: {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
},
|
||||
MuiFormControl: {
|
||||
root: {
|
||||
margin: '.5rem 0'
|
||||
}
|
||||
},
|
||||
MuiExpansionPanel: {
|
||||
root: {
|
||||
minHeight: 56,
|
||||
boxShadow: 'none',
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0
|
||||
},
|
||||
'&$expanded': {
|
||||
minHeight: 56,
|
||||
margin: '0'
|
||||
}
|
||||
},
|
||||
content: {
|
||||
'&$expanded': {
|
||||
margin: '0'
|
||||
}
|
||||
},
|
||||
expanded: {}
|
||||
},
|
||||
MuiExpansionPanelSummary: {
|
||||
root: {
|
||||
'&$disabled': {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
content: {
|
||||
margin: 8,
|
||||
'&$expanded': {
|
||||
margin: 8
|
||||
}
|
||||
},
|
||||
disabled: {},
|
||||
expanded: {}
|
||||
},
|
||||
MuiCssBaseline: {
|
||||
'@global': {
|
||||
body: {
|
||||
// height: '100vh'
|
||||
}
|
||||
// '@font-face': [UbuntuFont]
|
||||
}
|
||||
}
|
||||
},
|
||||
palette: {
|
||||
common: { black: '#000', white: '#fff' },
|
||||
background: {
|
||||
paper: '#fff',
|
||||
default: '#fafafa'
|
||||
},
|
||||
primary: {
|
||||
light: 'rgba(191, 230, 242, 1)',
|
||||
main: 'rgba(64, 179, 217, 1)',
|
||||
dark: 'rgba(0, 152, 195, 1)',
|
||||
contrastText: '#fff'
|
||||
},
|
||||
secondary: {
|
||||
light: 'rgba(199, 201, 200, 1)',
|
||||
main: 'rgba(87, 92, 91, 1)',
|
||||
dark: 'rgba(53, 55, 53, 1)',
|
||||
contrastText: '#fff'
|
||||
},
|
||||
error: {
|
||||
light: '#e57373',
|
||||
main: '#f44336',
|
||||
dark: '#d32f2f',
|
||||
contrastText: '#fff'
|
||||
},
|
||||
text: {
|
||||
primary: 'rgba(0, 0, 0, 0.87)',
|
||||
secondary: 'rgba(0, 0, 0, 0.54)',
|
||||
disabled: 'rgba(0, 0, 0, 0.38)',
|
||||
hint: 'rgba(0, 0, 0, 0.38)'
|
||||
}
|
||||
}
|
||||
});
|
||||
export const sidebarWidth = {
|
||||
minified: 60,
|
||||
fixed: 240
|
||||
};
|
||||
|
||||
export default responsiveFontSizes(theme);
|
||||
export default responsiveFontSizes(
|
||||
createMuiTheme({
|
||||
typography: {
|
||||
fontFamily: ['Ubuntu', 'Lato'].join(',')
|
||||
},
|
||||
overrides: {
|
||||
MuiFormControl: {
|
||||
root: {
|
||||
margin: '.5rem 0'
|
||||
}
|
||||
},
|
||||
MuiExpansionPanel: {
|
||||
root: {
|
||||
minHeight: 56,
|
||||
boxShadow: 'none',
|
||||
'&:not(:last-child)': {
|
||||
borderBottom: 0
|
||||
},
|
||||
'&$expanded': {
|
||||
minHeight: 56,
|
||||
margin: '0'
|
||||
}
|
||||
},
|
||||
content: {
|
||||
'&$expanded': {
|
||||
margin: '0'
|
||||
}
|
||||
},
|
||||
expanded: {}
|
||||
},
|
||||
MuiExpansionPanelSummary: {
|
||||
root: {
|
||||
'&$disabled': {
|
||||
opacity: 1
|
||||
}
|
||||
},
|
||||
content: {
|
||||
margin: 8,
|
||||
'&$expanded': {
|
||||
margin: 8
|
||||
}
|
||||
},
|
||||
disabled: {},
|
||||
expanded: {}
|
||||
},
|
||||
MuiCssBaseline: {
|
||||
'@global': {
|
||||
body: {
|
||||
// height: '100vh'
|
||||
}
|
||||
// '@font-face': [UbuntuFont]
|
||||
}
|
||||
}
|
||||
},
|
||||
palette: {
|
||||
common: { black: '#000', white: '#fff' },
|
||||
background: {
|
||||
paper: '#fff',
|
||||
default: '#fafafa'
|
||||
},
|
||||
primary: {
|
||||
light: 'rgba(191, 230, 242, 1)',
|
||||
main: 'rgba(64, 179, 217, 1)',
|
||||
dark: 'rgba(0, 152, 195, 1)',
|
||||
contrastText: '#fff'
|
||||
},
|
||||
secondary: {
|
||||
light: 'rgba(199, 201, 200, 1)',
|
||||
main: 'rgba(87, 92, 91, 1)',
|
||||
dark: 'rgba(53, 55, 53, 1)',
|
||||
contrastText: '#fff'
|
||||
},
|
||||
error: {
|
||||
light: '#e57373',
|
||||
main: '#f44336',
|
||||
dark: '#d32f2f',
|
||||
contrastText: '#fff'
|
||||
},
|
||||
text: {
|
||||
primary: 'rgba(0, 0, 0, 0.87)',
|
||||
secondary: 'rgba(0, 0, 0, 0.54)',
|
||||
disabled: 'rgba(0, 0, 0, 0.38)',
|
||||
hint: 'rgba(0, 0, 0, 0.38)'
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@ -26,7 +26,11 @@ const Footer = React.memo(() => {
|
||||
|
||||
return (
|
||||
<Box className={classes.footer} component="footer">
|
||||
{`❤️ by `}
|
||||
{'Made with'}
|
||||
<span className={classes.heartIcon} role="img" aria-label="heart-emoji">
|
||||
{'❤️'}
|
||||
</span>
|
||||
{'by'}
|
||||
<Link href={url} className={classes.link}>
|
||||
{text}
|
||||
</Link>
|
||||
|
@ -3,7 +3,7 @@ import { makeStyles } from '@material-ui/core';
|
||||
export default makeStyles(theme => ({
|
||||
footer: {
|
||||
color: theme.palette.primary.light,
|
||||
position: 'fixed',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 'auto',
|
||||
right: 0,
|
||||
@ -13,6 +13,10 @@ export default makeStyles(theme => ({
|
||||
textAlign: 'center',
|
||||
padding: 5
|
||||
},
|
||||
heartIcon: {
|
||||
margin: theme.spacing(0, 1),
|
||||
color: theme.palette.error.dark
|
||||
},
|
||||
link: {
|
||||
color: theme.palette.primary.light
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import { makeStyles, CircularProgress, Button } from '@material-ui/core';
|
||||
|
||||
import { Translate } from 'client/components/HOC';
|
||||
import { Tr } from 'client/components/HOC';
|
||||
import * as CONSTANT from 'client/constants';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
@ -26,7 +26,7 @@ const ButtonSubmit = ({ isSubmitting, label, ...rest }) => {
|
||||
{...rest}
|
||||
>
|
||||
{isSubmitting && <CircularProgress size={24} />}
|
||||
{!isSubmitting && <Translate word={label ?? CONSTANT.default.Submit} />}
|
||||
{!isSubmitting && Tr(label)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
@ -1,49 +0,0 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
/* a copy of the License at */
|
||||
/* */
|
||||
/* http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* */
|
||||
/* Unless required by applicable law or agreed to in writing, software */
|
||||
/* distributed under the License is distributed on an "AS IS" BASIS, */
|
||||
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
|
||||
/* See the License for the specific language governing permissions and */
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import { PATH } from 'client/router/endpoints';
|
||||
|
||||
const GuessLayout = ({ children }) => {
|
||||
const { isLoginInProcess, isLogged, firstRender } = useAuth();
|
||||
|
||||
if (firstRender) {
|
||||
return <LinearProgress style={{ width: '100%' }} />;
|
||||
} else if (isLogged && !isLoginInProcess) {
|
||||
return <Redirect to={PATH.DASHBOARD} />;
|
||||
}
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
};
|
||||
|
||||
GuessLayout.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
])
|
||||
};
|
||||
|
||||
GuessLayout.defaultProps = {
|
||||
children: ''
|
||||
};
|
||||
|
||||
export default GuessLayout;
|
@ -15,68 +15,32 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { makeStyles, Box, Container } from '@material-ui/core';
|
||||
import { Skeleton } from '@material-ui/lab';
|
||||
import { Box, Container } from '@material-ui/core';
|
||||
import useGeneral from 'client/hooks/useGeneral';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import useOpenNebula from 'client/hooks/useOpennebula';
|
||||
import Header from 'client/components/Header';
|
||||
import Footer from 'client/components/Footer';
|
||||
import Header from 'client/components/Header';
|
||||
|
||||
const internalStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
width: '100%'
|
||||
},
|
||||
main: {
|
||||
paddingTop: 64,
|
||||
paddingBottom: 30,
|
||||
height: '100vh',
|
||||
width: '100vw'
|
||||
},
|
||||
scrollable: {
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: 14
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundClip: 'content-box',
|
||||
border: '4px solid transparent',
|
||||
borderRadius: 7,
|
||||
boxShadow: 'inset 0 0 0 10px',
|
||||
color: theme.palette.primary.light
|
||||
}
|
||||
}
|
||||
}));
|
||||
import internalStyles from 'client/components/HOC/InternalLayout/styles';
|
||||
|
||||
const InternalLayout = ({ children, title }) => {
|
||||
const InternalLayout = ({ authRoute, label, children }) => {
|
||||
const classes = internalStyles();
|
||||
const { groups } = useOpenNebula();
|
||||
const { authUser } = useAuth();
|
||||
const { isFixMenu } = useGeneral();
|
||||
|
||||
const isAuthenticating = Boolean(!authUser && !groups?.length);
|
||||
|
||||
return isAuthenticating ? (
|
||||
<Box width="100%" display="flex" flexDirection="column">
|
||||
<Skeleton variant="rect" width="100%" height={64} />
|
||||
<Box padding={2}>
|
||||
<Skeleton variant="rect" width="50%" height={32} />
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<Header title={title} />
|
||||
return authRoute ? (
|
||||
<Box className={clsx(classes.root, { [classes.isDrawerFixed]: isFixMenu })}>
|
||||
<Header title={label} />
|
||||
<Box component="main" className={classes.main}>
|
||||
<Container component="div" className={classes.scrollable}>
|
||||
{children}
|
||||
</Container>
|
||||
</Box>
|
||||
<Footer />
|
||||
</>
|
||||
</Box>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
};
|
||||
|
||||
@ -86,12 +50,14 @@ InternalLayout.propTypes = {
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
]),
|
||||
title: PropTypes.string
|
||||
authRoute: PropTypes.bool.isRequired,
|
||||
label: PropTypes.string
|
||||
};
|
||||
|
||||
InternalLayout.defaultProps = {
|
||||
children: [],
|
||||
title: ''
|
||||
authRoute: false,
|
||||
label: null
|
||||
};
|
||||
|
||||
export default InternalLayout;
|
@ -0,0 +1,47 @@
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { sidebarWidth } from 'client/assets/theme';
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
root: {
|
||||
flex: '1 1 auto',
|
||||
display: 'flex',
|
||||
zIndex: '3',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
flexDirection: 'column',
|
||||
transition: theme.transitions.create('margin', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen
|
||||
}),
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
marginLeft: sidebarWidth.minified
|
||||
}
|
||||
},
|
||||
isDrawerFixed: {
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
marginLeft: sidebarWidth.fixed
|
||||
}
|
||||
},
|
||||
main: {
|
||||
paddingTop: 64,
|
||||
paddingBottom: 30,
|
||||
height: '100vh',
|
||||
width: '100%'
|
||||
},
|
||||
scrollable: {
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(2),
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: 14
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundClip: 'content-box',
|
||||
border: '4px solid transparent',
|
||||
borderRadius: 7,
|
||||
boxShadow: 'inset 0 0 0 10px',
|
||||
color: theme.palette.primary.light
|
||||
}
|
||||
}
|
||||
}));
|
@ -1,4 +1,4 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* Copyright 2002-2020, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
@ -13,17 +13,27 @@
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { Fragment, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { LinearProgress } from '@material-ui/core';
|
||||
import { useLocation, Redirect } from 'react-router-dom';
|
||||
|
||||
import useAuth from 'client/hooks/useAuth';
|
||||
import { PATH } from 'client/router/endpoints';
|
||||
import useOpennebula from 'client/hooks/useOpennebula';
|
||||
|
||||
const AuthLayout = ({ children }) => {
|
||||
const { isLoginInProcess, isLogged, firstRender, getAuthInfo } = useAuth();
|
||||
import LoadingScreen from 'client/components/LoadingScreen';
|
||||
import Sidebar from 'client/components/Sidebar';
|
||||
import { PATH, findRouteByPathname } from 'client/router/endpoints';
|
||||
|
||||
const MainLayout = ({ children }) => {
|
||||
const { pathname } = useLocation();
|
||||
const { groups } = useOpennebula();
|
||||
const {
|
||||
isLogged,
|
||||
isLoginInProcess,
|
||||
getAuthInfo,
|
||||
authUser,
|
||||
firstRender
|
||||
} = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
if (isLogged && !isLoginInProcess) {
|
||||
@ -31,16 +41,34 @@ const AuthLayout = ({ children }) => {
|
||||
}
|
||||
}, [isLogged, isLoginInProcess]);
|
||||
|
||||
if (firstRender) {
|
||||
return <LinearProgress style={{ width: '100%' }} />;
|
||||
} else if (!isLogged && !isLoginInProcess) {
|
||||
const { authenticated: authRoute } = findRouteByPathname(pathname);
|
||||
|
||||
// PENDING TO AUTHENTICATING OR FIRST RENDERING
|
||||
if (firstRender || (isLogged && authRoute && !authUser && !groups?.length)) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
// PROTECTED ROUTE
|
||||
if (authRoute && !isLogged && !isLoginInProcess) {
|
||||
console.log('protected route needs redirect to LOGIN');
|
||||
return <Redirect to={PATH.LOGIN} />;
|
||||
}
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
// PUBLIC ROUTE
|
||||
if (!authRoute && isLogged && !isLoginInProcess) {
|
||||
console.log('public route needs redirect to DASHBOARD');
|
||||
return <Redirect to={PATH.DASHBOARD} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{authRoute && isLogged && <Sidebar />}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AuthLayout.propTypes = {
|
||||
MainLayout.propTypes = {
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
@ -48,8 +76,8 @@ AuthLayout.propTypes = {
|
||||
])
|
||||
};
|
||||
|
||||
AuthLayout.defaultProps = {
|
||||
MainLayout.defaultProps = {
|
||||
children: ''
|
||||
};
|
||||
|
||||
export default AuthLayout;
|
||||
export default MainLayout;
|
@ -13,20 +13,18 @@
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import GuessLayout from './GuessLayout';
|
||||
import AuthLayout from './AuthLayout';
|
||||
import InternalLayout from './InternalLayout';
|
||||
import InternalLayout from 'client/components/HOC/InternalLayout';
|
||||
import MainLayout from 'client/components/HOC/MainLayout';
|
||||
import {
|
||||
TranslateContext,
|
||||
TranslateProvider,
|
||||
Translate,
|
||||
Tr
|
||||
} from './Translate';
|
||||
} from 'client/components/HOC/Translate';
|
||||
|
||||
export {
|
||||
GuessLayout,
|
||||
AuthLayout,
|
||||
InternalLayout,
|
||||
MainLayout,
|
||||
TranslateContext,
|
||||
TranslateProvider,
|
||||
Translate,
|
||||
|
@ -23,7 +23,8 @@ import {
|
||||
MenuItem,
|
||||
MenuList,
|
||||
ClickAwayListener,
|
||||
Divider
|
||||
Divider,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
|
||||
@ -36,6 +37,8 @@ import FilterPoolSelect from 'client/components/Header/FilterPoolSelect';
|
||||
const User = React.memo(() => {
|
||||
const history = useHistory();
|
||||
const { logout, authUser, isOneAdmin } = useAuth();
|
||||
const isUpSm = useMediaQuery(theme => theme.breakpoints.up('sm'));
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const anchorRef = useRef(null);
|
||||
const { current } = anchorRef;
|
||||
@ -63,7 +66,7 @@ const User = React.memo(() => {
|
||||
data-cy="header-user-button"
|
||||
>
|
||||
<AccountCircleIcon />
|
||||
<span style={{ paddingLeft: 5 }}>{authUser?.NAME}</span>
|
||||
{isUpSm && <span style={{ paddingLeft: 5 }}>{authUser?.NAME}</span>}
|
||||
</Button>
|
||||
<Popper
|
||||
open={open}
|
||||
|
@ -16,7 +16,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { AppBar, Toolbar, IconButton, Typography } from '@material-ui/core';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Typography,
|
||||
IconButton,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
|
||||
import useGeneral from 'client/hooks/useGeneral';
|
||||
@ -25,20 +31,23 @@ import Zone from 'client/components/Header/Zone';
|
||||
import headerStyles from 'client/components/Header/styles';
|
||||
|
||||
const Header = ({ title }) => {
|
||||
const { fixMenu } = useGeneral();
|
||||
const classes = headerStyles();
|
||||
const { isOpenMenu, openMenu } = useGeneral();
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'));
|
||||
|
||||
return React.useMemo(
|
||||
() => (
|
||||
<AppBar position="fixed" data-cy="header">
|
||||
<AppBar position="absolute" data-cy="header">
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
onClick={() => openMenu(!isOpenMenu)}
|
||||
edge="start"
|
||||
color="inherit"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
{!isUpLg && (
|
||||
<IconButton
|
||||
onClick={() => fixMenu(true)}
|
||||
edge="start"
|
||||
color="inherit"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography
|
||||
variant="h6"
|
||||
className={classes.title}
|
||||
@ -51,7 +60,7 @@ const Header = ({ title }) => {
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
),
|
||||
[isOpenMenu, openMenu]
|
||||
[fixMenu, isUpLg]
|
||||
);
|
||||
};
|
||||
|
||||
|
34
src/fireedge/src/public/components/LoadingScreen/index.js
Normal file
34
src/fireedge/src/public/components/LoadingScreen/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import { styled } from '@material-ui/core';
|
||||
import Logo from 'client/icons/logo';
|
||||
|
||||
const ScreenBox = styled('div')({
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
backgroundColor: '#ffffff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'fixed',
|
||||
zIndex: 10000
|
||||
});
|
||||
|
||||
const LoadingScreen = () => (
|
||||
<ScreenBox
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100vh',
|
||||
backgroundColor: '#ffffff',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'fixed',
|
||||
zIndex: 10000
|
||||
}}
|
||||
>
|
||||
<Logo width={360} height={360} spinner withText />
|
||||
</ScreenBox>
|
||||
);
|
||||
|
||||
export default LoadingScreen;
|
@ -1,22 +1,48 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { List, Collapse, ListItem, ListItemText } from '@material-ui/core';
|
||||
import {
|
||||
List,
|
||||
Collapse,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core';
|
||||
import ExpandLessIcon from '@material-ui/icons/ExpandLess';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
|
||||
import useGeneral from 'client/hooks/useGeneral';
|
||||
import SidebarLink from 'client/components/Sidebar/SidebarLink';
|
||||
import sidebarStyles from 'client/components/Sidebar/styles';
|
||||
|
||||
const SidebarCollapseItem = ({ label, routes }) => {
|
||||
const SidebarCollapseItem = ({ label, routes, icon: Icon }) => {
|
||||
const classes = sidebarStyles();
|
||||
const { isFixMenu } = useGeneral();
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'));
|
||||
|
||||
const handleExpand = () => setExpanded(!expanded);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem button onClick={handleExpand}>
|
||||
{Icon && (
|
||||
<ListItemIcon>
|
||||
<Icon />
|
||||
</ListItemIcon>
|
||||
)}
|
||||
<ListItemText primary={label} />
|
||||
{expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
|
||||
{expanded ? (
|
||||
<ExpandLessIcon
|
||||
className={clsx({ [classes.expandIcon]: isUpLg && !isFixMenu })}
|
||||
/>
|
||||
) : (
|
||||
<ExpandMoreIcon
|
||||
className={clsx({ [classes.expandIcon]: isUpLg && !isFixMenu })}
|
||||
/>
|
||||
)}
|
||||
</ListItem>
|
||||
{routes?.map((subItem, index) => (
|
||||
<Collapse
|
||||
@ -24,9 +50,10 @@ const SidebarCollapseItem = ({ label, routes }) => {
|
||||
in={expanded}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
className={clsx({ [classes.subItemWrapper]: isUpLg && !isFixMenu })}
|
||||
>
|
||||
<List component="div" disablePadding>
|
||||
<SidebarLink {...subItem} />
|
||||
<SidebarLink {...subItem} isSubItem />
|
||||
</List>
|
||||
</Collapse>
|
||||
))}
|
||||
@ -36,6 +63,7 @@ const SidebarCollapseItem = ({ label, routes }) => {
|
||||
|
||||
SidebarCollapseItem.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
icon: PropTypes.node,
|
||||
routes: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
label: PropTypes.string,
|
||||
@ -46,6 +74,7 @@ SidebarCollapseItem.propTypes = {
|
||||
|
||||
SidebarCollapseItem.defaultProps = {
|
||||
label: '',
|
||||
icon: null,
|
||||
routes: []
|
||||
};
|
||||
|
||||
|
@ -1,17 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import {
|
||||
withStyles,
|
||||
Badge,
|
||||
Typography,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core';
|
||||
|
||||
import useGeneral from 'client/hooks/useGeneral';
|
||||
import sidebarStyles from 'client/components/Sidebar/styles';
|
||||
|
||||
const StyledBadge = withStyles(() => ({
|
||||
badge: {
|
||||
@ -21,18 +24,33 @@ const StyledBadge = withStyles(() => ({
|
||||
}
|
||||
}))(Badge);
|
||||
|
||||
const SidebarLink = ({ label, path, devMode }) => {
|
||||
const SidebarLink = ({ label, path, icon: Icon, devMode, isSubItem }) => {
|
||||
const classes = sidebarStyles();
|
||||
const history = useHistory();
|
||||
const isDesktop = useMediaQuery(theme => theme.breakpoints.up('sm'));
|
||||
const { openMenu } = useGeneral();
|
||||
const { pathname } = useLocation();
|
||||
const { fixMenu } = useGeneral();
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'));
|
||||
|
||||
const handleClick = () => {
|
||||
history.push(path);
|
||||
!isDesktop && openMenu(false);
|
||||
!isUpLg && fixMenu(false);
|
||||
};
|
||||
|
||||
const isCurrentPathname = pathname === path;
|
||||
|
||||
return (
|
||||
<ListItem button onClick={handleClick}>
|
||||
<ListItem
|
||||
button
|
||||
onClick={handleClick}
|
||||
selected={isCurrentPathname}
|
||||
className={clsx({ [classes.subItem]: isSubItem })}
|
||||
classes={{ selected: classes.itemSelected }}
|
||||
>
|
||||
{Icon && (
|
||||
<ListItemIcon>
|
||||
<Icon />
|
||||
</ListItemIcon>
|
||||
)}
|
||||
<ListItemText
|
||||
primary={
|
||||
devMode ? (
|
||||
@ -51,13 +69,17 @@ const SidebarLink = ({ label, path, devMode }) => {
|
||||
SidebarLink.propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
devMode: PropTypes.bool
|
||||
icon: PropTypes.node,
|
||||
devMode: PropTypes.bool,
|
||||
isSubItem: PropTypes.bool
|
||||
};
|
||||
|
||||
SidebarLink.defaultProps = {
|
||||
label: '',
|
||||
path: '/',
|
||||
devMode: false
|
||||
icon: null,
|
||||
devMode: false,
|
||||
isSubItem: false
|
||||
};
|
||||
|
||||
export default SidebarLink;
|
||||
|
@ -14,8 +14,16 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { List, Drawer, Divider, Box } from '@material-ui/core';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
List,
|
||||
Drawer,
|
||||
Divider,
|
||||
Box,
|
||||
IconButton,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core';
|
||||
import { Menu as MenuIcon, Close as CloseIcon } from '@material-ui/icons';
|
||||
|
||||
import useGeneral from 'client/hooks/useGeneral';
|
||||
import endpoints from 'client/router/endpoints';
|
||||
@ -23,12 +31,11 @@ import endpoints from 'client/router/endpoints';
|
||||
import sidebarStyles from 'client/components/Sidebar/styles';
|
||||
import SidebarLink from 'client/components/Sidebar/SidebarLink';
|
||||
import SidebarCollapseItem from 'client/components/Sidebar/SidebarCollapseItem';
|
||||
import Logo from 'client/icons/logo';
|
||||
|
||||
const Endpoints = React.memo(() =>
|
||||
endpoints
|
||||
?.filter(
|
||||
({ authenticated = true, header = false }) => authenticated && !header
|
||||
)
|
||||
?.filter(({ authenticated, header = false }) => authenticated && !header)
|
||||
?.map((endpoint, index) =>
|
||||
endpoint.routes ? (
|
||||
<SidebarCollapseItem key={`item-${index}`} {...endpoint} />
|
||||
@ -40,27 +47,46 @@ const Endpoints = React.memo(() =>
|
||||
|
||||
const Sidebar = () => {
|
||||
const classes = sidebarStyles();
|
||||
const { isOpenMenu, openMenu } = useGeneral();
|
||||
const { isFixMenu, fixMenu } = useGeneral();
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'));
|
||||
|
||||
return React.useMemo(
|
||||
() => (
|
||||
<Drawer anchor="left" open={isOpenMenu} onClose={() => openMenu(false)}>
|
||||
<Box item className={classes.logo}>
|
||||
<img
|
||||
className={classes.img}
|
||||
src="/static/logo.png"
|
||||
alt="Opennebula"
|
||||
<Drawer
|
||||
variant={'permanent'}
|
||||
className={clsx({ [classes.drawerFixed]: isFixMenu })}
|
||||
classes={{
|
||||
paper: clsx(classes.drawerPaper, {
|
||||
[classes.drawerFixed]: isFixMenu
|
||||
})
|
||||
}}
|
||||
anchor="left"
|
||||
open={isFixMenu}
|
||||
>
|
||||
<Box item className={classes.header}>
|
||||
<Logo
|
||||
width="100%"
|
||||
height={100}
|
||||
withText
|
||||
viewBox="0 0 640 640"
|
||||
className={classes.svg}
|
||||
/>
|
||||
<IconButton
|
||||
color={isFixMenu ? 'primary' : 'default'}
|
||||
onClick={() => fixMenu(!isFixMenu)}
|
||||
>
|
||||
{isUpLg ? <MenuIcon /> : <CloseIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box className={classes.menu}>
|
||||
<List className={classes.list}>
|
||||
<List className={classes.list} disablePadding>
|
||||
<Endpoints />
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
),
|
||||
[isOpenMenu, openMenu]
|
||||
[isFixMenu, fixMenu, isUpLg]
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,85 @@
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import { sidebarWidth } from 'client/assets/theme';
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
// -------------------------------
|
||||
// CONTAINER MENU
|
||||
// -------------------------------
|
||||
drawerPaper: {
|
||||
width: 0,
|
||||
whiteSpace: 'nowrap',
|
||||
overflowX: 'hidden',
|
||||
flexShrink: 0,
|
||||
transition: theme.transitions.create(['width', 'visibility', 'display'], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen
|
||||
}),
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
width: sidebarWidth.minified,
|
||||
// CONTAINER ONLY WHEN EXPANDED
|
||||
'&:hover': {
|
||||
width: sidebarWidth.fixed,
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen
|
||||
}),
|
||||
'& #logo__text__top, & #logo__text__bottom': {
|
||||
visibility: 'visible'
|
||||
}
|
||||
},
|
||||
// CONTAINER ONLY WHEN MINIFIED
|
||||
'&:not(:hover)': {
|
||||
'& #logo__text__top, & #logo__text__bottom': {
|
||||
visibility: 'hidden'
|
||||
},
|
||||
'& $menu': {
|
||||
overflowY: 'hidden'
|
||||
},
|
||||
'& $expandIcon, & $subItemWrapper': {
|
||||
display: 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
drawerFixed: {
|
||||
width: '100%',
|
||||
transition: theme.transitions.create('width', {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen
|
||||
}),
|
||||
[theme.breakpoints.only('md')]: {
|
||||
width: sidebarWidth.fixed
|
||||
},
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
width: sidebarWidth.fixed,
|
||||
'& #logo__text__top, & #logo__text__bottom': {
|
||||
visibility: 'visible !important'
|
||||
},
|
||||
'& $expandIcon, & $subItemWrapper': {
|
||||
display: 'block !important'
|
||||
}
|
||||
}
|
||||
},
|
||||
// -------------------------------
|
||||
// HEADER MENU
|
||||
// -------------------------------
|
||||
header: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '1rem',
|
||||
overflow: 'hidden',
|
||||
height: 64, // appbar height
|
||||
minHeight: 64 // appbar height
|
||||
},
|
||||
svg: {
|
||||
minWidth: 100
|
||||
},
|
||||
// -------------------------------
|
||||
// LIST MENU
|
||||
// -------------------------------
|
||||
menu: {
|
||||
overflow: 'auto',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
textTransform: 'capitalize',
|
||||
color: 'transparent',
|
||||
transition: 'color 0.3s',
|
||||
@ -30,10 +107,15 @@ export default makeStyles(theme => ({
|
||||
list: {
|
||||
color: theme.palette.common.black
|
||||
},
|
||||
logo: {
|
||||
padding: '1rem 2rem'
|
||||
expandIcon: {},
|
||||
subItemWrapper: {},
|
||||
subItem: {
|
||||
paddingLeft: theme.spacing(4)
|
||||
},
|
||||
img: {
|
||||
width: '100%'
|
||||
itemSelected: {
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.light
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -18,6 +18,7 @@ module.exports = {
|
||||
classInputInvalid: 'is-invalid',
|
||||
NotFound: 'Not found',
|
||||
SignIn: 'Sign In',
|
||||
Next: 'Next',
|
||||
Language: 'Language',
|
||||
Username: 'Username',
|
||||
Password: 'Password',
|
||||
@ -26,10 +27,10 @@ module.exports = {
|
||||
SignOut: 'Sign Out',
|
||||
jwtName: 'SunstoneToken',
|
||||
Submit: 'Submit',
|
||||
Respose: 'Response',
|
||||
Response: 'Response',
|
||||
by: {
|
||||
text: 'Opennebula Systems',
|
||||
url: 'https://opennebula.org/'
|
||||
text: 'Opennebula',
|
||||
url: 'https://opennebula.io/'
|
||||
},
|
||||
endpointsRoutes: {
|
||||
login: '/api/auth/',
|
||||
|
@ -1,16 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
import { func, string } from 'prop-types';
|
||||
import { Box, Button, TextField } from '@material-ui/core';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers';
|
||||
import * as yup from 'yup';
|
||||
|
||||
import { Token2FA } from 'client/constants';
|
||||
import { Token2FA, Next } from 'client/constants';
|
||||
import loginStyles from 'client/containers/Login/styles';
|
||||
|
||||
import { Tr } from 'client/components/HOC';
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton';
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
|
||||
|
||||
const Form2fa = ({ classes, onBack, onSubmit, error }) => {
|
||||
const Form2fa = ({ onBack, onSubmit, error }) => {
|
||||
const classes = loginStyles();
|
||||
|
||||
const { register, handleSubmit, errors } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(
|
||||
@ -25,7 +29,7 @@ const Form2fa = ({ classes, onBack, onSubmit, error }) => {
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
className={classes?.form}
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<TextField
|
||||
@ -50,11 +54,23 @@ const Form2fa = ({ classes, onBack, onSubmit, error }) => {
|
||||
<ButtonSubmit
|
||||
data-cy="login-2fa-button"
|
||||
isSubmitting={false}
|
||||
label="Next"
|
||||
label={Tr(Next)}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Form2fa.propTypes = {
|
||||
onBack: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
error: string
|
||||
};
|
||||
|
||||
Form2fa.defaultProps = {
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
error: null
|
||||
};
|
||||
|
||||
export default Form2fa;
|
||||
|
@ -14,17 +14,21 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React from 'react';
|
||||
import { func, string } from 'prop-types';
|
||||
import { Box, Checkbox, TextField, FormControlLabel } from '@material-ui/core';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers';
|
||||
import * as yup from 'yup';
|
||||
|
||||
import { SignIn, Username, Password, keepLoggedIn } from 'client/constants';
|
||||
import { Translate, Tr } from 'client/components/HOC';
|
||||
import { Tr } from 'client/components/HOC';
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton';
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
|
||||
import loginStyles from 'client/containers/Login/styles';
|
||||
|
||||
function FormUser({ onSubmit, error }) {
|
||||
const classes = loginStyles();
|
||||
|
||||
function FormUser({ classes, onSubmit, error }) {
|
||||
const { register, handleSubmit, errors } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(
|
||||
@ -42,7 +46,7 @@ function FormUser({ classes, onSubmit, error }) {
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
className={classes?.form}
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<TextField
|
||||
@ -50,6 +54,7 @@ function FormUser({ classes, onSubmit, error }) {
|
||||
fullWidth
|
||||
required
|
||||
name="user"
|
||||
autoComplete="username"
|
||||
label={Tr(Username)}
|
||||
variant="outlined"
|
||||
inputRef={register}
|
||||
@ -90,14 +95,20 @@ function FormUser({ classes, onSubmit, error }) {
|
||||
<ButtonSubmit
|
||||
data-cy="login-button"
|
||||
isSubmitting={false}
|
||||
label={<Translate word={SignIn} />}
|
||||
label={Tr(SignIn)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
FormUser.propTypes = {};
|
||||
FormUser.propTypes = {
|
||||
onSubmit: func.isRequired,
|
||||
error: string
|
||||
};
|
||||
|
||||
FormUser.defaultProps = {};
|
||||
FormUser.defaultProps = {
|
||||
onSubmit: () => undefined,
|
||||
error: null
|
||||
};
|
||||
|
||||
export default FormUser;
|
||||
|
@ -84,11 +84,7 @@ function Login() {
|
||||
unmountOnExit
|
||||
>
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<FormUser
|
||||
classes={classes}
|
||||
onSubmit={handleSubmitUser}
|
||||
error={error}
|
||||
/>
|
||||
<FormUser onSubmit={handleSubmitUser} error={error} />
|
||||
</Box>
|
||||
</Slide>
|
||||
</Box>
|
||||
@ -102,7 +98,6 @@ function Login() {
|
||||
>
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<Form2fa
|
||||
classes={classes}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitUser}
|
||||
error={error}
|
||||
@ -121,7 +116,6 @@ function Login() {
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<FormGroup
|
||||
groups={groups}
|
||||
classes={classes}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitGroup}
|
||||
/>
|
||||
|
@ -3,6 +3,7 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||
|
||||
import { jwtName, FILTER_POOL, ONEADMIN_ID } from 'client/constants';
|
||||
import { storage, findStorageData, removeStoreData } from 'client/utils';
|
||||
import { fakeDelay } from 'client/utils/helpers';
|
||||
|
||||
import * as serviceAuth from 'client/services/auth';
|
||||
import * as serviceUsers from 'client/services/users';
|
||||
@ -16,10 +17,6 @@ import {
|
||||
} from 'client/actions/user';
|
||||
import { setGroups } from 'client/actions/pool';
|
||||
|
||||
// function delay(ms) {
|
||||
// return new Promise(resolve => setTimeout(resolve, ms));
|
||||
// }
|
||||
|
||||
export default function useAuth() {
|
||||
const {
|
||||
jwt,
|
||||
@ -36,8 +33,9 @@ export default function useAuth() {
|
||||
useEffect(() => {
|
||||
const tokenStorage = findStorageData(jwtName);
|
||||
|
||||
if ((tokenStorage && jwt && tokenStorage !== jwt) || firstRender)
|
||||
dispatch(successAuth({ jwt: tokenStorage }));
|
||||
if ((tokenStorage && jwt && tokenStorage !== jwt) || firstRender) {
|
||||
fakeDelay(1500).then(() => dispatch(successAuth({ jwt: tokenStorage })));
|
||||
}
|
||||
}, [jwt, firstRender]);
|
||||
|
||||
const login = useCallback(
|
||||
@ -89,7 +87,7 @@ export default function useAuth() {
|
||||
})
|
||||
)
|
||||
.catch(err => dispatch(failureAuth({ error: err })));
|
||||
}, [dispatch, baseURL, jwtName]);
|
||||
}, [dispatch, baseURL, jwtName, authUser]);
|
||||
|
||||
const setPrimaryGroup = useCallback(
|
||||
values => {
|
||||
|
@ -4,12 +4,16 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||
import * as actions from 'client/actions/general';
|
||||
|
||||
export default function useGeneral() {
|
||||
const { isLoading, isOpenMenu } = useSelector(
|
||||
const { isLoading, isOpenMenu, isFixMenu } = useSelector(
|
||||
state => state?.General,
|
||||
shallowEqual
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const fixMenu = useCallback(isFixed => dispatch(actions.fixMenu(isFixed)), [
|
||||
dispatch
|
||||
]);
|
||||
|
||||
const openMenu = useCallback(isOpen => dispatch(actions.openMenu(isOpen)), [
|
||||
dispatch
|
||||
]);
|
||||
@ -26,8 +30,10 @@ export default function useGeneral() {
|
||||
return {
|
||||
isLoading,
|
||||
isOpenMenu,
|
||||
isFixMenu,
|
||||
changeZone,
|
||||
openMenu,
|
||||
fixMenu,
|
||||
changeLoading
|
||||
};
|
||||
}
|
||||
|
98
src/fireedge/src/public/icons/logo.js
Normal file
98
src/fireedge/src/public/icons/logo.js
Normal file
@ -0,0 +1,98 @@
|
||||
import React from 'react';
|
||||
import { number, string, bool } from 'prop-types';
|
||||
|
||||
const Logo = ({ width, height, spinner, withText, viewBox, ...props }) => {
|
||||
const cloudColor = {
|
||||
child1: { from: '#0098c3', to: '#ffffff' },
|
||||
child2: { from: '#0098c3', to: '#ffffff' },
|
||||
child3: { from: '#40b3d9', to: '#ffffff' },
|
||||
child4: { from: '#80cde6', to: '#ffffff' },
|
||||
child5: { from: '#bfe6f2', to: '#ffffff' }
|
||||
};
|
||||
const textColor = {
|
||||
top: '#000000',
|
||||
bottom: '#0098c3'
|
||||
};
|
||||
return (
|
||||
<svg viewBox={viewBox} width={width} height={height} {...props}>
|
||||
<defs>
|
||||
{spinner &&
|
||||
Object.entries(cloudColor)?.map(([key, color]) => (
|
||||
<linearGradient id={`gradient__${key}`} x1="0%" x2="200%">
|
||||
<stop offset="0%" stopColor={color.from} />
|
||||
<stop offset="200%" stopColor={color.to} />
|
||||
<animate
|
||||
attributeName="x2"
|
||||
dur="2000ms"
|
||||
repeatCount="indefinite"
|
||||
values="10%; 100%; 200%"
|
||||
/>
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
<path
|
||||
fill={spinner ? 'url(#gradient__child1)' : cloudColor.child1.from}
|
||||
d="M207.7 233.57L196.2 245.35L184.29 256.66L171.97 267.48L159.27 277.8L146.19 287.6L132.74 296.88L118.94 305.62L104.81 313.79L90.36 321.4L75.59 328.41L60.54 334.83L45.19 340.63L29.58 345.81L13.72 350.34L12.05 350.74L12.23 350.33L13.27 348.21L14.39 346.15L15.58 344.13L16.85 342.16L18.19 340.25L19.61 338.39L21.09 336.58L22.63 334.84L24.24 333.16L25.92 331.54L27.65 329.99L29.44 328.5L31.29 327.08L33.2 325.73L35.16 324.46L37.16 323.26L39.22 322.13L41.33 321.09L42.05 320.76L41.6 319.67L40.7 317.18L39.89 314.65L39.18 312.08L38.58 309.47L38.08 306.82L37.69 304.14L37.41 301.42L37.23 298.68L37.18 295.9L37.23 293.13L37.41 290.38L37.69 287.67L38.08 284.99L38.58 282.34L39.18 279.73L39.89 277.16L40.7 274.63L41.6 272.14L42.6 269.7L43.7 267.31L44.88 264.96L46.16 262.67L47.52 260.44L48.97 258.26L50.5 256.14L52.11 254.08L53.8 252.08L55.56 250.15L57.4 248.29L59.31 246.5L61.29 244.77L63.33 243.13L65.44 241.55L67.62 240.06L69.85 238.65L72.14 237.32L74.49 236.08L76.89 234.92L79.34 233.85L81.84 232.87L84.39 231.99L86.98 231.2L89.62 230.51L92.29 229.92L95.01 229.44L97.75 229.05L100.54 228.78L103.35 228.61L106.19 228.55L109.04 228.61L111.85 228.78L114.63 229.05L117.38 229.44L120.1 229.92L122.77 230.51L125.41 231.2L128 231.99L130.55 232.87L133.05 233.85L135.5 234.92L137.9 236.08L140.25 237.32L141.66 238.14L143.92 234L146.4 229.83L149.04 225.77L151.84 221.82L154.78 217.99L157.86 214.27L161.08 210.67L164.43 207.2L167.92 203.86L171.53 200.65L175.26 197.58L179.11 194.66L183.08 191.88L187.15 189.24L191.33 186.77L195.62 184.45L200 182.29L204.47 180.3L208.36 178.75L208.36 232.84L207.7 233.57Z"
|
||||
/>
|
||||
<path
|
||||
fill={spinner ? 'url(#gradient__child2)' : cloudColor.child2.from}
|
||||
d="M198.85 304.87L182.93 314.47L166.61 323.51L149.89 331.98L132.78 339.85L115.31 347.11L97.49 353.75L79.33 359.76L60.86 365.12L42.08 369.8L23.02 373.81L4.8 376.94L4.78 376.15L4.83 373.65L4.98 371.17L5.23 368.71L5.57 366.29L6.01 363.9L6.16 363.22L20.47 359.78L36.34 355.25L51.95 350.08L67.29 344.28L82.35 337.86L97.12 330.84L111.57 323.24L125.7 315.06L139.5 306.33L152.94 297.05L166.03 287.25L178.73 276.92L191.05 266.1L202.96 254.79L206.62 251.05L206.62 299.77L198.85 304.87Z"
|
||||
/>
|
||||
<path
|
||||
fill={spinner ? 'url(#gradient__child3)' : cloudColor.child3.from}
|
||||
d="M184.12 353.2L156.39 362.67L128.1 371.33L99.29 379.16L69.96 386.13L40.16 392.25L9.9 397.47L8.67 397.64L8.66 397.61L7.87 395.36L7.16 393.08L6.54 390.76L6.01 388.4L5.57 386.01L5.49 385.45L10.39 384.67L30.75 380.67L50.81 376L70.54 370.66L89.94 364.68L108.98 358.05L127.64 350.81L145.91 342.97L163.78 334.53L181.22 325.52L198.22 315.95L206.62 310.81L206.62 344.7L184.12 353.2Z"
|
||||
/>
|
||||
<path
|
||||
fill={spinner ? 'url(#gradient__child4)' : cloudColor.child4.from}
|
||||
d="M187.35 393.66L157.55 399.77L127.29 405L96.6 409.32L65.51 412.71L34.04 415.15L19.8 415.82L19.71 415.72L18.23 413.91L16.82 412.05L15.47 410.14L14.21 408.17L13.01 406.15L12.48 405.17L22.87 404.25L43.8 401.66L64.45 398.36L84.82 394.36L104.87 389.69L124.61 384.35L144.01 378.36L163.04 371.74L181.71 364.5L199.98 356.66L206.98 353.35L206.98 388.99L187.35 393.66Z"
|
||||
/>
|
||||
<path
|
||||
fill={spinner ? 'url(#gradient__child5)' : cloudColor.child5.from}
|
||||
d="M65.28 436.96L65.28 436.96L62.79 436.91L60.32 436.76L57.88 436.51L55.47 436.16L53.09 435.72L50.75 435.19L48.44 434.57L46.17 433.86L43.93 433.06L41.74 432.18L39.59 431.21L37.49 430.17L35.43 429.04L33.42 427.84L31.46 426.57L29.56 425.22L27.71 423.8L25.92 422.31L25.7 422.12L43.1 421.31L74.57 418.87L105.66 415.48L136.35 411.16L166.61 405.93L196.41 399.82L206.62 397.39L206.62 436.96L65.28 436.96Z"
|
||||
/>
|
||||
{withText && (
|
||||
<>
|
||||
<text
|
||||
id="logo__text__top"
|
||||
x={230}
|
||||
y={280}
|
||||
fill={textColor.top}
|
||||
fontSize={125}
|
||||
fontFamily="Ubuntu,Lato"
|
||||
fontWeight={500}
|
||||
>
|
||||
{'Open'}
|
||||
</text>
|
||||
<text
|
||||
id="logo__text__bottom"
|
||||
x={230}
|
||||
y={430}
|
||||
fill={textColor.bottom}
|
||||
fontSize={125}
|
||||
fontFamily="Ubuntu,Lato"
|
||||
fontWeight={500}
|
||||
>
|
||||
{'Nebula'}
|
||||
</text>
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
Logo.propTypes = {
|
||||
width: number.isRequired,
|
||||
height: number.isRequired,
|
||||
viewBox: string,
|
||||
spinner: bool,
|
||||
withText: bool
|
||||
};
|
||||
|
||||
Logo.defaultProps = {
|
||||
width: 360,
|
||||
height: 360,
|
||||
viewBox: '0 0 640 640',
|
||||
spinner: false,
|
||||
withText: false
|
||||
};
|
||||
export default Logo;
|
@ -20,7 +20,8 @@ const { Actions: GeneralActions } = require('../actions/general');
|
||||
const initial = {
|
||||
zone: 0,
|
||||
isLoading: false,
|
||||
isOpenMenu: false
|
||||
isOpenMenu: false,
|
||||
isFixMenu: false
|
||||
};
|
||||
|
||||
const General = (state = initial, action) => {
|
||||
@ -35,6 +36,8 @@ const General = (state = initial, action) => {
|
||||
return { ...state, ...action.payload };
|
||||
case GeneralActions.TOGGLE_MENU:
|
||||
return { ...state, isOpenMenu: action.isOpen };
|
||||
case GeneralActions.FIX_MENU:
|
||||
return { ...state, isFixMenu: action.isFixed };
|
||||
case UserActions.LOGOUT:
|
||||
return { ...initial };
|
||||
default:
|
||||
|
@ -13,6 +13,12 @@
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import {
|
||||
Dashboard as DashboardIcon,
|
||||
Settings as SettingsIcon,
|
||||
Ballot as BallotIcon
|
||||
} from '@material-ui/icons';
|
||||
|
||||
import Login from 'client/containers/Login';
|
||||
import { Clusters, Hosts, Zones } from 'client/containers/Infrastructure';
|
||||
import { Users, Groups } from 'client/containers/System';
|
||||
@ -42,7 +48,7 @@ export const PATH = {
|
||||
}
|
||||
};
|
||||
|
||||
export default [
|
||||
const ENDPOINTS = [
|
||||
{
|
||||
label: 'login',
|
||||
path: PATH.LOGIN,
|
||||
@ -52,74 +58,102 @@ export default [
|
||||
{
|
||||
label: 'dashboard',
|
||||
path: PATH.DASHBOARD,
|
||||
authenticated: true,
|
||||
icon: DashboardIcon,
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
label: 'settings',
|
||||
path: PATH.SETTINGS,
|
||||
authenticated: true,
|
||||
header: true,
|
||||
icon: SettingsIcon,
|
||||
component: Settings
|
||||
},
|
||||
{
|
||||
label: 'test api',
|
||||
path: PATH.TEST_API,
|
||||
authenticated: true,
|
||||
devMode: true,
|
||||
icon: BallotIcon,
|
||||
component: TestApi
|
||||
},
|
||||
{
|
||||
label: 'infrastructure',
|
||||
authenticated: true,
|
||||
icon: BallotIcon,
|
||||
routes: [
|
||||
{
|
||||
label: 'clusters',
|
||||
path: PATH.INFRASTRUCTURE.CLUSTERS,
|
||||
authenticated: true,
|
||||
component: Clusters
|
||||
},
|
||||
{
|
||||
label: 'hosts',
|
||||
path: PATH.INFRASTRUCTURE.HOSTS,
|
||||
authenticated: true,
|
||||
component: Hosts
|
||||
},
|
||||
{
|
||||
label: 'zones',
|
||||
path: PATH.INFRASTRUCTURE.ZONES,
|
||||
authenticated: true,
|
||||
component: Zones
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'system',
|
||||
authenticated: true,
|
||||
icon: BallotIcon,
|
||||
routes: [
|
||||
{
|
||||
label: 'users',
|
||||
path: PATH.SYSTEM.USERS,
|
||||
authenticated: true,
|
||||
component: Users
|
||||
},
|
||||
{
|
||||
label: 'groups',
|
||||
path: PATH.SYSTEM.GROUPS,
|
||||
authenticated: true,
|
||||
component: Groups
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'networks',
|
||||
authenticated: true,
|
||||
icon: BallotIcon,
|
||||
routes: [
|
||||
{
|
||||
label: 'vnets',
|
||||
path: PATH.NETWORKS.VNETS
|
||||
path: PATH.NETWORKS.VNETS,
|
||||
authenticated: true
|
||||
},
|
||||
{
|
||||
label: 'vnets templates',
|
||||
path: PATH.NETWORKS.VNETS_TEMPLATES
|
||||
path: PATH.NETWORKS.VNETS_TEMPLATES,
|
||||
authenticated: true
|
||||
},
|
||||
{
|
||||
label: 'vnets topology',
|
||||
path: PATH.NETWORKS.VNETS_TOPOLOGY
|
||||
path: PATH.NETWORKS.VNETS_TOPOLOGY,
|
||||
authenticated: true
|
||||
},
|
||||
{
|
||||
label: 'vnets secgroup',
|
||||
path: PATH.NETWORKS.SEC_GROUPS
|
||||
path: PATH.NETWORKS.SEC_GROUPS,
|
||||
authenticated: true
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export const findRouteByPathname = pathname =>
|
||||
ENDPOINTS.flatMap(({ routes, ...item }) => routes ?? item)?.find(
|
||||
({ path }) => path === pathname
|
||||
) ?? {};
|
||||
|
||||
export default ENDPOINTS;
|
||||
|
@ -16,10 +16,8 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { AuthLayout, GuessLayout, InternalLayout } from 'client/components/HOC';
|
||||
import { InternalLayout, MainLayout } from 'client/components/HOC';
|
||||
import Error404 from 'client/containers/Error404';
|
||||
import Sidebar from 'client/components/Sidebar';
|
||||
|
||||
import endpoints from 'client/router/endpoints';
|
||||
|
||||
const renderRoute = ({
|
||||
@ -32,33 +30,23 @@ const renderRoute = ({
|
||||
key={`key-${label.replace(' ', '-')}`}
|
||||
exact
|
||||
path={path}
|
||||
component={() =>
|
||||
authenticated ? (
|
||||
<AuthLayout>
|
||||
<InternalLayout title={label}>
|
||||
<Component />
|
||||
</InternalLayout>
|
||||
</AuthLayout>
|
||||
) : (
|
||||
<GuessLayout>
|
||||
<Component />
|
||||
</GuessLayout>
|
||||
)
|
||||
}
|
||||
component={() => (
|
||||
<InternalLayout label={label} authRoute={authenticated}>
|
||||
<Component />
|
||||
</InternalLayout>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
const Router = () => (
|
||||
<>
|
||||
<Sidebar />
|
||||
<MainLayout>
|
||||
<Switch>
|
||||
{endpoints?.map(({ routes, ...endpoint }) =>
|
||||
endpoint.path ? renderRoute(endpoint) : routes?.map(renderRoute)
|
||||
)}
|
||||
<Route component={Error404} />
|
||||
</Switch>
|
||||
</>
|
||||
</MainLayout>
|
||||
);
|
||||
|
||||
export default Router;
|
||||
export { endpoints };
|
||||
|
1
src/fireedge/src/public/utils/helpers.js
Normal file
1
src/fireedge/src/public/utils/helpers.js
Normal file
@ -0,0 +1 @@
|
||||
export const fakeDelay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
Loading…
x
Reference in New Issue
Block a user