1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-22 18:50:08 +03:00

F #5637: Fix logout when refresh the browser (#1788)

(cherry picked from commit 60dbc2b734c04515b1d06def1b350b272dc7a538)
This commit is contained in:
Sergio Betanzos 2022-02-18 11:42:33 +01:00 committed by Tino Vazquez
parent 3e4f1c49bb
commit 5f8924c342
67 changed files with 609 additions and 551 deletions

View File

@ -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 <LoadingScreen />
}
return (
<>
<AuthLayout
subscriptions={[
provisionApi.endpoints.getProvisionTemplates,
providerApi.endpoints.getProviderConfig,
]}
>
{isLogged && (
<>
<Sidebar endpoints={endpoints} />
@ -99,7 +87,7 @@ const ProvisionApp = () => {
</>
)}
<Router redirectWhenAuth={PATH.DASHBOARD} endpoints={endpoints} />
</>
</AuthLayout>
)
}

View File

@ -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 <LoadingScreen />
}
if (!view) return fixedEndpoints
const viewEndpoints = getEndpointsByView(views?.[view], ONE_ENDPOINTS)
return fixedEndpoints.concat(viewEndpoints)
}, [view])
return (
<>
<AuthLayout
subscriptions={[
systemApi.endpoints.getOneConfig,
systemApi.endpoints.getSunstoneConfig,
systemApi.endpoints.getSunstoneViews,
]}
>
{isLogged && (
<>
<Sidebar endpoints={endpoints} />
@ -87,7 +77,7 @@ const SunstoneApp = () => {
</>
)}
<Router redirectWhenAuth={PATH.DASHBOARD} endpoints={endpoints} />
</>
</AuthLayout>
)
}

View File

@ -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])

View File

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

View File

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

View File

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

View File

@ -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])

View File

@ -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 <FullscreenProgress />
}
return <>{children}</>
}
AuthLayout.propTypes = {
subscriptions: PropTypes.array,
children: PropTypes.any,
}
AuthLayout.displayName = 'AuthLayout'
export default AuthLayout

View File

@ -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 (
<TranslateContext.Provider value={value}>
<TranslateContext.Provider value={{ lang, hash, changeLang }}>
{children}
</TranslateContext.Provider>
)

View File

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

View File

@ -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 (
<Button
fullWidth
disabled={disabled}
color="debug"
variant="outlined"
onClick={() => {
ID && changeGroup({ id: user.ID, group: ID })
handleClick()
}}
onClick={handleClick}
sx={{
color: (theme) => theme.palette.text.primary,
justifyContent: 'start',
@ -58,7 +55,8 @@ const ButtonGroup = memo(
</Button>
)
},
(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={<GroupIcon />}
tooltip={<Translate word={T.SwitchGroup} />}
buttonProps={{ 'data-cy': 'header-group-button' }}
headerTitle={<Translate word={T.SwitchGroup} />}
headerTitle={
<Stack direction="row" alignItems="center" gap="1em" component="span">
<Translate word={T.SwitchGroup} />
{isLoading && <CircularProgress size={20} />}
</Stack>
}
>
{({ handleClose }) => (
<Search
@ -101,7 +105,11 @@ const Group = () => {
<ButtonGroup
key={`switcher-group-${group?.ID}`}
group={group}
handleClick={handleClose}
disabled={isLoading}
handleClick={async () => {
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'

View File

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

View File

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

View File

@ -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 = () => (
<Box
sx={{
width: '100%',
height: '100vh',
backgroundColor: 'background.default',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'fixed',
zIndex: 10000,
}}
>
<Box className="loading_screen">
<OpenNebulaLogo width={360} height={360} spinner withText />
</Box>
)

View File

@ -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 ? (
<Redirect to={redirectWhenAuth} />

View File

@ -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 ? <Route {...props} /> : <Redirect to="/" />
return isAuthenticated ? <Route {...props} /> : <Redirect to="/" />
}
export default ProtectedRoute

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, {

View File

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

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

@ -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(() => {

View File

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

View File

@ -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 (
<Container
disableGutters
{...(stringToBoolean(disableanimations) && {
{...(stringToBoolean(DISABLE_ANIMATIONS) && {
sx: {
'& *, & *::before, & *::after': {
animation: 'none !important',

View File

@ -35,12 +35,12 @@ import { T } from 'client/constants'
/** @returns {ReactElement} Sunstone dashboard container */
function SunstoneDashboard() {
const { settings: { disableanimations } = {} } = useAuth()
const { settings: { DISABLE_ANIMATIONS } = {} } = useAuth()
return (
<Container
disableGutters
{...(stringToBoolean(disableanimations) && {
{...(stringToBoolean(DISABLE_ANIMATIONS) && {
sx: {
'& *, & *::before, & *::after': {
animation: 'none !important',

View File

@ -24,7 +24,10 @@ import {
} from '@mui/material'
import { useAuth, useAuthApi } from 'client/features/Auth'
import { useFetch } from 'client/hooks'
import {
useLoginMutation,
useChangeAuthGroupMutation,
} from 'client/features/AuthApi'
import Form from 'client/containers/Login/Form'
import * as FORMS from 'client/containers/Login/schema'
@ -41,14 +44,14 @@ function Login() {
const classes = loginStyles()
const isMobile = useMediaQuery((theme) => 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 (
<Container
component="main"
@ -111,7 +113,7 @@ function Login() {
onSubmit={handleSubmitUser}
resolver={FORMS.FORM_USER_SCHEMA}
fields={FORMS.FORM_USER_FIELDS}
error={error}
error={errorMessage}
isLoading={isLoading}
/>
)}
@ -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}
/>
)}

View File

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

View File

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

View File

@ -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://<host:port>/fireedge/<APP>.
*
* @param {object} props - Props from server
* @returns {JSXElementConstructor} Returns App
* @returns {ReactElement} Returns App
*/
const DevelopmentApp = (props) => {
let appName = ''

View File

@ -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 },
}))

View File

@ -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])
}

View File

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

View File

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

View File

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

View File

@ -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: {

View File

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

View File

@ -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' }],

View File

@ -192,6 +192,12 @@ const userApi = oneApi.injectEndpoints({
user && (user.GID = group)
})
)
dispatch(
userApi.util.updateQueryData('getUser', id, (draftUser) => {
draftUser.GID = group
})
)
} catch {}
},
}),

View File

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

View File

@ -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 (
<LocalizationProvider dateAdapter={AdapterLuxon}>

View File

@ -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,
})

View File

@ -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,
})

View File

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