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

F #5422: Add other app links to login (#2050)

This commit is contained in:
Sergio Betanzos 2022-05-18 12:48:31 +02:00 committed by GitHub
parent 2c7ed26c9a
commit ecd38674b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 112 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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