1
0
mirror of https://github.com/OpenNebula/one.git synced 2024-12-23 17:33:56 +03:00

F #3951: Add settings section (#671)

This commit is contained in:
Sergio Betanzos 2021-01-19 16:46:23 +01:00 committed by GitHub
parent 721bc8fa16
commit 7975f64054
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 405 additions and 189 deletions

View File

@ -1,4 +1,5 @@
const CHANGE_ZONE = 'CHANGE_ZONE'
const CHANGE_THEME_TYPE = 'CHANGE_THEME_TYPE'
const CHANGE_LOADING = 'CHANGE_LOADING'
const TOGGLE_MENU = 'TOGGLE_MENU'
const FIX_MENU = 'FIX_MENU'
@ -9,6 +10,7 @@ const REMOVE_SNACKBAR = 'REMOVE_SNACKBAR'
const Actions = {
CHANGE_ZONE,
CHANGE_THEME_TYPE,
CHANGE_LOADING,
TOGGLE_MENU,
FIX_MENU,
@ -23,6 +25,15 @@ module.exports = {
type: CHANGE_ZONE,
payload: { zone }
}),
updateTheme: (dispatch, getState) => {
const current = getState()
const currentTheme = current.Authenticated?.theme
const userTheme = current.Authenticated?.user?.TEMPLATE?.FIREEDGE?.THEME ?? 'dark'
if (['dark', 'light'].includes(userTheme) && currentTheme !== userTheme) {
dispatch(({ type: CHANGE_THEME_TYPE, payload: { theme: userTheme } }))
}
},
changeLoading: isLoading => ({
type: CHANGE_LOADING,
payload: { isLoading }

View File

@ -1,10 +1,15 @@
export default {
export default (mode = 'dark') => ({
palette: {
type: 'dark',
common: { black: '#000000', white: '#ffffff' },
type: mode,
common: {
black: '#000000',
white: '#ffffff'
},
background: {
paper: '#2a2d3d',
default: '#222431'
paper: mode === 'dark' ? '#2e3440' : '#ffffff',
// PREV paper: mode === 'dark' ? '#2a2d3d' : '#eceff4',
default: mode === 'dark' ? '#242933' : '#f2f4f8'
// PREV default: mode === 'dark' ? '#222431' : '#d8dee9'
},
primary: {
light: '#2a2d3d',
@ -28,4 +33,4 @@ export default {
main: '#7b7c7e'
}
}
}
})

View File

@ -8,10 +8,10 @@ import SelectCard from 'client/components/Cards/SelectCard'
import { StatusBadge, StatusChip } from 'client/components/Status'
import Datastore from 'client/constants/datastore'
const useStyles = makeStyles(theme => ({
card: { backgroundColor: theme.palette.primary.main },
const useStyles = makeStyles(() => ({
title: { display: 'flex', gap: '0.5rem' }
}))
const DatastoreCard = memo(
({ value, isSelected, handleClick, actions }) => {
const classes = useStyles()
@ -23,7 +23,6 @@ const DatastoreCard = memo(
return (
<SelectCard
stylesProps={{ minHeight: 160 }}
cardProps={{ className: classes.card, elevation: 2 }}
icon={
<StatusBadge stateColor={state.color}>
<DatastoreIcon />

View File

@ -8,8 +8,7 @@ import SelectCard from 'client/components/Cards/SelectCard'
import { StatusBadge, StatusChip } from 'client/components/Status'
import Host from 'client/constants/host'
const useStyles = makeStyles(theme => ({
card: { backgroundColor: theme.palette.primary.main },
const useStyles = makeStyles(() => ({
title: { display: 'flex', gap: '0.5rem' }
}))
@ -24,7 +23,6 @@ const HostCard = memo(
return (
<SelectCard
stylesProps={{ minHeight: 160 }}
cardProps={{ className: classes.card, elevation: 2 }}
icon={
<StatusBadge title={state?.name} stateColor={state.color}>
<HostIcon />

View File

@ -1,23 +1,16 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles } from '@material-ui/core'
import NetworkIcon from '@material-ui/icons/AccountTree'
import SelectCard from 'client/components/Cards/SelectCard'
const useStyles = makeStyles(theme => ({
card: { backgroundColor: theme.palette.primary.main }
}))
const NetworkCard = memo(
({ value, isSelected, handleClick, actions }) => {
const { ID, NAME } = value
const classes = useStyles()
return (
<SelectCard
stylesProps={{ minHeight: 120 }}
cardProps={{ className: classes.card, elevation: 2 }}
icon={<NetworkIcon />}
title={NAME}
subheader={`#${ID}`}

View File

@ -5,7 +5,11 @@ import ProvidersIcon from '@material-ui/icons/Public'
import SelectCard from 'client/components/Cards/SelectCard'
import { isExternalURL } from 'client/utils'
import { PROVIDER_IMAGES_URL, PROVISION_IMAGES_URL } from 'client/constants'
import {
PROVIDER_IMAGES_URL,
PROVISION_IMAGES_URL,
DEFAULT_IMAGE
} from 'client/constants'
const ProvisionTemplateCard = React.memo(
({ value, isProvider, isSelected, handleClick }) => {
@ -13,6 +17,10 @@ const ProvisionTemplateCard = React.memo(
const { image } = isProvider ? plain : value
const IMAGES_URL = isProvider ? PROVIDER_IMAGES_URL : PROVISION_IMAGES_URL
const onError = evt => {
evt.target.src = evt.target.src === DEFAULT_IMAGE ? DEFAULT_IMAGE : ''
}
const imgSource = React.useMemo(() =>
isExternalURL(image) ? image : `${IMAGES_URL}/${image}`
, [image])
@ -27,7 +35,8 @@ const ProvisionTemplateCard = React.memo(
mediaProps={image && {
component: 'img',
image: imgSource,
draggable: false
draggable: false,
onError
}}
/>
)

View File

@ -28,7 +28,7 @@ const SelectCard = memo(({
observerOff,
children
}) => {
const classes = selectCardStyles(stylesProps)
const classes = selectCardStyles({ ...stylesProps, isSelected })
const { isNearScreen, fromRef } = useNearScreen({
distance: '100px'
})
@ -40,8 +40,7 @@ const SelectCard = memo(({
{observerOff || isNearScreen ? (
<Card
className={clsx(classes.root, cardProps?.className, {
[classes.actionArea]: !handleClick,
[classes.selected]: isSelected
[classes.actionArea]: !handleClick
})}
{...cardProps}
>
@ -50,10 +49,7 @@ const SelectCard = memo(({
<ConditionalWrap
condition={handleClick && !action}
wrap={children =>
<CardActionArea
className={classes.actionArea}
onClick={handleClick}
>
<CardActionArea className={classes.actionArea} onClick={handleClick}>
{children}
</CardActionArea>
}

View File

@ -1,47 +1,49 @@
import { makeStyles } from '@material-ui/core'
export default makeStyles(theme => ({
root: {
root: ({ isSelected }) => ({
height: '100%',
'& $actionArea, & $mediaActionArea, & $media': {
transition: theme.transitions.create(
['filter', 'background-color'],
{ duration: '0.2s' }
)
}
},
selected: {
color: theme.palette.secondary.contrastText,
backgroundColor: theme.palette.secondary.main,
'& .badge': {
color: theme.palette.secondary.main,
backgroundColor: theme.palette.secondary.contrastText
transition: theme.transitions.create(
['background-color', 'box-shadow'], { duration: '0.2s' }
),
'&:hover': {
boxShadow: theme.shadows['5']
},
'& $subheader': { color: 'inherit' }
},
...(isSelected && {
color: theme.palette.secondary.contrastText,
backgroundColor: theme.palette.secondary.main,
'& .badge': {
color: theme.palette.secondary.main,
backgroundColor: theme.palette.secondary.contrastText
},
'& $subheader': { color: 'inherit' }
})
}),
actionArea: {
height: '100%',
minHeight: ({ minHeight = 140 }) => minHeight,
'& $media': { filter: 'contrast(0) brightness(2)' }
minHeight: ({ minHeight = 140 }) => minHeight
},
mediaActionArea: {
display: 'flex',
height: ({ mediaHeight = 140 }) => mediaHeight,
'& $media': { filter: 'contrast(0) brightness(2)' },
'&:hover': {
backgroundColor: theme.palette.primary.contrastText,
backgroundColor: theme.palette.secondary.contrastText,
'& $media': { filter: 'none' }
}
},
media: {},
media: {
transition: theme.transitions.create('filter', { duration: '0.2s' }),
filter: ({ isSelected }) => (theme.palette.type === 'dark' || isSelected)
? 'contrast(0) brightness(2)'
: 'contrast(0) brightness(0.8)'
},
headerRoot: { alignItems: 'end' },
headerContent: { overflow: 'auto' },
headerAvatar: {
display: 'flex',
color: theme.palette.primary.contrastText
},
header: {
color: theme.palette.primary.contrastText
color: ({ isSelected }) => isSelected
? theme.palette.secondary.contrastText
: theme.palette.text.primary
},
subheader: {
overflow: 'hidden',

View File

@ -2,46 +2,51 @@ import * as React from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { Paper, Typography, makeStyles, lighten } from '@material-ui/core'
import { Paper, Typography, makeStyles, lighten, darken } from '@material-ui/core'
import { addOpacityToColor } from 'client/utils'
const useStyles = makeStyles(theme => ({
root: {
padding: '2em',
position: 'relative',
overflow: 'hidden',
backgroundColor: ({ bgColor }) => bgColor
},
icon: {
position: 'absolute',
top: 0,
right: 0,
fontSize: '10em',
fill: addOpacityToColor(theme.palette.common.white, 0.2)
},
wave: {
display: 'block',
position: 'absolute',
opacity: 0.4,
top: '-5%',
left: '50%',
width: 220,
height: 220,
borderRadius: '43%'
},
wave1: {
backgroundColor: ({ bgColor }) => lighten(bgColor, 0.4),
animation: '$drift 7s infinite linear'
},
wave2: {
backgroundColor: ({ bgColor }) => lighten(bgColor, 0.6),
animation: '$drift 5s infinite linear'
},
'@keyframes drift': {
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' }
const useStyles = makeStyles(theme => {
const getBackgroundColor = theme.palette.type === 'dark' ? darken : lighten
const getContrastBackgroundColor = theme.palette.type === 'light' ? darken : lighten
return {
root: {
padding: '2em',
position: 'relative',
overflow: 'hidden',
backgroundColor: ({ bgColor }) => getBackgroundColor(bgColor, 0.3)
},
icon: {
position: 'absolute',
top: 0,
right: 0,
fontSize: '10em',
fill: addOpacityToColor(theme.palette.common.white, 0.2)
},
wave: {
display: 'block',
position: 'absolute',
opacity: 0.4,
top: '-5%',
left: '50%',
width: 220,
height: 220,
borderRadius: '43%'
},
wave1: {
backgroundColor: ({ bgColor }) => getContrastBackgroundColor(bgColor, 0.3),
animation: '$drift 7s infinite linear'
},
wave2: {
backgroundColor: ({ bgColor }) => getContrastBackgroundColor(bgColor, 0.5),
animation: '$drift 5s infinite linear'
},
'@keyframes drift': {
from: { transform: 'rotate(0deg)' },
to: { transform: 'rotate(360deg)' }
}
}
}))
})
const WavesCard = React.memo(({ text, value, bgColor, icon: Icon }) => {
const classes = useStyles({ bgColor })

View File

@ -19,6 +19,7 @@ const useStyles = makeStyles(theme => ({
padding: '0.5em 0',
cursor: ({ isMoreThanMaxChars }) => isMoreThanMaxChars ? 'pointer' : 'default',
fontFamily: 'monospace',
color: '#fafafa',
'&:hover': {
background: '#333537'
}
@ -30,9 +31,6 @@ const useStyles = makeStyles(theme => ({
time: {
minWidth: '220px'
},
message: {
color: '#fafafa'
},
[DEBUG_LEVEL.ERROR]: { borderLeft: `0.3em solid ${theme.palette.error.light}` },
[DEBUG_LEVEL.WARN]: { borderLeft: `0.3em solid ${theme.palette.warning.light}` },
[DEBUG_LEVEL.INFO]: { borderLeft: `0.3em solid ${theme.palette.info.light}` },
@ -62,9 +60,9 @@ const Message = memo(({ timestamp, severity, message }) => {
</div>
<div className={classes.time}>{timestamp}</div>
{(isCollapsed && isMoreThanMaxChars) ? (
<div className={classes.message}>{`${message?.slice(0, MAX_CHARS)}`}</div>
<div>{`${message?.slice(0, MAX_CHARS)}`}</div>
) : (
<div className={classes.message} dangerouslySetInnerHTML={{ __html: AnsiHtml(message) }} />
<div dangerouslySetInnerHTML={{ __html: AnsiHtml(message) }} />
)}
</div>
)

View File

@ -8,13 +8,26 @@ import {
DialogTitle,
DialogContent,
DialogActions,
Typography
Typography,
makeStyles
} from '@material-ui/core'
import SubmitButton from 'client/components/FormControl/SubmitButton'
import { Tr } from 'client/components/HOC'
import { T } from 'client/constants'
const useStyles = makeStyles(theme => ({
root: {
backgroundColor: theme.palette.background.default,
width: '80%',
height: '80%',
[theme.breakpoints.only('xs')]: {
width: '100%',
height: '100%'
}
}
}))
const DialogConfirmation = memo(
({
open,
@ -28,6 +41,7 @@ const DialogConfirmation = memo(
handleEntering,
children
}) => {
const classes = useStyles()
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
return (
@ -37,12 +51,9 @@ const DialogConfirmation = memo(
open={open}
onClose={handleCancel}
maxWidth='lg'
scroll="paper"
PaperProps={{
style: {
height: isMobile ? '100%' : '80%',
width: isMobile ? '100%' : '80%'
}
scroll='paper'
classes={{
paper: classes.root
}}
>
<DialogTitle disableTypography>

View File

@ -34,7 +34,7 @@ export default makeStyles(theme => ({
}
},
scrollable: {
backgroundColor: theme.palette.primary.main,
backgroundColor: theme.palette.background.default,
paddingTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
height: '100%',

View File

@ -103,7 +103,7 @@ const Tr = (str = '') => {
return translate(key, valuesTr)
}
const SelectTranslate = () => {
const SelectTranslate = props => {
const context = useContext(TranslateContext)
const languages = Array.isArray(root?.langs) ? root?.langs : []
@ -120,6 +120,7 @@ const SelectTranslate = () => {
fullWidth
onChange={e => handleChange(e, context.changeLang)}
defaultValue={context.lang}
{...props}
>
{languages.map(({ key, value }) => (
<option value={key} key={key}>

View File

@ -35,7 +35,8 @@ import headerStyles from 'client/components/Header/styles'
const Header = ({ title, scrollableContainer }) => {
const { isOneAdmin } = useAuth()
const { isFixMenu, fixMenu } = useGeneral()
const { theme, isFixMenu, fixMenu } = useGeneral()
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'))
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
@ -78,7 +79,7 @@ const Header = ({ title, scrollableContainer }) => {
</Toolbar>
</AppBar>
),
[isFixMenu, fixMenu, isUpLg, isMobile, isOneAdmin, classes]
[theme, isFixMenu, fixMenu, isUpLg, isMobile, isOneAdmin, classes]
)
}

View File

@ -47,11 +47,14 @@ export default makeStyles(theme => ({
}
},
/* GROUP SWITCHER */
modeThemeIcon: {
color: theme.palette.primary.contrastText
},
/* GROUP SWITCHER */
headerSwitcherLabel: { flexGrow: 1 },
groupButton: { justifyContent: 'start' },
groupSelectedIcon: {
fontSize: '1rem',
margin: theme.spacing(0, 2)
}
}
))
}))

View File

@ -27,6 +27,7 @@ const SidebarLink = ({ label, path, icon: Icon, devMode, isSubItem }) => {
history.push(path)
!isUpLg && fixMenu(false)
}
return (
<ListItem
button
@ -38,7 +39,7 @@ const SidebarLink = ({ label, path, icon: Icon, devMode, isSubItem }) => {
data-cy='main-menu-item'
>
{Icon && (
<ListItemIcon className={classes.itemIcon}>
<ListItemIcon>
<Icon />
</ListItemIcon>
)}

View File

@ -75,7 +75,7 @@ const Sidebar = memo(({ endpoints }) => {
className={classes.svg}
/>
<IconButton
className={classes.itemIcon}
className={classes.hamburger}
onClick={handleSwapMenu}
>
{isUpLg ? <MenuIcon /> : <CloseIcon />}

View File

@ -6,7 +6,8 @@ export default makeStyles(theme => ({
// CONTAINER MENU
// -------------------------------
drawerPaper: {
backgroundColor: theme.palette.primary.light,
backgroundColor: theme.palette.background.paper,
border: 'none',
width: 0,
visibility: 'hidden',
whiteSpace: 'nowrap',
@ -69,6 +70,10 @@ export default makeStyles(theme => ({
// -------------------------------
header: {
userSelect: 'none',
backgroundColor: theme.palette.type === 'dark'
? theme.palette.background.paper
: theme.palette.primary.main,
color: theme.palette.text.primary,
display: 'flex',
alignItems: 'center',
padding: '1rem',
@ -116,7 +121,7 @@ export default makeStyles(theme => ({
}
},
list: {
color: theme.palette.primary.contrastText
color: theme.palette.text.primary
},
expandIcon: {},
subItemWrapper: {},
@ -124,12 +129,11 @@ export default makeStyles(theme => ({
paddingLeft: theme.spacing(4)
},
itemSelected: {
color: theme.palette.secondary.main,
color: theme.palette.text.primary,
backgroundColor: theme.palette.primary.light,
'&:hover': { backgroundColor: theme.palette.primary.light },
'& $itemIcon': { color: theme.palette.secondary.main }
'&:hover': { backgroundColor: theme.palette.primary.light }
},
itemIcon: {
hamburger: {
color: theme.palette.primary.contrastText
}
}))

View File

@ -1,21 +1,25 @@
import React, { memo } from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Typography, lighten } from '@material-ui/core'
import { makeStyles, Typography, lighten, darken } from '@material-ui/core'
import { addOpacityToColor } from 'client/utils'
const useStyles = makeStyles(theme => ({
root: ({ stateColor = theme.palette.primary.main }) => ({
color: lighten(stateColor, 0.75),
backgroundColor: addOpacityToColor(stateColor, 0.2),
cursor: 'default',
padding: theme.spacing('0.25rem', '0.5rem'),
borderRadius: 2,
textTransform: 'uppercase',
fontSize: theme.typography.overline.fontSize,
fontWeight: theme.typography.fontWeightBold
})
}))
const useStyles = makeStyles(theme => {
const getBackgroundColor = theme.palette.type === 'dark' ? lighten : darken
return {
root: ({ stateColor = theme.palette.primary.main }) => ({
color: getBackgroundColor(stateColor, 0.75),
backgroundColor: addOpacityToColor(stateColor, 0.2),
cursor: 'default',
padding: theme.spacing('0.25rem', '0.5rem'),
borderRadius: 2,
textTransform: 'uppercase',
fontSize: theme.typography.overline.fontSize,
fontWeight: theme.typography.fontWeightBold
})
}
})
const StatusChip = memo(({ stateColor, children }) => {
const classes = useStyles({ stateColor })

View File

@ -30,7 +30,11 @@ const TypographyWithPoint = ({ pointColor, children }) => {
TypographyWithPoint.propTypes = {
pointColor: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
children: PropTypes.oneOfType([
PropTypes.string,
PropTypes.array,
PropTypes.element
])
}
TypographyWithPoint.defaultProps = {

View File

@ -21,7 +21,6 @@ const TotalProviders = () => {
const chartData = React.useMemo(() => {
const groups = groupBy(providers, 'TEMPLATE.PLAIN.provider')
console.log({ groups })
return PROVIDERS_TYPES?.map(({ name, color }) => ({
color,
title: name,

View File

@ -62,6 +62,9 @@ module.exports = {
/* sections */
Dashboard: 'Dashboard',
Settings: 'Settings',
/* sections - settgins */
Dark: 'Dark',
Light: 'Light',
/* sections - system */
User: 'User',
Users: 'Users',

View File

@ -14,11 +14,137 @@
/* -------------------------------------------------------------------------- */
import * as React from 'react'
import { Tr } from 'client/components/HOC'
import { T } from 'client/constants'
const Settings = () => (
<div>{Tr(T.Settings)}</div>
)
import {
makeStyles,
Container,
Paper,
Typography,
TextField
} from '@material-ui/core'
import { useAuth, useGeneral } from 'client/hooks'
import { Tr, TranslateContext } from 'client/components/HOC'
import { T } from 'client/constants'
import SubmitButton from 'client/components/FormControl/SubmitButton'
const useStyles = makeStyles(theme => ({
header: {
paddingTop: '1rem'
},
title: {
flexGrow: 1,
letterSpacing: 0.1,
fontWeight: 500
},
wrapper: {
backgroundColor: theme.palette.background.default,
maxWidth: 550,
padding: '1rem'
},
subheader: {
marginBottom: '1rem'
},
actions: {
padding: '1rem 0',
textAlign: 'end'
}
}))
const Settings = () => {
const classes = useStyles()
const context = React.useContext(TranslateContext)
// const langAvailables = Array.isArray(window?.langs) ? window?.langs : []
const { theme: currentTheme } = useGeneral()
const { updateUser } = useAuth()
const [{ theme, lang }, setSettings] = React.useState({
theme: currentTheme,
lang: context.lang
})
const handleChange = evt => {
evt.preventDefault()
evt.persist()
setSettings(prev => ({
...prev,
[evt.target.name]: evt.target.value
}))
}
const handleSubmit = evt => {
evt.preventDefault()
updateUser({
template: `FIREEDGE = [\n THEME = "${theme}",\n LANG = "${lang}" ]\n`
}).then(() => context.changeLang(lang))
}
return (
<Container disableGutters>
<div className={classes.header}>
<Typography variant='h5' className={classes.title}>
{Tr(T.Settings)}
</Typography>
</div>
<hr />
<Paper className={classes.wrapper} variant='outlined'>
<Typography variant='overline' component='div' className={classes.subheader}>
{`${Tr(T.Configuration)} UI`}
</Typography>
<TextField
id='select-theme-type'
select
fullWidth
name='theme'
color='secondary'
label='Theme'
value={theme}
onChange={handleChange}
SelectProps={{
native: true
}}
variant='outlined'
>
<option value='light'>{T.Light}</option>
<option value='dark'>{T.Dark}</option>
</TextField>
{/* is not operative yet */}
{/* <Select
color='secondary'
inputProps={{
name: 'lang',
id: 'select-lang',
'data-cy': 'select-lang'
}}
onChange={handleChange}
fullWidth
native
value={lang}
variant='outlined'
>
{langAvailables.map(({ key, value }) => (
<option value={key} key={key}>
{value}
</option>
))}
</Select> */}
<div className={classes.actions}>
<SubmitButton
color='secondary'
data-cy='settings-submit-button'
label={Tr(T.Save)}
onClick={handleSubmit}
/>
</div>
</Paper>
</Container>
)
}
export default Settings

View File

@ -35,9 +35,9 @@ const ResponseForm = ({
return (
<>
<Typography
color="textPrimary"
component="h2"
variant="h2"
color='textPrimary'
component='h2'
variant='h2'
style={{ padding: '16px 0' }}
>
{name || 'Request'}
@ -45,10 +45,10 @@ const ResponseForm = ({
<Grid
container
spacing={3}
justify="flex-start"
component="form"
justify='flex-start'
component='form'
onSubmit={handleSubmit(onSubmit)}
autoComplete="off"
autoComplete='off'
>
{Object.entries(params)?.map(([nameCommand, { default: value }]) => (
<Grid item xs={12} key={`param-${nameCommand}`}>
@ -56,7 +56,7 @@ const ResponseForm = ({
as={
typeof value === 'boolean' ? (
<FormControlLabel
control={<Checkbox color="primary" />}
control={<Checkbox color='primary' />}
label={nameCommand}
labelPlacement={nameCommand}
/>
@ -66,7 +66,8 @@ const ResponseForm = ({
helperText={errors[name]?.message}
fullWidth
label={nameCommand}
variant="outlined"
color='secondary'
variant='outlined'
/>
)
}

View File

@ -36,12 +36,13 @@ const TestApi = () => {
disableGutters
style={{ display: 'flex', flexFlow: 'column', height: '100%' }}
>
<Grid container direction="row" spacing={2} className={classes.root}>
<Grid container direction='row' spacing={2} className={classes.root}>
<Grid item xs={12} md={6}>
<TextField
fullWidth
select
variant="outlined"
color='secondary'
variant='outlined'
label={Tr(T.SelectRequest)}
value={name}
onChange={handleChangeCommand}

View File

@ -15,8 +15,9 @@ import {
logout as logoutRequest
} from 'client/actions/user'
import { setGroups } from 'client/actions/pool'
import { updateTheme, enqueueError, enqueueSuccess } from 'client/actions/general'
export default function useAuth () {
const useAuth = () => {
const {
jwt,
error,
@ -79,6 +80,7 @@ export default function useAuth () {
.then(user => dispatch(successAuth({ user })))
.then(serviceOne.getGroups)
.then(groups => dispatch(setGroups(groups)))
.then(() => dispatch(updateTheme))
.catch(err => dispatch(failureAuth({ error: err })))
}, [dispatch, JWT_NAME, authUser])
@ -105,11 +107,25 @@ export default function useAuth () {
[dispatch, authUser]
)
const updateUser = useCallback(
({ template }) =>
serviceOne
.updateUser({ id: authUser.ID, template })
.then(() => dispatch(enqueueSuccess(`User updated - ID: ${authUser.ID}`)))
.then(getAuthInfo)
.catch(err => {
dispatch(enqueueError(err ?? 'Error update user'))
throw err
})
, [dispatch, authUser]
)
return {
login,
logout,
getAuthInfo,
setPrimaryGroup,
updateUser,
isLogged: !!jwt,
authUser,
isOneAdmin: authUser?.ID === ONEADMIN_ID,
@ -120,3 +136,5 @@ export default function useAuth () {
filterPool
}
}
export default useAuth

View File

@ -4,10 +4,13 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux'
import * as actions from 'client/actions/general'
export default function useGeneral () {
const { zone, isLoading, isOpenMenu, isFixMenu } = useSelector(
state => state?.General,
shallowEqual
)
const {
zone,
isLoading,
isOpenMenu,
isFixMenu,
theme
} = useSelector(state => state?.General, shallowEqual)
const dispatch = useDispatch()
const fixMenu = useCallback(isFixed => dispatch(actions.fixMenu(isFixed)), [
@ -38,10 +41,11 @@ export default function useGeneral () {
)
return {
zone,
theme,
isFixMenu,
isLoading,
isOpenMenu,
isFixMenu,
zone,
changeZone,
openMenu,
fixMenu,

View File

@ -1,25 +1,28 @@
import React, { useEffect, useState } from 'react'
import * as React from 'react'
import PropTypes from 'prop-types'
import { CssBaseline, ThemeProvider, StylesProvider } from '@material-ui/core'
import { createTheme, generateClassName } from 'client/theme'
import { useGeneral } from 'client/hooks'
const MuiProvider = ({ theme: appTheme, children }) => {
const [theme, setTheme] = useState(() => createTheme(appTheme))
const { theme } = useGeneral()
useEffect(() => {
const changeThemeType = () => createTheme(appTheme(theme))
const [muitheme, setTheme] = React.useState(changeThemeType)
React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side')
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles)
}
}, [])
useEffect(() => {
appTheme && setTheme(() => createTheme(appTheme))
}, [appTheme])
React.useEffect(() => { setTheme(changeThemeType) }, [theme])
return (
<ThemeProvider theme={theme}>
<ThemeProvider theme={muitheme}>
<CssBaseline />
<StylesProvider generateClassName={generateClassName}>
{children}
@ -29,7 +32,7 @@ const MuiProvider = ({ theme: appTheme, children }) => {
}
MuiProvider.propTypes = {
theme: PropTypes.object,
theme: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node)
@ -37,7 +40,7 @@ MuiProvider.propTypes = {
}
MuiProvider.defaultProps = {
theme: {},
theme: () => {},
children: undefined
}

View File

@ -22,7 +22,8 @@ const initial = {
notifications: [],
isLoading: false,
isOpenMenu: false,
isFixMenu: false
isFixMenu: false,
theme: 'dark'
}
const General = (state = initial, action) => {
@ -57,6 +58,8 @@ const General = (state = initial, action) => {
notification => notification.key !== action.key
)
}
case GeneralActions.CHANGE_THEME_TYPE:
return { ...state, ...action.payload }
case GeneralActions.CHANGE_LOADING:
return { ...state, ...action.payload }
case GeneralActions.CHANGE_ZONE:

View File

@ -1,7 +1,8 @@
import {
Dashboard as DashboardIcon,
Public as ProvidersIcon,
SettingsSystemDaydream as ProvisionsIcon
SettingsSystemDaydream as ProvisionsIcon,
Settings as SettingsIcon
} from '@material-ui/icons'
import Login from 'client/containers/Login'
@ -46,13 +47,6 @@ export const ENDPOINTS = [
icon: DashboardIcon,
Component: Dashboard
},
{
label: 'Settings',
path: PATH.SETTINGS,
authenticated: true,
header: true,
Component: Settings
},
{
label: 'Providers',
path: PATH.PROVIDERS.LIST,
@ -92,6 +86,14 @@ export const ENDPOINTS = [
path: PATH.PROVISIONS.EDIT,
authenticated: true,
Component: ProvisionCreateForm
},
{
label: 'Settings',
path: PATH.SETTINGS,
authenticated: true,
sidebar: true,
icon: SettingsIcon,
Component: Settings
}
]

View File

@ -27,6 +27,17 @@ export const changeGroup = values => {
})
}
export const updateUser = values => {
const name = Actions.USER_UPDATE
const { url, options } = requestParams(values, { name, ...Commands[name] })
return requestData(url, options).then(res => {
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
return res?.data ?? {}
})
}
export default {
changeGroup
}

View File

@ -16,22 +16,22 @@
const {
from: { resource, postBody, query },
httpMethod: { GET, POST, PUT, DELETE }
} = require('../defaults');
} = require('../defaults')
const USER_ALLOCATE = 'user.allocate';
const USER_DELETE = 'user.delete';
const USER_PASSWD = 'user.passwd';
const USER_LOGIN = 'user.login';
const USER_UPDATE = 'user.update';
const USER_CHAUTH = 'user.chauth';
const USER_QUOTA = 'user.quota';
const USER_CHGRP = 'user.chgrp';
const USER_ADDGROUP = 'user.addgroup';
const USER_DELGROUP = 'user.delgroup';
const USER_INFO = 'user.info';
const USER_POOL_INFO = 'userpool.info';
const USER_QUOTA_INFO = 'userquota.info';
const USER_QUOTA_UPDATE = 'userquota.update';
const USER_ALLOCATE = 'user.allocate'
const USER_DELETE = 'user.delete'
const USER_PASSWD = 'user.passwd'
const USER_LOGIN = 'user.login'
const USER_UPDATE = 'user.update'
const USER_CHAUTH = 'user.chauth'
const USER_QUOTA = 'user.quota'
const USER_CHGRP = 'user.chgrp'
const USER_ADDGROUP = 'user.addgroup'
const USER_DELGROUP = 'user.delgroup'
const USER_INFO = 'user.info'
const USER_POOL_INFO = 'userpool.info'
const USER_QUOTA_INFO = 'userquota.info'
const USER_QUOTA_UPDATE = 'userquota.update'
const Actions = {
USER_ALLOCATE,
@ -48,7 +48,7 @@ const Actions = {
USER_POOL_INFO,
USER_QUOTA_INFO,
USER_QUOTA_UPDATE
};
}
module.exports = {
Actions,
@ -123,7 +123,7 @@ module.exports = {
},
[USER_UPDATE]: {
// inspected
httpMethod: POST,
httpMethod: PUT,
params: {
id: {
from: resource,
@ -248,4 +248,4 @@ module.exports = {
}
}
}
};
}