mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
2c7ed26c9a
commit
ecd38674b1
@ -37,6 +37,7 @@ import Group from 'client/components/Header/Group'
|
||||
import Zone from 'client/components/Header/Zone'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { sentenceCase } from 'client/utils'
|
||||
import { APPS_WITH_ONE_PREFIX } from 'client/constants'
|
||||
|
||||
const Header = memo(({ route: { title, description } = {} }) => {
|
||||
const { isOneAdmin } = useAuth()
|
||||
@ -77,7 +78,7 @@ const Header = memo(({ route: { title, description } = {} }) => {
|
||||
sx={{ userSelect: 'none' }}
|
||||
>
|
||||
<Typography variant="h6" data-cy="header-app-title">
|
||||
{'One'}
|
||||
{APPS_WITH_ONE_PREFIX.includes(appTitle) && 'One'}
|
||||
<Typography
|
||||
variant={'inherit'}
|
||||
color="secondary.800"
|
||||
|
@ -38,8 +38,10 @@ export const SERVER_CONFIG = (() => {
|
||||
// should be equal to the apps in src/server/utils/constants/defaults.js
|
||||
export const _APPS = { sunstone: 'sunstone', provision: 'provision' }
|
||||
export const APPS = Object.keys(_APPS)
|
||||
|
||||
export const APPS_IN_BETA = []
|
||||
export const APPS_WITH_SWITCHER = [_APPS.sunstone]
|
||||
export const APPS_WITH_ONE_PREFIX = [_APPS.provision]
|
||||
|
||||
export const APP_URL = '/fireedge'
|
||||
export const WEBSOCKET_URL = `${APP_URL}/websockets`
|
||||
|
@ -209,7 +209,7 @@ module.exports = {
|
||||
HoursBetween0_168: 'Hours should be between 0 and 168',
|
||||
WhenYouWantThatTheActionFinishes: 'When you want that the action finishes',
|
||||
|
||||
/* dashboard */
|
||||
/* footer */
|
||||
MadeWith: 'Made with',
|
||||
|
||||
/* dashboard */
|
||||
@ -224,6 +224,7 @@ module.exports = {
|
||||
Credentials: 'Credentials',
|
||||
SwitchView: 'Switch view',
|
||||
SwitchGroup: 'Switch group',
|
||||
TakeMeToTheAppGui: 'Take me to the %s GUI',
|
||||
|
||||
/* errors */
|
||||
SessionExpired: 'Sorry, your session has expired',
|
||||
|
@ -17,16 +17,13 @@
|
||||
import { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { Button, Box, Slide, Stack } from '@mui/material'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const Form = ({
|
||||
@ -38,12 +35,9 @@ const Form = ({
|
||||
isLoading,
|
||||
transitionProps,
|
||||
}) => {
|
||||
const defaultValues = resolver.default()
|
||||
const classes = loginStyles()
|
||||
|
||||
const { handleSubmit, setError, ...methods } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues,
|
||||
defaultValues: resolver.default(),
|
||||
resolver: yupResolver(resolver),
|
||||
})
|
||||
|
||||
@ -61,23 +55,28 @@ const Form = ({
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className={clsx(classes.form, { [classes.loading]: isLoading })}
|
||||
width="100%"
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
flexShrink={0}
|
||||
justifyContent={{ sm: 'center' }}
|
||||
sx={{ opacity: isLoading ? 0.7 : 1 }}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<FormWithSchema cy="login" fields={fields} />
|
||||
</FormProvider>
|
||||
<Stack direction="row" gap={1} m={2}>
|
||||
<Stack direction="row" gap={1} my={2}>
|
||||
{onBack && (
|
||||
<Button color="secondary" onClick={onBack} disabled={isLoading}>
|
||||
{Tr(T.Back)}
|
||||
<Translate word={T.Back} />
|
||||
</Button>
|
||||
)}
|
||||
<SubmitButton
|
||||
color="secondary"
|
||||
data-cy="login-button"
|
||||
isSubmitting={isLoading}
|
||||
sx={{ textTransform: 'uppercase', padding: '0.5em' }}
|
||||
label={onBack ? Tr(T.Next) : Tr(T.SignIn)}
|
||||
sx={{ textTransform: 'uppercase' }}
|
||||
label={<Translate word={onBack ? T.Next : T.SignIn} />}
|
||||
/>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
@ -13,26 +13,30 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { ReactElement, useState, memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
LinearProgress,
|
||||
Paper,
|
||||
Link,
|
||||
useMediaQuery,
|
||||
} from '@mui/material'
|
||||
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import {
|
||||
useLoginMutation,
|
||||
useChangeAuthGroupMutation,
|
||||
} from 'client/features/AuthApi'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import { useGeneral } from 'client/features/General'
|
||||
|
||||
import Form from 'client/containers/Login/Form'
|
||||
import * as FORMS from 'client/containers/Login/schema'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
import { OpenNebulaLogo } from 'client/components/Icons'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { sentenceCase } from 'client/utils'
|
||||
import { T, APPS, APP_URL, APPS_WITH_ONE_PREFIX } from 'client/constants'
|
||||
|
||||
const STEPS = {
|
||||
USER_FORM: 0,
|
||||
@ -40,18 +44,23 @@ const STEPS = {
|
||||
GROUP_FORM: 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the login form and handles the login process.
|
||||
*
|
||||
* @returns {ReactElement} The login form.
|
||||
*/
|
||||
function Login() {
|
||||
const classes = loginStyles()
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.only('xs'))
|
||||
|
||||
const { logout } = useAuthApi()
|
||||
const { error: otherError, isLoginInProgress: needGroupToContinue } =
|
||||
useAuth()
|
||||
const { error: authError, isLoginInProgress: needGroupToContinue } = useAuth()
|
||||
|
||||
const { appTitle } = useGeneral()
|
||||
|
||||
const [changeAuthGroup, changeAuthGroupState] = useChangeAuthGroupMutation()
|
||||
const [login, loginState] = useLoginMutation()
|
||||
const isLoading = loginState.isLoading || changeAuthGroupState.isLoading
|
||||
const errorMessage = loginState.error?.data?.message ?? otherError
|
||||
const errorMessage = loginState.error?.data?.message ?? authError
|
||||
|
||||
const [dataUserForm, setDataUserForm] = useState(undefined)
|
||||
const [step, setStep] = useState(() =>
|
||||
@ -72,7 +81,9 @@ function Login() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const handleSubmitGroup = (dataForm) => changeAuthGroup(dataForm)
|
||||
const handleSubmitGroup = (dataForm) => {
|
||||
changeAuthGroup(dataForm)
|
||||
}
|
||||
|
||||
const handleBack = () => {
|
||||
logout()
|
||||
@ -85,24 +96,39 @@ function Login() {
|
||||
component="main"
|
||||
disableGutters={isMobile}
|
||||
maxWidth={isMobile ? 'lg' : 'xs'}
|
||||
className={classes.root}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
{isLoading && (
|
||||
<LinearProgress color="secondary" className={classes.loading} />
|
||||
)}
|
||||
<Paper variant="outlined" className={classes.paper}>
|
||||
{useMemo(
|
||||
() => (
|
||||
<OpenNebulaLogo
|
||||
data-cy="opennebula-logo"
|
||||
height={100}
|
||||
width="100%"
|
||||
withText
|
||||
/>
|
||||
),
|
||||
[]
|
||||
)}
|
||||
<Box className={classes.wrapperForm}>
|
||||
<LinearProgress
|
||||
color="secondary"
|
||||
sx={{ visibility: isLoading ? 'visible' : 'hidden' }}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
p: 2,
|
||||
overflow: 'hidden',
|
||||
minHeight: 440,
|
||||
border: ({ palette }) => ({
|
||||
xs: 'none',
|
||||
sm: `1px solid ${palette.divider}`,
|
||||
}),
|
||||
borderRadius: ({ shape }) => shape.borderRadius / 2,
|
||||
height: { xs: 'calc(100vh - 4px)', sm: 'auto' },
|
||||
backgroundColor: { xs: 'transparent', sm: 'background.paper' },
|
||||
}}
|
||||
>
|
||||
<OpenNebulaLogo
|
||||
data-cy="opennebula-logo"
|
||||
height={100}
|
||||
width="100%"
|
||||
withText
|
||||
/>
|
||||
|
||||
<Box display="flex" py={2} px={1} overflow="hidden">
|
||||
{step === STEPS.USER_FORM && (
|
||||
<Form
|
||||
transitionProps={{
|
||||
@ -146,13 +172,36 @@ function Login() {
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{APPS?.filter((app) => app !== `${appTitle}`.toLowerCase())?.map(
|
||||
(app) => (
|
||||
<AppLink key={app} app={app} />
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
Login.propTypes = {}
|
||||
const AppLink = memo(({ app }) => {
|
||||
const name = APPS_WITH_ONE_PREFIX.includes(app)
|
||||
? `One${sentenceCase(app)}`
|
||||
: sentenceCase(app)
|
||||
|
||||
Login.defaultProps = {}
|
||||
return (
|
||||
<Link
|
||||
key={app}
|
||||
href={`${APP_URL}/${app}`}
|
||||
variant="caption"
|
||||
color="text.secondary"
|
||||
padding={1}
|
||||
>
|
||||
<Translate word={T.TakeMeToTheAppGui} value={name} />
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
|
||||
AppLink.displayName = 'AppLink'
|
||||
AppLink.propTypes = { app: PropTypes.string.isRequired }
|
||||
|
||||
export default Login
|
||||
|
@ -1,67 +0,0 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2022, 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 makeStyles from '@mui/styles/makeStyles'
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
},
|
||||
progress: {
|
||||
height: 4,
|
||||
width: '100%',
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
top: 0,
|
||||
position: 'fixed',
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
overflow: 'hidden',
|
||||
padding: theme.spacing(2),
|
||||
minHeight: 440,
|
||||
[theme.breakpoints.up('xs')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
border: 'none',
|
||||
height: 'calc(100vh - 4px)',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
wrapperForm: {
|
||||
padding: theme.spacing(2, 1),
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
form: {
|
||||
width: '100%',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
[theme.breakpoints.up('xs')]: {
|
||||
justifyContent: 'center',
|
||||
},
|
||||
},
|
||||
loading: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
helper: {
|
||||
animation: '1s ease-out 0s 1',
|
||||
},
|
||||
}))
|
Loading…
x
Reference in New Issue
Block a user