diff --git a/src/fireedge/src/client/apps/provision/_app.js b/src/fireedge/src/client/apps/provision/_app.js index bd5f20e550..e41f8129cb 100644 --- a/src/fireedge/src/client/apps/provision/_app.js +++ b/src/fireedge/src/client/apps/provision/_app.js @@ -20,14 +20,14 @@ import { ENDPOINTS, PATH } from 'client/apps/provision/routes' import { ENDPOINTS as DEV_ENDPOINTS } from 'client/router/dev' import { useGeneral, useGeneralApi } from 'client/features/General' -import { useAuth, useAuthApi } from 'client/features/Auth' +import { useAuth } from 'client/features/Auth' import provisionApi from 'client/features/OneApi/provision' import providerApi from 'client/features/OneApi/provider' import { useSocket } from 'client/hooks' import Sidebar from 'client/components/Sidebar' import Notifier from 'client/components/Notifier' -import LoadingScreen from 'client/components/LoadingScreen' +import { AuthLayout } from 'client/components/HOC' import { isDevelopment } from 'client/utils' import { _APPS } from 'client/constants' @@ -42,27 +42,14 @@ const MESSAGE_PROVISION_SUCCESS_CREATED = 'Provision successfully created' */ const ProvisionApp = () => { const { getProvisionSocket } = useSocket() - const { isLogged, jwt, firstRender } = useAuth() - const { getAuthUser, logout } = useAuthApi() + const { isLogged, jwt } = useAuth() - const { appTitle, zone } = useGeneral() - const { changeAppTitle, enqueueSuccess } = useGeneralApi() - - const queryProps = [undefined, { skip: !jwt }] - provisionApi.endpoints.getProvisionTemplates.useQuery(...queryProps) - providerApi.endpoints.getProviderConfig.useQuery(...queryProps) + const { zone } = useGeneral() + const { enqueueSuccess, changeAppTitle } = useGeneralApi() useEffect(() => { - ;(async () => { - appTitle !== APP_NAME && changeAppTitle(APP_NAME) - - try { - jwt && getAuthUser() - } catch { - logout() - } - })() - }, [jwt]) + changeAppTitle(APP_NAME) + }, []) useEffect(() => { if (!jwt || !zone) return @@ -86,12 +73,13 @@ const ProvisionApp = () => { [] ) - if (jwt && firstRender) { - return - } - return ( - <> + {isLogged && ( <> @@ -99,7 +87,7 @@ const ProvisionApp = () => { )} - + ) } diff --git a/src/fireedge/src/client/apps/sunstone/_app.js b/src/fireedge/src/client/apps/sunstone/_app.js index 6bea61cc29..a20376f067 100644 --- a/src/fireedge/src/client/apps/sunstone/_app.js +++ b/src/fireedge/src/client/apps/sunstone/_app.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { useEffect, useMemo, JSXElementConstructor } from 'react' +import { useEffect, useMemo, ReactElement } from 'react' import Router from 'client/router' import { @@ -24,13 +24,12 @@ import { import { ENDPOINTS as ONE_ENDPOINTS } from 'client/apps/sunstone/routesOne' import { ENDPOINTS as DEV_ENDPOINTS } from 'client/router/dev' -import { useGeneral, useGeneralApi } from 'client/features/General' -import { useAuth, useAuthApi } from 'client/features/Auth' +import { useAuth, useViews } from 'client/features/Auth' +import { useGeneralApi } from 'client/features/General' import systemApi from 'client/features/OneApi/system' - import Sidebar from 'client/components/Sidebar' import Notifier from 'client/components/Notifier' -import LoadingScreen from 'client/components/LoadingScreen' +import { AuthLayout } from 'client/components/HOC' import { isDevelopment } from 'client/utils' import { _APPS } from 'client/constants' @@ -39,47 +38,38 @@ export const APP_NAME = _APPS.sunstone.name /** * Sunstone App component. * - * @returns {JSXElementConstructor} App rendered. + * @returns {ReactElement} App rendered. */ const SunstoneApp = () => { - const { isLogged, jwt, firstRender, view } = useAuth() - const { getAuthUser, logout } = useAuthApi() - - const { appTitle } = useGeneral() const { changeAppTitle } = useGeneralApi() - - const queryProps = [undefined, { skip: !jwt }] - systemApi.endpoints.getOneConfig.useQuery(...queryProps) - systemApi.endpoints.getSunstoneConfig.useQuery(...queryProps) - const views = systemApi.endpoints.getSunstoneViews.useQuery(...queryProps) + const { isLogged } = useAuth() + const { views, view } = useViews() useEffect(() => { - ;(async () => { - appTitle !== APP_NAME && changeAppTitle(APP_NAME) + changeAppTitle(APP_NAME) + }, []) - try { - jwt && getAuthUser() - } catch { - logout() - } - })() - }, [jwt]) - - const endpoints = useMemo( - () => [ + const endpoints = useMemo(() => { + const fixedEndpoints = [ ...ENDPOINTS, - ...(view ? getEndpointsByView(views?.data?.[view], ONE_ENDPOINTS) : []), ...(isDevelopment() ? DEV_ENDPOINTS : []), - ], - [view] - ) + ] - if (jwt && firstRender) { - return - } + if (!view) return fixedEndpoints + + const viewEndpoints = getEndpointsByView(views?.[view], ONE_ENDPOINTS) + + return fixedEndpoints.concat(viewEndpoints) + }, [view]) return ( - <> + {isLogged && ( <> @@ -87,7 +77,7 @@ const SunstoneApp = () => { )} - + ) } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js index 6deda3e3b1..3c1e80e385 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js @@ -15,12 +15,12 @@ * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ // eslint-disable-next-line no-unused-vars -import { useMemo, JSXElementConstructor } from 'react' +import { useMemo, ReactElement } from 'react' import PropTypes from 'prop-types' // eslint-disable-next-line no-unused-vars import { useFormContext, FieldErrors } from 'react-hook-form' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { Translate } from 'client/components/HOC' import Tabs from 'client/components/Tabs' @@ -36,13 +36,13 @@ import Numa from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfi import { STEP_ID as GENERAL_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General' import { SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper' -import { T } from 'client/constants' +import { T, RESOURCE_NAMES } from 'client/constants' /** * @typedef {object} TabType * @property {string} id - Id will be to use in view yaml to hide/display the tab * @property {string} name - Label of tab - * @property {JSXElementConstructor} Content - Content tab + * @property {ReactElement} Content - Content tab * @property {object} [icon] - Icon of tab * @property {function(FieldErrors):boolean} [getError] - Returns `true` if the tab contains an error in form */ @@ -67,12 +67,13 @@ const Content = ({ data, setFormData }) => { formState: { errors }, control, } = useFormContext() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const hypervisor = useMemo(() => watch(`${GENERAL_ID}.HYPERVISOR`), []) const sectionsAvailable = useMemo(() => { - const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.create_dialog + const resource = RESOURCE_NAMES.VM_TEMPLATE + const dialog = getResourceView(resource)?.dialogs?.create_dialog return getSectionsAvailable(dialog, hypervisor) }, [view]) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/index.js index a257e0b4ec..04b82c9145 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/index.js @@ -18,7 +18,7 @@ import { useMemo } from 'react' import PropTypes from 'prop-types' import { useWatch } from 'react-hook-form' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import FormWithSchema from 'client/components/Forms/FormWithSchema' import useStyles from 'client/components/Forms/VmTemplate/CreateForm/Steps/General/styles' @@ -28,17 +28,18 @@ import { SECTIONS, } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General/schema' import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper' -import { T } from 'client/constants' +import { T, RESOURCE_NAMES } from 'client/constants' export const STEP_ID = 'general' const Content = ({ isUpdate }) => { const classes = useStyles() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const hypervisor = useWatch({ name: `${STEP_ID}.HYPERVISOR` }) const sections = useMemo(() => { - const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.create_dialog + const resource = RESOURCE_NAMES.VM_TEMPLATE + const dialog = getResourceView(resource)?.dialogs?.create_dialog const sectionsAvailable = getSectionsAvailable(dialog, hypervisor) return SECTIONS(hypervisor, isUpdate).filter( diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js index 953905c4c4..737d457b4b 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js @@ -17,7 +17,7 @@ import { useMemo } from 'react' import { useFormContext } from 'react-hook-form' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import FormWithSchema from 'client/components/Forms/FormWithSchema' import useStyles from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles' @@ -27,18 +27,19 @@ import { FIELDS, } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/schema' import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper' -import { T } from 'client/constants' +import { T, RESOURCE_NAMES } from 'client/constants' export const STEP_ID = 'configuration' const Content = () => { const classes = useStyles() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { watch } = useFormContext() const sections = useMemo(() => { const hypervisor = watch(`${TEMPLATE_ID}[0].TEMPLATE.HYPERVISOR`) - const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.instantiate_dialog + const resource = RESOURCE_NAMES.VM_TEMPLATE + const dialog = getResourceView(resource)?.dialogs?.instantiate_dialog const sectionsAvailable = getSectionsAvailable(dialog, hypervisor) return FIELDS(hypervisor).filter(({ id }) => sectionsAvailable.includes(id)) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js index 7552752eed..ed3c0fc172 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js @@ -17,9 +17,9 @@ import { useMemo } from 'react' import { string, number, boolean } from 'yup' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { getActionsAvailable } from 'client/models/Helper' -import { INPUT_TYPES, VM_ACTIONS } from 'client/constants' +import { RESOURCE_NAMES, INPUT_TYPES, VM_ACTIONS } from 'client/constants' const NAME = { name: 'name', @@ -51,10 +51,11 @@ const HOLD = { label: 'Start VM on hold state', type: INPUT_TYPES.SWITCH, htmlType: () => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() return useMemo(() => { - const actions = getResourceView('VM')?.actions + const resource = RESOURCE_NAMES.VM + const actions = getResourceView(resource)?.actions const actionsAvailable = getActionsAvailable(actions) return ( diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js index bbabe39264..d7624fee79 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js @@ -19,7 +19,7 @@ import PropTypes from 'prop-types' import { useFormContext } from 'react-hook-form' import { SystemShut as OsIcon } from 'iconoir-react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { Translate } from 'client/components/HOC' import Tabs from 'client/components/Tabs' @@ -33,7 +33,7 @@ import BootOrder from 'client/components/Forms/VmTemplate/CreateForm/Steps/Extra import { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable' import { SCHEMA } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema' import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper' -import { T } from 'client/constants' +import { T, RESOURCE_NAMES } from 'client/constants' export const STEP_ID = 'extra' @@ -58,12 +58,13 @@ const Content = ({ data, setFormData }) => { formState: { errors }, control, } = useFormContext() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const hypervisor = useMemo(() => watch(`${TEMPLATE_ID}.0.HYPERVISOR`), []) const sectionsAvailable = useMemo(() => { - const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.instantiate_dialog + const resource = RESOURCE_NAMES.VM_TEMPLATE + const dialog = getResourceView(resource)?.dialogs?.instantiate_dialog return getSectionsAvailable(dialog, hypervisor) }, [view]) diff --git a/src/fireedge/src/client/components/HOC/AuthLayout.js b/src/fireedge/src/client/components/HOC/AuthLayout.js new file mode 100644 index 0000000000..4d8464bc7c --- /dev/null +++ b/src/fireedge/src/client/components/HOC/AuthLayout.js @@ -0,0 +1,82 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, 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 { useEffect, ReactElement } from 'react' +import { useDispatch } from 'react-redux' +import PropTypes from 'prop-types' + +import { useAuth, useAuthApi } from 'client/features/Auth' +import { authApi } from 'client/features/AuthApi' +import groupApi from 'client/features/OneApi/group' +import FullscreenProgress from 'client/components/LoadingScreen' +import { findStorageData } from 'client/utils' +import { JWT_NAME } from 'client/constants' + +/** + * Renders loading screen while validate JWT. + * + * @param {object} props - Props + * @param {object[]} [props.subscriptions] - Subscriptions after login + * @param {ReactElement} [props.children] - Children + * @returns {ReactElement} App rendered. + */ +const AuthLayout = ({ subscriptions = [], children }) => { + const dispatch = useDispatch() + const { changeJwt, stopFirstRender } = useAuthApi() + const { jwt, isLogged, isLoginInProgress, firstRender } = useAuth() + + useEffect(() => { + if (!jwt) return + + const endpoints = [ + groupApi.endpoints.getGroups, + authApi.endpoints.getAuthUser, + ...subscriptions, + ].map((endpoint) => + dispatch(endpoint.initiate(undefined, { forceRefetch: true })) + ) + + return () => { + endpoints.forEach((endpoint) => { + endpoint.unsubscribe() + }) + } + }, [dispatch, jwt]) + + useEffect(() => { + if (!jwt) { + const token = findStorageData(JWT_NAME) + token && changeJwt(token) + } + + // first rendering on client + stopFirstRender() + }, []) + + if ((jwt && !isLoginInProgress && !isLogged) || firstRender) { + return + } + + return <>{children} +} + +AuthLayout.propTypes = { + subscriptions: PropTypes.array, + children: PropTypes.any, +} + +AuthLayout.displayName = 'AuthLayout' + +export default AuthLayout diff --git a/src/fireedge/src/client/components/HOC/Translate.js b/src/fireedge/src/client/components/HOC/Translate.js index c6c2da67ad..7cd6fa405a 100644 --- a/src/fireedge/src/client/components/HOC/Translate.js +++ b/src/fireedge/src/client/components/HOC/Translate.js @@ -56,7 +56,7 @@ const RemoveScript = () => { const TranslateProvider = ({ children = [] }) => { const [hash, setHash] = useState({}) - const { settings: { lang } = {} } = useAuth() + const { settings: { LANG: lang } = {} } = useAuth() useEffect(() => { GenerateScript(lang, setHash) @@ -71,14 +71,8 @@ const TranslateProvider = ({ children = [] }) => { GenerateScript(language, setHash) } - const value = { - lang, - hash, - changeLang, - } - return ( - + {children} ) diff --git a/src/fireedge/src/client/components/HOC/index.js b/src/fireedge/src/client/components/HOC/index.js index ab794d3632..efe0d8fb7c 100644 --- a/src/fireedge/src/client/components/HOC/index.js +++ b/src/fireedge/src/client/components/HOC/index.js @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ +export { default as AuthLayout } from 'client/components/HOC/AuthLayout' export { default as ConditionalWrap } from 'client/components/HOC/ConditionalWrap' export { default as InternalLayout } from 'client/components/HOC/InternalLayout' export * from 'client/components/HOC/Translate' diff --git a/src/fireedge/src/client/components/Header/Group.js b/src/fireedge/src/client/components/Header/Group.js index f9b1b098a6..cc0064af18 100644 --- a/src/fireedge/src/client/components/Header/Group.js +++ b/src/fireedge/src/client/components/Header/Group.js @@ -16,10 +16,11 @@ import { useMemo, memo, JSXElementConstructor } from 'react' import PropTypes from 'prop-types' -import { Button } from '@mui/material' +import { Button, Stack, CircularProgress } from '@mui/material' import { Group as GroupIcon, VerifiedBadge as SelectIcon } from 'iconoir-react' -import { useAuth, useAuthApi } from 'client/features/Auth' +import { useAuth } from 'client/features/Auth' +import { useChangeAuthGroupMutation } from 'client/features/AuthApi' import Search from 'client/components/Search' import HeaderPopover from 'client/components/Header/Popover' import { Tr, Translate } from 'client/components/HOC' @@ -28,10 +29,8 @@ import { T, FILTER_POOL } from 'client/constants' const { ALL_RESOURCES, PRIMARY_GROUP_RESOURCES } = FILTER_POOL const ButtonGroup = memo( - ({ group, handleClick }) => { - const { changeGroup } = useAuthApi() + ({ group, handleClick, disabled }) => { const { user, filterPool } = useAuth() - const { ID, NAME } = group const isSelected = @@ -41,12 +40,10 @@ const ButtonGroup = memo( return ( ) }, - (prev, next) => prev.group.ID === next.group.ID + (prev, next) => + prev.group.ID === next.group.ID && prev.disabled === next.disabled ) /** @@ -68,6 +66,7 @@ const ButtonGroup = memo( * @returns {JSXElementConstructor} Returns group list */ const Group = () => { + const [changeGroup, { isLoading }] = useChangeAuthGroupMutation() const { user, groups } = useAuth() const sortGroupAsMainFirst = (a, b) => @@ -86,7 +85,12 @@ const Group = () => { icon={} tooltip={} buttonProps={{ 'data-cy': 'header-group-button' }} - headerTitle={} + headerTitle={ + + + {isLoading && } + + } > {({ handleClose }) => ( { { + group?.ID && (await changeGroup({ group: group.ID })) + handleClose() + }} /> )} /> @@ -111,11 +119,9 @@ const Group = () => { } ButtonGroup.propTypes = { - group: PropTypes.shape({ - ID: PropTypes.string, - NAME: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), - }).isRequired, + group: PropTypes.object, handleClick: PropTypes.func, + disabled: PropTypes.bool, } ButtonGroup.displayName = 'ButtonGroup' diff --git a/src/fireedge/src/client/components/Header/User.js b/src/fireedge/src/client/components/Header/User.js index 45723999f1..0b49eb079f 100644 --- a/src/fireedge/src/client/components/Header/User.js +++ b/src/fireedge/src/client/components/Header/User.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { JSXElementConstructor } from 'react' +import { ReactElement } from 'react' import { MenuItem, MenuList, Link } from '@mui/material' import { ProfileCircled as UserIcon } from 'iconoir-react' @@ -28,7 +28,7 @@ import { T, APPS, APP_URL } from 'client/constants' /** * Menu with actions about App: signOut, etc. * - * @returns {JSXElementConstructor} Returns user actions list + * @returns {ReactElement} Returns user actions list */ const User = () => { const { user } = useAuth() diff --git a/src/fireedge/src/client/components/Header/View.js b/src/fireedge/src/client/components/Header/View.js index 5b14304aa0..55b23651c3 100644 --- a/src/fireedge/src/client/components/Header/View.js +++ b/src/fireedge/src/client/components/Header/View.js @@ -22,7 +22,7 @@ import { VerifiedBadge as SelectIcon, } from 'iconoir-react' -import { useAuth, useAuthApi } from 'client/features/Auth' +import { useAuthApi, useViews } from 'client/features/Auth' import Search from 'client/components/Search' import HeaderPopover from 'client/components/Header/Popover' import { Translate } from 'client/components/HOC' @@ -31,7 +31,7 @@ import { T } from 'client/constants' const ButtonView = memo( ({ view, handleClick }) => { const { changeView } = useAuthApi() - const { view: currentView } = useAuth() + const { view: currentView } = useViews() const isCurrentView = currentView === view return ( @@ -73,7 +73,7 @@ ButtonView.displayName = 'ButtonView' * @returns {ReactElement} Returns interface views list */ const View = () => { - const { view: currentView, views = {} } = useAuth() + const { view: currentView, views = {} } = useViews() const viewNames = useMemo(() => Object.keys(views), [currentView]) return ( diff --git a/src/fireedge/src/client/components/LoadingScreen/index.js b/src/fireedge/src/client/components/LoadingScreen/index.js index 8b63fe2ebe..916f131ebf 100644 --- a/src/fireedge/src/client/components/LoadingScreen/index.js +++ b/src/fireedge/src/client/components/LoadingScreen/index.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { JSXElementConstructor } from 'react' +import { ReactElement } from 'react' import { Box } from '@mui/material' import { OpenNebulaLogo } from 'client/components/Icons' @@ -21,21 +21,10 @@ import { OpenNebulaLogo } from 'client/components/Icons' /** * Component with OpenNebula logo as spinner in full width and height. * - * @returns {JSXElementConstructor} Container with logo inside + * @returns {ReactElement} Container with logo inside */ const LoadingScreen = () => ( - + ) diff --git a/src/fireedge/src/client/components/Route/NoAuthRoute.js b/src/fireedge/src/client/components/Route/NoAuthRoute.js index da908e133b..822b1e1175 100644 --- a/src/fireedge/src/client/components/Route/NoAuthRoute.js +++ b/src/fireedge/src/client/components/Route/NoAuthRoute.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { JSXElementConstructor } from 'react' +import { ReactElement } from 'react' import PropTypes from 'prop-types' import { Redirect, Route } from 'react-router-dom' @@ -23,14 +23,13 @@ import { useAuth } from 'client/features/Auth' * Public route. * * @param {object} props - Route props - * @param {JSXElementConstructor} props.redirectWhenAuth + * @param {ReactElement} props.redirectWhenAuth * - Route to redirect in case of user is authenticated * @returns {Redirect|Route} * - If current user is authenticated, then redirect to private route */ const NoAuthRoute = ({ redirectWhenAuth, ...props }) => { - const { isLogged, isLoginInProgress, isLoading } = useAuth() - const isAuthenticated = isLogged && !isLoginInProgress && !isLoading + const { isLogged: isAuthenticated } = useAuth() return isAuthenticated ? ( diff --git a/src/fireedge/src/client/components/Route/ProtectedRoute.js b/src/fireedge/src/client/components/Route/ProtectedRoute.js index 584fcf5131..419eaf6243 100644 --- a/src/fireedge/src/client/components/Route/ProtectedRoute.js +++ b/src/fireedge/src/client/components/Route/ProtectedRoute.js @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { useEffect } from 'react' import { Redirect, Route } from 'react-router-dom' -import { useAuth, useAuthApi } from 'client/features/Auth' +import { useAuth } from 'client/features/Auth' /** * Private route. @@ -26,14 +25,9 @@ import { useAuth, useAuthApi } from 'client/features/Auth' * - If current user isn't authenticated, then redirect to landing page */ const ProtectedRoute = (props) => { - const { isLogged, jwt } = useAuth() - const { getAuthUser } = useAuthApi() + const { isLogged: isAuthenticated } = useAuth() - useEffect(() => { - jwt && getAuthUser() - }, []) - - return isLogged ? : + return isAuthenticated ? : } export default ProtectedRoute diff --git a/src/fireedge/src/client/components/Tables/Clusters/index.js b/src/fireedge/src/client/components/Tables/Clusters/index.js index 506f7ab054..feedb470e3 100644 --- a/src/fireedge/src/client/components/Tables/Clusters/index.js +++ b/src/fireedge/src/client/components/Tables/Clusters/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetClustersQuery } from 'client/features/OneApi/cluster' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const ClustersTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetClustersQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Datastores/index.js b/src/fireedge/src/client/components/Tables/Datastores/index.js index 2b1ea66a49..4d0563b2f2 100644 --- a/src/fireedge/src/client/components/Tables/Datastores/index.js +++ b/src/fireedge/src/client/components/Tables/Datastores/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetDatastoresQuery } from 'client/features/OneApi/datastore' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -39,7 +39,7 @@ const DatastoresTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js index 8214a71e7e..dcccab6b20 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js @@ -20,8 +20,8 @@ import { GlobalAction } from 'client/components/Tables/Enhanced/Utils/GlobalActi /** * Add filters defined in view yaml to columns. * - * @param {object} config - - * @param {object[]} config.filters - List of criteria to filter the columns. + * @param {object} config - Config + * @param {object} config.filters - List of criteria to filter the columns. * @param {Column[]} config.columns - Columns * @returns {object} Column with filters */ @@ -64,7 +64,7 @@ export const createCategoryFilter = (title) => ({ * Add filters defined in view yaml to bulk actions. * * @param {object} params - Config parameters - * @param {object[]} params.filters - Which buttons are visible to operate over the resources + * @param {object} params.filters - Which buttons are visible to operate over the resources * @param {GlobalAction[]} params.actions - Actions * @returns {object} Action with filters */ @@ -82,7 +82,7 @@ export const createActions = ({ filters = {}, actions = [] }) => { if (accessor) return action const groupActions = options?.filter( - (option) => filters[String(option.accessor?.toLowerCase())] === true + (option) => filters[`${option.accessor?.toLowerCase()}`] === true ) return groupActions?.length > 0 diff --git a/src/fireedge/src/client/components/Tables/Groups/index.js b/src/fireedge/src/client/components/Tables/Groups/index.js index e1bf553eb8..c3431dee52 100644 --- a/src/fireedge/src/client/components/Tables/Groups/index.js +++ b/src/fireedge/src/client/components/Tables/Groups/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetGroupsQuery } from 'client/features/OneApi/group' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const GroupsTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetGroupsQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Hosts/index.js b/src/fireedge/src/client/components/Tables/Hosts/index.js index eaa7e79c5a..f8b56e7d71 100644 --- a/src/fireedge/src/client/components/Tables/Hosts/index.js +++ b/src/fireedge/src/client/components/Tables/Hosts/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetHostsQuery } from 'client/features/OneApi/host' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -39,7 +39,7 @@ const HostsTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Images/index.js b/src/fireedge/src/client/components/Tables/Images/index.js index 6c54036937..f075620f09 100644 --- a/src/fireedge/src/client/components/Tables/Images/index.js +++ b/src/fireedge/src/client/components/Tables/Images/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetImagesQuery } from 'client/features/OneApi/image' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const ImagesTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetImagesQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js index 2188e75c9b..0f5ed1813c 100644 --- a/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/actions.js @@ -18,7 +18,7 @@ import { useMemo } from 'react' import { useHistory } from 'react-router-dom' import { AddSquare, CloudDownload } from 'iconoir-react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGeneralApi } from 'client/features/General' import { Translate } from 'client/components/HOC' import { useExportAppMutation } from 'client/features/OneApi/marketplaceApp' @@ -48,7 +48,7 @@ MessageToConfirmAction.displayName = 'MessageToConfirmAction' const Actions = () => { const history = useHistory() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { enqueueSuccess } = useGeneralApi() const [exportApp] = useExportAppMutation() diff --git a/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js b/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js index 0c10a3bbc4..f9ef57497e 100644 --- a/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js +++ b/src/fireedge/src/client/components/Tables/MarketplaceApps/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetMarketplaceAppsQuery } from 'client/features/OneApi/marketplaceApp' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const MarketplaceAppsTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetMarketplaceAppsQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Marketplaces/index.js b/src/fireedge/src/client/components/Tables/Marketplaces/index.js index 99651fe7a4..7d88d25c46 100644 --- a/src/fireedge/src/client/components/Tables/Marketplaces/index.js +++ b/src/fireedge/src/client/components/Tables/Marketplaces/index.js @@ -16,7 +16,7 @@ import { useMemo, ReactElement } from 'react' import PropTypes from 'prop-types' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetMarketplacesQuery } from 'client/features/OneApi/marketplace' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -41,7 +41,7 @@ const MarketplacesTable = ({ filter, ...props }) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Users/index.js b/src/fireedge/src/client/components/Tables/Users/index.js index b2b5531317..8d5bef5f93 100644 --- a/src/fireedge/src/client/components/Tables/Users/index.js +++ b/src/fireedge/src/client/components/Tables/Users/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetUsersQuery } from 'client/features/OneApi/user' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const UsersTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetUsersQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/VNetworkTemplates/index.js b/src/fireedge/src/client/components/Tables/VNetworkTemplates/index.js index 94d726ebd3..a44adad230 100644 --- a/src/fireedge/src/client/components/Tables/VNetworkTemplates/index.js +++ b/src/fireedge/src/client/components/Tables/VNetworkTemplates/index.js @@ -16,7 +16,7 @@ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVNTemplatesQuery } from 'client/features/OneApi/networkTemplate' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -35,7 +35,7 @@ const VNetworkTemplatesTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetVNTemplatesQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/VNetworks/index.js b/src/fireedge/src/client/components/Tables/VNetworks/index.js index 4bd98630ee..28f4d41dd6 100644 --- a/src/fireedge/src/client/components/Tables/VNetworks/index.js +++ b/src/fireedge/src/client/components/Tables/VNetworks/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVNetworksQuery } from 'client/features/OneApi/network' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -39,7 +39,7 @@ const VNetworksTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/VRouters/index.js b/src/fireedge/src/client/components/Tables/VRouters/index.js index dfeceb8794..e657fd48ac 100644 --- a/src/fireedge/src/client/components/Tables/VRouters/index.js +++ b/src/fireedge/src/client/components/Tables/VRouters/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVRoutersQuery } from 'client/features/OneApi/vrouter' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const VRoutersTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetVRoutersQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js index 646c1c4850..13f8af2034 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js @@ -26,7 +26,7 @@ import { Cart, } from 'iconoir-react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useLockTemplateMutation, useUnlockTemplateMutation, @@ -66,7 +66,7 @@ MessageToConfirmAction.displayName = 'MessageToConfirmAction' const Actions = () => { const history = useHistory() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const [lock] = useLockTemplateMutation() const [unlock] = useUnlockTemplateMutation() const [clone] = useCloneTemplateMutation() @@ -281,7 +281,7 @@ const Actions = () => { const marketplaceAppActions = useMemo( () => createActions({ - filters: getResourceView('MARKETPLACE-APP')?.actions, + filters: getResourceView(RESOURCE_NAMES.APP)?.actions, actions: [ { accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG, diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/index.js b/src/fireedge/src/client/components/Tables/VmTemplates/index.js index 9a3cfce018..9ab8d03303 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/index.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetTemplatesQuery } from 'client/features/OneApi/vmTemplate' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const VmTemplatesTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetTemplatesQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tables/Vms/actions.js b/src/fireedge/src/client/components/Tables/Vms/actions.js index c272cd06b3..6fe8ed70eb 100644 --- a/src/fireedge/src/client/components/Tables/Vms/actions.js +++ b/src/fireedge/src/client/components/Tables/Vms/actions.js @@ -28,7 +28,7 @@ import { Cart, } from 'iconoir-react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGeneralApi } from 'client/features/General' import { useGetDatastoresQuery } from 'client/features/OneApi/datastore' import { @@ -106,7 +106,7 @@ const MessageToConfirmAction = (rows) => ( */ const Actions = () => { const history = useHistory() - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { enqueueSuccess } = useGeneralApi() const [saveAsTemplate] = useSaveAsTemplateMutation() diff --git a/src/fireedge/src/client/components/Tables/Vms/index.js b/src/fireedge/src/client/components/Tables/Vms/index.js index 3611d83ee1..a87b2d95bf 100644 --- a/src/fireedge/src/client/components/Tables/Vms/index.js +++ b/src/fireedge/src/client/components/Tables/Vms/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useState, useEffect, useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVmsQuery } from 'client/features/OneApi/vm' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -43,7 +43,7 @@ const VmsTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const [totalData, setTotalData] = useState(() => []) const [args, setArgs] = useState(() => INITIAL_ARGS) const { data, isSuccess, isFetching } = useGetVmsQuery(args, { diff --git a/src/fireedge/src/client/components/Tables/Zones/index.js b/src/fireedge/src/client/components/Tables/Zones/index.js index 3f4834f083..7ea2ce0fb8 100644 --- a/src/fireedge/src/client/components/Tables/Zones/index.js +++ b/src/fireedge/src/client/components/Tables/Zones/index.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { useMemo, ReactElement } from 'react' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetZonesQuery } from 'client/features/OneApi/zone' import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced' @@ -34,7 +34,7 @@ const ZonesTable = (props) => { rootProps['data-cy'] ??= DEFAULT_DATA_CY searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}` - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { data = [], isFetching, refetch } = useGetZonesQuery() const columns = useMemo( diff --git a/src/fireedge/src/client/components/Tabs/Cluster/index.js b/src/fireedge/src/client/components/Tabs/Cluster/index.js index 0e08ba49eb..3a1973a057 100644 --- a/src/fireedge/src/client/components/Tabs/Cluster/index.js +++ b/src/fireedge/src/client/components/Tabs/Cluster/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetClusterQuery } from 'client/features/OneApi/cluster' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const ClusterTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetClusterQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/Datastore/index.js b/src/fireedge/src/client/components/Tabs/Datastore/index.js index 60a6c21d5f..612801fdf1 100644 --- a/src/fireedge/src/client/components/Tabs/Datastore/index.js +++ b/src/fireedge/src/client/components/Tabs/Datastore/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetDatastoreQuery } from 'client/features/OneApi/datastore' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const DatastoreTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetDatastoreQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/Group/index.js b/src/fireedge/src/client/components/Tabs/Group/index.js index e718c37ea9..704513778b 100644 --- a/src/fireedge/src/client/components/Tabs/Group/index.js +++ b/src/fireedge/src/client/components/Tabs/Group/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetGroupQuery } from 'client/features/OneApi/group' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const GroupTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetGroupQuery(id) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/Image/index.js b/src/fireedge/src/client/components/Tabs/Image/index.js index 12e7e22359..0f2e741ebb 100644 --- a/src/fireedge/src/client/components/Tabs/Image/index.js +++ b/src/fireedge/src/client/components/Tabs/Image/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetImageQuery } from 'client/features/OneApi/image' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const ImageTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetImageQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/Marketplace/index.js b/src/fireedge/src/client/components/Tabs/Marketplace/index.js index 9e17719f1c..49b60ea7ce 100644 --- a/src/fireedge/src/client/components/Tabs/Marketplace/index.js +++ b/src/fireedge/src/client/components/Tabs/Marketplace/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetMarketplaceQuery } from 'client/features/OneApi/marketplace' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const MarketplaceTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetMarketplaceQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js b/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js index bb0900ce7d..0dc5ee59ac 100644 --- a/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js +++ b/src/fireedge/src/client/components/Tabs/MarketplaceApp/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetMarketplaceAppQuery } from 'client/features/OneApi/marketplaceApp' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -33,7 +33,7 @@ const getTabComponent = (tabName) => }[tabName]) const MarketplaceAppTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetMarketplaceAppQuery(id) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/User/index.js b/src/fireedge/src/client/components/Tabs/User/index.js index c1baaa0d5b..0291dd9623 100644 --- a/src/fireedge/src/client/components/Tabs/User/index.js +++ b/src/fireedge/src/client/components/Tabs/User/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetUserQuery } from 'client/features/OneApi/user' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const UserTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetUserQuery(id) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/VNetwork/index.js b/src/fireedge/src/client/components/Tabs/VNetwork/index.js index 98e84067fd..e845e91105 100644 --- a/src/fireedge/src/client/components/Tabs/VNetwork/index.js +++ b/src/fireedge/src/client/components/Tabs/VNetwork/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVNetworkQuery } from 'client/features/OneApi/network' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const VNetworkTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetVNetworkQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js b/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js index 9b4185751c..5b74f12d43 100644 --- a/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js +++ b/src/fireedge/src/client/components/Tabs/VNetworkTemplate/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVNTemplateQuery } from 'client/features/OneApi/networkTemplate' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const VNetTemplateTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetVNTemplateQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/Vm/index.js b/src/fireedge/src/client/components/Tabs/Vm/index.js index 1b46e3736c..2a53db92a3 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetVmQuery } from 'client/features/OneApi/vm' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -45,7 +45,7 @@ const getTabComponent = (tabName) => }[tabName]) const VmTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetVmQuery(id) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/VmTemplate/index.js b/src/fireedge/src/client/components/Tabs/VmTemplate/index.js index cbca687d2f..b4c3b0e959 100644 --- a/src/fireedge/src/client/components/Tabs/VmTemplate/index.js +++ b/src/fireedge/src/client/components/Tabs/VmTemplate/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetTemplateQuery } from 'client/features/OneApi/vmTemplate' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -33,7 +33,7 @@ const getTabComponent = (tabName) => }[tabName]) const VmTemplateTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetTemplateQuery({ id }) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/components/Tabs/Zone/index.js b/src/fireedge/src/client/components/Tabs/Zone/index.js index 2a9d1d8197..58fc377c36 100644 --- a/src/fireedge/src/client/components/Tabs/Zone/index.js +++ b/src/fireedge/src/client/components/Tabs/Zone/index.js @@ -17,7 +17,7 @@ import { memo, useMemo } from 'react' import PropTypes from 'prop-types' import { LinearProgress } from '@mui/material' -import { useAuth } from 'client/features/Auth' +import { useViews } from 'client/features/Auth' import { useGetZoneQuery } from 'client/features/OneApi/zone' import { getAvailableInfoTabs } from 'client/models/Helper' import { RESOURCE_NAMES } from 'client/constants' @@ -31,7 +31,7 @@ const getTabComponent = (tabName) => }[tabName]) const ZoneTabs = memo(({ id }) => { - const { view, getResourceView } = useAuth() + const { view, getResourceView } = useViews() const { isLoading } = useGetZoneQuery(id) const tabsAvailable = useMemo(() => { diff --git a/src/fireedge/src/client/constants/index.js b/src/fireedge/src/client/constants/index.js index 71edd034f3..3a674cbe3a 100644 --- a/src/fireedge/src/client/constants/index.js +++ b/src/fireedge/src/client/constants/index.js @@ -27,7 +27,6 @@ export const BY = { url: 'https://opennebula.io/', } -export const TIME_HIDE_LOGO = 1500 export const _APPS = defaultApps export const APPS = Object.keys(defaultApps) export const APPS_IN_BETA = [_APPS.sunstone.name] diff --git a/src/fireedge/src/client/containers/Dashboard/Provision/index.js b/src/fireedge/src/client/containers/Dashboard/Provision/index.js index 061d136fb3..0d73d4eda9 100644 --- a/src/fireedge/src/client/containers/Dashboard/Provision/index.js +++ b/src/fireedge/src/client/containers/Dashboard/Provision/index.js @@ -37,12 +37,12 @@ import { T } from 'client/constants' /** @returns {ReactElement} Provision dashboard container */ function ProvisionDashboard() { - const { settings: { disableanimations } = {} } = useAuth() + const { settings: { DISABLE_ANIMATIONS } = {} } = useAuth() return ( theme.breakpoints.only('xs')) - const { - error, - isLoading: authLoading, - isLoginInProgress: needGroupToContinue, - } = useAuth() + const { error: otherError, isLoginInProgress: needGroupToContinue } = + useAuth() + const { logout } = useAuthApi() - const { login, getAuthUser, changeGroup, logout } = useAuthApi() - const { fetchRequest: fetchLogin, loading: loginIsLoading } = useFetch(login) + const [changeAuthGroup, changeAuthGroupState] = useChangeAuthGroupMutation() + const [login, loginState] = useLoginMutation() + const isLoading = loginState.isLoading || changeAuthGroupState.isLoading + const errorMessage = loginState.error?.data?.message ?? otherError const [dataUserForm, setDataUserForm] = useState(undefined) const [step, setStep] = useState(() => @@ -56,19 +59,20 @@ function Login() { ) const handleSubmitUser = async (dataForm) => { - const response = await fetchLogin({ ...dataUserForm, ...dataForm }) - const { jwt, user, isLoginInProgress } = response || {} + try { + const response = await login({ ...dataUserForm, ...dataForm }).unwrap() + const { jwt, user, isLoginInProgress } = response || {} - if (jwt && isLoginInProgress) { - getAuthUser() - setStep(STEPS.GROUP_FORM) - } else if (!jwt && user?.ID) { - setStep(STEPS.FA2_FORM) - setDataUserForm(dataForm) - } + if (jwt && isLoginInProgress) { + setStep(STEPS.GROUP_FORM) + } else if (!jwt && user?.ID) { + setStep(STEPS.FA2_FORM) + setDataUserForm(dataForm) + } + } catch {} } - const handleSubmitGroup = (dataForm) => changeGroup(dataForm) + const handleSubmitGroup = (dataForm) => changeAuthGroup(dataForm) const handleBack = () => { logout() @@ -76,8 +80,6 @@ function Login() { setStep(STEPS.USER_FORM) } - const isLoading = loginIsLoading || authLoading - return ( )} @@ -125,7 +127,7 @@ function Login() { onSubmit={handleSubmitUser} resolver={FORMS.FORM_2FA_SCHEMA} fields={FORMS.FORM_2FA_FIELDS} - error={error} + error={errorMessage} isLoading={isLoading} /> )} @@ -139,7 +141,7 @@ function Login() { onSubmit={handleSubmitGroup} resolver={FORMS.FORM_GROUP_SCHEMA} fields={FORMS.FORM_GROUP_FIELDS} - error={error} + error={errorMessage} isLoading={isLoading} /> )} diff --git a/src/fireedge/src/client/containers/Settings/index.js b/src/fireedge/src/client/containers/Settings/index.js index 6b8c2bb986..4875dc1136 100644 --- a/src/fireedge/src/client/containers/Settings/index.js +++ b/src/fireedge/src/client/containers/Settings/index.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { JSXElementConstructor } from 'react' +import { ReactElement, useMemo } from 'react' import { Container, Paper, Box, Typography, Divider } from '@mui/material' import { useForm, FormProvider } from 'react-hook-form' import { yupResolver } from '@hookform/resolvers/yup' @@ -21,7 +21,8 @@ import { yupResolver } from '@hookform/resolvers/yup' import FormWithSchema from 'client/components/Forms/FormWithSchema' import SubmitButton from 'client/components/FormControl/SubmitButton' -import { useAuth, useAuthApi } from 'client/features/Auth' +import { useAuth } from 'client/features/Auth' +import { useLazyGetAuthUserQuery } from 'client/features/AuthApi' import { useUpdateUserMutation } from 'client/features/OneApi/user' import { useGeneralApi } from 'client/features/General' import { Translate, Tr } from 'client/components/HOC' @@ -30,24 +31,28 @@ import { T } from 'client/constants' import { FORM_FIELDS, FORM_SCHEMA } from 'client/containers/Settings/schema' import * as Helper from 'client/models/Helper' -/** @returns {JSXElementConstructor} Settings container */ +/** @returns {ReactElement} Settings container */ const Settings = () => { const { user, settings } = useAuth() - const { getAuthUser } = useAuthApi() + const [getAuthUser] = useLazyGetAuthUserQuery() const [updateUser] = useUpdateUserMutation() const { enqueueError } = useGeneralApi() - const { handleSubmit, setError, reset, formState, ...methods } = useForm({ + const { handleSubmit, reset, formState, ...methods } = useForm({ reValidateMode: 'onSubmit', - defaultValues: FORM_SCHEMA.cast(settings), + defaultValues: useMemo(() => FORM_SCHEMA.cast(settings), [settings]), resolver: yupResolver(FORM_SCHEMA), }) - const onSubmit = async (dataForm) => { + const onSubmit = async (formData) => { try { - const template = Helper.jsonToXml({ FIREEDGE: dataForm }) + const data = FORM_SCHEMA.cast(formData, { isSubmit: true }) + const template = Helper.jsonToXml({ FIREEDGE: data }) await updateUser({ id: user.ID, template }) - getAuthUser() + await getAuthUser() + + // Reset either the entire form state or part of the form state + reset(formData) } catch { enqueueError(T.SomethingWrong) } diff --git a/src/fireedge/src/client/containers/Settings/schema.js b/src/fireedge/src/client/containers/Settings/schema.js index f29a3629b4..ea403b9bf5 100644 --- a/src/fireedge/src/client/containers/Settings/schema.js +++ b/src/fireedge/src/client/containers/Settings/schema.js @@ -24,7 +24,7 @@ import { import { getValidationFromFields } from 'client/utils' const SCHEME = { - name: 'scheme', + name: 'SCHEME', label: T.Schema, type: INPUT_TYPES.SELECT, values: [ @@ -40,7 +40,7 @@ const SCHEME = { } const LANGUAGES = { - name: 'lang', + name: 'LANG', label: T.Language, type: INPUT_TYPES.SELECT, values: () => @@ -53,7 +53,7 @@ const LANGUAGES = { } const DISABLE_ANIMATIONS = { - name: 'disableanimations', + name: 'DISABLE_ANIMATIONS', label: T.DisableDashboardAnimations, type: INPUT_TYPES.CHECKBOX, validation: boolean() diff --git a/src/fireedge/src/client/dev/_app.js b/src/fireedge/src/client/dev/_app.js index 03e89edd28..e42daeeb5c 100644 --- a/src/fireedge/src/client/dev/_app.js +++ b/src/fireedge/src/client/dev/_app.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { JSXElementConstructor } from 'react' +import { ReactElement } from 'react' import SunstoneApp from 'client/apps/sunstone' import ProvisionApp from 'client/apps/provision' @@ -26,7 +26,7 @@ import { _APPS, APPS } from 'client/constants' * Render App by url: http:///fireedge/. * * @param {object} props - Props from server - * @returns {JSXElementConstructor} Returns App + * @returns {ReactElement} Returns App */ const DevelopmentApp = (props) => { let appName = '' diff --git a/src/fireedge/src/client/features/Auth/actions.js b/src/fireedge/src/client/features/Auth/actions.js deleted file mode 100644 index 437478ef90..0000000000 --- a/src/fireedge/src/client/features/Auth/actions.js +++ /dev/null @@ -1,135 +0,0 @@ -/* ------------------------------------------------------------------------- * - * Copyright 2002-2021, 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. * - * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import { createAction, createAsyncThunk } from '@reduxjs/toolkit' - -import { authService } from 'client/features/Auth/services' -import { dismissSnackbar } from 'client/features/General/actions' -import apiUser from 'client/features/OneApi/user' - -import { httpCodes } from 'server/utils/constants' -import { removeStoreData, storage } from 'client/utils' -import { FILTER_POOL, JWT_NAME, ONEADMIN_ID, T } from 'client/constants' - -export const login = createAsyncThunk( - 'auth/login', - async ({ remember, ...user }, { rejectWithValue, dispatch }) => { - try { - const response = await authService.login({ ...user, remember }) - const { id, token } = response - const isOneAdmin = id === ONEADMIN_ID - - if (token) { - storage(JWT_NAME, token, remember) - dispatch(dismissSnackbar({ dismissAll: true })) - } - - return { - jwt: token, - user: { ID: id }, - isOneAdmin, - isLoginInProgress: !!token && !isOneAdmin, - } - } catch (error) { - const { message, data, statusText } = error - - return rejectWithValue({ error: message ?? data?.message ?? statusText }) - } - } -) - -export const getUser = createAsyncThunk( - 'auth/user', - async (_, { dispatch, getState }) => { - try { - const { auth = {} } = getState() - - const user = await authService.getUser() - const isOneAdmin = user?.ID === ONEADMIN_ID - const userSettings = user?.TEMPLATE?.FIREEDGE ?? {} - - // Merge user settings with the existing one - const settings = { - ...auth?.settings, - ...Object.entries(userSettings).reduce( - (res, [key, value]) => ({ - ...res, - [String(key).toLowerCase()]: value, - }), - {} - ), - } - - return { user, settings, isOneAdmin } - } catch (error) { - console.log({ error }) - dispatch(logout(T.SessionExpired)) - } - }, - { - condition: (_, { getState }) => { - const { isLoading } = getState().auth - - return !isLoading - }, - } -) - -export const logout = createAction('logout', (errorMessage) => { - removeStoreData([JWT_NAME]) - - return { error: errorMessage } -}) - -export const changeFilter = createAction( - 'auth/change-filter', - (filterPool) => ({ payload: { filterPool, isLoginInProgress: false } }) -) - -export const changeGroup = createAsyncThunk( - 'auth/change-group', - async ({ group }, { getState, dispatch, rejectWithValue }) => { - try { - if (group === FILTER_POOL.ALL_RESOURCES) { - dispatch(changeFilter(FILTER_POOL.ALL_RESOURCES)) - } else { - const { user } = getState().auth - - const data = { id: user?.ID, group } - dispatch(apiUser.endpoints.changeGroup.initiate(data)).reset() - - dispatch(changeFilter(FILTER_POOL.PRIMARY_GROUP_RESOURCES)) - - return { - user: { - ...user, - GID: group, - }, - } - } - } catch (error) { - const { message, data, status, statusText } = error - - status === httpCodes.unauthorized.id && dispatch(logout(T.SessionExpired)) - - return rejectWithValue({ error: message ?? data?.message ?? statusText }) - } - } -) - -export const changeView = createAction('auth/change-view', (view) => ({ - payload: { view }, -})) diff --git a/src/fireedge/src/client/features/Auth/hooks.js b/src/fireedge/src/client/features/Auth/hooks.js index 7e4755d78f..55b08a3481 100644 --- a/src/fireedge/src/client/features/Auth/hooks.js +++ b/src/fireedge/src/client/features/Auth/hooks.js @@ -14,29 +14,38 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { useDispatch, useSelector, shallowEqual } from 'react-redux' -import { unwrapResult } from '@reduxjs/toolkit' -import * as actions from 'client/features/Auth/actions' -import { name as authSlice } from 'client/features/Auth/slice' -import apiGroup from 'client/features/OneApi/group' -import apiSystem from 'client/features/OneApi/system' -import { RESOURCE_NAMES } from 'client/constants' +import { name as generalSlice } from 'client/features/General/slice' +import { name as authSlice, actions } from 'client/features/Auth/slice' +import groupApi from 'client/features/OneApi/group' +import systemApi from 'client/features/OneApi/system' +import { _APPS, RESOURCE_NAMES, ONEADMIN_ID } from 'client/constants' +import { ResourceView } from 'client/apps/sunstone/routes' + +const APPS_WITH_VIEWS = [_APPS.sunstone.name].map((app) => app.toLowerCase()) + +const appNeedViews = () => { + const { appTitle } = useSelector((state) => state[generalSlice], shallowEqual) + + return useMemo(() => APPS_WITH_VIEWS.includes(appTitle), [appTitle]) +} + +// -------------------------------------------------------------- +// Authenticate Hooks +// -------------------------------------------------------------- export const useAuth = () => { const auth = useSelector((state) => state[authSlice], shallowEqual) - const { user, jwt, view, isLoginInProgress } = auth + const { jwt, user, view, settings, isLoginInProgress } = auth - const { data: views } = apiSystem.endpoints.getSunstoneViews.useQuery( - undefined, - { skip: !jwt } - ) + const waitViewToLogin = appNeedViews() ? !!view : true - const { data: userGroups } = apiGroup.endpoints.getGroups.useQuery( + const { data: authGroups } = groupApi.endpoints.getGroups.useQueryState( undefined, { - skip: !jwt, + skip: !jwt || !user?.GROUPS?.ID, selectFromResult: ({ data: groups = [] }) => ({ data: [user?.GROUPS?.ID] .flat() @@ -46,52 +55,62 @@ export const useAuth = () => { } ) - const isLogged = !!jwt && !!userGroups?.length && !isLoginInProgress - - /** - * Looking for resource view of user authenticated. - * - * @param {RESOURCE_NAMES} resourceName - Name of resource - * @returns {{ - * resource_name: string, - * actions: object[], - * filters: object[], - * info-tabs: object, - * dialogs: object[] - * }} Returns view of resource - */ - const getResourceView = useCallback( - (resourceName) => - views?.[view]?.find( - ({ resource_name: name }) => - String(name).toLowerCase() === String(resourceName).toLowerCase() - ), - [view] + return useMemo( + () => ({ + ...auth, + user, + isOneAdmin: user?.ID === ONEADMIN_ID, + groups: authGroups, + // Merge user settings with the existing one + settings: { ...settings, ...(user?.TEMPLATE?.FIREEDGE ?? {}) }, + isLogged: + !!jwt && + !!user && + !!authGroups?.length && + !isLoginInProgress && + waitViewToLogin, + }), + [user, jwt, isLoginInProgress, authGroups, auth, waitViewToLogin] ) - - return { - ...auth, - groups: userGroups, - isLogged, - getResourceView, - views, - } } export const useAuthApi = () => { const dispatch = useDispatch() - const unwrapDispatch = useCallback( - (action) => dispatch(action).then(unwrapResult), - [dispatch] - ) - return { - login: (user) => unwrapDispatch(actions.login(user)), - getAuthUser: () => dispatch(actions.getUser()), - changeGroup: (group) => unwrapDispatch(actions.changeGroup(group)), + stopFirstRender: () => dispatch(actions.stopFirstRender()), logout: () => dispatch(actions.logout()), - changeView: (view) => dispatch(actions.changeView(view)), + changeJwt: (jwt) => dispatch(actions.changeJwt(jwt)), } } + +// -------------------------------------------------------------- +// View Hooks +// -------------------------------------------------------------- + +export const useViews = () => { + const { jwt, view } = useSelector((state) => state[authSlice], shallowEqual) + + const { data: views } = systemApi.endpoints.getSunstoneViews.useQueryState( + undefined, + { skip: !jwt } + ) + + /** + * Looking for resource view of user authenticated. + * + * @param {RESOURCE_NAMES} resourceName - Name of resource + * @returns {ResourceView} Returns view of resource + */ + const getResourceView = useCallback( + (resourceName) => + views?.[view]?.find( + ({ resource_name: name }) => + `${name}`.toLowerCase() === `${resourceName}`.toLowerCase() + ), + [view] + ) + + return useMemo(() => ({ getResourceView, views, view }), [views, view]) +} diff --git a/src/fireedge/src/client/features/Auth/slice.js b/src/fireedge/src/client/features/Auth/slice.js index a1689d8e20..63a375dea4 100644 --- a/src/fireedge/src/client/features/Auth/slice.js +++ b/src/fireedge/src/client/features/Auth/slice.js @@ -13,82 +13,65 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { createSlice } from '@reduxjs/toolkit' +import { createAction, createSlice } from '@reduxjs/toolkit' -import { - login, - getUser, - logout, - changeFilter, - changeGroup, - changeView, -} from 'client/features/Auth/actions' +import { removeStoreData } from 'client/utils' import { JWT_NAME, FILTER_POOL, DEFAULT_SCHEME, DEFAULT_LANGUAGE, } from 'client/constants' -import { isBackend } from 'client/utils' + +export const logout = createAction('logout') const initial = () => ({ - jwt: !isBackend() - ? window.localStorage.getItem(JWT_NAME) ?? - window.sessionStorage.getItem(JWT_NAME) ?? - null - : null, + jwt: null, user: null, - error: null, filterPool: FILTER_POOL.ALL_RESOURCES, settings: { - scheme: DEFAULT_SCHEME, - lang: DEFAULT_LANGUAGE, - disableanimations: 'NO', + SCHEME: DEFAULT_SCHEME, + LANG: DEFAULT_LANGUAGE, + DISABLE_ANIMATIONS: 'NO', }, isLoginInProgress: false, - isLoading: false, }) -const { name, actions, reducer } = createSlice({ +const slice = createSlice({ name: 'auth', initialState: { ...initial(), firstRender: true }, + reducers: { + changeAuthUser: (state, { payload }) => ({ + ...state, + ...payload, + }), + changeJwt: (state, { payload }) => { + state.jwt = payload + }, + changeSettings: (state, { payload }) => { + state.settings = { ...state.settings, payload } + }, + changeFilterPool: (state, { payload: filterPool }) => { + state.filterPool = filterPool + state.isLoginInProgress = false + }, + changeView: (state, { payload }) => { + state.view = payload + }, + stopFirstRender: (state) => { + state.firstRender = false + }, + }, extraReducers: (builder) => { - builder - .addMatcher( - ({ type }) => type === logout.type, - (_, { error }) => ({ ...initial(), error }) - ) - .addMatcher( - ({ type }) => - [ - changeFilter.type, - login.fulfilled.type, - getUser.fulfilled.type, - changeGroup.fulfilled.type, - changeView.type, - ].includes(type), - (state, { payload }) => ({ ...state, ...payload }) - ) - .addMatcher( - ({ type }) => type.startsWith('auth/') && type.endsWith('/pending'), - (state) => ({ ...state, isLoading: true, error: null }) - ) - .addMatcher( - ({ type }) => type.startsWith('auth/') && type.endsWith('/fulfilled'), - (state) => ({ ...state, isLoading: false, firstRender: false }) - ) - .addMatcher( - ({ type }) => type.startsWith('auth/') && type.endsWith('/rejected'), - (state, { payload }) => ({ - ...state, - ...payload, - isLoginInProgress: false, - isLoading: false, - firstRender: false, - jwt: null, - }) - ) + builder.addCase(logout, (_, { payload }) => { + removeStoreData([JWT_NAME]) + + return { ...initial(), error: payload } + }) }, }) -export { name, actions, reducer } +export const { name, reducer } = slice + +const actions = { ...slice.actions, logout } +export { actions } diff --git a/src/fireedge/src/client/features/AuthApi/index.js b/src/fireedge/src/client/features/AuthApi/index.js new file mode 100644 index 0000000000..61d59013a5 --- /dev/null +++ b/src/fireedge/src/client/features/AuthApi/index.js @@ -0,0 +1,131 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, 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 { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' + +import { dismissSnackbar } from 'client/features/General/actions' +import { actions } from 'client/features/Auth/slice' +import userApi from 'client/features/OneApi/user' + +import { storage } from 'client/utils' +import { APP_URL, JWT_NAME, FILTER_POOL, ONEADMIN_ID } from 'client/constants' + +const { ALL_RESOURCES, PRIMARY_GROUP_RESOURCES } = FILTER_POOL + +const authApi = createApi({ + reducerPath: 'authApi', + baseQuery: fetchBaseQuery({ + baseUrl: `${APP_URL}/api/`, + prepareHeaders: (headers, { getState }) => { + const token = getState().auth.jwt + + // If we have a token set in state, + // let's assume that we should be passing it. + token && headers.set('authorization', `Bearer ${token}`) + + return headers + }, + }), + endpoints: (builder) => ({ + getAuthUser: builder.query({ + /** + * @returns {object} Information about authenticated user + * @throws Fails when response isn't code 200 + */ + query: () => ({ url: 'user/info' }), + transformResponse: (response) => response?.data?.USER, + async onQueryStarted(_, { queryFulfilled, dispatch }) { + try { + const { data: user } = await queryFulfilled + dispatch(actions.changeAuthUser({ user })) + } catch {} + }, + }), + login: builder.mutation({ + /** + * @param {object} data - User credentials + * @param {string} data.user - Username + * @param {string} data.token - Password + * @param {boolean} [data.remember] - Remember session + * @param {string} [data.token2fa] - Token for Two factor authentication + * @returns {object} Response data from request + * @throws Fails when response isn't code 200 + */ + query: (data) => ({ url: 'auth', method: 'POST', body: data }), + transformResponse: (response) => { + const { id, token } = response?.data + const isOneAdmin = id === ONEADMIN_ID + + return { + jwt: token, + user: { ID: id }, + isLoginInProgress: !!token && !isOneAdmin, + } + }, + async onQueryStarted({ remember }, { queryFulfilled, dispatch }) { + try { + const { data: queryData } = await queryFulfilled + + if (queryData?.jwt) { + storage(JWT_NAME, queryData?.jwt, remember) + dispatch(dismissSnackbar({ dismissAll: true })) + } + + dispatch(actions.changeAuthUser(queryData)) + } catch {} + }, + }), + changeAuthGroup: builder.mutation({ + /** + * @param {object} data - User credentials + * @param {string} data.group - Group id + * @returns {Promise} Response data from request + * @throws Fails when response isn't code 200 + */ + queryFn: async ({ group } = {}, { getState, dispatch }) => { + try { + if (group === ALL_RESOURCES) { + dispatch(actions.changeFilterPool(ALL_RESOURCES)) + + return { data: '' } + } + + const authUser = getState().auth.user + const queryData = { id: authUser.ID, group: group } + + const response = await dispatch( + userApi.endpoints.changeGroup.initiate(queryData) + ).unwrap() + + dispatch(actions.changeFilterPool(PRIMARY_GROUP_RESOURCES)) + + return { data: response } + } catch (error) { + return { error } + } + }, + }), + }), +}) + +export const { + useGetAuthUserQuery, + useLazyGetAuthUserQuery, + + useLoginMutation, + useChangeAuthGroupMutation, +} = authApi + +export { authApi } diff --git a/src/fireedge/src/client/features/General/slice.js b/src/fireedge/src/client/features/General/slice.js index 90aaf88b57..f9c20023bb 100644 --- a/src/fireedge/src/client/features/General/slice.js +++ b/src/fireedge/src/client/features/General/slice.js @@ -15,7 +15,7 @@ * ------------------------------------------------------------------------- */ import { createSlice } from '@reduxjs/toolkit' -import { logout } from 'client/features/Auth/actions' +import { actions as authActions } from 'client/features/Auth/slice' import * as actions from 'client/features/General/actions' import { generateKey } from 'client/utils' import { APPS_IN_BETA } from 'client/constants' @@ -36,6 +36,13 @@ const { name, reducer } = createSlice({ initialState: initial, extraReducers: (builder) => { builder + /* LOGOUT ACTION */ + .addCase(authActions.logout, (state) => ({ + ...initial, + appTitle: state.appTitle, + isBeta: state.isBeta, + })) + /* UI ACTIONS */ .addCase(actions.fixMenu, (state, { payload }) => ({ ...state, @@ -92,10 +99,6 @@ const { name, reducer } = createSlice({ }) /* REQUESTS API MATCHES */ - .addMatcher( - ({ type }) => type === logout.type, - () => initial - ) .addMatcher( ({ type }) => type.endsWith('/pending') && !type.includes('auth'), (state) => ({ ...state, isLoading: true }) diff --git a/src/fireedge/src/client/features/OneApi/index.js b/src/fireedge/src/client/features/OneApi/index.js index b6f26edda4..5ddaba709c 100644 --- a/src/fireedge/src/client/features/OneApi/index.js +++ b/src/fireedge/src/client/features/OneApi/index.js @@ -16,10 +16,8 @@ import { createApi } from '@reduxjs/toolkit/query/react' import { enqueueSnackbar } from 'client/features/General/actions' -import { logout } from 'client/features/Auth/actions' import { httpCodes } from 'server/utils/constants' import { requestConfig, generateKey } from 'client/utils' -import { T } from 'client/constants' import http from 'client/utils/rest' const ONE_RESOURCES = { @@ -92,15 +90,14 @@ const oneApi = createApi({ const error = message ?? errorFromOned ?? messageFromServer ?? statusText - status === httpCodes.unauthorized.id - ? dispatch(logout(T.SessionExpired)) - : dispatch( - enqueueSnackbar({ - key: generateKey(), - message: error, - options: { variant: 'error' }, - }) - ) + status !== httpCodes.unauthorized.id && + dispatch( + enqueueSnackbar({ + key: generateKey(), + message: error, + options: { variant: 'error' }, + }) + ) return { error: { diff --git a/src/fireedge/src/client/features/OneApi/marketplaceApp.js b/src/fireedge/src/client/features/OneApi/marketplaceApp.js index 007f134472..31db9d2194 100644 --- a/src/fireedge/src/client/features/OneApi/marketplaceApp.js +++ b/src/fireedge/src/client/features/OneApi/marketplaceApp.js @@ -297,7 +297,7 @@ const marketAppApi = oneApi.injectEndpoints({ * @returns {number} Marketplace app id * @throws Fails when response isn't code 200 */ - queryFn: async (params) => { + query: (params) => { const name = ExtraActions.MARKETAPP_IMPORT const command = { name, ...ExtraCommands[name] } diff --git a/src/fireedge/src/client/features/OneApi/system.js b/src/fireedge/src/client/features/OneApi/system.js index 39b47657b4..71d99f9b0d 100644 --- a/src/fireedge/src/client/features/OneApi/system.js +++ b/src/fireedge/src/client/features/OneApi/system.js @@ -18,7 +18,7 @@ import { Actions as SunstoneActions, Commands as SunstoneCommands, } from 'server/routes/api/sunstone/routes' -import { changeView } from 'client/features/Auth/actions' +import { actions } from 'client/features/Auth/slice' import { oneApi, ONE_RESOURCES } from 'client/features/OneApi' const { SYSTEM } = ONE_RESOURCES @@ -86,10 +86,12 @@ const systemApi = oneApi.injectEndpoints({ return { command } }, - async onQueryStarted(_, { dispatch, queryFulfilled }) { + async onQueryStarted(_, { dispatch, getState, queryFulfilled }) { try { const { data: views = {} } = await queryFulfilled - dispatch(changeView(Object.keys(views)[0])) + + const currentView = getState().auth?.view + !currentView && dispatch(actions.changeView(Object.keys(views)[0])) } catch {} }, providesTags: [{ type: SYSTEM, id: 'sunstone-views' }], diff --git a/src/fireedge/src/client/features/OneApi/user.js b/src/fireedge/src/client/features/OneApi/user.js index 6e41952fbf..ce3d6bb4a1 100644 --- a/src/fireedge/src/client/features/OneApi/user.js +++ b/src/fireedge/src/client/features/OneApi/user.js @@ -192,6 +192,12 @@ const userApi = oneApi.injectEndpoints({ user && (user.GID = group) }) ) + + dispatch( + userApi.util.updateQueryData('getUser', id, (draftUser) => { + draftUser.GID = group + }) + ) } catch {} }, }), diff --git a/src/fireedge/src/client/features/Auth/services.js b/src/fireedge/src/client/features/middleware.js similarity index 52% rename from src/fireedge/src/client/features/Auth/services.js rename to src/fireedge/src/client/features/middleware.js index 1eed1e62ac..d3b1cb330c 100644 --- a/src/fireedge/src/client/features/Auth/services.js +++ b/src/fireedge/src/client/features/middleware.js @@ -13,42 +13,21 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { httpCodes } from 'server/utils/constants' -import { RestClient } from 'client/utils' +import { isRejectedWithValue, Middleware, Dispatch } from '@reduxjs/toolkit' +import { actions } from 'client/features/Auth/slice' +import { T } from 'client/constants' -export const authService = { - /** - * @param {object} data - User credentials - * @param {string} data.user - Username - * @param {string} data.token - Password - * @param {boolean} [data.remember] - Remember session - * @param {string} [data.token2fa] - Token for Two factor authentication - * @returns {object} Response data from request - * @throws Fails when response isn't code 200 - */ - login: async (data) => { - const res = await RestClient.request({ - url: '/api/auth', - data, - method: 'POST', - }) - - if (!res?.id || res?.id !== httpCodes.ok.id) { - if (res?.id === httpCodes.accepted.id) return res - throw res +/** + * @param {{ dispatch: Dispatch }} params - Redux parameters + * @returns {Middleware} - Unauthenticated middleware + */ +export const unauthenticatedMiddleware = + ({ dispatch }) => + (next) => + (action) => { + if (isRejectedWithValue(action) && action.payload.status === 401) { + dispatch(actions.logout(T.SessionExpired)) } - return res?.data - }, - /** - * @returns {object} Information about user authenticated - * @throws Fails when response isn't code 200 - */ - getUser: async () => { - const res = await RestClient.request({ url: '/api/user/info' }) - - if (!res?.id || res?.id !== httpCodes.ok.id) throw res - - return res?.data?.USER ?? {} - }, -} + return next(action) + } diff --git a/src/fireedge/src/client/providers/muiProvider.js b/src/fireedge/src/client/providers/muiProvider.js index 233a94215c..a05bfc49c0 100644 --- a/src/fireedge/src/client/providers/muiProvider.js +++ b/src/fireedge/src/client/providers/muiProvider.js @@ -34,12 +34,12 @@ import { SCHEMES } from 'client/constants' const { DARK, LIGHT, SYSTEM } = SCHEMES const MuiProvider = ({ theme: appTheme, children }) => { - const { settings: { scheme } = {} } = useAuth() + const { settings: { SCHEME } = {} } = useAuth() const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)') const changeScheme = () => { const prefersScheme = prefersDarkMode ? DARK : LIGHT - const newScheme = scheme === SYSTEM ? prefersScheme : scheme + const newScheme = SCHEME === SYSTEM ? prefersScheme : SCHEME return createTheme(appTheme, newScheme) } @@ -55,7 +55,7 @@ const MuiProvider = ({ theme: appTheme, children }) => { useEffect(() => { setTheme(changeScheme) - }, [scheme, prefersDarkMode]) + }, [SCHEME, prefersDarkMode]) return ( diff --git a/src/fireedge/src/client/store/index.js b/src/fireedge/src/client/store/index.js index 0b64f7f8da..decb2bae74 100644 --- a/src/fireedge/src/client/store/index.js +++ b/src/fireedge/src/client/store/index.js @@ -20,7 +20,9 @@ import { isDevelopment } from 'client/utils' import * as Auth from 'client/features/Auth/slice' import * as General from 'client/features/General/slice' +import { authApi } from 'client/features/AuthApi' import { oneApi } from 'client/features/OneApi' +import { unauthenticatedMiddleware } from 'client/features/middleware' /** * @param {object} props - Props @@ -32,13 +34,18 @@ export const createStore = ({ initState = {} }) => { reducer: { [Auth.name]: Auth.reducer, [General.name]: General.reducer, + [authApi.reducerPath]: authApi.reducer, [oneApi.reducerPath]: oneApi.reducer, }, devTools: isDevelopment(), middleware: (getDefaultMiddleware) => getDefaultMiddleware({ immutableCheck: true, - }).concat(oneApi.middleware), + }).concat([ + unauthenticatedMiddleware, + authApi.middleware, + oneApi.middleware, + ]), preloadedState: initState, }) diff --git a/src/fireedge/src/client/store/reducers.js b/src/fireedge/src/client/store/reducers.js index a2c46410f9..742a8b2165 100644 --- a/src/fireedge/src/client/store/reducers.js +++ b/src/fireedge/src/client/store/reducers.js @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -const { combineReducers } = require('redux') +const { combineReducers } = require('@reduxjs/toolkit') const Auth = require('client/features/Auth/slice') const General = require('client/features/General/slice') +const { authApi } = require('client/features/AuthApi') const { oneApi } = require('client/features/OneApi') const rootReducer = combineReducers({ general: General.reducer, auth: Auth.reducer, + [authApi.reducerPath]: authApi.reducer, [oneApi.reducerPath]: oneApi.reducer, }) diff --git a/src/fireedge/src/client/theme/defaults.js b/src/fireedge/src/client/theme/defaults.js index a648bbfae5..7c22fda1f0 100644 --- a/src/fireedge/src/client/theme/defaults.js +++ b/src/fireedge/src/client/theme/defaults.js @@ -71,7 +71,7 @@ const buttonSvgStyle = { */ export default (appTheme, mode = SCHEMES.DARK) => { const { primary, secondary } = appTheme.palette - const isDarkMode = mode === SCHEMES.DARK + const isDarkMode = `${mode}`.toLowerCase() === SCHEMES.DARK return { palette: { @@ -237,6 +237,16 @@ export default (appTheme, mode = SCHEMES.DARK) => { MuiCssBaseline: { styleOverrides: { '@font-face': UbuntuFont, + '.loading_screen': { + width: '100%', + height: '100vh', + backgroundColor: 'background.default', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + position: 'fixed', + zIndex: 10000, + }, '.description__link': { margin: 0, color: isDarkMode ? secondary.main : secondary.dark,