mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
cbc6b21822
commit
bb7ea25185
2150
src/fireedge/package-lock.json
generated
2150
src/fireedge/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -60,6 +60,7 @@
|
||||
"@material-ui/core": "4.11.0",
|
||||
"@material-ui/icons": "4.11.2",
|
||||
"@material-ui/lab": "4.0.0-alpha.58",
|
||||
"@reduxjs/toolkit": "1.5.1",
|
||||
"ace-builds": "1.4.12",
|
||||
"atob": "2.1.2",
|
||||
"axios": "0.21.1",
|
||||
@ -108,7 +109,7 @@
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-transition-group": "4.4.1",
|
||||
"redux": "4.0.4",
|
||||
"redux": "4.1.0",
|
||||
"redux-thunk": "2.3.0",
|
||||
"rimraf": "3.0.2",
|
||||
"socket.io": "4.1.2",
|
||||
|
@ -16,13 +16,46 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import Router from 'client/router'
|
||||
import routes from 'client/router/flow'
|
||||
import routes from 'client/apps/flow/routes'
|
||||
|
||||
import { _APPS } from 'client/constants'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
|
||||
import LoadingScreen from 'client/components/LoadingScreen'
|
||||
import { fakeDelay } from 'client/utils'
|
||||
import { _APPS, TIME_HIDE_LOGO } from 'client/constants'
|
||||
|
||||
const APP_NAME = _APPS.flow.name
|
||||
|
||||
const FlowApp = () => <Router title={APP_NAME} routes={routes} />
|
||||
const FlowApp = () => {
|
||||
const [firstRender, setFirstRender] = React.useState(() => true)
|
||||
|
||||
const { jwt } = useAuth()
|
||||
const { getAuthUser } = useAuthApi()
|
||||
const { changeTitle } = useGeneralApi()
|
||||
|
||||
React.useEffect(() => {
|
||||
if (firstRender) {
|
||||
jwt && (async () => {
|
||||
await getAuthUser()
|
||||
})()
|
||||
|
||||
fakeDelay(TIME_HIDE_LOGO).then(() => setFirstRender(false))
|
||||
}
|
||||
}, [firstRender, jwt])
|
||||
|
||||
React.useEffect(() => {
|
||||
changeTitle(APP_NAME)
|
||||
}, [])
|
||||
|
||||
if (firstRender) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
return (
|
||||
<Router routes={routes} />
|
||||
)
|
||||
}
|
||||
|
||||
FlowApp.displayName = '_FlowApp'
|
||||
|
||||
|
@ -6,12 +6,25 @@ import {
|
||||
|
||||
import loadable from '@loadable/component'
|
||||
|
||||
const Login = loadable(() => import('client/containers/Login'), { ssr: false })
|
||||
const Dashboard = loadable(
|
||||
() => import('client/containers/Dashboard'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const Dashboard = loadable(() => import('client/containers/Dashboard'), { ssr: false })
|
||||
const Settings = loadable(() => import('client/containers/Settings'), { ssr: false })
|
||||
const ApplicationsTemplates = loadable(() => import('client/containers/ApplicationsTemplates'), { ssr: false })
|
||||
const ApplicationsInstances = loadable(() => import('client/containers/ApplicationsInstances'), { ssr: false })
|
||||
const Settings = loadable(
|
||||
() => import('client/containers/Settings'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const ApplicationsTemplates = loadable(
|
||||
() => import('client/containers/ApplicationsTemplates'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const ApplicationsInstances = loadable(
|
||||
() => import('client/containers/ApplicationsInstances'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const ApplicationsTemplatesFormCreate = loadable(
|
||||
() => import('client/containers/ApplicationsTemplates/Form/Create'),
|
||||
@ -19,7 +32,6 @@ const ApplicationsTemplatesFormCreate = loadable(
|
||||
)
|
||||
|
||||
export const PATH = {
|
||||
LOGIN: '/',
|
||||
DASHBOARD: '/dashboard',
|
||||
APPLICATIONS_TEMPLATES: {
|
||||
LIST: '/applications-templates',
|
||||
@ -33,16 +45,9 @@ export const PATH = {
|
||||
}
|
||||
|
||||
export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Login',
|
||||
path: PATH.LOGIN,
|
||||
authenticated: false,
|
||||
Component: Login
|
||||
},
|
||||
{
|
||||
label: 'Dashboard',
|
||||
path: PATH.DASHBOARD,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: DashboardIcon,
|
||||
Component: Dashboard
|
||||
@ -50,14 +55,12 @@ export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Settings',
|
||||
path: PATH.SETTINGS,
|
||||
authenticated: true,
|
||||
header: true,
|
||||
Component: Settings
|
||||
},
|
||||
{
|
||||
label: 'Templates',
|
||||
path: PATH.APPLICATIONS_TEMPLATES.LIST,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: TemplatesIcons,
|
||||
Component: ApplicationsTemplates
|
||||
@ -65,19 +68,16 @@ export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Create Application template',
|
||||
path: PATH.APPLICATIONS_TEMPLATES.CREATE,
|
||||
authenticated: true,
|
||||
Component: ApplicationsTemplatesFormCreate
|
||||
},
|
||||
{
|
||||
label: 'Edit Application template',
|
||||
path: PATH.APPLICATIONS_TEMPLATES.EDIT,
|
||||
authenticated: true,
|
||||
Component: ApplicationsTemplatesFormCreate
|
||||
},
|
||||
{
|
||||
label: 'Instances',
|
||||
path: PATH.APPLICATIONS.LIST,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: InstancesIcons,
|
||||
Component: ApplicationsInstances
|
@ -15,25 +15,52 @@
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import { useAuth, useProvision } from 'client/hooks'
|
||||
import Router from 'client/router'
|
||||
import routes from 'client/router/provision'
|
||||
import routes from 'client/apps/provision/routes'
|
||||
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import { useProvisionTemplate, useProvisionApi } from 'client/features/One'
|
||||
|
||||
import LoadingScreen from 'client/components/LoadingScreen'
|
||||
import { _APPS } from 'client/constants'
|
||||
|
||||
const APP_NAME = _APPS.provision.name
|
||||
|
||||
const ProvisionApp = () => {
|
||||
const { isLogged, isLoginInProcess } = useAuth()
|
||||
const { getProvisionsTemplates } = useProvision()
|
||||
const { jwt } = useAuth()
|
||||
const { getAuthUser, logout } = useAuthApi()
|
||||
|
||||
const provisionTemplate = useProvisionTemplate()
|
||||
const { getProvisionsTemplates } = useProvisionApi()
|
||||
const { changeTitle } = useGeneralApi()
|
||||
|
||||
const [firstRender, setFirstRender] = React.useState(() => !!jwt)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isLogged && !isLoginInProcess) {
|
||||
getProvisionsTemplates()
|
||||
}
|
||||
}, [isLogged])
|
||||
(async () => {
|
||||
try {
|
||||
if (jwt) {
|
||||
getAuthUser()
|
||||
!provisionTemplate?.length && await getProvisionsTemplates()
|
||||
}
|
||||
|
||||
return <Router title={APP_NAME} routes={routes} />
|
||||
firstRender && setFirstRender(false)
|
||||
} catch {
|
||||
logout()
|
||||
}
|
||||
})()
|
||||
}, [firstRender, jwt])
|
||||
|
||||
React.useEffect(() => {
|
||||
changeTitle(APP_NAME)
|
||||
}, [])
|
||||
|
||||
if (firstRender) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
return <Router routes={routes} />
|
||||
}
|
||||
|
||||
ProvisionApp.displayName = '_ProvisionApp'
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
|
||||
import loadable from '@loadable/component'
|
||||
|
||||
const Login = loadable(() => import('client/containers/Login'), { ssr: false })
|
||||
const Dashboard = loadable(() => import('client/containers/Dashboard/Provision'), { ssr: false })
|
||||
|
||||
const Providers = loadable(() => import('client/containers/Providers'), { ssr: false })
|
||||
@ -19,7 +18,6 @@ const ProvisionsFormCreate = loadable(() => import('client/containers/Provisions
|
||||
const Settings = loadable(() => import('client/containers/Settings'), { ssr: false })
|
||||
|
||||
export const PATH = {
|
||||
LOGIN: '/',
|
||||
DASHBOARD: '/dashboard',
|
||||
PROVIDERS: {
|
||||
LIST: '/providers',
|
||||
@ -36,16 +34,9 @@ export const PATH = {
|
||||
}
|
||||
|
||||
export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Login',
|
||||
path: PATH.LOGIN,
|
||||
authenticated: false,
|
||||
Component: Login
|
||||
},
|
||||
{
|
||||
label: 'Dashboard',
|
||||
path: PATH.DASHBOARD,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: DashboardIcon,
|
||||
Component: Dashboard
|
||||
@ -53,7 +44,6 @@ export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Providers',
|
||||
path: PATH.PROVIDERS.LIST,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: ProvidersIcon,
|
||||
Component: Providers
|
||||
@ -61,19 +51,16 @@ export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Create Provider',
|
||||
path: PATH.PROVIDERS.CREATE,
|
||||
authenticated: true,
|
||||
Component: ProvidersFormCreate
|
||||
},
|
||||
{
|
||||
label: 'Edit Provider template',
|
||||
path: PATH.PROVIDERS.EDIT,
|
||||
authenticated: true,
|
||||
Component: ProvidersFormCreate
|
||||
},
|
||||
{
|
||||
label: 'Provisions',
|
||||
path: PATH.PROVISIONS.LIST,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: ProvisionsIcon,
|
||||
Component: Provisions
|
||||
@ -81,19 +68,16 @@ export const ENDPOINTS = [
|
||||
{
|
||||
label: 'Create Provision',
|
||||
path: PATH.PROVISIONS.CREATE,
|
||||
authenticated: true,
|
||||
Component: ProvisionsFormCreate
|
||||
},
|
||||
{
|
||||
label: 'Edit Provision template',
|
||||
path: PATH.PROVISIONS.EDIT,
|
||||
authenticated: true,
|
||||
Component: ProvisionsFormCreate
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
path: PATH.SETTINGS,
|
||||
authenticated: true,
|
||||
sidebar: true,
|
||||
icon: SettingsIcon,
|
||||
Component: Settings
|
@ -5,7 +5,7 @@ import useFetch from 'client/hooks/useFetch'
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
|
||||
const Action = memo(({ handleClick, icon, cy, ...props }) => {
|
||||
const { fetchRequest, loading } = useFetch(
|
||||
const { fetchRequest, data, loading } = useFetch(
|
||||
() => Promise.resolve(handleClick())
|
||||
)
|
||||
|
||||
@ -15,7 +15,8 @@ const Action = memo(({ handleClick, icon, cy, ...props }) => {
|
||||
icon
|
||||
isSubmitting={loading}
|
||||
label={icon}
|
||||
onClick={() => fetchRequest()}
|
||||
onClick={fetchRequest}
|
||||
disabled={!!data}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
@ -54,7 +54,7 @@ export default makeStyles(theme => ({
|
||||
},
|
||||
headerRoot: {
|
||||
// align header icon to top
|
||||
alignItems: 'end'
|
||||
alignItems: 'start'
|
||||
},
|
||||
headerContent: { overflow: 'auto' },
|
||||
headerAvatar: {
|
||||
|
@ -1,31 +1,31 @@
|
||||
import ClusterCard from 'client/components/Cards/ClusterCard'
|
||||
import DatastoreCard from 'client/components/Cards/DatastoreCard'
|
||||
import HostCard from 'client/components/Cards/HostCard'
|
||||
import NetworkCard from 'client/components/Cards/NetworkCard'
|
||||
import TierCard from 'client/components/Cards/TierCard'
|
||||
import EmptyCard from 'client/components/Cards/EmptyCard'
|
||||
import SelectCard from 'client/components/Cards/SelectCard'
|
||||
import ApplicationTemplateCard from 'client/components/Cards/ApplicationTemplateCard'
|
||||
import ApplicationCard from 'client/components/Cards/ApplicationCard'
|
||||
import ApplicationNetworkCard from 'client/components/Cards/ApplicationNetworkCard'
|
||||
import ApplicationTemplateCard from 'client/components/Cards/ApplicationTemplateCard'
|
||||
import ClusterCard from 'client/components/Cards/ClusterCard'
|
||||
import DatastoreCard from 'client/components/Cards/DatastoreCard'
|
||||
import EmptyCard from 'client/components/Cards/EmptyCard'
|
||||
import HostCard from 'client/components/Cards/HostCard'
|
||||
import NetworkCard from 'client/components/Cards/NetworkCard'
|
||||
import PolicyCard from 'client/components/Cards/PolicyCard'
|
||||
import ProvisionCard from 'client/components/Cards/ProvisionCard'
|
||||
import ProvisionTemplateCard from 'client/components/Cards/ProvisionTemplateCard'
|
||||
import SelectCard from 'client/components/Cards/SelectCard'
|
||||
import TierCard from 'client/components/Cards/TierCard'
|
||||
import WavesCard from 'client/components/Cards/WavesCard'
|
||||
|
||||
export {
|
||||
ClusterCard,
|
||||
DatastoreCard,
|
||||
HostCard,
|
||||
NetworkCard,
|
||||
TierCard,
|
||||
EmptyCard,
|
||||
SelectCard,
|
||||
ApplicationTemplateCard,
|
||||
ApplicationCard,
|
||||
ApplicationNetworkCard,
|
||||
ApplicationTemplateCard,
|
||||
ClusterCard,
|
||||
DatastoreCard,
|
||||
EmptyCard,
|
||||
HostCard,
|
||||
NetworkCard,
|
||||
PolicyCard,
|
||||
ProvisionTemplateCard,
|
||||
ProvisionCard,
|
||||
ProvisionTemplateCard,
|
||||
SelectCard,
|
||||
TierCard,
|
||||
WavesCard
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ const Count = ({ start, number, duration }) => {
|
||||
|
||||
const timer = setInterval(() => {
|
||||
start += 1
|
||||
setCount(String(start) + number.substring(3))
|
||||
setCount(String(start) + String(number).substring(3))
|
||||
if (start === end) clearInterval(timer)
|
||||
}, incrementTime)
|
||||
|
||||
|
@ -19,7 +19,6 @@ const debugLogStyles = makeStyles(theme => ({
|
||||
overflow: 'auto',
|
||||
borderRadius: 5,
|
||||
backgroundColor: '#1d1f21',
|
||||
fontSize: '1.1em',
|
||||
wordBreak: 'break-word',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: 14
|
||||
|
@ -13,25 +13,23 @@ const MAX_CHARS = 80
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '0.3em',
|
||||
padding: '0.5em 0',
|
||||
cursor: ({ isMoreThanMaxChars }) => isMoreThanMaxChars ? 'pointer' : 'default',
|
||||
fontSize: '14px',
|
||||
fontFamily: 'monospace',
|
||||
color: '#fafafa',
|
||||
'&:hover': {
|
||||
background: '#333537'
|
||||
}
|
||||
},
|
||||
arrow: {
|
||||
padding: '0 0.5em',
|
||||
width: '32px'
|
||||
},
|
||||
time: {
|
||||
minWidth: '220px'
|
||||
},
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '32px 220px 1fr',
|
||||
gap: '1em',
|
||||
alignItems: 'center',
|
||||
cursor: ({ isMoreThanMaxChars }) =>
|
||||
isMoreThanMaxChars ? 'pointer' : 'default'
|
||||
},
|
||||
message: {
|
||||
transition: 'all 0.3s ease-out',
|
||||
whiteSpace: 'pre-line'
|
||||
},
|
||||
[DEBUG_LEVEL.ERROR]: { borderLeft: `0.3em solid ${theme.palette.error.light}` },
|
||||
@ -56,14 +54,14 @@ const Message = memo(({ timestamp, severity, message }) => {
|
||||
className={clsx(classes.root, classes[severity])}
|
||||
onClick={() => setCollapse(prev => !prev)}
|
||||
>
|
||||
<div className={classes.arrow}>
|
||||
<span>
|
||||
{isMoreThanMaxChars && (isCollapsed ? (
|
||||
<ChevronRightIcon fontSize='small' />
|
||||
) : (
|
||||
<ExpandMoreIcon fontSize='small' />
|
||||
))}
|
||||
</div>
|
||||
<div className={classes.time}>{timestamp}</div>
|
||||
</span>
|
||||
<div>{timestamp}</div>
|
||||
<div className={classes.message}
|
||||
dangerouslySetInnerHTML={{ __html: html }} />
|
||||
</div>
|
||||
|
@ -21,8 +21,8 @@ const useStyles = makeStyles(theme => ({
|
||||
|
||||
const DialogRequest = ({ withTabs, request, dialogProps, children }) => {
|
||||
const classes = useStyles()
|
||||
const methods = useFetch(request)
|
||||
const { data, fetchRequest, loading, error } = methods
|
||||
const fetchProps = useFetch(request)
|
||||
const { data, fetchRequest, loading, error } = fetchProps
|
||||
|
||||
useEffect(() => { fetchRequest() }, [])
|
||||
|
||||
@ -47,7 +47,7 @@ const DialogRequest = ({ withTabs, request, dialogProps, children }) => {
|
||||
|
||||
return (
|
||||
<DialogConfirmation {...dialogProps}>
|
||||
{children(methods)}
|
||||
{children({ fetchProps })}
|
||||
</DialogConfirmation>
|
||||
)
|
||||
}
|
||||
|
@ -1,24 +1,28 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
makeStyles, CircularProgress, Button, IconButton
|
||||
} from '@material-ui/core'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
import { makeStyles, CircularProgress, Button, IconButton } from '@material-ui/core'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
transition: 'disabled 0.5s ease',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
disabled: {
|
||||
'& svg': {
|
||||
color: theme.palette.action.disabled
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const ButtonComponent = ({ icon, children, ...props }) => icon ? (
|
||||
<IconButton {...props}>{children}</IconButton>
|
||||
) : (
|
||||
<Button type="submit" variant="contained" {...props}>
|
||||
<Button type='submit' variant='contained' {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
@ -29,25 +33,28 @@ ButtonComponent.propTypes = {
|
||||
}
|
||||
|
||||
const SubmitButton = React.memo(
|
||||
({ isSubmitting, label, icon, color, size, className, ...props }) => {
|
||||
({ isSubmitting, disabled, label, icon, color, size, className, ...props }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<ButtonComponent
|
||||
className={clsx(classes.root, className)}
|
||||
className={clsx(classes.root, className, {
|
||||
[classes.disabled]: disabled
|
||||
})}
|
||||
color={color}
|
||||
disabled={isSubmitting}
|
||||
disabled={disabled || isSubmitting}
|
||||
icon={icon}
|
||||
size={size}
|
||||
{...props}
|
||||
>
|
||||
{isSubmitting && <CircularProgress color="secondary" size={24} />}
|
||||
{isSubmitting && <CircularProgress color='secondary' size={24} />}
|
||||
{!isSubmitting && (label ?? Tr(T.Submit))}
|
||||
</ButtonComponent>
|
||||
)
|
||||
},
|
||||
(prev, next) =>
|
||||
prev.isSubmitting === next.isSubmitting &&
|
||||
prev.disabled === next.disabled &&
|
||||
prev.label === next.label &&
|
||||
prev.onClick === next.onClick
|
||||
)
|
||||
@ -56,6 +63,7 @@ SubmitButton.propTypes = {
|
||||
icon: PropTypes.bool,
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
|
||||
isSubmitting: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
color: PropTypes.oneOf(['default', 'inherit', 'primary', 'secondary']),
|
||||
size: PropTypes.oneOf(['large', 'medium', 'small'])
|
||||
@ -65,6 +73,7 @@ SubmitButton.defaultProps = {
|
||||
icon: false,
|
||||
label: undefined,
|
||||
isSubmitting: false,
|
||||
disabled: false,
|
||||
className: undefined,
|
||||
color: 'default'
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
|
||||
import { useGeneral } from 'client/hooks'
|
||||
import { useGeneral } from 'client/features/General'
|
||||
import CustomMobileStepper from 'client/components/FormStepper/MobileStepper'
|
||||
import CustomStepper from 'client/components/FormStepper/Stepper'
|
||||
import { groupBy } from 'client/utils'
|
||||
@ -14,7 +14,7 @@ const FIRST_STEP = 0
|
||||
const FormStepper = ({ steps, schema, onSubmit }) => {
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
|
||||
const { watch, reset, errors, setError } = useFormContext()
|
||||
const { isLoading, changeLoading } = useGeneral()
|
||||
const { isLoading } = useGeneral()
|
||||
|
||||
const [formData, setFormData] = useState(() => watch())
|
||||
const [activeStep, setActiveStep] = useState(FIRST_STEP)
|
||||
@ -38,7 +38,6 @@ const FormStepper = ({ steps, schema, onSubmit }) => {
|
||||
}
|
||||
|
||||
const setErrors = ({ inner = [], ...rest }) => {
|
||||
changeLoading(false)
|
||||
const errorsByPath = groupBy(inner, 'path') ?? {}
|
||||
const totalErrors = Object.keys(errorsByPath).length
|
||||
|
||||
|
@ -20,19 +20,19 @@ import clsx from 'clsx'
|
||||
import { Box, Container } from '@material-ui/core'
|
||||
import { CSSTransition } from 'react-transition-group'
|
||||
|
||||
import { useGeneral } from 'client/hooks'
|
||||
import { useGeneral } from 'client/features/General'
|
||||
import Header from 'client/components/Header'
|
||||
import Footer from 'client/components/Footer'
|
||||
import internalStyles from 'client/components/HOC/InternalLayout/styles'
|
||||
|
||||
const InternalLayout = ({ label, children }) => {
|
||||
const InternalLayout = ({ children }) => {
|
||||
const classes = internalStyles()
|
||||
const scroll = React.useRef()
|
||||
const container = React.useRef()
|
||||
const { isFixMenu } = useGeneral()
|
||||
|
||||
return (
|
||||
<Box className={clsx(classes.root, { [classes.isDrawerFixed]: isFixMenu })}>
|
||||
<Header title={label} scrollableContainer={scroll?.current} />
|
||||
<Header scrollContainer={container.current} />
|
||||
<Box component="main" className={classes.main}>
|
||||
<CSSTransition
|
||||
in
|
||||
@ -49,7 +49,7 @@ const InternalLayout = ({ label, children }) => {
|
||||
timeout={300}
|
||||
unmountOnExit
|
||||
>
|
||||
<Container ref={scroll} maxWidth={false} className={classes.scrollable}>
|
||||
<Container ref={container} maxWidth={false} className={classes.scrollable}>
|
||||
{children}
|
||||
</Container>
|
||||
</CSSTransition>
|
||||
@ -60,18 +60,15 @@ const InternalLayout = ({ label, children }) => {
|
||||
}
|
||||
|
||||
InternalLayout.propTypes = {
|
||||
endpoints: PropTypes.arrayOf(PropTypes.object),
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
]),
|
||||
label: PropTypes.string
|
||||
])
|
||||
}
|
||||
|
||||
InternalLayout.defaultProps = {
|
||||
children: [],
|
||||
label: null
|
||||
children: []
|
||||
}
|
||||
|
||||
export default InternalLayout
|
||||
|
@ -1,103 +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. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useLocation, Redirect, matchPath } from 'react-router-dom'
|
||||
|
||||
import { useAuth, useOpennebula } from 'client/hooks'
|
||||
import Sidebar from 'client/components/Sidebar'
|
||||
import Notifier from 'client/components/Notifier'
|
||||
import LoadingScreen from 'client/components/LoadingScreen'
|
||||
|
||||
const findRouteByPathname = (endpoints = [], pathname = '') => {
|
||||
const route = endpoints?.find(({ path }) =>
|
||||
matchPath(pathname, { path, exact: true })
|
||||
)
|
||||
|
||||
return route ?? {}
|
||||
}
|
||||
|
||||
const MainLayout = ({ endpoints, children }) => {
|
||||
const { PATH, ENDPOINTS } = endpoints
|
||||
const { pathname } = useLocation()
|
||||
const { groups } = useOpennebula()
|
||||
const {
|
||||
isLogged,
|
||||
isLoginInProcess,
|
||||
getAuthInfo,
|
||||
authUser,
|
||||
firstRender,
|
||||
isLoading
|
||||
} = useAuth()
|
||||
|
||||
useEffect(() => {
|
||||
if (isLogged && !isLoginInProcess && !isLoading) {
|
||||
getAuthInfo()
|
||||
}
|
||||
}, [isLogged, isLoginInProcess, pathname])
|
||||
|
||||
const { authenticated } = findRouteByPathname(ENDPOINTS, pathname)
|
||||
const authRoute = Boolean(authenticated)
|
||||
|
||||
// PENDING TO AUTHENTICATING OR FIRST RENDERING
|
||||
if (firstRender || (isLogged && authRoute && !authUser && !groups?.length)) {
|
||||
return <LoadingScreen />
|
||||
}
|
||||
|
||||
// PROTECTED ROUTE
|
||||
if (authRoute && !isLogged && !isLoginInProcess) {
|
||||
return <Redirect to={PATH.LOGIN} />
|
||||
}
|
||||
|
||||
// PUBLIC ROUTE
|
||||
if (!authRoute && isLogged && !isLoginInProcess) {
|
||||
return <Redirect to={PATH.DASHBOARD} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{authRoute && isLogged && (
|
||||
<>
|
||||
<Sidebar endpoints={ENDPOINTS} />
|
||||
<Notifier />
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
MainLayout.propTypes = {
|
||||
endpoints: PropTypes.shape({
|
||||
PATH: PropTypes.object,
|
||||
ENDPOINTS: PropTypes.array
|
||||
}),
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
])
|
||||
}
|
||||
|
||||
MainLayout.defaultProps = {
|
||||
endpoints: {
|
||||
PATH: {},
|
||||
ENDPOINTS: []
|
||||
},
|
||||
children: ''
|
||||
}
|
||||
|
||||
export default MainLayout
|
@ -18,7 +18,7 @@ import PropTypes from 'prop-types'
|
||||
import root from 'window-or-global'
|
||||
import { sprintf } from 'sprintf-js'
|
||||
|
||||
import { useAuth } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { DEFAULT_LANGUAGE, LANGUAGES_URL } from 'client/constants'
|
||||
|
||||
const TranslateContext = createContext()
|
||||
|
@ -14,5 +14,4 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
export { default as InternalLayout } from 'client/components/HOC/InternalLayout'
|
||||
export { default as MainLayout } from 'client/components/HOC/MainLayout'
|
||||
export * from 'client/components/HOC/Translate'
|
||||
|
@ -4,7 +4,7 @@ import { Button } from '@material-ui/core'
|
||||
import FilterIcon from '@material-ui/icons/FilterDrama'
|
||||
import SelectedIcon from '@material-ui/icons/FilterVintage'
|
||||
|
||||
import { useAuth, useOpennebula } from 'client/hooks'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import Search from 'client/components/Search'
|
||||
|
||||
import { FILTER_POOL } from 'client/constants'
|
||||
@ -15,17 +15,17 @@ const { ALL_RESOURCES, PRIMARY_GROUP_RESOURCES } = FILTER_POOL
|
||||
|
||||
const Group = () => {
|
||||
const classes = headerStyles()
|
||||
const { authUser, filterPool, setPrimaryGroup } = useAuth()
|
||||
const { groups } = useOpennebula()
|
||||
const { user, groups, filterPool } = useAuth()
|
||||
const { changeGroup } = useAuthApi()
|
||||
|
||||
const handleChangeGroup = group => {
|
||||
group && setPrimaryGroup({ group })
|
||||
group && changeGroup({ id: user.ID, group })
|
||||
}
|
||||
|
||||
const renderResult = ({ ID, NAME }, handleClose) => {
|
||||
const isSelected =
|
||||
(filterPool === ALL_RESOURCES && ALL_RESOURCES === ID) ||
|
||||
(filterPool === PRIMARY_GROUP_RESOURCES && authUser?.GID === ID)
|
||||
(filterPool === PRIMARY_GROUP_RESOURCES && user?.GID === ID)
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -44,9 +44,9 @@ const Group = () => {
|
||||
}
|
||||
|
||||
const sortGroupAsMainFirst = (a, b) => {
|
||||
if (a.ID === authUser?.GUID) {
|
||||
if (a.ID === user?.GUID) {
|
||||
return -1
|
||||
} else if (b.ID === authUser?.GUID) {
|
||||
} else if (b.ID === user?.GUID) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
|
@ -10,11 +10,10 @@ import {
|
||||
Button
|
||||
} from '@material-ui/core'
|
||||
import CloseIcon from '@material-ui/icons/Close'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
|
||||
import headerStyles from 'client/components/Header/styles'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const HeaderPopover = ({
|
||||
id,
|
||||
@ -39,17 +38,17 @@ const HeaderPopover = ({
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
color="inherit"
|
||||
color='inherit'
|
||||
aria-controls={anchorId}
|
||||
aria-describedby={anchorId}
|
||||
aria-haspopup="true"
|
||||
aria-haspopup='true'
|
||||
onClick={handleOpen}
|
||||
{...buttonProps}
|
||||
style={{ margin: '0 2px' }}
|
||||
>
|
||||
{icon}
|
||||
{buttonLabel && (
|
||||
<span className={classes.buttonLabel} data-cy="header-username">{buttonLabel}</span>
|
||||
<span className={classes.buttonLabel}>{buttonLabel}</span>
|
||||
)}
|
||||
</Button>
|
||||
<Popover
|
||||
@ -75,7 +74,7 @@ const HeaderPopover = ({
|
||||
{(headerTitle || isMobile) && (
|
||||
<Box className={classes.header}>
|
||||
{headerTitle && (
|
||||
<Typography className={classes.title} variant="body1">
|
||||
<Typography className={classes.title} variant='body1'>
|
||||
{Tr(headerTitle)}
|
||||
</Typography>
|
||||
)}
|
||||
|
@ -3,31 +3,33 @@ import * as React from 'react'
|
||||
import { MenuItem, MenuList, Link } from '@material-ui/core'
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle'
|
||||
|
||||
import { useAuth } from 'client/hooks'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import HeaderPopover from 'client/components/Header/Popover'
|
||||
import { DevTypography } from 'client/components/Typography'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { isDevelopment } from 'client/utils'
|
||||
import { T, APPS, APP_URL } from 'client/constants'
|
||||
|
||||
const User = React.memo(() => {
|
||||
const { logout, authUser } = useAuth()
|
||||
const { user } = useAuth()
|
||||
const { logout } = useAuthApi()
|
||||
|
||||
const handleLogout = () => logout()
|
||||
|
||||
return (
|
||||
<HeaderPopover
|
||||
id="user-menu"
|
||||
buttonLabel={authUser?.NAME}
|
||||
id='user-menu'
|
||||
buttonLabel={user?.NAME}
|
||||
icon={<AccountCircleIcon />}
|
||||
buttonProps={{ 'data-cy': 'header-user-button', variant: 'outlined' }}
|
||||
disablePadding
|
||||
>
|
||||
{() => (
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleLogout} data-cy="header-logout-button">
|
||||
<MenuItem onClick={handleLogout} data-cy='header-logout-button'>
|
||||
{Tr(T.SignOut)}
|
||||
</MenuItem>
|
||||
{process?.env?.NODE_ENV === 'development' &&
|
||||
{isDevelopment() &&
|
||||
APPS?.map(appName => (
|
||||
<MenuItem key={appName}>
|
||||
<Link color='secondary' href={`${APP_URL}/${appName}`} style={{ width: '100%' }}>
|
||||
|
@ -13,7 +13,7 @@
|
||||
/* limitations under the License. */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
import React, { useMemo } from 'react'
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
@ -22,73 +22,73 @@ import {
|
||||
Toolbar,
|
||||
Typography,
|
||||
IconButton,
|
||||
useMediaQuery,
|
||||
useScrollTrigger
|
||||
useMediaQuery
|
||||
// useScrollTrigger
|
||||
} from '@material-ui/core'
|
||||
import MenuIcon from '@material-ui/icons/Menu'
|
||||
|
||||
import { useAuth, useGeneral } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGeneral, useGeneralApi } from 'client/features/General'
|
||||
|
||||
import User from 'client/components/Header/User'
|
||||
import Group from 'client/components/Header/Group'
|
||||
import Zone from 'client/components/Header/Zone'
|
||||
import headerStyles from 'client/components/Header/styles'
|
||||
|
||||
const Header = ({ title, scrollableContainer }) => {
|
||||
const Header = ({ scrollContainer }) => {
|
||||
const { isOneAdmin } = useAuth()
|
||||
const { theme, isFixMenu, fixMenu } = useGeneral()
|
||||
const { title } = useGeneral()
|
||||
const { fixMenu } = useGeneralApi()
|
||||
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'))
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
|
||||
|
||||
const isScroll = useScrollTrigger({
|
||||
/* const isScroll = scrollContainer && useScrollTrigger({
|
||||
disableHysteresis: true,
|
||||
threshold: 100,
|
||||
target: scrollableContainer ?? undefined
|
||||
})
|
||||
target: scrollContainer
|
||||
}) */
|
||||
|
||||
const classes = headerStyles({ isScroll })
|
||||
const classes = headerStyles({ isScroll: false })
|
||||
|
||||
const handleFixMenu = () => fixMenu(true)
|
||||
const handleFixMenu = () => {
|
||||
fixMenu(true)
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<AppBar className={classes.appbar} data-cy="header" elevation={1}>
|
||||
<Toolbar>
|
||||
{!isUpLg && (
|
||||
<IconButton onClick={handleFixMenu} edge="start" color="inherit">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<Typography
|
||||
variant="h6"
|
||||
className={classes.title}
|
||||
data-cy="header-title"
|
||||
>
|
||||
{'One'}
|
||||
<span className={classes.app}>
|
||||
{title}
|
||||
</span>
|
||||
</Typography>
|
||||
)}
|
||||
<Box flexGrow={isMobile ? 1 : 0} textAlign="end">
|
||||
<User />
|
||||
{!isOneAdmin && <Group />}
|
||||
<Zone />
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
),
|
||||
[theme, isFixMenu, fixMenu, isUpLg, isMobile, isOneAdmin, classes]
|
||||
return (
|
||||
<AppBar className={classes.appbar} data-cy="header" elevation={1}>
|
||||
<Toolbar>
|
||||
{!isUpLg && (
|
||||
<IconButton onClick={handleFixMenu} edge="start" color="inherit">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<Typography
|
||||
variant="h6"
|
||||
className={classes.title}
|
||||
data-cy="header-title"
|
||||
>
|
||||
{'One'}
|
||||
<span className={classes.app}>{title}</span>
|
||||
</Typography>
|
||||
)}
|
||||
<Box flexGrow={isMobile ? 1 : 0} textAlign="end">
|
||||
<User />
|
||||
{!isOneAdmin && <Group />}
|
||||
<Zone />
|
||||
</Box>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
)
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
title: PropTypes.string
|
||||
scrollContainer: PropTypes.object
|
||||
}
|
||||
|
||||
Header.defaultProps = {
|
||||
title: ''
|
||||
scrollContainer: null
|
||||
}
|
||||
|
||||
export default Header
|
||||
|
@ -12,7 +12,6 @@ export default makeStyles(theme => ({
|
||||
userSelect: 'none',
|
||||
flexGrow: 1,
|
||||
display: 'inline-flex',
|
||||
color: theme.palette.primary.contrastText,
|
||||
'& span': { textTransform: 'capitalize' }
|
||||
},
|
||||
app: {
|
||||
|
@ -27,57 +27,58 @@ const ListCards = ({
|
||||
const classes = listCardsStyles()
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
|
||||
|
||||
if (isLoading) {
|
||||
return <LinearProgress color='secondary' className={classes.loading} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={3} {...gridProps}>
|
||||
{/* CREATE CARD COMPONENT */}
|
||||
{handleCreate && (ButtonCreateComponent ? (
|
||||
<ButtonCreateComponent onClick={handleCreate} />
|
||||
) : (
|
||||
isMobile ? (
|
||||
<FloatingActionButton icon={<AddIcon />} onClick={handleCreate} />
|
||||
) : (
|
||||
<Grid item {...breakpoints}>
|
||||
<Card className={classes.cardPlus} raised>
|
||||
<CardActionArea onClick={handleCreate}>
|
||||
<AddIcon />
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
))}
|
||||
|
||||
{/* LIST */}
|
||||
{list.length > 0 ? (
|
||||
<TransitionGroup component={null}>
|
||||
{list?.map((value, index) => {
|
||||
const key = value[keyProp] ?? value[keyProp.toUpperCase()]
|
||||
|
||||
return (
|
||||
<CSSTransition
|
||||
// use key to render transition (default: id or ID)
|
||||
key={`card-${key.replace(/\s/g, '')}`}
|
||||
classNames={classes.item}
|
||||
timeout={400}
|
||||
>
|
||||
<Grid item {...breakpoints} {...value?.breakpoints}>
|
||||
<CardComponent value={value} {...cardsProps({ index, value })} />
|
||||
</Grid>
|
||||
</CSSTransition>
|
||||
)
|
||||
})}
|
||||
</TransitionGroup>
|
||||
) : (
|
||||
(displayEmpty || EmptyComponent) && (
|
||||
<Grid item {...breakpoints}>
|
||||
{EmptyComponent ?? <EmptyCard title={'Your list is empty'} />}
|
||||
</Grid>
|
||||
)
|
||||
<>
|
||||
{isLoading && (
|
||||
<LinearProgress color='secondary' className={classes.loading} />
|
||||
)}
|
||||
</Grid>
|
||||
<Grid container spacing={3} {...gridProps}>
|
||||
{/* CREATE CARD COMPONENT */}
|
||||
{handleCreate && (ButtonCreateComponent ? (
|
||||
<ButtonCreateComponent onClick={handleCreate} />
|
||||
) : (
|
||||
isMobile ? (
|
||||
<FloatingActionButton icon={<AddIcon />} onClick={handleCreate} />
|
||||
) : (
|
||||
<Grid item {...breakpoints}>
|
||||
<Card className={classes.cardPlus} raised>
|
||||
<CardActionArea onClick={handleCreate}>
|
||||
<AddIcon />
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
))}
|
||||
|
||||
{/* LIST */}
|
||||
{list.length > 0 ? (
|
||||
<TransitionGroup component={null}>
|
||||
{list?.map((value, index) => {
|
||||
const key = value[keyProp] ?? value[keyProp.toUpperCase()]
|
||||
|
||||
return (
|
||||
<CSSTransition
|
||||
// use key to render transition (default: id or ID)
|
||||
key={`card-${key.replace(/\s/g, '')}`}
|
||||
classNames={classes.item}
|
||||
timeout={400}
|
||||
>
|
||||
<Grid item {...breakpoints} {...value?.breakpoints}>
|
||||
<CardComponent value={value} {...cardsProps({ index, value })} />
|
||||
</Grid>
|
||||
</CSSTransition>
|
||||
)
|
||||
})}
|
||||
</TransitionGroup>
|
||||
) : (
|
||||
(displayEmpty || EmptyComponent) && (
|
||||
<Grid item {...breakpoints}>
|
||||
{EmptyComponent ?? <EmptyCard title={'Your list is empty'} />}
|
||||
</Grid>
|
||||
)
|
||||
)}
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,10 @@ export default makeStyles(() => ({
|
||||
display: 'flex',
|
||||
textAlign: 'center'
|
||||
},
|
||||
loading: { width: '100%' },
|
||||
loading: {
|
||||
width: '100%',
|
||||
marginBottom: '1em'
|
||||
},
|
||||
item: {
|
||||
'&-enter': { opacity: 0 },
|
||||
'&-enter-active': {
|
||||
|
@ -1,12 +1,11 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import { IconButton } from '@material-ui/core'
|
||||
import CloseIcon from '@material-ui/icons/Close'
|
||||
|
||||
import { removeSnackbar } from 'client/actions/general'
|
||||
import { useGeneral, useGeneralApi } from 'client/features/General'
|
||||
|
||||
const CloseButton = ({ handleClick }) => (
|
||||
<IconButton onClick={handleClick} component="span">
|
||||
@ -17,8 +16,9 @@ const CloseButton = ({ handleClick }) => (
|
||||
let displayed = []
|
||||
|
||||
const Notifier = () => {
|
||||
const dispatch = useDispatch()
|
||||
const notifications = useSelector(store => store.General.notifications || [])
|
||||
const { notifications } = useGeneral()
|
||||
const { deleteSnackbar } = useGeneralApi()
|
||||
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
||||
|
||||
const storeDisplayed = id => {
|
||||
@ -43,13 +43,8 @@ const Notifier = () => {
|
||||
key,
|
||||
...options,
|
||||
action: CloseButton({ handleClick: () => closeSnackbar(key) }),
|
||||
onClose: (event, reason, myKey) => {
|
||||
if (options.onClose) {
|
||||
options.onClose(event, reason, myKey)
|
||||
}
|
||||
},
|
||||
onExited: (_, myKey) => {
|
||||
dispatch(removeSnackbar(myKey))
|
||||
deleteSnackbar(myKey)
|
||||
removeDisplayed(myKey)
|
||||
}
|
||||
})
|
||||
@ -58,7 +53,7 @@ const Notifier = () => {
|
||||
storeDisplayed(key)
|
||||
}
|
||||
)
|
||||
}, [notifications, closeSnackbar, enqueueSnackbar, dispatch])
|
||||
}, [notifications, closeSnackbar, enqueueSnackbar, deleteSnackbar])
|
||||
|
||||
return null
|
||||
}
|
||||
|
28
src/fireedge/src/client/components/Route/NoAuthRoute.js
Normal file
28
src/fireedge/src/client/components/Route/NoAuthRoute.js
Normal file
@ -0,0 +1,28 @@
|
||||
/* 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 * as React from 'react'
|
||||
|
||||
import { Redirect, Route } from 'react-router-dom'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
|
||||
const NoAuthRoute = props => {
|
||||
const { isLogged, isLoginInProgress, isLoading } = useAuth()
|
||||
const isAuthenticated = isLogged && !isLoginInProgress && !isLoading
|
||||
|
||||
return isAuthenticated ? <Redirect to='/dashboard' /> : <Route exact {...props}/>
|
||||
}
|
||||
|
||||
export default NoAuthRoute
|
32
src/fireedge/src/client/components/Route/ProtectedRoute.js
Normal file
32
src/fireedge/src/client/components/Route/ProtectedRoute.js
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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 * as React from 'react'
|
||||
import { Redirect, Route } from 'react-router-dom'
|
||||
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
|
||||
const ProtectedRoute = props => {
|
||||
const { isLogged, jwt } = useAuth()
|
||||
const { getAuthUser } = useAuthApi()
|
||||
|
||||
React.useEffect(() => {
|
||||
jwt && getAuthUser()
|
||||
}, [])
|
||||
|
||||
return isLogged ? <Route {...props}/> : <Redirect to='/' />
|
||||
}
|
||||
|
||||
export default ProtectedRoute
|
4
src/fireedge/src/client/components/Route/index.js
Normal file
4
src/fireedge/src/client/components/Route/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import NoAuthRoute from 'client/components/Route/NoAuthRoute'
|
||||
import ProtectedRoute from 'client/components/Route/ProtectedRoute'
|
||||
|
||||
export { NoAuthRoute, ProtectedRoute }
|
@ -10,7 +10,7 @@ import {
|
||||
useMediaQuery
|
||||
} from '@material-ui/core'
|
||||
|
||||
import { useGeneral } from 'client/hooks'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import sidebarStyles from 'client/components/Sidebar/styles'
|
||||
import { DevTypography } from 'client/components/Typography'
|
||||
|
||||
@ -18,10 +18,11 @@ const STATIC_LABEL_PROPS = { 'data-cy': 'main-menu-item-text' }
|
||||
|
||||
const SidebarLink = ({ label, path, icon: Icon, devMode, isSubItem }) => {
|
||||
const classes = sidebarStyles()
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'), { noSsr: true })
|
||||
|
||||
const history = useHistory()
|
||||
const { pathname } = useLocation()
|
||||
const { fixMenu } = useGeneral()
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'), { noSsr: true })
|
||||
const { fixMenu } = useGeneralApi()
|
||||
|
||||
const handleClick = () => {
|
||||
history.push(path)
|
||||
|
@ -27,7 +27,7 @@ import {
|
||||
} from '@material-ui/core'
|
||||
import { Menu as MenuIcon, Close as CloseIcon } from '@material-ui/icons'
|
||||
|
||||
import { useGeneral } from 'client/hooks'
|
||||
import { useGeneral, useGeneralApi } from 'client/features/General'
|
||||
import sidebarStyles from 'client/components/Sidebar/styles'
|
||||
import SidebarLink from 'client/components/Sidebar/SidebarLink'
|
||||
import SidebarCollapseItem from 'client/components/Sidebar/SidebarCollapseItem'
|
||||
@ -35,15 +35,17 @@ import Logo from 'client/icons/logo'
|
||||
|
||||
const Sidebar = memo(({ endpoints }) => {
|
||||
const classes = sidebarStyles()
|
||||
const { isFixMenu, fixMenu } = useGeneral()
|
||||
const isUpLg = useMediaQuery(theme => theme.breakpoints.up('lg'), { noSsr: true })
|
||||
|
||||
const { isFixMenu } = useGeneral()
|
||||
const { fixMenu } = useGeneralApi()
|
||||
|
||||
const handleSwapMenu = () => fixMenu(!isFixMenu)
|
||||
|
||||
const SidebarEndpoints = useMemo(
|
||||
() =>
|
||||
endpoints
|
||||
?.filter(({ authenticated, sidebar = false }) => authenticated && sidebar)
|
||||
?.filter(({ sidebar = false }) => sidebar)
|
||||
?.map((endpoint, index) =>
|
||||
endpoint.routes ? (
|
||||
<SidebarCollapseItem key={`item-${index}`} {...endpoint} />
|
||||
|
@ -3,7 +3,7 @@ import * as React from 'react'
|
||||
import { PieChart } from 'react-minimal-pie-chart'
|
||||
import { Typography, Paper } from '@material-ui/core'
|
||||
|
||||
import { useProvision } from 'client/hooks'
|
||||
import { useOne } from 'client/features/One'
|
||||
import { TypographyWithPoint } from 'client/components/Typography'
|
||||
import Count from 'client/components/Count'
|
||||
import { groupBy } from 'client/utils'
|
||||
@ -12,9 +12,8 @@ import { T, PROVIDERS_TYPES } from 'client/constants'
|
||||
import useStyles from 'client/components/Widgets/TotalProviders/styles'
|
||||
|
||||
const TotalProviders = () => {
|
||||
const { providers } = useProvision()
|
||||
|
||||
const classes = useStyles()
|
||||
const { providers } = useOne()
|
||||
|
||||
const totalProviders = React.useMemo(() => providers.length, [providers.length])
|
||||
|
||||
|
@ -7,14 +7,14 @@ import {
|
||||
AccountTree as NetworkIcon
|
||||
} from '@material-ui/icons'
|
||||
|
||||
import { useProvision } from 'client/hooks'
|
||||
import { useOne } from 'client/features/One'
|
||||
import Count from 'client/components/Count'
|
||||
import { WavesCard } from 'client/components/Cards'
|
||||
import { get } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const TotalProvisionInfrastructures = () => {
|
||||
const { provisions } = useProvision()
|
||||
const { provisions } = useOne()
|
||||
|
||||
const provisionsByProvider = React.useMemo(() =>
|
||||
provisions
|
||||
|
@ -2,7 +2,7 @@ import * as React from 'react'
|
||||
|
||||
import { Paper, Typography } from '@material-ui/core'
|
||||
|
||||
import { useProvision } from 'client/hooks'
|
||||
import { useOne } from 'client/features/One'
|
||||
import { SingleBar } from 'client/components/Charts'
|
||||
import Count from 'client/components/Count'
|
||||
import { groupBy } from 'client/utils'
|
||||
@ -11,8 +11,8 @@ import { T, PROVISIONS_STATES } from 'client/constants'
|
||||
import useStyles from 'client/components/Widgets/TotalProvisionsByState/styles'
|
||||
|
||||
const TotalProvisionsByState = () => {
|
||||
const { provisions } = useProvision()
|
||||
const classes = useStyles()
|
||||
const { provisions } = useOne()
|
||||
|
||||
const chartData = React.useMemo(() => {
|
||||
const groups = groupBy(provisions, 'TEMPLATE.BODY.state')
|
||||
|
@ -1,28 +1,34 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { Container, Box } from '@material-ui/core'
|
||||
|
||||
import { useApplication, useFetch } from 'client/hooks'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useApplication, useApplicationApi } from 'client/features/One'
|
||||
|
||||
import DialogInfo from 'client/containers/ApplicationsInstances/DialogInfo'
|
||||
import { ListHeader, ListCards } from 'client/components/List'
|
||||
import AlertError from 'client/components/Alerts/Error'
|
||||
import { ApplicationCard, EmptyCard } from 'client/components/Cards'
|
||||
import { T, DONE, APPLICATION_STATES } from 'client/constants'
|
||||
import { ApplicationCard } from 'client/components/Cards'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
|
||||
function ApplicationsInstances () {
|
||||
const { applications, getApplications } = useApplication()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
|
||||
const applications = useApplication()
|
||||
const { getApplications } = useApplicationApi()
|
||||
|
||||
const { error, fetchRequest, loading, reloading } = useFetch(getApplications)
|
||||
|
||||
useEffect(() => { fetchRequest() }, [])
|
||||
|
||||
const list = useMemo(() => (
|
||||
applications.length > 0
|
||||
? applications?.filter(({ TEMPLATE: { BODY: { state } } }) =>
|
||||
APPLICATION_STATES[state]?.name !== DONE
|
||||
)
|
||||
: applications
|
||||
), [applications])
|
||||
// const list = useMemo(() => (
|
||||
// applications.length > 0
|
||||
// ? applications?.filter(({ TEMPLATE: { BODY: { state } } }) =>
|
||||
// APPLICATION_STATES[state]?.name !== DONE
|
||||
// )
|
||||
// : applications
|
||||
// ), [applications])
|
||||
|
||||
return (
|
||||
<Container disableGutters>
|
||||
@ -30,6 +36,7 @@ function ApplicationsInstances () {
|
||||
title={T.ApplicationsInstances}
|
||||
hasReloadButton
|
||||
reloadButtonProps={{
|
||||
'data-cy': 'refresh-application-list',
|
||||
onClick: () => fetchRequest(undefined, { reload: true, delay: 500 }),
|
||||
isSubmitting: Boolean(loading || reloading)
|
||||
}}
|
||||
@ -39,24 +46,22 @@ function ApplicationsInstances () {
|
||||
<AlertError>{T.CannotConnectOneFlow}</AlertError>
|
||||
) : (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={list.length === 0 && loading}
|
||||
EmptyComponent={
|
||||
<EmptyCard title={'Your applications instances list is empty'} />
|
||||
}
|
||||
list={applications}
|
||||
isLoading={applications.length === 0 && loading}
|
||||
gridProps={{ 'data-cy': 'applications' }}
|
||||
CardComponent={ApplicationCard}
|
||||
cardsProps={({ value }) => ({
|
||||
handleShow: () => setShowDialog(value)
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{showDialog !== false && (
|
||||
<DialogInfo
|
||||
info={showDialog}
|
||||
handleClose={() => setShowDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{showDialog !== false && (
|
||||
<DialogInfo
|
||||
info={showDialog}
|
||||
handleClose={() => setShowDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React, { useEffect, useCallback } from 'react'
|
||||
|
||||
import { useOpennebula, useListForm, useFetch } from 'client/hooks'
|
||||
import { useListForm, useFetch } from 'client/hooks'
|
||||
import { useCluster, useClusterApi } from 'client/features/One'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { ClusterCard, EmptyCard } from 'client/components/Cards'
|
||||
|
||||
import { STEP_FORM_SCHEMA } from './schema'
|
||||
import { STEP_FORM_SCHEMA } from 'client/containers/ApplicationsTemplates/Form/Create/Steps/Clusters/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'clusters'
|
||||
@ -14,8 +15,11 @@ const Clusters = () => ({
|
||||
label: T.WhereWillItRun,
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const { clusters, getClusters } = useOpennebula()
|
||||
const clusters = useCluster()
|
||||
const { getClusters } = useClusterApi()
|
||||
|
||||
const { fetchRequest, loading } = useFetch(getClusters)
|
||||
|
||||
const { handleSelect, handleUnselect } = useListForm({
|
||||
key: STEP_ID,
|
||||
setList: setFormData
|
||||
|
@ -2,7 +2,9 @@ import React, { useState, useEffect, useCallback } from 'react'
|
||||
|
||||
import { useWatch } from 'react-hook-form'
|
||||
|
||||
import { useOpennebula, useListForm } from 'client/hooks'
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useVNetworkApi, useVNetworkTemplateApi } from 'client/features/One'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { DialogForm } from 'client/components/Dialogs'
|
||||
@ -21,7 +23,10 @@ const Networks = () => ({
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const form = useWatch({})
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const { getVNetworks, getVNetworksTemplates } = useOpennebula()
|
||||
|
||||
const { getVNetworks } = useVNetworkApi()
|
||||
const { getVNetworksTemplates } = useVNetworkTemplateApi()
|
||||
|
||||
const {
|
||||
editingData,
|
||||
handleSave,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as yup from 'yup'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { useOpennebula } from 'client/hooks'
|
||||
import { useVNetwork, useVNetworkTemplate } from 'client/features/One'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
|
||||
@ -87,7 +87,8 @@ const ID_VNET = {
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
dependOf: TYPE.name,
|
||||
values: dependValue => {
|
||||
const { vNetworks, vNetworksTemplates } = useOpennebula()
|
||||
const vNetworks = useVNetwork()
|
||||
const vNetworksTemplates = useVNetworkTemplate()
|
||||
|
||||
const values = isNetworkSelector(dependValue) ? vNetworks : vNetworksTemplates
|
||||
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useOpennebula } from 'client/hooks'
|
||||
import { useMarketApp, useMarketAppApi } from 'client/features/One'
|
||||
import Search from 'client/components/Search'
|
||||
import { SelectCard } from 'client/components/Cards'
|
||||
|
||||
const sortByID = (a, b) => a.ID - b.ID
|
||||
|
||||
const ListMarketApp = ({ backButton, currentValue, handleSetData }) => {
|
||||
const { apps, getMarketApps } = useOpennebula()
|
||||
const apps = useMarketApp()
|
||||
const { getMarketApps } = useMarketAppApi()
|
||||
|
||||
useEffect(() => {
|
||||
getMarketApps()
|
||||
|
@ -1,14 +1,15 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useOpennebula } from 'client/hooks'
|
||||
import { useVmTemplate, useVmTemplateApi } from 'client/features/One'
|
||||
import Search from 'client/components/Search'
|
||||
import { SelectCard } from 'client/components/Cards'
|
||||
|
||||
const sortByID = (a, b) => a.ID - b.ID
|
||||
|
||||
const ListTemplates = ({ backButton, currentValue, handleSetData }) => {
|
||||
const { templates, getTemplates } = useOpennebula()
|
||||
const templates = useVmTemplate()
|
||||
const { getTemplates } = useVmTemplateApi()
|
||||
|
||||
useEffect(() => {
|
||||
getTemplates()
|
||||
|
@ -8,19 +8,22 @@ import { yupResolver } from '@hookform/resolvers'
|
||||
import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/containers/ApplicationsTemplates/Form/Create/Steps'
|
||||
|
||||
import { PATH } from 'client/router/flow'
|
||||
import { useApplication, useFetch } from 'client/hooks'
|
||||
import { PATH } from 'client/apps/flow/routes'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useApplicationTemplateApi } from 'client/features/One'
|
||||
import { parseApplicationToForm, parseFormToApplication } from 'client/utils'
|
||||
|
||||
function ApplicationsTemplatesCreateForm () {
|
||||
const history = useHistory()
|
||||
const { id } = useParams()
|
||||
const { steps, defaultValues, resolvers } = Steps()
|
||||
|
||||
const {
|
||||
getApplicationTemplate,
|
||||
createApplicationTemplate,
|
||||
updateApplicationTemplate
|
||||
} = useApplication()
|
||||
} = useApplicationTemplateApi()
|
||||
|
||||
const { data, fetchRequest, loading, error } = useFetch(
|
||||
getApplicationTemplate
|
||||
)
|
||||
@ -35,13 +38,11 @@ function ApplicationsTemplatesCreateForm () {
|
||||
const application = parseFormToApplication(formData)
|
||||
|
||||
if (id) {
|
||||
updateApplicationTemplate({ id, data: application }).then(
|
||||
res => res && history.push(PATH.APPLICATIONS_TEMPLATES.LIST)
|
||||
)
|
||||
updateApplicationTemplate(id, application)
|
||||
.then(res => res && history.push(PATH.APPLICATIONS_TEMPLATES.LIST))
|
||||
} else {
|
||||
createApplicationTemplate({ data: application }).then(
|
||||
res => res && history.push(PATH.APPLICATIONS_TEMPLATES.LIST)
|
||||
)
|
||||
createApplicationTemplate(application)
|
||||
.then(res => res && history.push(PATH.APPLICATIONS_TEMPLATES.LIST))
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +52,7 @@ function ApplicationsTemplatesCreateForm () {
|
||||
|
||||
useEffect(() => {
|
||||
const formData = data ? parseApplicationToForm(data) : {}
|
||||
|
||||
methods.reset(resolvers().cast(formData), { errors: false })
|
||||
}, [data])
|
||||
|
||||
|
@ -2,7 +2,7 @@ import React, { useEffect, useCallback } from 'react'
|
||||
|
||||
import { Divider, Paper, Typography } from '@material-ui/core'
|
||||
|
||||
import { useOpennebula } from 'client/hooks'
|
||||
import { useVNetworkApi, useVNetworkTemplateApi } from 'client/features/One'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
@ -16,7 +16,8 @@ const Networks = () => ({
|
||||
resolver: STEP_FORM_SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(({ data }) => {
|
||||
const { getVNetworks, getVNetworksTemplates } = useOpennebula()
|
||||
const { getVNetworks } = useVNetworkApi()
|
||||
const { getVNetworksTemplates } = useVNetworkTemplateApi()
|
||||
|
||||
useEffect(() => {
|
||||
getVNetworks()
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as yup from 'yup'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { useOpennebula } from 'client/hooks'
|
||||
import { useVNetwork, useVNetworkTemplate } from 'client/features/One'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
|
||||
const SELECT = {
|
||||
@ -36,7 +36,9 @@ const ID_VNET = {
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
dependOf: TYPE.name,
|
||||
values: dependValue => {
|
||||
const { vNetworks, vNetworksTemplates } = useOpennebula()
|
||||
const vNetworks = useVNetwork()
|
||||
const vNetworksTemplates = useVNetworkTemplate()
|
||||
|
||||
const type = TYPES_NETWORKS.find(({ value }) => value === dependValue)
|
||||
|
||||
const values =
|
||||
|
@ -3,12 +3,14 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, CircularProgress, Backdrop } from '@material-ui/core'
|
||||
|
||||
import { useFetchAll, useOpennebula, useApplication } from 'client/hooks'
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useApplicationTemplateApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
|
||||
import { DialogForm } from 'client/components/Dialogs'
|
||||
import FormStepper from 'client/components/FormStepper'
|
||||
|
||||
import { parseApplicationToForm, parseFormToDeployApplication } from 'client/utils'
|
||||
import Steps from 'client/containers/ApplicationsTemplates/Form/Deploy/Steps'
|
||||
import { parseApplicationToForm, parseFormToDeployApplication } from 'client/utils'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
backdrop: {
|
||||
@ -20,8 +22,9 @@ const useStyles = makeStyles(theme => ({
|
||||
const DeployForm = ({ applicationTemplate, handleCancel }) => {
|
||||
const classes = useStyles()
|
||||
const [vmTemplates, setVmTemplates] = useState([])
|
||||
const { instantiateApplicationTemplate } = useApplication()
|
||||
const { getTemplate } = useOpennebula()
|
||||
|
||||
const { enqueueInfo } = useGeneralApi()
|
||||
const { getApplicationsTemplates, instantiateApplicationTemplate } = useApplicationTemplateApi()
|
||||
const { data, fetchRequestAll, loading } = useFetchAll()
|
||||
|
||||
const applicationParsed = useMemo(() =>
|
||||
@ -42,7 +45,7 @@ const DeployForm = ({ applicationTemplate, handleCancel }) => {
|
||||
list.includes(templateId) ? list : [...list, templateId]
|
||||
, [])
|
||||
?.map(templateId =>
|
||||
getTemplate({ id: templateId }).then(vmTemplate =>
|
||||
getApplicationsTemplates(templateId).then(vmTemplate =>
|
||||
setVmTemplates(prev => [...prev, vmTemplate])
|
||||
)
|
||||
)
|
||||
@ -56,11 +59,12 @@ const DeployForm = ({ applicationTemplate, handleCancel }) => {
|
||||
...application
|
||||
} = parseFormToDeployApplication(values, applicationParsed)
|
||||
|
||||
return instantiateApplicationTemplate({
|
||||
id: applicationTemplate.ID,
|
||||
data: application,
|
||||
instances
|
||||
}).then(() => handleCancel())
|
||||
return Promise
|
||||
.all([...new Array(instances)]
|
||||
.map(() => instantiateApplicationTemplate(applicationTemplate.ID, application))
|
||||
)
|
||||
.then(() => handleCancel())
|
||||
.then(() => enqueueInfo(`Template instantiate - x${instances}`))
|
||||
}
|
||||
|
||||
if ((applicationTemplate && !data) || loading) {
|
||||
|
@ -3,8 +3,9 @@ import React, { useEffect, useState } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { Container, Box } from '@material-ui/core'
|
||||
|
||||
import { PATH } from 'client/router/flow'
|
||||
import { useApplication, useFetch } from 'client/hooks'
|
||||
import { PATH } from 'client/apps/flow/routes'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useApplicationTemplate, useApplicationTemplateApi } from 'client/features/One'
|
||||
|
||||
import DeployForm from 'client/containers/ApplicationsTemplates/Form/Deploy'
|
||||
import { ListHeader, ListCards } from 'client/components/List'
|
||||
@ -15,14 +16,12 @@ import { T } from 'client/constants'
|
||||
function ApplicationsTemplates () {
|
||||
const history = useHistory()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const { applicationsTemplates, getApplicationsTemplates } = useApplication()
|
||||
const {
|
||||
error,
|
||||
fetchRequest,
|
||||
loading,
|
||||
reloading
|
||||
} = useFetch(getApplicationsTemplates)
|
||||
|
||||
const applicationsTemplates = useApplicationTemplate()
|
||||
const { getApplicationsTemplates } = useApplicationTemplateApi()
|
||||
|
||||
const { error, fetchRequest, loading, reloading } = useFetch(getApplicationsTemplates)
|
||||
console.log({ error })
|
||||
useEffect(() => { fetchRequest() }, [])
|
||||
|
||||
return (
|
||||
@ -31,9 +30,14 @@ function ApplicationsTemplates () {
|
||||
title={T.ApplicationsTemplates}
|
||||
hasReloadButton
|
||||
reloadButtonProps={{
|
||||
'data-cy': 'refresh-application-template-list',
|
||||
onClick: () => fetchRequest(undefined, { reload: true, delay: 500 }),
|
||||
isSubmitting: Boolean(loading || reloading)
|
||||
}}
|
||||
addButtonProps={{
|
||||
'data-cy': 'create-application-template',
|
||||
onClick: () => history.push(PATH.APPLICATIONS_TEMPLATES.CREATE)
|
||||
}}
|
||||
/>
|
||||
<Box p={3}>
|
||||
{error ? (
|
||||
@ -42,24 +46,24 @@ function ApplicationsTemplates () {
|
||||
<ListCards
|
||||
list={applicationsTemplates}
|
||||
isLoading={applicationsTemplates?.length === 0 && loading}
|
||||
handleCreate={() => history.push(PATH.APPLICATIONS_TEMPLATES.CREATE)}
|
||||
gridProps={{ 'data-cy': 'applications-templates' }}
|
||||
CardComponent={ApplicationTemplateCard}
|
||||
cardsProps={({ value }) => ({
|
||||
handleEdit: () => history.push(
|
||||
PATH.APPLICATIONS_TEMPLATES.EDIT.replace(':id', value?.ID)
|
||||
),
|
||||
handleDeploy: () => setShowDialog(value),
|
||||
handleRemove: undefined
|
||||
handleRemove: undefined // TODO
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{showDialog !== false && (
|
||||
<DeployForm
|
||||
applicationTemplate={showDialog}
|
||||
handleCancel={() => setShowDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{showDialog !== false && (
|
||||
<DeployForm
|
||||
applicationTemplate={showDialog}
|
||||
handleCancel={() => setShowDialog(false)}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -3,19 +3,21 @@ import * as React from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { Container, Box, Grid } from '@material-ui/core'
|
||||
|
||||
import { useAuth, useFetchAll, useProvision } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useProvisionApi, useProviderApi } from 'client/features/One'
|
||||
import * as Widgets from 'client/components/Widgets'
|
||||
import dashboardStyles from 'client/containers/Dashboard/Provision/styles'
|
||||
|
||||
function Dashboard () {
|
||||
const { settings: { disableanimations } = {} } = useAuth()
|
||||
const { getProviders, getProvisions } = useProvision()
|
||||
const { fetchRequestAll } = useFetchAll()
|
||||
const { getProvisions } = useProvisionApi()
|
||||
const { getProviders } = useProviderApi()
|
||||
|
||||
const classes = dashboardStyles({ disableanimations })
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchRequestAll([getProviders(), getProvisions()])
|
||||
getProviders()
|
||||
getProvisions()
|
||||
}, [])
|
||||
|
||||
const withoutAnimations = String(disableanimations).toUpperCase() === 'YES'
|
||||
|
@ -44,7 +44,7 @@ const Form = ({ onBack, onSubmit, resolver, fields, error, isLoading, transition
|
||||
</FormProvider>
|
||||
<Box>
|
||||
{onBack && (
|
||||
<Button onClick={onBack} color="primary" disabled={isLoading}>
|
||||
<Button onClick={onBack} disabled={isLoading}>
|
||||
{Tr(T.Back)}
|
||||
</Button>
|
||||
)}
|
||||
|
@ -2,18 +2,10 @@ import React, { useMemo, useState } from 'react'
|
||||
|
||||
import { Paper, Box, Container, LinearProgress, useMediaQuery } from '@material-ui/core'
|
||||
|
||||
import { useAuth } from 'client/hooks'
|
||||
import Logo from 'client/icons/logo'
|
||||
import { ONEADMIN_ID } from 'client/constants'
|
||||
|
||||
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 * as FORMS from 'client/containers/Login/schema'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import Form from 'client/containers/Login/Form'
|
||||
import loginStyles from 'client/containers/Login/styles'
|
||||
|
||||
@ -26,35 +18,29 @@ const STEPS = {
|
||||
function Login () {
|
||||
const classes = loginStyles()
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'))
|
||||
const [user, setUser] = useState(undefined)
|
||||
const [dataUserForm, setDataUserForm] = useState(undefined)
|
||||
const [step, setStep] = useState(STEPS.USER_FORM)
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
login,
|
||||
logout,
|
||||
getAuthInfo,
|
||||
setPrimaryGroup
|
||||
} = useAuth()
|
||||
|
||||
const { isLoading, error } = useAuth()
|
||||
const { login, getAuthUser, changeGroup, logout } = useAuthApi()
|
||||
|
||||
const handleSubmitUser = dataForm => {
|
||||
login({ ...user, ...dataForm }).then(data => {
|
||||
if (data?.token) {
|
||||
getAuthInfo().then(() => {
|
||||
data?.id !== ONEADMIN_ID && setStep(STEPS.GROUP_FORM)
|
||||
})
|
||||
} else {
|
||||
setStep(data ? STEPS.FA2_FORM : step)
|
||||
setUser(data ? dataForm : user)
|
||||
}
|
||||
})
|
||||
login({ ...dataUserForm, ...dataForm })
|
||||
.then(({ jwt, user, isLoginInProgress }) => {
|
||||
if (jwt && isLoginInProgress) {
|
||||
getAuthUser().then(() => setStep(STEPS.GROUP_FORM))
|
||||
} else if (!jwt && user?.ID) {
|
||||
setStep(STEPS.FA2_FORM)
|
||||
setDataUserForm(dataForm)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmitGroup = dataForm => setPrimaryGroup(dataForm)
|
||||
const handleSubmitGroup = dataForm => changeGroup(dataForm)
|
||||
|
||||
const handleBack = () => {
|
||||
logout()
|
||||
setUser(undefined)
|
||||
setDataUserForm(undefined)
|
||||
setStep(STEPS.USER_FORM)
|
||||
}
|
||||
|
||||
@ -75,11 +61,11 @@ function Login () {
|
||||
transitionProps={{
|
||||
direction: 'right',
|
||||
in: step === STEPS.USER_FORM,
|
||||
appear: false
|
||||
enter: false
|
||||
}}
|
||||
onSubmit={handleSubmitUser}
|
||||
resolver={FORM_USER_SCHEMA}
|
||||
fields={FORM_USER_FIELDS}
|
||||
resolver={FORMS.FORM_USER_SCHEMA}
|
||||
fields={FORMS.FORM_USER_FIELDS}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>}
|
||||
@ -90,8 +76,8 @@ function Login () {
|
||||
}}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitUser}
|
||||
resolver={FORM_2FA_SCHEMA}
|
||||
fields={FORM_2FA_FIELDS}
|
||||
resolver={FORMS.FORM_2FA_SCHEMA}
|
||||
fields={FORMS.FORM_2FA_FIELDS}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>}
|
||||
@ -102,8 +88,8 @@ function Login () {
|
||||
}}
|
||||
onBack={handleBack}
|
||||
onSubmit={handleSubmitGroup}
|
||||
resolver={FORM_GROUP_SCHEMA}
|
||||
fields={FORM_GROUP_FIELDS}
|
||||
resolver={FORMS.FORM_GROUP_SCHEMA}
|
||||
fields={FORMS.FORM_GROUP_FIELDS}
|
||||
error={error}
|
||||
isLoading={isLoading}
|
||||
/>}
|
||||
|
@ -3,9 +3,9 @@ import * as React from 'react'
|
||||
import SelectedIcon from '@material-ui/icons/FilterVintage'
|
||||
import * as yup from 'yup'
|
||||
|
||||
import { useAuth, useOpennebula } from 'client/hooks'
|
||||
import { T, INPUT_TYPES, FILTER_POOL } from 'client/constants'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES, FILTER_POOL } from 'client/constants'
|
||||
|
||||
export const USERNAME = {
|
||||
name: 'user',
|
||||
@ -75,17 +75,16 @@ export const GROUP = {
|
||||
label: T.SelectGroup,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const { authUser } = useAuth()
|
||||
const { groups } = useOpennebula()
|
||||
const { user, groups } = useAuth()
|
||||
|
||||
return [{ text: T.ShowAll, value: FILTER_POOL.ALL_RESOURCES }]
|
||||
.concat(groups
|
||||
.concat([...groups]
|
||||
.sort((a, b) => a.ID - b.ID)
|
||||
.map(({ ID, NAME }) => ({
|
||||
text: (
|
||||
<>
|
||||
{`${ID} - ${String(NAME)}`}
|
||||
{authUser?.GID === ID && (
|
||||
{user?.GID === ID && (
|
||||
<SelectedIcon style={{ fontSize: '1rem', marginLeft: 16 }} />
|
||||
)}
|
||||
</>
|
||||
|
@ -47,7 +47,7 @@ const Connection = ({ isUpdate }) => ({
|
||||
}, [data])
|
||||
|
||||
return (fields?.length === 0) ? (
|
||||
<EmptyCard title={"✔️ There aren't connections to fill"} />
|
||||
<EmptyCard title={"There aren't connections to fill"} />
|
||||
) : (
|
||||
<FormWithSchema cy="form-provider" fields={fields} id={STEP_ID} />
|
||||
)
|
||||
|
@ -3,7 +3,8 @@ import { Divider, Select, Breadcrumbs, InputLabel, FormControl } from '@material
|
||||
import ArrowIcon from '@material-ui/icons/ArrowForwardIosRounded'
|
||||
import Marked from 'marked'
|
||||
|
||||
import { useProvision, useListForm } from 'client/hooks'
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useOne } from 'client/features/One'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { ProvisionTemplateCard } from 'client/components/Cards'
|
||||
import { sanitize, groupBy } from 'client/utils'
|
||||
@ -23,7 +24,8 @@ const Template = () => ({
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(
|
||||
({ data, setFormData }) => {
|
||||
const { provisionsTemplates } = useProvision()
|
||||
const { provisionsTemplates } = useOne()
|
||||
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const [provisionSelected, setProvision] = React.useState(
|
||||
|
@ -8,9 +8,11 @@ import { yupResolver } from '@hookform/resolvers'
|
||||
import FormStepper from 'client/components/FormStepper'
|
||||
import Steps from 'client/containers/Providers/Form/Create/Steps'
|
||||
|
||||
import { useFetchAll, useGeneral, useProvision } from 'client/hooks'
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import * as ProviderTemplateModel from 'client/models/ProviderTemplate'
|
||||
import { PATH } from 'client/router/provision'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
|
||||
function ProviderCreateForm () {
|
||||
const history = useHistory()
|
||||
@ -19,14 +21,15 @@ function ProviderCreateForm () {
|
||||
|
||||
const {
|
||||
getProvider,
|
||||
getProviderConnection,
|
||||
createProvider,
|
||||
updateProvider
|
||||
} = useProvision()
|
||||
updateProvider,
|
||||
getProviderConnection
|
||||
} = useProviderApi()
|
||||
|
||||
const { enqueueError, enqueueSuccess, changeLoading } = useGeneralApi()
|
||||
|
||||
const { data, fetchRequestAll, loading, error } = useFetchAll()
|
||||
const { steps, defaultValues, resolvers } = Steps({ isUpdate })
|
||||
const { showError, changeLoading } = useGeneral()
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onSubmit',
|
||||
@ -35,7 +38,7 @@ function ProviderCreateForm () {
|
||||
})
|
||||
|
||||
const redirectWithError = (message = 'Error') => {
|
||||
showError({ message })
|
||||
enqueueError(message)
|
||||
history.push(PATH.PROVIDERS.LIST)
|
||||
}
|
||||
|
||||
@ -47,8 +50,8 @@ function ProviderCreateForm () {
|
||||
const isValid = ProviderTemplateModel.isValidProviderTemplate(templateSelected)
|
||||
|
||||
!isValid && redirectWithError(`
|
||||
The template selected has a bad format.
|
||||
Ask your cloud administrator`
|
||||
The template selected has a bad format.
|
||||
Ask your cloud administrator`
|
||||
)
|
||||
|
||||
const { name, description } = configuration
|
||||
@ -61,7 +64,8 @@ function ProviderCreateForm () {
|
||||
name
|
||||
}
|
||||
|
||||
createProvider({ data: formatData })
|
||||
createProvider(formatData)
|
||||
.then(id => enqueueSuccess(`Provider created - ID: ${id}`))
|
||||
.then(() => history.push(PATH.PROVIDERS.LIST))
|
||||
}
|
||||
|
||||
@ -78,7 +82,8 @@ function ProviderCreateForm () {
|
||||
connection: { ...connection, ...connectionEditable }
|
||||
}
|
||||
|
||||
updateProvider({ id, data: formatData })
|
||||
updateProvider(id, formatData)
|
||||
.then(() => enqueueSuccess(`Provider updated - ID: ${id}`))
|
||||
.then(() => history.push(PATH.PROVIDERS.LIST))
|
||||
}
|
||||
|
||||
@ -89,8 +94,8 @@ function ProviderCreateForm () {
|
||||
|
||||
useEffect(() => {
|
||||
isUpdate && fetchRequestAll([
|
||||
getProvider({ id }),
|
||||
getProviderConnection({ id })
|
||||
getProvider(id),
|
||||
getProviderConnection(id)
|
||||
])
|
||||
}, [isUpdate])
|
||||
|
||||
|
@ -5,20 +5,21 @@ import { List, ListItem, Typography, Grid, Paper, Divider } from '@material-ui/c
|
||||
import { CheckBox, CheckBoxOutlineBlank, Visibility } from '@material-ui/icons'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { useProvision } from 'client/hooks'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import { Action } from 'client/components/Cards/SelectCard'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import * as Types from 'client/types/provision'
|
||||
import useStyles from 'client/containers/Providers/Sections/styles'
|
||||
|
||||
const Info = memo(({ data }) => {
|
||||
const Info = memo(({ fetchProps }) => {
|
||||
const classes = useStyles()
|
||||
const { getProviderConnection } = useProvision()
|
||||
const { getProviderConnection } = useProviderApi()
|
||||
|
||||
const [showConnection, setShowConnection] = useState(undefined)
|
||||
|
||||
const { ID, NAME, GNAME, UNAME, PERMISSIONS, TEMPLATE } = data
|
||||
const { ID, NAME, GNAME, UNAME, PERMISSIONS, TEMPLATE } = fetchProps?.data
|
||||
const {
|
||||
connection,
|
||||
description,
|
||||
@ -35,8 +36,7 @@ const Info = memo(({ data }) => {
|
||||
<Action
|
||||
icon={<Visibility />}
|
||||
cy='provider-connection'
|
||||
handleClick={() => getProviderConnection({ id: ID })
|
||||
.then(setShowConnection)}
|
||||
handleClick={() => getProviderConnection(ID).then(setShowConnection)}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -148,11 +148,15 @@ const Info = memo(({ data }) => {
|
||||
})
|
||||
|
||||
Info.propTypes = {
|
||||
data: PropTypes.object.isRequired
|
||||
fetchProps: PropTypes.shape({
|
||||
data: Types.Provision.isRequired
|
||||
}).isRequired
|
||||
}
|
||||
|
||||
Info.defaultProps = {
|
||||
data: {}
|
||||
fetchProps: {
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
|
||||
Info.displayName = 'Info'
|
||||
|
@ -5,12 +5,14 @@ import { Container, Box } from '@material-ui/core'
|
||||
import EditIcon from '@material-ui/icons/Settings'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
|
||||
import { PATH } from 'client/router/provision'
|
||||
import { useProvision, useFetch, useSearch } from 'client/hooks'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
import { useProvider, useProviderApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useFetch, useSearch } from 'client/hooks'
|
||||
|
||||
import { ListHeader, ListCards } from 'client/components/List'
|
||||
import AlertError from 'client/components/Alerts/Error'
|
||||
import { ProvisionCard } from 'client/components/Cards'
|
||||
|
||||
import { DialogRequest } from 'client/components/Dialogs'
|
||||
import Information from 'client/containers/Providers/Sections/info'
|
||||
import { T } from 'client/constants'
|
||||
@ -19,9 +21,12 @@ function Providers () {
|
||||
const history = useHistory()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
|
||||
const { providers, getProviders, getProvider, deleteProvider } = useProvision()
|
||||
const providers = useProvider()
|
||||
const { getProviders, getProvider, deleteProvider } = useProviderApi()
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
|
||||
const { error, fetchRequest, loading, reloading } = useFetch(getProviders)
|
||||
|
||||
const { result, handleChange } = useSearch({
|
||||
list: providers,
|
||||
listOptions: { shouldSort: true, keys: ['ID', 'NAME'] }
|
||||
@ -36,10 +41,14 @@ function Providers () {
|
||||
<ListHeader
|
||||
title={T.Providers}
|
||||
reloadButtonProps={{
|
||||
'data-cy': 'refresh-provider-list',
|
||||
onClick: () => fetchRequest(undefined, { reload: true }),
|
||||
isSubmitting: Boolean(loading || reloading)
|
||||
}}
|
||||
addButtonProps={{ 'data-cy': 'create-provider', onClick: () => history.push(PATH.PROVIDERS.CREATE) }}
|
||||
addButtonProps={{
|
||||
'data-cy': 'create-provider',
|
||||
onClick: () => history.push(PATH.PROVIDERS.CREATE)
|
||||
}}
|
||||
searchProps={{ handleChange }}
|
||||
/>
|
||||
<Box p={3}>
|
||||
@ -68,10 +77,14 @@ function Providers () {
|
||||
{
|
||||
handleClick: () => setShowDialog({
|
||||
id: ID,
|
||||
title: `DELETE provider - #${ID} - ${NAME}`,
|
||||
title: `DELETE - ${NAME}`,
|
||||
subheader: `#${ID}`,
|
||||
handleAccept: () => {
|
||||
deleteProvider({ id: ID })
|
||||
setShowDialog(false)
|
||||
|
||||
return deleteProvider(ID)
|
||||
.then(() => enqueueSuccess(`Provider deleted - ID: ${ID}`))
|
||||
.then(() => fetchRequest(undefined, { reload: true }))
|
||||
}
|
||||
}),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
@ -84,10 +97,10 @@ function Providers () {
|
||||
</Box>
|
||||
{showDialog !== false && (
|
||||
<DialogRequest
|
||||
request={() => getProvider({ id: showDialog.id })}
|
||||
request={() => getProvider(showDialog.id)}
|
||||
dialogProps={{ handleCancel, ...showDialog }}
|
||||
>
|
||||
{({ data }) => <Information data={data} />}
|
||||
{fetchProps => <Information {...fetchProps} />}
|
||||
</DialogRequest>
|
||||
)}
|
||||
</Container>
|
||||
|
@ -3,64 +3,70 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
|
||||
import { useProvision, useOpennebula, useFetchAll } from 'client/hooks'
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useDatastoreApi, useProvisionApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { DatastoreCard } from 'client/components/Cards'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Datastores = memo(({ hidden, data, fetchRequest }) => {
|
||||
const {
|
||||
datastores
|
||||
} = data?.TEMPLATE?.BODY?.provision?.infrastructure
|
||||
const Datastores = memo(
|
||||
({ hidden, data, reloading, refetchProvision, disableAllActions }) => {
|
||||
const {
|
||||
datastores = []
|
||||
} = data?.TEMPLATE?.BODY?.provision?.infrastructure
|
||||
|
||||
const { deleteDatastore } = useProvision()
|
||||
const { getDatastore } = useOpennebula()
|
||||
const { data: list, fetchRequestAll, loading } = useFetchAll()
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const { deleteDatastore } = useProvisionApi()
|
||||
const { getDatastore } = useDatastoreApi()
|
||||
|
||||
useEffect(() => {
|
||||
if (!hidden) {
|
||||
const requests = datastores?.map(getDatastore) ?? []
|
||||
fetchRequestAll(requests)
|
||||
}
|
||||
}, [datastores])
|
||||
const { data: list, fetchRequestAll, loading } = useFetchAll()
|
||||
const fetchDatastores = () => fetchRequestAll(datastores?.map(({ id }) => getDatastore(id)))
|
||||
|
||||
useEffect(() => {
|
||||
if (!list && !hidden) {
|
||||
const requests = datastores?.map(getDatastore) ?? []
|
||||
fetchRequestAll(requests)
|
||||
}
|
||||
}, [hidden])
|
||||
useEffect(() => {
|
||||
!hidden && !list && fetchDatastores()
|
||||
}, [hidden])
|
||||
|
||||
return (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={!list && loading}
|
||||
CardComponent={DatastoreCard}
|
||||
cardsProps={({ value: { ID } }) => ({
|
||||
actions: [{
|
||||
handleClick: () => deleteDatastore({ id: ID })
|
||||
.then(() => fetchRequest(undefined, { reload: true })),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: `provision-datastore-delete-${ID}`
|
||||
}]
|
||||
})}
|
||||
displayEmpty
|
||||
breakpoints={{ xs: 12, md: 6 }}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
useEffect(() => {
|
||||
!reloading && !loading && fetchDatastores()
|
||||
}, [reloading])
|
||||
|
||||
return (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={!list && loading}
|
||||
CardComponent={DatastoreCard}
|
||||
cardsProps={({ value: { ID } }) => !disableAllActions && ({
|
||||
actions: [{
|
||||
handleClick: () => deleteDatastore(ID)
|
||||
.then(refetchProvision)
|
||||
.then(() => enqueueSuccess(`Datastore deleted - ID: ${ID}`)),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: `provision-datastore-delete-${ID}`
|
||||
}]
|
||||
})}
|
||||
displayEmpty
|
||||
breakpoints={{ xs: 12, md: 6 }}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.reloading === next.reloading
|
||||
)
|
||||
|
||||
Datastores.propTypes = {
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
refetchProvision: PropTypes.func,
|
||||
reloading: PropTypes.bool,
|
||||
disableAllActions: PropTypes.bool
|
||||
}
|
||||
|
||||
Datastores.defaultProps = {
|
||||
data: {},
|
||||
hidden: false,
|
||||
fetchRequest: () => undefined
|
||||
refetchProvision: () => undefined,
|
||||
reloading: false,
|
||||
disableAllActions: false
|
||||
}
|
||||
|
||||
Datastores.displayName = 'Datastores'
|
||||
|
@ -4,71 +4,78 @@ import PropTypes from 'prop-types'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
import ConfigureIcon from '@material-ui/icons/Settings'
|
||||
|
||||
import { useProvision, useOpennebula, useFetchAll } from 'client/hooks'
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useHostApi, useProvisionApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { HostCard } from 'client/components/Cards'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Hosts = memo(({ hidden, data, fetchRequest }) => {
|
||||
const {
|
||||
hosts
|
||||
} = data?.TEMPLATE?.BODY?.provision?.infrastructure
|
||||
const Hosts = memo(
|
||||
({ hidden, data, reloading, refetchProvision, disableAllActions }) => {
|
||||
const {
|
||||
hosts = []
|
||||
} = data?.TEMPLATE?.BODY?.provision?.infrastructure
|
||||
|
||||
const { configureHost, deleteHost } = useProvision()
|
||||
const { getHost } = useOpennebula()
|
||||
const { data: list, fetchRequestAll, loading } = useFetchAll()
|
||||
const { enqueueSuccess, enqueueInfo } = useGeneralApi()
|
||||
const { configureHost, deleteHost } = useProvisionApi()
|
||||
const { getHost } = useHostApi()
|
||||
|
||||
useEffect(() => {
|
||||
if (!hidden) {
|
||||
const requests = hosts?.map(getHost) ?? []
|
||||
fetchRequestAll(requests)
|
||||
}
|
||||
}, [hosts])
|
||||
const { data: list, fetchRequestAll, loading } = useFetchAll()
|
||||
const fetchHosts = () => fetchRequestAll(hosts?.map(({ id }) => getHost(id)))
|
||||
|
||||
useEffect(() => {
|
||||
if (!list && !hidden) {
|
||||
const requests = hosts?.map(getHost) ?? []
|
||||
fetchRequestAll(requests)
|
||||
}
|
||||
}, [hidden])
|
||||
useEffect(() => {
|
||||
!hidden && !list && fetchHosts()
|
||||
}, [hidden])
|
||||
|
||||
return (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={!list && loading}
|
||||
CardComponent={HostCard}
|
||||
cardsProps={({ value: { ID } }) => ({
|
||||
actions: [
|
||||
{
|
||||
handleClick: () => configureHost({ id: ID }),
|
||||
icon: <ConfigureIcon />,
|
||||
cy: `provision-host-configure-${ID}`
|
||||
},
|
||||
{
|
||||
handleClick: () => deleteHost({ id: ID })
|
||||
.then(() => fetchRequest(undefined, { reload: true })),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: `provision-host-delete-${ID}`
|
||||
}
|
||||
]
|
||||
})}
|
||||
displayEmpty
|
||||
breakpoints={{ xs: 12, md: 6 }}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
useEffect(() => {
|
||||
!reloading && !loading && fetchHosts()
|
||||
}, [reloading])
|
||||
|
||||
return (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={!list && loading}
|
||||
CardComponent={HostCard}
|
||||
cardsProps={({ value: { ID } }) => !disableAllActions && ({
|
||||
actions: [
|
||||
{
|
||||
handleClick: () => configureHost(ID)
|
||||
.then(() => enqueueInfo(`Configuring host - ID: ${ID}`))
|
||||
.then(refetchProvision),
|
||||
icon: <ConfigureIcon />,
|
||||
cy: `provision-host-configure-${ID}`
|
||||
},
|
||||
{
|
||||
handleClick: () => deleteHost(ID)
|
||||
.then(refetchProvision)
|
||||
.then(() => enqueueSuccess(`Host deleted - ID: ${ID}`)),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: `provision-host-delete-${ID}`
|
||||
}
|
||||
]
|
||||
})}
|
||||
displayEmpty
|
||||
breakpoints={{ xs: 12, md: 6 }}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.reloading === next.reloading)
|
||||
|
||||
Hosts.propTypes = {
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
refetchProvision: PropTypes.func,
|
||||
reloading: PropTypes.bool,
|
||||
disableAllActions: PropTypes.bool
|
||||
}
|
||||
|
||||
Hosts.defaultProps = {
|
||||
data: {},
|
||||
hidde: false,
|
||||
fetchRequest: () => undefined
|
||||
hidden: false,
|
||||
refetchProvision: () => undefined,
|
||||
reloading: false,
|
||||
disableAllActions: false
|
||||
}
|
||||
|
||||
Hosts.displayName = 'Hosts'
|
||||
|
@ -24,15 +24,16 @@ const TABS = [
|
||||
{ name: 'log', icon: LogIcon, content: LogTab }
|
||||
]
|
||||
|
||||
const DialogInfo = ({ data, ...methods }) => {
|
||||
const DialogInfo = ({ disableAllActions, fetchProps }) => {
|
||||
const [tabSelected, setTab] = useState(0)
|
||||
const { data, fetchRequest, reloading } = fetchProps
|
||||
|
||||
const renderTabs = useMemo(() => (
|
||||
<AppBar position="static">
|
||||
<AppBar position='static'>
|
||||
<Tabs
|
||||
value={tabSelected}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
variant='scrollable'
|
||||
scrollButtons='auto'
|
||||
onChange={(_, tab) => setTab(tab)}
|
||||
>
|
||||
{TABS.map(({ name, icon: Icon }, idx) =>
|
||||
@ -57,27 +58,37 @@ const DialogInfo = ({ data, ...methods }) => {
|
||||
height={1}
|
||||
hidden={tabSelected !== idx}
|
||||
key={`tab-${name}`}
|
||||
overflow="auto"
|
||||
overflow='auto'
|
||||
>
|
||||
<Content
|
||||
data={data}
|
||||
hidden={tabSelected !== idx}
|
||||
{...methods}
|
||||
disableAllActions={disableAllActions}
|
||||
refetchProvision={() => fetchRequest(undefined, { reload: true })}
|
||||
reloading={reloading}
|
||||
/>
|
||||
</Box>
|
||||
)), [tabSelected, data])}
|
||||
)), [tabSelected, reloading])}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
DialogInfo.propTypes = {
|
||||
data: Types.Provision.isRequired,
|
||||
fetchRequest: PropTypes.func
|
||||
disableAllActions: PropTypes.bool,
|
||||
fetchProps: PropTypes.shape({
|
||||
data: Types.Provision.isRequired,
|
||||
fetchRequest: PropTypes.func,
|
||||
reloading: PropTypes.bool
|
||||
}).isRequired
|
||||
}
|
||||
|
||||
DialogInfo.defaultProps = {
|
||||
data: {},
|
||||
fetchRequest: undefined
|
||||
disableAllActions: false,
|
||||
fetchProps: {
|
||||
data: {},
|
||||
fetchRequest: undefined,
|
||||
reloading: false
|
||||
}
|
||||
}
|
||||
|
||||
export default DialogInfo
|
||||
|
@ -31,7 +31,7 @@ const Info = memo(({ data }) => {
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper variant="outlined">
|
||||
<Paper variant='outlined'>
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Information)}</Typography>
|
||||
@ -43,19 +43,19 @@ const Info = memo(({ data }) => {
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Name)}</Typography>
|
||||
<Typography data-cy="provision-name">{name}</Typography>
|
||||
<Typography data-cy='provision-name'>{name}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Description)}</Typography>
|
||||
<Typography data-cy="provision-description" noWrap>{description}</Typography>
|
||||
<Typography data-cy='provision-description' noWrap>{description}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Provider)}</Typography>
|
||||
<Typography data-cy="provider-provider-name">{providerName}</Typography>
|
||||
<Typography data-cy='provider-name'>{providerName}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Cluster)}</Typography>
|
||||
<Typography data-cy="provider-cluster">{`${clusterId} - ${clusterName}`}</Typography>
|
||||
<Typography data-cy='provider-cluster'>{`${clusterId} - ${clusterName}`}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.StartTime)}</Typography>
|
||||
@ -73,7 +73,7 @@ const Info = memo(({ data }) => {
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper variant="outlined" className={classes.permissions}>
|
||||
<Paper variant='outlined' className={classes.permissions}>
|
||||
<List className={clsx(classes.list, 'w-25')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Permissions)}</Typography>
|
||||
@ -102,7 +102,7 @@ const Info = memo(({ data }) => {
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
<Paper variant="outlined">
|
||||
<Paper variant='outlined'>
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Ownership)}</Typography>
|
||||
|
@ -3,26 +3,23 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import { LinearProgress } from '@material-ui/core'
|
||||
|
||||
import { useProvision, useFetch, useSocket } from 'client/hooks'
|
||||
import { useFetch, useSocket } from 'client/hooks'
|
||||
import { useProvisionApi } from 'client/features/One'
|
||||
import DebugLog, { LogUtils } from 'client/components/DebugLog'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Log = React.memo(({ hidden, data: { ID: id } }) => {
|
||||
const Log = React.memo(({ hidden, data: { ID } }) => {
|
||||
const { getProvision } = useSocket()
|
||||
const { getProvisionLog } = useProvision()
|
||||
const { getProvisionLog } = useProvisionApi()
|
||||
|
||||
const {
|
||||
data: { uuid = id, log } = {},
|
||||
data: { uuid = ID, log } = {},
|
||||
fetchRequest,
|
||||
loading
|
||||
} = useFetch(getProvisionLog)
|
||||
|
||||
React.useEffect(() => {
|
||||
!hidden && fetchRequest({ id })
|
||||
}, [id])
|
||||
|
||||
React.useEffect(() => {
|
||||
(!log && !hidden) && fetchRequest({ id })
|
||||
(!log && !hidden) && fetchRequest(ID)
|
||||
}, [hidden])
|
||||
|
||||
const parsedLog = React.useMemo(() =>
|
||||
@ -39,7 +36,8 @@ const Log = React.memo(({ hidden, data: { ID: id } }) => {
|
||||
<DebugLog uuid={uuid} socket={getProvision} logDefault={parsedLog} />
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
prev.hidden === next.hidden && prev.reloading === next.reloading
|
||||
)
|
||||
|
||||
Log.propTypes = {
|
||||
data: Types.Provision.isRequired,
|
||||
|
@ -3,64 +3,70 @@ import PropTypes from 'prop-types'
|
||||
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
|
||||
import { useProvision, useOpennebula, useFetchAll } from 'client/hooks'
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useVNetworkApi, useProvisionApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { NetworkCard } from 'client/components/Cards'
|
||||
import * as Types from 'client/types/provision'
|
||||
|
||||
const Networks = memo(({ hidden, data, fetchRequest }) => {
|
||||
const {
|
||||
networks
|
||||
} = data?.TEMPLATE?.BODY?.provision?.infrastructure
|
||||
const Networks = memo(
|
||||
({ hidden, data, reloading, refetchProvision, disableAllActions }) => {
|
||||
const {
|
||||
networks = []
|
||||
} = data?.TEMPLATE?.BODY?.provision?.infrastructure
|
||||
|
||||
const { deleteVNetwork } = useProvision()
|
||||
const { getVNetwork } = useOpennebula()
|
||||
const { data: list, fetchRequestAll, loading } = useFetchAll()
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const { deleteVNetwork } = useProvisionApi()
|
||||
const { getVNetwork } = useVNetworkApi()
|
||||
|
||||
useEffect(() => {
|
||||
if (!hidden) {
|
||||
const requests = networks?.map(getVNetwork) ?? []
|
||||
fetchRequestAll(requests)
|
||||
}
|
||||
}, [networks])
|
||||
const { data: list, fetchRequestAll, loading } = useFetchAll()
|
||||
const fetchVNetworks = () => fetchRequestAll(networks?.map(({ id }) => getVNetwork(id)))
|
||||
|
||||
useEffect(() => {
|
||||
if (!list && !hidden) {
|
||||
const requests = networks?.map(getVNetwork) ?? []
|
||||
fetchRequestAll(requests)
|
||||
}
|
||||
}, [hidden])
|
||||
useEffect(() => {
|
||||
!hidden && !list && fetchVNetworks()
|
||||
}, [hidden])
|
||||
|
||||
return (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={!list && loading}
|
||||
CardComponent={NetworkCard}
|
||||
cardsProps={({ value: { ID } }) => ({
|
||||
actions: [{
|
||||
handleClick: () => deleteVNetwork({ id: ID })
|
||||
.then(() => fetchRequest(undefined, { reload: true })),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: `provision-vnet-delete-${ID}`
|
||||
}]
|
||||
})}
|
||||
displayEmpty
|
||||
breakpoints={{ xs: 12, md: 6 }}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.data === next.data)
|
||||
useEffect(() => {
|
||||
!reloading && !loading && fetchVNetworks()
|
||||
}, [reloading])
|
||||
|
||||
return (
|
||||
<ListCards
|
||||
list={list}
|
||||
isLoading={!list && loading}
|
||||
CardComponent={NetworkCard}
|
||||
cardsProps={({ value: { ID } }) => !disableAllActions && ({
|
||||
actions: [{
|
||||
handleClick: () => deleteVNetwork(ID)
|
||||
.then(refetchProvision)
|
||||
.then(() => enqueueSuccess(`VNetwork deleted - ID: ${ID}`)),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: `provision-vnet-delete-${ID}`
|
||||
}]
|
||||
})}
|
||||
displayEmpty
|
||||
breakpoints={{ xs: 12, md: 6 }}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) =>
|
||||
prev.hidden === next.hidden && prev.reloading === next.reloading
|
||||
)
|
||||
|
||||
Networks.propTypes = {
|
||||
data: Types.Provision.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
fetchRequest: PropTypes.func
|
||||
refetchProvision: PropTypes.func,
|
||||
reloading: PropTypes.bool,
|
||||
disableAllActions: PropTypes.bool
|
||||
}
|
||||
|
||||
Networks.defaultProps = {
|
||||
data: {},
|
||||
hidden: false,
|
||||
fetchRequest: () => undefined
|
||||
refetchProvision: () => undefined,
|
||||
reloading: false,
|
||||
disableAllActions: false
|
||||
}
|
||||
|
||||
Networks.displayName = 'Networks'
|
||||
|
@ -3,7 +3,9 @@ import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
import { LinearProgress } from '@material-ui/core'
|
||||
|
||||
import { useProvision, useFetch, useGeneral } from 'client/hooks'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { EmptyCard } from 'client/components/Cards'
|
||||
import { T } from 'client/constants'
|
||||
@ -25,8 +27,8 @@ const Inputs = () => ({
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(() => {
|
||||
const [fields, setFields] = useState(undefined)
|
||||
const { changeLoading } = useGeneral()
|
||||
const { getProvider } = useProvision()
|
||||
const { changeLoading } = useGeneralApi()
|
||||
const { getProvider } = useProviderApi()
|
||||
const { data: fetchData, fetchRequest, loading } = useFetch(getProvider)
|
||||
const { watch, reset } = useFormContext()
|
||||
|
||||
@ -35,7 +37,7 @@ const Inputs = () => ({
|
||||
|
||||
if (!currentInputs) {
|
||||
changeLoading(true) // disable finish button until provider is fetched
|
||||
fetchRequest({ id: providerSelected[0]?.ID })
|
||||
fetchRequest(providerSelected[0]?.ID)
|
||||
} else {
|
||||
setFields(FORM_FIELDS(inputs))
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useWatch } from 'react-hook-form'
|
||||
|
||||
import { useProvision, useListForm } from 'client/hooks'
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useOne } from 'client/features/One'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { EmptyCard, ProvisionCard } from 'client/components/Cards'
|
||||
import { T } from 'client/constants'
|
||||
@ -17,7 +18,7 @@ const Provider = () => ({
|
||||
label: T.Provider,
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const { providers } = useProvision()
|
||||
const { providers } = useOne()
|
||||
const provisionTemplate = useWatch({ name: TEMPLATE_ID })
|
||||
const provisionTemplateSelected = provisionTemplate?.[0] ?? {}
|
||||
|
||||
|
@ -3,7 +3,8 @@ import { Divider, Select, Breadcrumbs, InputLabel, FormControl } from '@material
|
||||
import ArrowIcon from '@material-ui/icons/ArrowForwardIosRounded'
|
||||
import Marked from 'marked'
|
||||
|
||||
import { useProvision, useListForm } from 'client/hooks'
|
||||
import { useListForm } from 'client/hooks'
|
||||
import { useOne } from 'client/features/One'
|
||||
import { ListCards } from 'client/components/List'
|
||||
import { ProvisionTemplateCard } from 'client/components/Cards'
|
||||
import { sanitize, groupBy } from 'client/utils'
|
||||
@ -22,7 +23,8 @@ const Template = () => ({
|
||||
label: T.ProvisionTemplate,
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(({ data, setFormData }) => {
|
||||
const { provisionsTemplates, providers } = useProvision()
|
||||
const { provisionsTemplates, providers } = useOne()
|
||||
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const [provisionSelected, setProvision] = React.useState(
|
||||
|
@ -11,8 +11,10 @@ import Steps from 'client/containers/Provisions/Form/Create/Steps'
|
||||
import formCreateStyles from 'client/containers/Provisions/Form/Create/styles'
|
||||
import DebugLog from 'client/components/DebugLog'
|
||||
|
||||
import { useProvision, useSocket, useFetch } from 'client/hooks'
|
||||
import { PATH } from 'client/router/provision'
|
||||
import { useSocket, useFetch } from 'client/hooks'
|
||||
import { useProviderApi, useProvisionApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
import { set, cloneObject, mapUserInputs } from 'client/utils'
|
||||
|
||||
import { Translate } from 'client/components/HOC'
|
||||
@ -23,9 +25,11 @@ function ProvisionCreateForm () {
|
||||
const history = useHistory()
|
||||
|
||||
const [uuid, setUuid] = useState(undefined)
|
||||
const { getProvision } = useSocket()
|
||||
|
||||
const { getProviders, createProvision } = useProvision()
|
||||
const { getProvision } = useSocket()
|
||||
const { getProviders } = useProviderApi()
|
||||
const { createProvision } = useProvisionApi()
|
||||
const { enqueueInfo } = useGeneralApi()
|
||||
|
||||
const { data, fetchRequest, loading, error } = useFetch(getProviders)
|
||||
|
||||
@ -64,7 +68,9 @@ function ProvisionCreateForm () {
|
||||
?.map(input => ({ ...input, value: `${parseInputs[input?.name]}` }))
|
||||
}
|
||||
|
||||
createProvision({ data: formatData }).then(res => res && setUuid(res))
|
||||
createProvision(formatData)
|
||||
.then(res => res && setUuid(res))
|
||||
.then(() => enqueueInfo('Creating provision'))
|
||||
}
|
||||
|
||||
useEffect(() => { fetchRequest() }, [])
|
||||
|
@ -5,8 +5,11 @@ import { Container, Box } from '@material-ui/core'
|
||||
import EditIcon from '@material-ui/icons/Settings'
|
||||
import DeleteIcon from '@material-ui/icons/Delete'
|
||||
|
||||
import { PATH } from 'client/router/provision'
|
||||
import { useProvision, useFetch, useSearch } from 'client/hooks'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
import { useFetch, useSearch } from 'client/hooks'
|
||||
import { useProvision, useProvisionApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
|
||||
import { ListHeader, ListCards } from 'client/components/List'
|
||||
import AlertError from 'client/components/Alerts/Error'
|
||||
import { ProvisionCard } from 'client/components/Cards'
|
||||
@ -17,17 +20,21 @@ import { T } from 'client/constants'
|
||||
|
||||
function Provisions () {
|
||||
const history = useHistory()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
const [{ content, ...showDialog } = {}, setShowDialog] = useState()
|
||||
const handleCloseDialog = () => setShowDialog()
|
||||
|
||||
const { enqueueInfo } = useGeneralApi()
|
||||
const provisions = useProvision()
|
||||
|
||||
const {
|
||||
provisions,
|
||||
getProvisions,
|
||||
getProvision,
|
||||
configureProvision,
|
||||
deleteProvision
|
||||
} = useProvision()
|
||||
} = useProvisionApi()
|
||||
|
||||
const { error, fetchRequest, loading, reloading } = useFetch(getProvisions)
|
||||
|
||||
const { result, handleChange } = useSearch({
|
||||
list: provisions,
|
||||
listOptions: { shouldSort: true, keys: ['ID', 'NAME'] }
|
||||
@ -35,17 +42,19 @@ function Provisions () {
|
||||
|
||||
useEffect(() => { fetchRequest() }, [])
|
||||
|
||||
const handleCancel = () => setShowDialog(false)
|
||||
|
||||
return (
|
||||
<Container disableGutters>
|
||||
<ListHeader
|
||||
title={T.Provisions}
|
||||
reloadButtonProps={{
|
||||
'data-cy': 'refresh-provision-list',
|
||||
onClick: () => fetchRequest(undefined, { reload: true }),
|
||||
isSubmitting: Boolean(loading || reloading)
|
||||
}}
|
||||
addButtonProps={{ 'data-cy': 'create-provision', onClick: () => history.push(PATH.PROVISIONS.CREATE) }}
|
||||
addButtonProps={{
|
||||
'data-cy': 'create-provision',
|
||||
onClick: () => history.push(PATH.PROVISIONS.CREATE)
|
||||
}}
|
||||
searchProps={{ handleChange }}
|
||||
/>
|
||||
<Box p={3}>
|
||||
@ -62,23 +71,36 @@ function Provisions () {
|
||||
id: ID,
|
||||
title: NAME,
|
||||
subheader: `#${ID}`,
|
||||
content: DialogInfo
|
||||
content: props => createElement(DialogInfo, {
|
||||
...props,
|
||||
displayName: 'DialogDetailProvision'
|
||||
})
|
||||
}),
|
||||
actions: [
|
||||
{
|
||||
handleClick: () => configureProvision({ id: ID }),
|
||||
handleClick: () => configureProvision(ID)
|
||||
.then(() => enqueueInfo(`Configuring provision - ID: ${ID}`))
|
||||
.then(() => fetchRequest(undefined, { reload: true })),
|
||||
icon: <EditIcon />,
|
||||
cy: 'provision-configure'
|
||||
},
|
||||
{
|
||||
handleClick: () => setShowDialog({
|
||||
id: ID,
|
||||
title: `DELETE provision - #${ID} - ${NAME}`,
|
||||
content: props => createElement(DialogInfo, {
|
||||
...props,
|
||||
disableAllActions: true,
|
||||
displayName: 'DialogDeleteProvision'
|
||||
}),
|
||||
title: `DELETE - ${NAME}`,
|
||||
subheader: `#${ID}`,
|
||||
handleAccept: () => {
|
||||
deleteProvision({ id: ID })
|
||||
setShowDialog(false)
|
||||
},
|
||||
content: DialogInfo
|
||||
handleCloseDialog()
|
||||
|
||||
return deleteProvision(ID)
|
||||
.then(() => enqueueInfo(`Deleting provision - ID: ${ID}`))
|
||||
.then(() => fetchRequest(undefined, { reload: true }))
|
||||
}
|
||||
}),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: 'provision-delete'
|
||||
@ -88,13 +110,13 @@ function Provisions () {
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
{showDialog !== false && (
|
||||
{content && (
|
||||
<DialogRequest
|
||||
withTabs
|
||||
request={() => getProvision({ id: showDialog.id })}
|
||||
dialogProps={{ handleCancel, ...showDialog }}
|
||||
request={() => getProvision(showDialog.id)}
|
||||
dialogProps={{ handleCancel: handleCloseDialog, ...showDialog }}
|
||||
>
|
||||
{props => createElement(showDialog.content, props)}
|
||||
{props => content(props)}
|
||||
</DialogRequest>
|
||||
)}
|
||||
</Container>
|
||||
|
@ -21,9 +21,10 @@ import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import SubmitButton from 'client/components/FormControl/SubmitButton'
|
||||
|
||||
import { useAuth } from 'client/hooks'
|
||||
import { useAuth, useAuthApi } from 'client/features/Auth'
|
||||
import { useUserApi } from 'client/features/One'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
@ -55,8 +56,9 @@ const useStyles = makeStyles(theme => ({
|
||||
|
||||
const Settings = () => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { updateUser, settings = {} } = useAuth()
|
||||
const { user, settings } = useAuth()
|
||||
const { getAuthUser } = useAuthApi()
|
||||
const { updateUser } = useUserApi()
|
||||
|
||||
const { handleSubmit, setError, reset, formState, ...methods } = useForm({
|
||||
reValidateMode: 'onSubmit',
|
||||
@ -79,7 +81,8 @@ const Settings = () => {
|
||||
.map(([key, value]) => `\n ${String(key).toUpperCase()} = "${value}"`)
|
||||
.join(',')
|
||||
|
||||
return updateUser({ template: `FIREEDGE = [${values}]\n` })
|
||||
return updateUser({ id: user.ID, template: `FIREEDGE = [${values}]\n` })
|
||||
.then(getAuthUser)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
} from '@material-ui/core'
|
||||
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import { requestData, requestParams } from 'client/utils'
|
||||
import { RestClient, requestParams } from 'client/utils'
|
||||
|
||||
const ResponseForm = ({
|
||||
handleChangeResponse,
|
||||
@ -20,13 +20,10 @@ const ResponseForm = ({
|
||||
const { control, handleSubmit, errors, formState } = useForm()
|
||||
|
||||
const onSubmit = dataForm => {
|
||||
const { url, options } = requestParams(dataForm, {
|
||||
name,
|
||||
httpMethod,
|
||||
params
|
||||
})
|
||||
const command = { name, httpMethod, params }
|
||||
const { url, options: { method, data } } = requestParams(dataForm, command)
|
||||
|
||||
requestData(url, options).then(({ id, ...res }) => {
|
||||
RestClient[method](url, data).then(({ id, ...res }) => {
|
||||
id === 401 && console.log('ERROR')
|
||||
id === 200 && handleChangeResponse(JSON.stringify(res, null, '\t'))
|
||||
})
|
||||
|
@ -18,15 +18,13 @@ import * as React from 'react'
|
||||
import FlowApp from 'client/apps/flow'
|
||||
import ProvisionApp from 'client/apps/provision'
|
||||
|
||||
import { isDevelopment, isBackend } from 'client/utils'
|
||||
import { _APPS, APPS } from 'client/constants'
|
||||
|
||||
const DevelopmentApp = props => {
|
||||
let appName = ''
|
||||
|
||||
if (
|
||||
process?.env?.NODE_ENV === 'development' &&
|
||||
typeof window !== 'undefined'
|
||||
) {
|
||||
if (isDevelopment() && !isBackend()) {
|
||||
const parseUrl = window.location.pathname
|
||||
.split(/\//gi)
|
||||
.filter(sub => sub?.length > 0)
|
||||
|
@ -16,13 +16,12 @@
|
||||
import * as React from 'react'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
import store from 'client/store'
|
||||
import { createStore } from 'client/store'
|
||||
import App from 'client/dev/_app'
|
||||
|
||||
render(
|
||||
<App store={store} />,
|
||||
document.getElementById('root')
|
||||
)
|
||||
const { store } = createStore({ initState: window.REDUX_DATA })
|
||||
|
||||
render(<App store={store} />, document.getElementById('root'))
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && module.hot) {
|
||||
module.hot.accept('./_app', () => {
|
||||
|
103
src/fireedge/src/client/features/Auth/actions.js
Normal file
103
src/fireedge/src/client/features/Auth/actions.js
Normal file
@ -0,0 +1,103 @@
|
||||
import { createAsyncThunk, createAction } from '@reduxjs/toolkit'
|
||||
|
||||
import { T, JWT_NAME, ONEADMIN_ID, FILTER_POOL } from 'client/constants'
|
||||
import { authService } from 'client/features/Auth/services'
|
||||
import { userService } from 'client/features/One/user/services'
|
||||
import { getGroups } from 'client/features/One/group/actions'
|
||||
import { dismissSnackbar } from 'client/features/General/actions'
|
||||
|
||||
import { storage, removeStoreData } from 'client/utils'
|
||||
|
||||
const login = createAsyncThunk(
|
||||
'auth/login',
|
||||
async ({ remember, ...user }, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const response = await authService.login(user)
|
||||
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) {
|
||||
return rejectWithValue({ error })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const getUser = createAsyncThunk(
|
||||
'auth/user',
|
||||
async (_, { dispatch, getState }) => {
|
||||
try {
|
||||
const user = await authService.getUser()
|
||||
await dispatch(getGroups())
|
||||
|
||||
const isOneAdmin = user?.ID === ONEADMIN_ID
|
||||
const userSettings = user?.TEMPLATE?.FIREEDGE ?? {}
|
||||
|
||||
const settings = {
|
||||
...getState().auth?.settings,
|
||||
...Object.entries(userSettings).reduce((res, [key, value]) =>
|
||||
({ ...res, [String(key).toLowerCase()]: value })
|
||||
, {})
|
||||
}
|
||||
|
||||
return { user, settings, isOneAdmin }
|
||||
} catch (error) {
|
||||
dispatch(logout(T.SessionExpired))
|
||||
}
|
||||
}, {
|
||||
condition: (_, { getState }) => {
|
||||
const { isLoading } = getState().auth
|
||||
|
||||
return !isLoading
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const logout = createAction('logout', errorMessage => {
|
||||
removeStoreData([JWT_NAME])
|
||||
|
||||
return { error: errorMessage }
|
||||
})
|
||||
|
||||
const changeFilter = createAction(
|
||||
'auth/change-filter',
|
||||
filterPool => ({ payload: { filterPool, isLoginInProgress: false } })
|
||||
)
|
||||
|
||||
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 }
|
||||
await userService.changeGroup({ data })
|
||||
|
||||
dispatch(changeFilter(FILTER_POOL.PRIMARY_GROUP_RESOURCES))
|
||||
return {
|
||||
user: {
|
||||
...user,
|
||||
GID: group
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue({ error })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export { login, getUser, logout, changeFilter, changeGroup }
|
37
src/fireedge/src/client/features/Auth/hooks.js
Normal file
37
src/fireedge/src/client/features/Auth/hooks.js
Normal file
@ -0,0 +1,37 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector, shallowEqual } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/Auth/actions'
|
||||
|
||||
export const useAuth = () => {
|
||||
const auth = useSelector(state => state.auth, shallowEqual)
|
||||
const groups = useSelector(state => state.one.groups, shallowEqual)
|
||||
|
||||
const { user, jwt } = auth
|
||||
|
||||
const userGroups = [user?.GROUPS?.ID]
|
||||
.flat()
|
||||
.map(id => groups.find(({ ID }) => ID === id))
|
||||
.filter(Boolean)
|
||||
|
||||
const isLogged = !!jwt && !!userGroups?.length
|
||||
|
||||
return { ...auth, groups: userGroups, isLogged }
|
||||
}
|
||||
|
||||
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: data => unwrapDispatch(actions.changeGroup(data)),
|
||||
logout: () => dispatch(actions.logout())
|
||||
}
|
||||
}
|
4
src/fireedge/src/client/features/Auth/index.js
Normal file
4
src/fireedge/src/client/features/Auth/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
export * from 'client/features/Auth/slice'
|
||||
export * from 'client/features/Auth/actions'
|
||||
export * from 'client/features/Auth/hooks'
|
||||
export * from 'client/features/Auth/services'
|
18
src/fireedge/src/client/features/Auth/services.js
Normal file
18
src/fireedge/src/client/features/Auth/services.js
Normal file
@ -0,0 +1,18 @@
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { RestClient } from 'client/utils'
|
||||
|
||||
export const authService = ({
|
||||
login: user => RestClient.post('/api/auth', user).then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) {
|
||||
if (res?.id === httpCodes.accepted.id) return res
|
||||
throw res
|
||||
}
|
||||
|
||||
return res?.data
|
||||
}),
|
||||
getUser: () => RestClient.get('/api/user/info').then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.USER ?? {}
|
||||
})
|
||||
})
|
66
src/fireedge/src/client/features/Auth/slice.js
Normal file
66
src/fireedge/src/client/features/Auth/slice.js
Normal file
@ -0,0 +1,66 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import { login, getUser, logout, changeFilter, changeGroup } from 'client/features/Auth/actions'
|
||||
import { JWT_NAME, FILTER_POOL, DEFAULT_SCHEME, DEFAULT_LANGUAGE } from 'client/constants'
|
||||
import { isBackend } from 'client/utils'
|
||||
|
||||
const initial = () => ({
|
||||
jwt: !isBackend()
|
||||
? window.localStorage.getItem(JWT_NAME) ??
|
||||
window.sessionStorage.getItem(JWT_NAME) ??
|
||||
null
|
||||
: null,
|
||||
user: null,
|
||||
error: null,
|
||||
filterPool: FILTER_POOL.ALL_RESOURCES,
|
||||
settings: {
|
||||
scheme: DEFAULT_SCHEME,
|
||||
lang: DEFAULT_LANGUAGE,
|
||||
disableAnimations: 'NO'
|
||||
},
|
||||
isLoginInProgress: false,
|
||||
isLoading: false
|
||||
})
|
||||
|
||||
const { actions, reducer } = createSlice({
|
||||
name: 'auth',
|
||||
initialState: initial(),
|
||||
extraReducers: builder => {
|
||||
builder
|
||||
.addCase(
|
||||
logout,
|
||||
(_, { error }) => ({ ...initial(), error })
|
||||
)
|
||||
.addMatcher(
|
||||
({ type }) => {
|
||||
return [
|
||||
changeFilter.type,
|
||||
login.fulfilled.type,
|
||||
getUser.fulfilled.type,
|
||||
changeGroup.fulfilled.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 })
|
||||
)
|
||||
.addMatcher(
|
||||
({ type }) => type.startsWith('auth/') && type.endsWith('/rejected'),
|
||||
(state, { payload }) => ({
|
||||
...state,
|
||||
...payload,
|
||||
isLoginInProgress: false,
|
||||
isLoading: false,
|
||||
jwt: null
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export { actions, reducer }
|
10
src/fireedge/src/client/features/General/actions.js
Normal file
10
src/fireedge/src/client/features/General/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from '@reduxjs/toolkit'
|
||||
|
||||
export const fixMenu = createAction('Fix menu')
|
||||
export const changeZone = createAction('Change zone')
|
||||
export const changeLoading = createAction('Change loading')
|
||||
export const changeTitle = createAction('Change title')
|
||||
|
||||
export const enqueueSnackbar = createAction('Enqueue snackbar')
|
||||
export const dismissSnackbar = createAction('Dismiss snackbar')
|
||||
export const deleteSnackbar = createAction('Delete snackbar')
|
59
src/fireedge/src/client/features/General/hooks.js
Normal file
59
src/fireedge/src/client/features/General/hooks.js
Normal file
@ -0,0 +1,59 @@
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import * as actions from 'client/features/General/actions'
|
||||
|
||||
const generateKey = () => new Date().getTime() + Math.random()
|
||||
|
||||
export const useGeneral = () => (
|
||||
useSelector(state => state.general)
|
||||
)
|
||||
|
||||
export const useGeneralApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return {
|
||||
fixMenu: isFixed => dispatch(actions.fixMenu(isFixed)),
|
||||
changeLoading: isLoading => dispatch(actions.changeLoading(isLoading)),
|
||||
changeTitle: title => dispatch(actions.changeTitle(title)),
|
||||
changeZone: zone => dispatch(actions.changeZone(zone)),
|
||||
|
||||
enqueueSnackbar: notification => {
|
||||
const key = notification.options && notification.options.key
|
||||
|
||||
return dispatch(actions.enqueueSnackbar({
|
||||
key: key || generateKey(),
|
||||
message: String(notification.message) || '',
|
||||
options: notification.options || {}
|
||||
}))
|
||||
},
|
||||
// dismiss all if no key has been defined
|
||||
dismissSnackbar: key => dispatch(
|
||||
actions.dismissSnackbar({ key, dismissAll: !key })
|
||||
),
|
||||
deleteSnackbar: key => dispatch(
|
||||
actions.deleteSnackbar({ key })
|
||||
),
|
||||
|
||||
enqueueSuccess: message => dispatch(
|
||||
actions.enqueueSnackbar({
|
||||
key: generateKey(),
|
||||
message,
|
||||
options: { variant: 'success' }
|
||||
})
|
||||
),
|
||||
enqueueError: message => dispatch(
|
||||
actions.enqueueSnackbar({
|
||||
key: generateKey(),
|
||||
message,
|
||||
options: { variant: 'success' }
|
||||
})
|
||||
),
|
||||
enqueueInfo: message => dispatch(
|
||||
actions.enqueueSnackbar({
|
||||
key: generateKey(),
|
||||
message,
|
||||
options: { variant: 'success' }
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
3
src/fireedge/src/client/features/General/index.js
Normal file
3
src/fireedge/src/client/features/General/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export * from 'client/features/General/slice'
|
||||
export * from 'client/features/General/actions'
|
||||
export * from 'client/features/General/hooks'
|
98
src/fireedge/src/client/features/General/slice.js
Normal file
98
src/fireedge/src/client/features/General/slice.js
Normal file
@ -0,0 +1,98 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/General/actions'
|
||||
|
||||
const initial = {
|
||||
zone: 0,
|
||||
title: null,
|
||||
isLoading: false,
|
||||
isFixMenu: false,
|
||||
|
||||
notifications: []
|
||||
}
|
||||
|
||||
const { reducer } = createSlice({
|
||||
name: 'general',
|
||||
initialState: initial,
|
||||
extraReducers: builder => {
|
||||
builder
|
||||
.addCase('logout', ({ title }) => ({ ...initial, title }))
|
||||
|
||||
/* UI ACTIONS */
|
||||
.addCase(actions.fixMenu, (state, { payload }) => {
|
||||
return { ...state, isFixMenu: !!payload }
|
||||
})
|
||||
.addCase(actions.changeLoading, (state, { payload }) => {
|
||||
return { ...state, isLoading: !!payload }
|
||||
})
|
||||
.addCase(actions.changeTitle, (state, { payload }) => {
|
||||
return { ...state, title: payload }
|
||||
})
|
||||
.addCase(actions.changeZone, (state, { payload }) => {
|
||||
return { ...state, zone: payload }
|
||||
})
|
||||
|
||||
/* NOTIFICATION ACTIONS */
|
||||
.addCase(actions.enqueueSnackbar, (state, { payload }) => {
|
||||
const { key, options, message } = payload
|
||||
|
||||
return {
|
||||
...state,
|
||||
notifications: [
|
||||
...state.notifications,
|
||||
{ key, options, message }
|
||||
]
|
||||
}
|
||||
})
|
||||
.addCase(actions.dismissSnackbar, (state, { payload }) => {
|
||||
const { key, dismissAll } = payload
|
||||
|
||||
return {
|
||||
...state,
|
||||
notifications: state.notifications.map(notification =>
|
||||
dismissAll || notification.key !== key
|
||||
? { ...notification, dismissed: true }
|
||||
: { ...notification }
|
||||
)
|
||||
}
|
||||
})
|
||||
.addCase(actions.deleteSnackbar, (state, { payload }) => {
|
||||
const { key } = payload
|
||||
|
||||
return {
|
||||
...state,
|
||||
notifications: state.notifications.filter(
|
||||
notification => notification.key !== key
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/* REQUESTS API MATCHES */
|
||||
.addMatcher(
|
||||
({ type }) => type.endsWith('/pending') && !type.includes('auth'),
|
||||
state => ({ ...state, isLoading: true })
|
||||
)
|
||||
.addMatcher(
|
||||
({ type }) => type.endsWith('/fulfilled') && !type.includes('auth'),
|
||||
state => ({ ...state, isLoading: false })
|
||||
)
|
||||
.addMatcher(
|
||||
({ type, meta }) =>
|
||||
!meta?.aborted && type.endsWith('/rejected') && !type.includes('auth'),
|
||||
(state, { payload }) => ({
|
||||
...state,
|
||||
isLoading: false,
|
||||
notifications: [
|
||||
...state.notifications,
|
||||
{
|
||||
key: new Date().getTime() + Math.random(),
|
||||
message: payload,
|
||||
options: { variant: 'error' }
|
||||
}
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export { reducer }
|
10
src/fireedge/src/client/features/One/application/actions.js
Normal file
10
src/fireedge/src/client/features/One/application/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { applicationService } from 'client/features/One/application/services'
|
||||
|
||||
export const getApplication = createAction('cluster', applicationService.getApplication)
|
||||
|
||||
export const getApplications = createAction(
|
||||
'application/pool',
|
||||
applicationService.getApplications,
|
||||
response => ({ applications: response })
|
||||
)
|
23
src/fireedge/src/client/features/One/application/hooks.js
Normal file
23
src/fireedge/src/client/features/One/application/hooks.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/One/application/actions'
|
||||
|
||||
export const useApplication = () => (
|
||||
useSelector(state => state.one.applications)
|
||||
)
|
||||
|
||||
export const useApplicationApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const unwrapDispatch = useCallback(
|
||||
action => dispatch(action).then(unwrapResult)
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
getApplication: id => unwrapDispatch(actions.getApplication({ id })),
|
||||
getApplications: () => unwrapDispatch(actions.getApplications())
|
||||
}
|
||||
}
|
19
src/fireedge/src/client/features/One/application/services.js
Normal file
19
src/fireedge/src/client/features/One/application/services.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { SERVICE } from 'server/routes/api/oneflow/string-routes'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { RestClient } from 'client/utils'
|
||||
import { poolRequest } from 'client/features/One/utils'
|
||||
|
||||
export const applicationService = ({
|
||||
getApplication: ({ filter, id }) => RestClient
|
||||
.get(`/api/${SERVICE}/list/${id}`, { filter })
|
||||
.then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.DOCUMENT ?? {}
|
||||
}),
|
||||
|
||||
getApplications: data => {
|
||||
const command = { name: `${SERVICE}.list`, params: {} }
|
||||
return poolRequest(data, command, 'DOCUMENT')
|
||||
}
|
||||
})
|
@ -0,0 +1,28 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { applicationTemplateService } from 'client/features/One/applicationTemplate/services'
|
||||
|
||||
export const getApplicationTemplate = createAction(
|
||||
'application-template',
|
||||
applicationTemplateService.getApplicationTemplate
|
||||
)
|
||||
|
||||
export const getApplicationsTemplates = createAction(
|
||||
'application-template/pool',
|
||||
applicationTemplateService.getApplicationsTemplates,
|
||||
response => ({ applicationsTemplates: response })
|
||||
)
|
||||
|
||||
export const createApplicationTemplate = createAction(
|
||||
'application-template/create',
|
||||
applicationTemplateService.createApplicationTemplate
|
||||
)
|
||||
|
||||
export const updateApplicationTemplate = createAction(
|
||||
'application-template/update',
|
||||
applicationTemplateService.updateApplicationTemplate
|
||||
)
|
||||
|
||||
export const instantiateApplicationTemplate = createAction(
|
||||
'application-template/instantiate',
|
||||
applicationTemplateService.instantiateApplicationTemplate
|
||||
)
|
@ -0,0 +1,30 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/One/applicationTemplate/actions'
|
||||
|
||||
export const useApplicationTemplate = () => (
|
||||
useSelector(state => state.one.applicationsTemplates)
|
||||
)
|
||||
|
||||
export const useApplicationTemplateApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const unwrapDispatch = useCallback(
|
||||
action => dispatch(action).then(unwrapResult)
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
getApplicationTemplate: id => unwrapDispatch(actions.getApplicationTemplate({ id })),
|
||||
getApplicationsTemplates: () => unwrapDispatch(actions.getApplicationsTemplates()),
|
||||
createApplicationTemplate: data => unwrapDispatch(actions.createApplicationTemplate({ data })),
|
||||
|
||||
updateApplicationTemplate: (id, data) =>
|
||||
unwrapDispatch(actions.updateApplicationTemplate({ id, data })),
|
||||
|
||||
instantiateApplicationTemplate: (id, data) =>
|
||||
unwrapDispatch(actions.instantiateApplicationTemplate({ id, data }))
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { SERVICE_TEMPLATE } from 'server/routes/api/oneflow/string-routes'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { RestClient } from 'client/utils'
|
||||
import { poolRequest } from 'client/features/One/utils'
|
||||
|
||||
export const applicationTemplateService = ({
|
||||
getApplicationTemplate: ({ filter, id }) => RestClient
|
||||
.get(`/api/${SERVICE_TEMPLATE}/list/${id}`, { filter })
|
||||
.then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.DOCUMENT ?? {}
|
||||
}),
|
||||
|
||||
getApplicationsTemplates: data => {
|
||||
const command = { name: `${SERVICE_TEMPLATE}.list`, params: {} }
|
||||
return poolRequest(data, command, 'DOCUMENT')
|
||||
},
|
||||
|
||||
createApplicationTemplate: ({ data = {} }) => RestClient
|
||||
.post(`/api/${SERVICE_TEMPLATE}/create`, data)
|
||||
.then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.DOCUMENT ?? {}
|
||||
}),
|
||||
|
||||
updateApplicationTemplate: ({ id, data = {} }) => RestClient
|
||||
.put(`/api/${SERVICE_TEMPLATE}/update/${id}`, data)
|
||||
.then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.DOCUMENT ?? {}
|
||||
}),
|
||||
|
||||
instantiateApplicationTemplate: ({ id, data = {} }) => RestClient
|
||||
.post(`/api/${SERVICE_TEMPLATE}/action/${id}`, {
|
||||
data: {
|
||||
action: {
|
||||
perform: 'instantiate',
|
||||
params: { merge_template: data }
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.DOCUMENT ?? {}
|
||||
})
|
||||
})
|
10
src/fireedge/src/client/features/One/cluster/actions.js
Normal file
10
src/fireedge/src/client/features/One/cluster/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { clusterService } from 'client/features/One/cluster/services'
|
||||
|
||||
export const getCluster = createAction('cluster', clusterService.getCluster)
|
||||
|
||||
export const getClusters = createAction(
|
||||
'cluster/pool',
|
||||
clusterService.getClusters,
|
||||
response => ({ clusters: response })
|
||||
)
|
23
src/fireedge/src/client/features/One/cluster/hooks.js
Normal file
23
src/fireedge/src/client/features/One/cluster/hooks.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/One/cluster/actions'
|
||||
|
||||
export const useCluster = () => (
|
||||
useSelector(state => state.one.clusters)
|
||||
)
|
||||
|
||||
export const useClusterApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const unwrapDispatch = useCallback(
|
||||
action => dispatch(action).then(unwrapResult)
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
getCluster: id => unwrapDispatch(actions.getCluster({ id })),
|
||||
getClusters: () => unwrapDispatch(actions.getClusters())
|
||||
}
|
||||
}
|
25
src/fireedge/src/client/features/One/cluster/services.js
Normal file
25
src/fireedge/src/client/features/One/cluster/services.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/cluster'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { requestParams, RestClient } from 'client/utils'
|
||||
import { poolRequest } from 'client/features/One/utils'
|
||||
|
||||
export const clusterService = ({
|
||||
getCluster: ({ filter, id }) => {
|
||||
const name = Actions.CLUSTER_INFO
|
||||
const { url, options } = requestParams(
|
||||
{ filter, id },
|
||||
{ name, ...Commands[name] }
|
||||
)
|
||||
|
||||
return RestClient.get(url, options).then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.CLUSTER ?? {}
|
||||
})
|
||||
},
|
||||
getClusters: data => {
|
||||
const name = Actions.CLUSTER_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
return poolRequest(data, command, 'CLUSTER')
|
||||
}
|
||||
})
|
10
src/fireedge/src/client/features/One/datastore/actions.js
Normal file
10
src/fireedge/src/client/features/One/datastore/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { datastoreService } from 'client/features/One/datastore/services'
|
||||
|
||||
export const getDatastore = createAction('datastore', datastoreService.getDatastore)
|
||||
|
||||
export const getDatastores = createAction(
|
||||
'datastore',
|
||||
datastoreService.getDatastores,
|
||||
response => ({ datastores: response })
|
||||
)
|
23
src/fireedge/src/client/features/One/datastore/hooks.js
Normal file
23
src/fireedge/src/client/features/One/datastore/hooks.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/One/datastore/actions'
|
||||
|
||||
export const useDatastore = () => (
|
||||
useSelector(state => state.one.datastores)
|
||||
)
|
||||
|
||||
export const useDatastoreApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const unwrapDispatch = useCallback(
|
||||
action => dispatch(action).then(unwrapResult)
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
getDatastore: id => unwrapDispatch(actions.getDatastore({ id })),
|
||||
getDatastores: () => unwrapDispatch(actions.getDatastores())
|
||||
}
|
||||
}
|
25
src/fireedge/src/client/features/One/datastore/services.js
Normal file
25
src/fireedge/src/client/features/One/datastore/services.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/datastore'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { requestParams, RestClient } from 'client/utils'
|
||||
import { poolRequest } from 'client/features/One/utils'
|
||||
|
||||
export const datastoreService = ({
|
||||
getDatastore: ({ filter, id }) => {
|
||||
const name = Actions.DATASTORE_INFO
|
||||
const { url, options } = requestParams(
|
||||
{ filter, id },
|
||||
{ name, ...Commands[name] }
|
||||
)
|
||||
|
||||
return RestClient.get(url, options).then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.DATASTORE ?? {}
|
||||
})
|
||||
},
|
||||
getDatastores: data => {
|
||||
const name = Actions.DATASTORE_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
return poolRequest(data, command, 'DATASTORE')
|
||||
}
|
||||
})
|
10
src/fireedge/src/client/features/One/group/actions.js
Normal file
10
src/fireedge/src/client/features/One/group/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { groupService } from 'client/features/One/group/services'
|
||||
|
||||
export const getGroup = createAction('group', groupService.getGroup)
|
||||
|
||||
export const getGroups = createAction(
|
||||
'group/pool',
|
||||
groupService.getGroups,
|
||||
response => ({ groups: response })
|
||||
)
|
23
src/fireedge/src/client/features/One/group/hooks.js
Normal file
23
src/fireedge/src/client/features/One/group/hooks.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/One/group/actions'
|
||||
|
||||
export const useGroup = () => (
|
||||
useSelector(state => state.one.groups)
|
||||
)
|
||||
|
||||
export const useGroupApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const unwrapDispatch = useCallback(
|
||||
action => dispatch(action).then(unwrapResult)
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
getGroup: id => unwrapDispatch(actions.getGroup({ id })),
|
||||
getGroups: () => unwrapDispatch(actions.getGroups())
|
||||
}
|
||||
}
|
25
src/fireedge/src/client/features/One/group/services.js
Normal file
25
src/fireedge/src/client/features/One/group/services.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/group'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { requestParams, RestClient } from 'client/utils'
|
||||
import { poolRequest } from 'client/features/One/utils'
|
||||
|
||||
export const groupService = ({
|
||||
getGroup: ({ filter, id }) => {
|
||||
const name = Actions.GROUP_INFO
|
||||
const { url, options } = requestParams(
|
||||
{ filter, id },
|
||||
{ name, ...Commands[name] }
|
||||
)
|
||||
|
||||
return RestClient.get(url, options).then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.GROUP ?? {}
|
||||
})
|
||||
},
|
||||
getGroups: data => {
|
||||
const name = Actions.GROUP_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
return poolRequest(data, command, 'GROUP')
|
||||
}
|
||||
})
|
20
src/fireedge/src/client/features/One/hooks.js
Normal file
20
src/fireedge/src/client/features/One/hooks.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { useSelector, shallowEqual } from 'react-redux'
|
||||
|
||||
export const useOne = () => (
|
||||
useSelector(state => state.one, shallowEqual)
|
||||
)
|
||||
|
||||
export * from 'client/features/One/application/hooks'
|
||||
export * from 'client/features/One/applicationTemplate/hooks'
|
||||
export * from 'client/features/One/cluster/hooks'
|
||||
export * from 'client/features/One/datastore/hooks'
|
||||
export * from 'client/features/One/group/hooks'
|
||||
export * from 'client/features/One/host/hooks'
|
||||
export * from 'client/features/One/marketApp/hooks'
|
||||
export * from 'client/features/One/provider/hooks'
|
||||
export * from 'client/features/One/provision/hooks'
|
||||
export * from 'client/features/One/user/hooks'
|
||||
export * from 'client/features/One/vm/hooks'
|
||||
export * from 'client/features/One/vmTemplate/hooks'
|
||||
export * from 'client/features/One/vnetwork/hooks'
|
||||
export * from 'client/features/One/vnetworkTemplate/hooks'
|
10
src/fireedge/src/client/features/One/host/actions.js
Normal file
10
src/fireedge/src/client/features/One/host/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { hostService } from 'client/features/One/host/services'
|
||||
|
||||
export const getHost = createAction('host', hostService.getHost)
|
||||
|
||||
export const getHosts = createAction(
|
||||
'host/pool',
|
||||
hostService.getHosts,
|
||||
response => ({ hosts: response })
|
||||
)
|
23
src/fireedge/src/client/features/One/host/hooks.js
Normal file
23
src/fireedge/src/client/features/One/host/hooks.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { unwrapResult } from '@reduxjs/toolkit'
|
||||
|
||||
import * as actions from 'client/features/One/host/actions'
|
||||
|
||||
export const useHost = () => (
|
||||
useSelector(state => state.one.hosts)
|
||||
)
|
||||
|
||||
export const useHostApi = () => {
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const unwrapDispatch = useCallback(
|
||||
action => dispatch(action).then(unwrapResult)
|
||||
, [dispatch]
|
||||
)
|
||||
|
||||
return {
|
||||
getHost: id => unwrapDispatch(actions.getHost({ id })),
|
||||
getHosts: () => unwrapDispatch(actions.getHosts())
|
||||
}
|
||||
}
|
25
src/fireedge/src/client/features/One/host/services.js
Normal file
25
src/fireedge/src/client/features/One/host/services.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/host'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { requestParams, RestClient } from 'client/utils'
|
||||
import { poolRequest } from 'client/features/One/utils'
|
||||
|
||||
export const hostService = ({
|
||||
getHost: ({ filter, id }) => {
|
||||
const name = Actions.HOST_INFO
|
||||
const { url, options } = requestParams(
|
||||
{ filter, id },
|
||||
{ name, ...Commands[name] }
|
||||
)
|
||||
|
||||
return RestClient.get(url, options).then(res => {
|
||||
if (!res?.id || res?.id !== httpCodes.ok.id) throw res
|
||||
|
||||
return res?.data?.HOST ?? {}
|
||||
})
|
||||
},
|
||||
getHosts: data => {
|
||||
const name = Actions.HOST_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
return poolRequest(data, command, 'HOST')
|
||||
}
|
||||
})
|
2
src/fireedge/src/client/features/One/index.js
Normal file
2
src/fireedge/src/client/features/One/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
export * from 'client/features/One/slice'
|
||||
export * from 'client/features/One/hooks'
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user