mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-27 10:50:10 +03:00
parent
440941d712
commit
2bba7a7bab
@ -52,7 +52,7 @@ const App = ({ location, context, store, app }) => {
|
||||
{location && context ? (
|
||||
// server build
|
||||
<StaticRouter location={location} context={context}>
|
||||
{/* <Router app={appName} /> */}
|
||||
<Router app={appName} />
|
||||
</StaticRouter>
|
||||
) : (
|
||||
// browser build
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
makeStyles,
|
||||
Box,
|
||||
Fade,
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
|
@ -13,7 +13,7 @@ import ErrorHelper from 'client/components/FormControl/ErrorHelper'
|
||||
import { Tr } from 'client/components/HOC/Translate'
|
||||
|
||||
const CheckboxController = memo(
|
||||
({ control, cy, name, label, tooltip, error }) => (
|
||||
({ control, cy, name, label, tooltip, error, fieldProps }) => (
|
||||
<Controller
|
||||
render={({ onChange, value }) => (
|
||||
<Tooltip title={Tr(tooltip) ?? ''}>
|
||||
@ -26,6 +26,7 @@ const CheckboxController = memo(
|
||||
checked={value}
|
||||
color="primary"
|
||||
inputProps={{ 'data-cy': cy }}
|
||||
{...fieldProps}
|
||||
/>
|
||||
}
|
||||
label={Tr(label)}
|
||||
@ -51,7 +52,8 @@ CheckboxController.propTypes = {
|
||||
error: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.objectOf(PropTypes.any)
|
||||
])
|
||||
]),
|
||||
fieldProps: PropTypes.object
|
||||
}
|
||||
|
||||
CheckboxController.defaultProps = {
|
||||
@ -61,7 +63,8 @@ CheckboxController.defaultProps = {
|
||||
label: '',
|
||||
tooltip: undefined,
|
||||
values: [],
|
||||
error: false
|
||||
error: false,
|
||||
fieldProps: undefined
|
||||
}
|
||||
|
||||
CheckboxController.displayName = 'CheckboxController'
|
||||
|
@ -1,79 +0,0 @@
|
||||
/* Copyright 2002-2019, OpenNebula Project, OpenNebula Systems */
|
||||
/* */
|
||||
/* Licensed under the Apache License, Version 2.0 (the "License"); you may */
|
||||
/* not use this file except in compliance with the License. You may obtain */
|
||||
/* a copy of the License at */
|
||||
/* */
|
||||
/* http://www.apache.org/licenses/LICENSE-2.0 */
|
||||
/* */
|
||||
/* Unless required by applicable law or agreed to in writing, software */
|
||||
/* distributed under the License is distributed on an "AS IS" BASIS, */
|
||||
/* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
|
||||
/* See the License for the specific language governing permissions and */
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import { MenuItem, TextField } from '@material-ui/core'
|
||||
import { FilterVintage } from '@material-ui/icons'
|
||||
|
||||
import useAuth from 'client/hooks/useAuth'
|
||||
import useOpennebula from 'client/hooks/useOpennebula'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { FILTER_POOL } from 'client/constants'
|
||||
|
||||
const GroupSelect = props => {
|
||||
const { filterPool, authUser } = useAuth()
|
||||
const { groups } = useOpennebula()
|
||||
|
||||
const defaultValue = useMemo(
|
||||
() =>
|
||||
filterPool === FILTER_POOL.ALL_RESOURCES
|
||||
? FILTER_POOL.ALL_RESOURCES
|
||||
: authUser?.GID,
|
||||
[filterPool]
|
||||
)
|
||||
|
||||
const orderGroups = useMemo(
|
||||
() =>
|
||||
groups
|
||||
?.sort((a, b) => a.ID - b.ID)
|
||||
?.map(({ ID, NAME }) => (
|
||||
<MenuItem key={`selector-group-${ID}`} value={String(ID)}>
|
||||
{`${ID} - ${String(NAME)}`}
|
||||
{authUser?.GID === ID && (
|
||||
<FilterVintage
|
||||
style={{
|
||||
fontSize: '1rem',
|
||||
marginLeft: 16
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
)),
|
||||
[groups]
|
||||
)
|
||||
|
||||
return (
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
defaultValue={defaultValue}
|
||||
variant="outlined"
|
||||
inputProps={{ 'data-cy': 'select-group' }}
|
||||
label={Tr('Select a group')}
|
||||
FormHelperTextProps={{ 'data-cy': 'select-group-error' }}
|
||||
{...props}
|
||||
>
|
||||
<MenuItem value={FILTER_POOL.ALL_RESOURCES}>{Tr('Show all')}</MenuItem>
|
||||
{orderGroups}
|
||||
</TextField>
|
||||
)
|
||||
}
|
||||
|
||||
GroupSelect.propTypes = {}
|
||||
|
||||
GroupSelect.defaultProps = {}
|
||||
|
||||
export default GroupSelect
|
@ -8,18 +8,19 @@ import ErrorHelper from 'client/components/FormControl/ErrorHelper'
|
||||
import { Tr } from 'client/components/HOC/Translate'
|
||||
|
||||
const SelectController = memo(
|
||||
({ control, cy, name, label, values, error }) => (
|
||||
({ control, cy, name, label, values, error, fieldProps }) => (
|
||||
<Controller
|
||||
as={
|
||||
<TextField
|
||||
select
|
||||
SelectProps={{ displayEmpty: true }}
|
||||
fullWidth
|
||||
SelectProps={{ displayEmpty: true }}
|
||||
label={Tr(label)}
|
||||
inputProps={{ 'data-cy': cy }}
|
||||
error={Boolean(error)}
|
||||
helperText={Boolean(error) && <ErrorHelper label={error?.message} />}
|
||||
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
|
||||
{...fieldProps}
|
||||
>
|
||||
{Array.isArray(values) &&
|
||||
values?.map(({ text, value }) => (
|
||||
@ -31,6 +32,7 @@ const SelectController = memo(
|
||||
}
|
||||
name={name}
|
||||
control={control}
|
||||
defaultValue={values[0]?.value}
|
||||
/>
|
||||
),
|
||||
(prevProps, nextProps) => prevProps.error === nextProps.error
|
||||
@ -45,7 +47,8 @@ SelectController.propTypes = {
|
||||
error: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.objectOf(PropTypes.any)
|
||||
])
|
||||
]),
|
||||
fieldProps: PropTypes.object
|
||||
}
|
||||
|
||||
SelectController.defaultProps = {
|
||||
@ -54,7 +57,8 @@ SelectController.defaultProps = {
|
||||
name: '',
|
||||
label: '',
|
||||
values: [],
|
||||
error: false
|
||||
error: false,
|
||||
fieldProps: undefined
|
||||
}
|
||||
|
||||
SelectController.displayName = 'SelectController'
|
||||
|
@ -8,7 +8,7 @@ import { Tr } from 'client/components/HOC'
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
|
||||
|
||||
const TextController = memo(
|
||||
({ control, cy, type, name, label, error }) => (
|
||||
({ control, cy, type, name, label, error, fieldProps }) => (
|
||||
<Controller
|
||||
render={({ value, ...props }) =>
|
||||
<TextField
|
||||
@ -21,6 +21,7 @@ const TextController = memo(
|
||||
helperText={Boolean(error) && <ErrorHelper label={error?.message} />}
|
||||
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
|
||||
{...props}
|
||||
{...fieldProps}
|
||||
/>
|
||||
}
|
||||
name={name}
|
||||
@ -40,7 +41,8 @@ TextController.propTypes = {
|
||||
error: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.objectOf(PropTypes.any)
|
||||
])
|
||||
]),
|
||||
fieldProps: PropTypes.object
|
||||
}
|
||||
|
||||
TextController.defaultProps = {
|
||||
@ -49,7 +51,8 @@ TextController.defaultProps = {
|
||||
type: 'text',
|
||||
name: '',
|
||||
label: '',
|
||||
error: false
|
||||
error: false,
|
||||
fieldProps: undefined
|
||||
}
|
||||
|
||||
TextController.displayName = 'TextController'
|
||||
|
@ -26,7 +26,7 @@ const FormWithSchema = ({ id, cy, fields }) => {
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
{fields?.map(
|
||||
({ name, type, htmlType, label, values, dependOf, tooltip, grid }) => {
|
||||
({ name, type, htmlType, label, values, dependOf, tooltip, grid, fieldProps }) => {
|
||||
const dataCy = `${cy}-${name}`
|
||||
const inputName = id ? `${id}.${name}` : name
|
||||
|
||||
@ -36,7 +36,7 @@ const FormWithSchema = ({ id, cy, fields }) => {
|
||||
? useWatch({ control, name: id ? `${id}.${dependOf}` : dependOf })
|
||||
: null
|
||||
|
||||
const htmlTypeValue = typeof htmlType === 'function' && dependOf
|
||||
const htmlTypeValue = typeof htmlType === 'function'
|
||||
? htmlType(dependValue)
|
||||
: htmlType
|
||||
|
||||
@ -53,10 +53,11 @@ const FormWithSchema = ({ id, cy, fields }) => {
|
||||
name: inputName,
|
||||
label,
|
||||
tooltip,
|
||||
values: typeof values === 'function' && dependOf
|
||||
values: typeof values === 'function'
|
||||
? values(dependValue)
|
||||
: values,
|
||||
error: inputError
|
||||
error: inputError,
|
||||
fieldProps
|
||||
})}
|
||||
</Grid>
|
||||
</HiddenInput>
|
||||
|
@ -1,12 +1,15 @@
|
||||
module.exports = {
|
||||
SignIn: 'Sign In',
|
||||
Back: 'Back',
|
||||
Next: 'Next',
|
||||
Username: 'Username',
|
||||
Password: 'Password',
|
||||
Token2FA: '2FA Token',
|
||||
SelectGroup: 'Select a group',
|
||||
ShowAll: 'Show all',
|
||||
NotFound: 'Not found',
|
||||
Language: 'Language',
|
||||
KeepLoggedIn: 'Keep me logged in',
|
||||
Token2FA: '2FA Token',
|
||||
SignOut: 'Sign Out',
|
||||
Submit: 'Submit',
|
||||
Response: 'Response',
|
||||
|
@ -20,12 +20,12 @@ const CustomDialog = ({ title, handleClose, children }) => {
|
||||
fullScreen={isMobile}
|
||||
open
|
||||
onClose={handleClose}
|
||||
maxWidth="lg"
|
||||
maxWidth="xl"
|
||||
scroll="paper"
|
||||
PaperProps={{
|
||||
style: {
|
||||
height: isMobile ? '100%' : '80%',
|
||||
width: isMobile ? '100%' : '80%'
|
||||
height: isMobile ? '100%' : '90%',
|
||||
width: isMobile ? '100%' : '90%'
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -21,7 +21,12 @@ const DialogInfo = ({ info, handleClose }) => {
|
||||
|
||||
const renderTabs = useMemo(() => (
|
||||
<AppBar position="static">
|
||||
<Tabs value={tabSelected} onChange={(_, tab) => setTab(tab)}>
|
||||
<Tabs
|
||||
value={tabSelected}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
onChange={(_, tab) => setTab(tab)}
|
||||
>
|
||||
{TABS.map(({ name, icon: Icon }, idx) =>
|
||||
<Tab
|
||||
key={`tab-${name}`}
|
||||
|
91
src/fireedge/src/client/containers/Login/Form.js
Normal file
91
src/fireedge/src/client/containers/Login/Form.js
Normal file
@ -0,0 +1,91 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { Box, Button, Slide } from '@material-ui/core'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers'
|
||||
|
||||
import { SignIn, Back, Next } from 'client/constants/translates'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
const Form = ({ onBack, onSubmit, resolver, fields, error, isLoading, transitionProps }) => {
|
||||
const defaultValues = resolver.default()
|
||||
const classes = loginStyles()
|
||||
|
||||
console.log(defaultValues)
|
||||
|
||||
const { handleSubmit, setError, ...methods } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
defaultValues,
|
||||
resolver: yupResolver(resolver)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
error && setError(fields[0].name, { type: 'manual', message: error })
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<Slide
|
||||
timeout={{ enter: 400 }}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
{...transitionProps}
|
||||
>
|
||||
<Box
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className={clsx(classes.form, { [classes.loading]: isLoading })}
|
||||
>
|
||||
<FormProvider {...methods}>
|
||||
<FormWithSchema cy="login" fields={fields} />
|
||||
</FormProvider>
|
||||
<Box>
|
||||
{onBack && (
|
||||
<Button onClick={onBack} color="primary" disabled={isLoading}>
|
||||
{Tr(Back)}
|
||||
</Button>
|
||||
)}
|
||||
<ButtonSubmit
|
||||
data-cy="login-button"
|
||||
isSubmitting={isLoading}
|
||||
label={onBack ? Tr(Next) : Tr(SignIn)}
|
||||
className={classes.submit}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Slide>
|
||||
)
|
||||
}
|
||||
|
||||
Form.propTypes = {
|
||||
onBack: PropTypes.func,
|
||||
resolver: PropTypes.object,
|
||||
fields: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
})
|
||||
),
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
error: PropTypes.string,
|
||||
isLoading: PropTypes.bool,
|
||||
transitionProps: PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
Form.defaultProps = {
|
||||
onBack: undefined,
|
||||
onSubmit: () => undefined,
|
||||
resolver: {},
|
||||
fields: [],
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
transitionProps: undefined
|
||||
}
|
||||
|
||||
export default Form
|
@ -1,78 +0,0 @@
|
||||
import React from 'react'
|
||||
import { func, string } from 'prop-types'
|
||||
|
||||
import { Box, Button, TextField } from '@material-ui/core'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers'
|
||||
import * as yup from 'yup'
|
||||
|
||||
import { Token2FA, Next } from 'client/constants/translates'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
|
||||
|
||||
const Form2fa = ({ onBack, onSubmit, error }) => {
|
||||
const classes = loginStyles()
|
||||
|
||||
const { register, handleSubmit, errors } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(
|
||||
yup.object().shape({
|
||||
token2fa: yup.string().required('Authenticator is a required field')
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const tokenError = Boolean(errors.token || error)
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
required
|
||||
name="token2fa"
|
||||
label={Tr(Token2FA)}
|
||||
variant="outlined"
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': 'login-token' }}
|
||||
error={tokenError}
|
||||
helperText={
|
||||
tokenError && <ErrorHelper label={errors.token?.message || error} />
|
||||
}
|
||||
FormHelperTextProps={{ 'data-cy': 'login-username-error' }}
|
||||
/>
|
||||
<Box>
|
||||
<Button onClick={onBack} color="primary">
|
||||
Back
|
||||
</Button>
|
||||
<ButtonSubmit
|
||||
data-cy="login-2fa-button"
|
||||
isSubmitting={false}
|
||||
label={Tr(Next)}
|
||||
className={classes.submit}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
Form2fa.propTypes = {
|
||||
onBack: func.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
error: string
|
||||
}
|
||||
|
||||
Form2fa.defaultProps = {
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined,
|
||||
error: null
|
||||
}
|
||||
|
||||
export default Form2fa
|
@ -1,49 +0,0 @@
|
||||
import React from 'react'
|
||||
import { func } from 'prop-types'
|
||||
|
||||
import { Box, Button } from '@material-ui/core'
|
||||
import { useForm, Controller } from 'react-hook-form'
|
||||
|
||||
import GroupSelect from 'client/components/FormControl/GroupSelect'
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
import { Next } from 'client/constants/translates'
|
||||
|
||||
function FormGroup ({ onBack, onSubmit }) {
|
||||
const classes = loginStyles()
|
||||
const { control, handleSubmit } = useForm()
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<Controller as={GroupSelect} name="group" control={control} />
|
||||
<Button onClick={onBack}>Logout</Button>
|
||||
<ButtonSubmit
|
||||
data-cy="login-group-button"
|
||||
isSubmitting={false}
|
||||
label={Tr(Next)}
|
||||
className={classes.submit}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
FormGroup.propTypes = {
|
||||
onBack: func.isRequired,
|
||||
onSubmit: func.isRequired
|
||||
}
|
||||
|
||||
FormGroup.defaultProps = {
|
||||
onBack: () => undefined,
|
||||
onSubmit: () => undefined
|
||||
}
|
||||
|
||||
FormGroup.propTypes = {}
|
||||
|
||||
FormGroup.defaultProps = {}
|
||||
|
||||
export default FormGroup
|
@ -1,105 +0,0 @@
|
||||
import React from 'react'
|
||||
import { func, string } from 'prop-types'
|
||||
import { Box, Checkbox, TextField, FormControlLabel } from '@material-ui/core'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers'
|
||||
import * as yup from 'yup'
|
||||
|
||||
import {
|
||||
SignIn,
|
||||
Username,
|
||||
Password,
|
||||
KeepLoggedIn
|
||||
} from 'client/constants/translates'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import ButtonSubmit from 'client/components/FormControl/SubmitButton'
|
||||
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
|
||||
function FormUser ({ onSubmit, error }) {
|
||||
const classes = loginStyles()
|
||||
|
||||
const { register, handleSubmit, errors } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
resolver: yupResolver(
|
||||
yup.object().shape({
|
||||
user: yup.string().required('Username is a required field'),
|
||||
token: yup.string().required('Password is a required field'),
|
||||
remember: yup.boolean()
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const userError = Boolean(errors.user || error)
|
||||
const passError = Boolean(errors.pass)
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="form"
|
||||
className={classes.form}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
required
|
||||
name="user"
|
||||
autoComplete="username"
|
||||
label={Tr(Username)}
|
||||
variant="outlined"
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': 'login-username' }}
|
||||
error={userError}
|
||||
helperText={
|
||||
userError && <ErrorHelper label={errors.user?.message || error} />
|
||||
}
|
||||
FormHelperTextProps={{ 'data-cy': 'login-username-error' }}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
required
|
||||
name="token"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
label={Tr(Password)}
|
||||
variant="outlined"
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': 'login-password' }}
|
||||
error={passError}
|
||||
helperText={passError && <ErrorHelper label={errors.pass?.message} />}
|
||||
FormHelperTextProps={{ 'data-cy': 'login-password-error' }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
name="remember"
|
||||
defaultValue={false}
|
||||
color="primary"
|
||||
inputRef={register}
|
||||
inputProps={{ 'data-cy': 'login-remember' }}
|
||||
/>
|
||||
}
|
||||
label={Tr(KeepLoggedIn)}
|
||||
labelPlacement="end"
|
||||
/>
|
||||
<ButtonSubmit
|
||||
data-cy="login-button"
|
||||
isSubmitting={false}
|
||||
label={Tr(SignIn)}
|
||||
className={classes.submit}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
FormUser.propTypes = {
|
||||
onSubmit: func.isRequired,
|
||||
error: string
|
||||
}
|
||||
|
||||
FormUser.defaultProps = {
|
||||
onSubmit: () => undefined,
|
||||
error: null
|
||||
}
|
||||
|
||||
export default FormUser
|
@ -1,23 +1,23 @@
|
||||
import React, { useState } from 'react'
|
||||
import {
|
||||
Paper,
|
||||
Box,
|
||||
Container,
|
||||
Slide,
|
||||
LinearProgress,
|
||||
useMediaQuery
|
||||
} from '@material-ui/core'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
|
||||
import { Paper, Box, Container, LinearProgress, useMediaQuery } from '@material-ui/core'
|
||||
|
||||
import useAuth from 'client/hooks/useAuth'
|
||||
|
||||
import FormUser from 'client/containers/Login/Forms/FormUser'
|
||||
import Form2fa from 'client/containers/Login/Forms/Form2fa'
|
||||
import FormGroup from 'client/containers/Login/Forms/FormGroup'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
import Logo from 'client/icons/logo'
|
||||
import { ONEADMIN_ID } from 'client/constants'
|
||||
|
||||
const STEP = {
|
||||
import {
|
||||
FORM_USER_FIELDS,
|
||||
FORM_USER_SCHEMA,
|
||||
FORM_2FA_FIELDS,
|
||||
FORM_2FA_SCHEMA,
|
||||
FORM_GROUP_FIELDS,
|
||||
FORM_GROUP_SCHEMA
|
||||
} from 'client/containers/Login/schema'
|
||||
import Form from 'client/containers/Login/Form'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
|
||||
const STEPS = {
|
||||
USER_FORM: 0,
|
||||
FA2_FORM: 1,
|
||||
GROUP_FORM: 2
|
||||
@ -27,7 +27,7 @@ function Login () {
|
||||
const classes = loginStyles()
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
|
||||
const [user, setUser] = useState(undefined)
|
||||
const [step, setStep] = useState(STEP.USER_FORM)
|
||||
const [step, setStep] = useState(STEPS.USER_FORM)
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
@ -41,10 +41,10 @@ function Login () {
|
||||
login({ ...user, ...dataForm }).then(data => {
|
||||
if (data?.token) {
|
||||
getAuthInfo().then(() => {
|
||||
data?.id !== ONEADMIN_ID && setStep(STEP.GROUP_FORM)
|
||||
data?.id !== ONEADMIN_ID && setStep(STEPS.GROUP_FORM)
|
||||
})
|
||||
} else {
|
||||
setStep(data ? STEP.FA2_FORM : step)
|
||||
setStep(data ? STEPS.FA2_FORM : step)
|
||||
setUser(data ? dataForm : user)
|
||||
}
|
||||
})
|
||||
@ -55,7 +55,7 @@ function Login () {
|
||||
const handleBack = () => {
|
||||
logout()
|
||||
setUser(undefined)
|
||||
setStep(STEP.USER_FORM)
|
||||
setStep(STEPS.USER_FORM)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -67,50 +67,46 @@ function Login () {
|
||||
>
|
||||
{isLoading && <LinearProgress className={classes.loading} />}
|
||||
<Paper variant="outlined" className={classes.paper}>
|
||||
<Logo width="100%" height={100} withText viewBox="140 140 380 360" />
|
||||
<Box className={classes.wrapper}>
|
||||
<Slide
|
||||
direction="right"
|
||||
timeout={{ enter: 400 }}
|
||||
in={step === STEP.USER_FORM}
|
||||
appear={false}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<FormUser onSubmit={handleSubmitUser} error={error} />
|
||||
</Box>
|
||||
</Slide>
|
||||
</Box>
|
||||
<Box>
|
||||
<Slide
|
||||
direction="left"
|
||||
timeout={{ enter: 400 }}
|
||||
in={step === STEP.FA2_FORM}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<Form2fa
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitUser}
|
||||
error={error}
|
||||
/>
|
||||
</Box>
|
||||
</Slide>
|
||||
</Box>
|
||||
<Box className={classes.wrapper}>
|
||||
<Slide
|
||||
direction="left"
|
||||
timeout={{ enter: 400 }}
|
||||
in={step === STEP.GROUP_FORM}
|
||||
mountOnEnter
|
||||
unmountOnExit
|
||||
>
|
||||
<Box style={{ opacity: isLoading ? 0.7 : 1 }}>
|
||||
<FormGroup onBack={handleBack} onSubmit={handleSubmitGroup} />
|
||||
</Box>
|
||||
</Slide>
|
||||
{useMemo(() => (
|
||||
<Logo width="100%" height={100} withText viewBox="140 140 380 360" />
|
||||
), [])}
|
||||
<Box className={classes.wrapperForm}>
|
||||
{step === STEPS.USER_FORM && <Form
|
||||
transitionProps={{
|
||||
direction: 'right',
|
||||
in: step === STEPS.USER_FORM,
|
||||
appear: false
|
||||
}}
|
||||
onSubmit={handleSubmitUser}
|
||||
resolver={FORM_USER_SCHEMA}
|
||||
fields={FORM_USER_FIELDS}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>}
|
||||
{step === STEPS.FA2_FORM && <Form
|
||||
transitionProps={{
|
||||
direction: 'left',
|
||||
in: step === STEPS.FA2_FORM
|
||||
}}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitUser}
|
||||
resolver={FORM_2FA_SCHEMA}
|
||||
fields={FORM_2FA_FIELDS}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>}
|
||||
{step === STEPS.GROUP_FORM && <Form
|
||||
transitionProps={{
|
||||
direction: 'left',
|
||||
in: step === STEPS.GROUP_FORM
|
||||
}}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitGroup}
|
||||
resolver={FORM_GROUP_SCHEMA}
|
||||
fields={FORM_GROUP_FIELDS}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>}
|
||||
</Box>
|
||||
</Paper>
|
||||
</Container>
|
||||
|
123
src/fireedge/src/client/containers/Login/schema.js
Normal file
123
src/fireedge/src/client/containers/Login/schema.js
Normal file
@ -0,0 +1,123 @@
|
||||
import React from 'react'
|
||||
|
||||
import SelectedIcon from '@material-ui/icons/FilterVintage'
|
||||
import * as yup from 'yup'
|
||||
|
||||
import useAuth from 'client/hooks/useAuth'
|
||||
import useOpennebula from 'client/hooks/useOpennebula'
|
||||
import { INPUT_TYPES, FILTER_POOL } from 'client/constants'
|
||||
import { getValidationFromFields } from 'client/utils/helpers'
|
||||
import {
|
||||
Username,
|
||||
Password,
|
||||
KeepLoggedIn,
|
||||
Token2FA,
|
||||
SelectGroup,
|
||||
ShowAll
|
||||
} from 'client/constants/translates'
|
||||
|
||||
export const USERNAME = {
|
||||
name: 'user',
|
||||
label: Username,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Username is a required field')
|
||||
.default(null),
|
||||
grid: { md: 12 },
|
||||
fieldProps: {
|
||||
autoFocus: true,
|
||||
required: true,
|
||||
autoComplete: 'username',
|
||||
variant: 'outlined'
|
||||
}
|
||||
}
|
||||
|
||||
export const PASSWORD = {
|
||||
name: 'token',
|
||||
label: Password,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
htmlType: 'password',
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Password is a required field')
|
||||
.default(null),
|
||||
grid: { md: 12 },
|
||||
fieldProps: {
|
||||
required: true,
|
||||
autoComplete: 'current-password',
|
||||
variant: 'outlined'
|
||||
}
|
||||
}
|
||||
|
||||
export const REMEMBER = {
|
||||
name: 'remember',
|
||||
label: KeepLoggedIn,
|
||||
type: INPUT_TYPES.CHECKBOX,
|
||||
validation: yup
|
||||
.boolean()
|
||||
.default(false),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
export const TOKEN = {
|
||||
name: 'token2fa',
|
||||
label: Token2FA,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.required('Authenticator is a required field')
|
||||
.default(null),
|
||||
grid: { md: 12 },
|
||||
fieldProps: {
|
||||
autoFocus: true,
|
||||
required: true,
|
||||
variant: 'outlined'
|
||||
}
|
||||
}
|
||||
|
||||
export const GROUP = {
|
||||
name: 'group',
|
||||
label: SelectGroup,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const { authUser } = useAuth()
|
||||
const { groups } = useOpennebula()
|
||||
|
||||
return [{ text: ShowAll, value: FILTER_POOL.ALL_RESOURCES }]
|
||||
.concat(groups
|
||||
.sort((a, b) => a.ID - b.ID)
|
||||
.map(({ ID, NAME }) => ({
|
||||
text: (
|
||||
<>
|
||||
{`${ID} - ${String(NAME)}`}
|
||||
{authUser?.GID === ID && (
|
||||
<SelectedIcon style={{ fontSize: '1rem', marginLeft: 16 }} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
value: String(ID)
|
||||
}))
|
||||
)
|
||||
},
|
||||
validation: yup
|
||||
.string()
|
||||
.trim()
|
||||
.nullable()
|
||||
.default(FILTER_POOL.ALL_RESOURCES),
|
||||
grid: { md: 12 },
|
||||
fieldProps: {
|
||||
variant: 'outlined'
|
||||
}
|
||||
}
|
||||
|
||||
export const FORM_USER_FIELDS = [USERNAME, PASSWORD, REMEMBER]
|
||||
export const FORM_2FA_FIELDS = [TOKEN]
|
||||
export const FORM_GROUP_FIELDS = [GROUP]
|
||||
|
||||
export const FORM_USER_SCHEMA = yup.object(getValidationFromFields(FORM_USER_FIELDS))
|
||||
export const FORM_2FA_SCHEMA = yup.object(getValidationFromFields(FORM_2FA_FIELDS))
|
||||
export const FORM_GROUP_SCHEMA = yup.object(getValidationFromFields(FORM_GROUP_FIELDS))
|
@ -13,7 +13,7 @@ export default makeStyles(theme =>
|
||||
justifyContent: 'center',
|
||||
height: '100vh'
|
||||
},
|
||||
loading: {
|
||||
progress: {
|
||||
height: 4,
|
||||
width: '100%',
|
||||
[theme.breakpoints.only('xs')]: {
|
||||
@ -23,18 +23,35 @@ export default makeStyles(theme =>
|
||||
},
|
||||
paper: {
|
||||
overflow: 'hidden',
|
||||
padding: theme.spacing(3),
|
||||
minHeight: 400,
|
||||
padding: theme.spacing(2),
|
||||
height: 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(),
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
overflow: 'hidden'
|
||||
},
|
||||
form: {
|
||||
width: '100%',
|
||||
flexShrink: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
[theme.breakpoints.up('xs')]: {
|
||||
justifyContent: 'center'
|
||||
}
|
||||
},
|
||||
loading: {
|
||||
opacity: 0.7
|
||||
},
|
||||
helper: {
|
||||
animation: '1s ease-out 0s 1'
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import React, { memo } from 'react'
|
||||
import { number, string, bool, oneOfType } from 'prop-types'
|
||||
|
||||
const Logo = ({ width, height, spinner, withText, viewBox, ...props }) => {
|
||||
const Logo = memo(({ width, height, spinner, withText, viewBox, ...props }) => {
|
||||
const cloudColor = {
|
||||
child1: { from: '#0098c3', to: '#ffffff' },
|
||||
child2: { from: '#0098c3', to: '#ffffff' },
|
||||
@ -83,7 +83,7 @@ const Logo = ({ width, height, spinner, withText, viewBox, ...props }) => {
|
||||
)}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
Logo.propTypes = {
|
||||
width: oneOfType([number, string]).isRequired,
|
||||
@ -101,4 +101,6 @@ Logo.defaultProps = {
|
||||
withText: false
|
||||
}
|
||||
|
||||
Logo.displayName = 'LogoOne'
|
||||
|
||||
export default Logo
|
||||
|
Loading…
x
Reference in New Issue
Block a user