diff --git a/src/fireedge/package.json b/src/fireedge/package.json
index c285080f8c..d80e8c51f3 100644
--- a/src/fireedge/package.json
+++ b/src/fireedge/package.json
@@ -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",
diff --git a/src/fireedge/src/public/actions/general.js b/src/fireedge/src/public/actions/general.js
index abd007ecdf..8b1f6dfafc 100644
--- a/src/fireedge/src/public/actions/general.js
+++ b/src/fireedge/src/public/actions/general.js
@@ -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
})
};
diff --git a/src/fireedge/src/public/app.js b/src/fireedge/src/public/app.js
index 31dd644b60..34468fe3ab 100644
--- a/src/fireedge/src/public/app.js
+++ b/src/fireedge/src/public/app.js
@@ -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 (
-
-
- {location && context ? (
- // server build
-
-
-
- ) : (
- // browser build
-
-
-
- )}
-
-
+
+
+
+ {location && context ? (
+ // server build
+
+
+
+ ) : (
+ // browser build
+
+
+
+ )}
+
+
+
);
};
diff --git a/src/fireedge/src/public/assets/theme.js b/src/fireedge/src/public/assets/theme.js
index 5eedfe133f..3ba75349d2 100644
--- a/src/fireedge/src/public/assets/theme.js
+++ b/src/fireedge/src/public/assets/theme.js
@@ -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)'
+ }
+ }
+ })
+);
diff --git a/src/fireedge/src/public/components/Footer/index.js b/src/fireedge/src/public/components/Footer/index.js
index cc11e4d636..80ad160676 100644
--- a/src/fireedge/src/public/components/Footer/index.js
+++ b/src/fireedge/src/public/components/Footer/index.js
@@ -26,7 +26,11 @@ const Footer = React.memo(() => {
return (
- {`❤️ by `}
+ {'Made with'}
+
+ {'❤️'}
+
+ {'by'}
{text}
diff --git a/src/fireedge/src/public/components/Footer/styles.js b/src/fireedge/src/public/components/Footer/styles.js
index a616d4e49f..a647b591c5 100644
--- a/src/fireedge/src/public/components/Footer/styles.js
+++ b/src/fireedge/src/public/components/Footer/styles.js
@@ -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
}
diff --git a/src/fireedge/src/public/components/FormControl/SubmitButton.js b/src/fireedge/src/public/components/FormControl/SubmitButton.js
index 7c7635df3b..86c6d1a85b 100644
--- a/src/fireedge/src/public/components/FormControl/SubmitButton.js
+++ b/src/fireedge/src/public/components/FormControl/SubmitButton.js
@@ -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 && }
- {!isSubmitting && }
+ {!isSubmitting && Tr(label)}
);
};
diff --git a/src/fireedge/src/public/components/HOC/GuessLayout.js b/src/fireedge/src/public/components/HOC/GuessLayout.js
deleted file mode 100644
index 3873a61195..0000000000
--- a/src/fireedge/src/public/components/HOC/GuessLayout.js
+++ /dev/null
@@ -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 ;
- } else if (isLogged && !isLoginInProcess) {
- return ;
- }
-
- return {children};
-};
-
-GuessLayout.propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.arrayOf(PropTypes.node),
- PropTypes.node,
- PropTypes.string
- ])
-};
-
-GuessLayout.defaultProps = {
- children: ''
-};
-
-export default GuessLayout;
diff --git a/src/fireedge/src/public/components/HOC/InternalLayout.js b/src/fireedge/src/public/components/HOC/InternalLayout/index.js
similarity index 57%
rename from src/fireedge/src/public/components/HOC/InternalLayout.js
rename to src/fireedge/src/public/components/HOC/InternalLayout/index.js
index e6042f167e..798a9a3615 100644
--- a/src/fireedge/src/public/components/HOC/InternalLayout.js
+++ b/src/fireedge/src/public/components/HOC/InternalLayout/index.js
@@ -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 ? (
-
-
-
-
-
-
- ) : (
- <>
-
+ return authRoute ? (
+
+
{children}
- >
+
+ ) : (
+ 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;
diff --git a/src/fireedge/src/public/components/HOC/InternalLayout/styles.js b/src/fireedge/src/public/components/HOC/InternalLayout/styles.js
new file mode 100644
index 0000000000..8a38bdd871
--- /dev/null
+++ b/src/fireedge/src/public/components/HOC/InternalLayout/styles.js
@@ -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
+ }
+ }
+}));
diff --git a/src/fireedge/src/public/components/HOC/AuthLayout.js b/src/fireedge/src/public/components/HOC/MainLayout.js
similarity index 52%
rename from src/fireedge/src/public/components/HOC/AuthLayout.js
rename to src/fireedge/src/public/components/HOC/MainLayout.js
index 244d5c0dcb..4bd9d16d7b 100644
--- a/src/fireedge/src/public/components/HOC/AuthLayout.js
+++ b/src/fireedge/src/public/components/HOC/MainLayout.js
@@ -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 ;
- } else if (!isLogged && !isLoginInProcess) {
+ const { authenticated: authRoute } = findRouteByPathname(pathname);
+
+ // PENDING TO AUTHENTICATING OR FIRST RENDERING
+ if (firstRender || (isLogged && authRoute && !authUser && !groups?.length)) {
+ return ;
+ }
+
+ // PROTECTED ROUTE
+ if (authRoute && !isLogged && !isLoginInProcess) {
+ console.log('protected route needs redirect to LOGIN');
return ;
}
- return {children};
+ // PUBLIC ROUTE
+ if (!authRoute && isLogged && !isLoginInProcess) {
+ console.log('public route needs redirect to DASHBOARD');
+ return ;
+ }
+
+ return (
+ <>
+ {authRoute && isLogged && }
+ {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;
diff --git a/src/fireedge/src/public/components/HOC/index.js b/src/fireedge/src/public/components/HOC/index.js
index 8201fef928..856c939dd1 100644
--- a/src/fireedge/src/public/components/HOC/index.js
+++ b/src/fireedge/src/public/components/HOC/index.js
@@ -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,
diff --git a/src/fireedge/src/public/components/Header/User.js b/src/fireedge/src/public/components/Header/User.js
index 3ef9399609..779f48e8f0 100644
--- a/src/fireedge/src/public/components/Header/User.js
+++ b/src/fireedge/src/public/components/Header/User.js
@@ -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"
>
- {authUser?.NAME}
+ {isUpSm && {authUser?.NAME}}
{
+ const { fixMenu } = useGeneral();
const classes = headerStyles();
- const { isOpenMenu, openMenu } = useGeneral();
+ const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'));
return React.useMemo(
() => (
-
+
- openMenu(!isOpenMenu)}
- edge="start"
- color="inherit"
- >
-
-
+ {!isUpLg && (
+ fixMenu(true)}
+ edge="start"
+ color="inherit"
+ >
+
+
+ )}
{
),
- [isOpenMenu, openMenu]
+ [fixMenu, isUpLg]
);
};
diff --git a/src/fireedge/src/public/components/LoadingScreen/index.js b/src/fireedge/src/public/components/LoadingScreen/index.js
new file mode 100644
index 0000000000..473ecb709b
--- /dev/null
+++ b/src/fireedge/src/public/components/LoadingScreen/index.js
@@ -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 = () => (
+
+
+
+);
+
+export default LoadingScreen;
diff --git a/src/fireedge/src/public/components/Sidebar/SidebarCollapseItem.js b/src/fireedge/src/public/components/Sidebar/SidebarCollapseItem.js
index bd4f1f1335..b90fe23d8e 100644
--- a/src/fireedge/src/public/components/Sidebar/SidebarCollapseItem.js
+++ b/src/fireedge/src/public/components/Sidebar/SidebarCollapseItem.js
@@ -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 (
<>
+ {Icon && (
+
+
+
+ )}
- {expanded ? : }
+ {expanded ? (
+
+ ) : (
+
+ )}
{routes?.map((subItem, index) => (
{
in={expanded}
timeout="auto"
unmountOnExit
+ className={clsx({ [classes.subItemWrapper]: isUpLg && !isFixMenu })}
>
-
+
))}
@@ -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: []
};
diff --git a/src/fireedge/src/public/components/Sidebar/SidebarLink.js b/src/fireedge/src/public/components/Sidebar/SidebarLink.js
index 7ceacb61ec..35259c691b 100644
--- a/src/fireedge/src/public/components/Sidebar/SidebarLink.js
+++ b/src/fireedge/src/public/components/Sidebar/SidebarLink.js
@@ -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 (
-
+
+ {Icon && (
+
+
+
+ )}
{
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;
diff --git a/src/fireedge/src/public/components/Sidebar/index.js b/src/fireedge/src/public/components/Sidebar/index.js
index f2eeefde20..bf1f4b0198 100644
--- a/src/fireedge/src/public/components/Sidebar/index.js
+++ b/src/fireedge/src/public/components/Sidebar/index.js
@@ -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 ? (
@@ -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(
() => (
- openMenu(false)}>
-
-
+
+
+ fixMenu(!isFixMenu)}
+ >
+ {isUpLg ? : }
+
-
+
),
- [isOpenMenu, openMenu]
+ [isFixMenu, fixMenu, isUpLg]
);
};
diff --git a/src/fireedge/src/public/components/Sidebar/styles.js b/src/fireedge/src/public/components/Sidebar/styles.js
index 15878e0731..3d5c793336 100644
--- a/src/fireedge/src/public/components/Sidebar/styles.js
+++ b/src/fireedge/src/public/components/Sidebar/styles.js
@@ -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
+ }
}
}));
diff --git a/src/fireedge/src/public/constants/index.js b/src/fireedge/src/public/constants/index.js
index 7d999bd0fe..dc36b321ab 100644
--- a/src/fireedge/src/public/constants/index.js
+++ b/src/fireedge/src/public/constants/index.js
@@ -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/',
diff --git a/src/fireedge/src/public/containers/Login/Form2fa.js b/src/fireedge/src/public/containers/Login/Form2fa.js
index 7d2579ae65..7e33444efd 100644
--- a/src/fireedge/src/public/containers/Login/Form2fa.js
+++ b/src/fireedge/src/public/containers/Login/Form2fa.js
@@ -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 (
{
);
};
+Form2fa.propTypes = {
+ onBack: func.isRequired,
+ onSubmit: func.isRequired,
+ error: string
+};
+
+Form2fa.defaultProps = {
+ onBack: () => undefined,
+ onSubmit: () => undefined,
+ error: null
+};
+
export default Form2fa;
diff --git a/src/fireedge/src/public/containers/Login/FormUser.js b/src/fireedge/src/public/containers/Login/FormUser.js
index b8d05e93f9..cc1303c4b0 100644
--- a/src/fireedge/src/public/containers/Login/FormUser.js
+++ b/src/fireedge/src/public/containers/Login/FormUser.js
@@ -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 (
}
+ label={Tr(SignIn)}
/>
);
}
-FormUser.propTypes = {};
+FormUser.propTypes = {
+ onSubmit: func.isRequired,
+ error: string
+};
-FormUser.defaultProps = {};
+FormUser.defaultProps = {
+ onSubmit: () => undefined,
+ error: null
+};
export default FormUser;
diff --git a/src/fireedge/src/public/containers/Login/index.js b/src/fireedge/src/public/containers/Login/index.js
index 68fabf69c9..f8e4a3653f 100644
--- a/src/fireedge/src/public/containers/Login/index.js
+++ b/src/fireedge/src/public/containers/Login/index.js
@@ -84,11 +84,7 @@ function Login() {
unmountOnExit
>
-
+
@@ -102,7 +98,6 @@ function Login() {
>
diff --git a/src/fireedge/src/public/hooks/useAuth.js b/src/fireedge/src/public/hooks/useAuth.js
index dbb949cd1b..5543fe642a 100644
--- a/src/fireedge/src/public/hooks/useAuth.js
+++ b/src/fireedge/src/public/hooks/useAuth.js
@@ -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 => {
diff --git a/src/fireedge/src/public/hooks/useGeneral.js b/src/fireedge/src/public/hooks/useGeneral.js
index ada80817ea..c8dbb342f0 100644
--- a/src/fireedge/src/public/hooks/useGeneral.js
+++ b/src/fireedge/src/public/hooks/useGeneral.js
@@ -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
};
}
diff --git a/src/fireedge/src/public/icons/logo.js b/src/fireedge/src/public/icons/logo.js
new file mode 100644
index 0000000000..9ebe9b4201
--- /dev/null
+++ b/src/fireedge/src/public/icons/logo.js
@@ -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 (
+
+ );
+};
+
+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;
diff --git a/src/fireedge/src/public/reducers/general.js b/src/fireedge/src/public/reducers/general.js
index 966b2a18f3..d76f414521 100644
--- a/src/fireedge/src/public/reducers/general.js
+++ b/src/fireedge/src/public/reducers/general.js
@@ -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:
diff --git a/src/fireedge/src/public/router/endpoints.js b/src/fireedge/src/public/router/endpoints.js
index 7562e86f69..e7f09042f0 100644
--- a/src/fireedge/src/public/router/endpoints.js
+++ b/src/fireedge/src/public/router/endpoints.js
@@ -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;
diff --git a/src/fireedge/src/public/router/index.js b/src/fireedge/src/public/router/index.js
index ca919c1ebe..f74a27d617 100644
--- a/src/fireedge/src/public/router/index.js
+++ b/src/fireedge/src/public/router/index.js
@@ -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 ? (
-
-
-
-
-
- ) : (
-
-
-
- )
- }
+ component={() => (
+
+
+
+ )}
/>
);
const Router = () => (
- <>
-
+
{endpoints?.map(({ routes, ...endpoint }) =>
endpoint.path ? renderRoute(endpoint) : routes?.map(renderRoute)
)}
- >
+
);
export default Router;
-export { endpoints };
diff --git a/src/fireedge/src/public/utils/helpers.js b/src/fireedge/src/public/utils/helpers.js
new file mode 100644
index 0000000000..94cab89185
--- /dev/null
+++ b/src/fireedge/src/public/utils/helpers.js
@@ -0,0 +1 @@
+export const fakeDelay = ms => new Promise(resolve => setTimeout(resolve, ms));