mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
parent
1d83bd2de6
commit
f16f214e16
@ -105,9 +105,11 @@ const DialogConfirmation = memo(
|
||||
>
|
||||
<DialogTitle disableTypography className={classes.title}>
|
||||
<div className={classes.titleText}>
|
||||
<Typography variant='h6'>
|
||||
{typeof title === 'string' ? Tr(title) : title}
|
||||
</Typography>
|
||||
{title && (
|
||||
<Typography variant='h6'>
|
||||
{typeof title === 'string' ? Tr(title) : title}
|
||||
</Typography>
|
||||
)}
|
||||
{subheader && (
|
||||
<Typography variant='subtitle1'>
|
||||
{typeof subheader === 'string' ? Tr(subheader) : subheader}
|
||||
@ -152,7 +154,7 @@ export const DialogPropTypes = {
|
||||
title: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
]).isRequired,
|
||||
]),
|
||||
subheader: PropTypes.string,
|
||||
contentProps: PropTypes.object,
|
||||
handleAccept: PropTypes.func,
|
||||
|
@ -47,7 +47,7 @@ const ButtonToTriggerForm = ({
|
||||
const open = Boolean(anchorEl)
|
||||
|
||||
const { display, show, hide, values: Form } = useDialog()
|
||||
const { onSubmit: handleSubmit, form, isConfirmDialog = true } = Form ?? {}
|
||||
const { onSubmit: handleSubmit, form, isConfirmDialog = false } = Form ?? {}
|
||||
|
||||
const formConfig = useMemo(() => form?.() ?? {}, [form])
|
||||
const { steps, defaultValues, resolver, fields, transformBeforeSubmit } = formConfig
|
||||
|
@ -14,26 +14,35 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { FORM_FIELDS, STEP_FORM_SCHEMA } from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import {
|
||||
FORM_FIELDS, STEP_FORM_SCHEMA
|
||||
} from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/schema'
|
||||
|
||||
export const STEP_ID = 'configuration'
|
||||
|
||||
const Content = ({ isUpdate }) => {
|
||||
return (
|
||||
<FormWithSchema
|
||||
cy='form-provider'
|
||||
id={STEP_ID}
|
||||
fields={FORM_FIELDS({ isUpdate })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const BasicConfiguration = ({ isUpdate }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.ProviderOverview,
|
||||
resolver: () => STEP_FORM_SCHEMA({ isUpdate }),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(
|
||||
() => <FormWithSchema cy="form-provider" fields={FORM_FIELDS({ isUpdate })} id={STEP_ID} />,
|
||||
[]
|
||||
)
|
||||
content: () => Content({ isUpdate })
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool
|
||||
}
|
||||
|
||||
export * from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/schema'
|
||||
export default BasicConfiguration
|
||||
|
@ -14,7 +14,8 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
@ -25,56 +26,57 @@ import { getConnectionEditable } from 'client/models/ProviderTemplate'
|
||||
import { sentenceCase } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import {
|
||||
FORM_FIELDS, STEP_FORM_SCHEMA
|
||||
} from 'client/components/Forms/Provider/CreateForm/Steps/Connection/schema'
|
||||
|
||||
import {
|
||||
STEP_ID as TEMPLATE_ID
|
||||
} from 'client/components/Forms/Provider/CreateForm/Steps/Template'
|
||||
import { FORM_FIELDS, STEP_FORM_SCHEMA } from 'client/components/Forms/Provider/CreateForm/Steps/Connection/schema'
|
||||
import { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/Provider/CreateForm/Steps/Template'
|
||||
|
||||
export const STEP_ID = 'connection'
|
||||
|
||||
let connection = {}
|
||||
let fileCredentials = false
|
||||
|
||||
const Content = ({ isUpdate }) => {
|
||||
const [fields, setFields] = useState([])
|
||||
const { providerConfig } = useAuth()
|
||||
const { watch } = useFormContext()
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
[TEMPLATE_ID]: templateSelected,
|
||||
[STEP_ID]: currentConnection = {}
|
||||
} = watch()
|
||||
|
||||
const template = templateSelected?.[0] ?? {}
|
||||
|
||||
fileCredentials = Boolean(providerConfig?.[template?.provider]?.file_credentials)
|
||||
|
||||
connection = isUpdate
|
||||
// when is updating, connections have the name as input label
|
||||
? Object.keys(currentConnection)
|
||||
.reduce((res, name) => ({ ...res, [name]: sentenceCase(name) }), {})
|
||||
// set connections from template, to take values as input labels
|
||||
: getConnectionEditable(template, providerConfig)
|
||||
|
||||
setFields(FORM_FIELDS({ connection, fileCredentials }))
|
||||
}, [])
|
||||
|
||||
return (fields?.length === 0) ? (
|
||||
<EmptyCard title={"There aren't connections to fill"} />
|
||||
) : (
|
||||
<FormWithSchema cy='form-provider' fields={fields} id={STEP_ID} />
|
||||
)
|
||||
}
|
||||
|
||||
const Connection = ({ isUpdate }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.ConfigureConnection,
|
||||
resolver: () => STEP_FORM_SCHEMA({ connection, fileCredentials }),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: useCallback(() => {
|
||||
const [fields, setFields] = useState([])
|
||||
const { providerConfig } = useAuth()
|
||||
const { watch } = useFormContext()
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
[TEMPLATE_ID]: templateSelected,
|
||||
[STEP_ID]: currentConnection = {}
|
||||
} = watch()
|
||||
|
||||
const template = templateSelected?.[0] ?? {}
|
||||
|
||||
fileCredentials = Boolean(providerConfig?.[template?.provider]?.file_credentials)
|
||||
|
||||
connection = isUpdate
|
||||
// when is updating, connections have the name as input label
|
||||
? Object.keys(currentConnection)
|
||||
.reduce((res, name) => ({ ...res, [name]: sentenceCase(name) }), {})
|
||||
// set connections from template, to take values as input labels
|
||||
: getConnectionEditable(template, providerConfig)
|
||||
|
||||
setFields(FORM_FIELDS({ connection, fileCredentials }))
|
||||
}, [])
|
||||
|
||||
return (fields?.length === 0) ? (
|
||||
<EmptyCard title={"There aren't connections to fill"} />
|
||||
) : (
|
||||
<FormWithSchema cy="form-provider" fields={fields} id={STEP_ID} />
|
||||
)
|
||||
}, [])
|
||||
content: () => Content({ isUpdate })
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool
|
||||
}
|
||||
|
||||
export * from 'client/components/Forms/Provider/CreateForm/Steps/Connection/schema'
|
||||
export default Connection
|
||||
|
@ -14,7 +14,8 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useState, useCallback, useEffect, useMemo } from 'react'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Divider, Select, Breadcrumbs, InputLabel, FormControl } from '@material-ui/core'
|
||||
import { NavArrowRight } from 'iconoir-react'
|
||||
import Marked from 'marked'
|
||||
@ -35,172 +36,179 @@ import { STEP_ID as CONNECTION_ID } from 'client/components/Forms/Provider/Creat
|
||||
|
||||
export const STEP_ID = 'template'
|
||||
|
||||
const Content = ({ data, setFormData }) => {
|
||||
const provisionTemplates = useProvisionTemplate()
|
||||
const { providerConfig } = useAuth()
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const provisionTypes = useMemo(() => [
|
||||
...new Set(
|
||||
Object.values(providerConfig)
|
||||
.map(provider => provider?.provision_type).flat()
|
||||
)
|
||||
], [])
|
||||
|
||||
const provisionTypeSelected = useMemo(() => (
|
||||
getProvisionTypeFromTemplate(provisionTemplates, templateSelected)
|
||||
), [])
|
||||
|
||||
const [provisionSelected, setProvision] = useState(() => provisionTypeSelected ?? provisionTypes[0])
|
||||
const [providerSelected, setProvider] = useState(() => templateSelected?.provider)
|
||||
|
||||
const [templatesByProvisionSelected, providerTypes, description] = useMemo(() => {
|
||||
const templates = Object.values(provisionTemplates[provisionSelected]?.providers).flat()
|
||||
const types = [...new Set(templates.map(({ provider }) => provider))]
|
||||
const provisionDescription = provisionTemplates?.[provisionSelected]?.description
|
||||
|
||||
return [templates, types, provisionDescription]
|
||||
}, [provisionSelected])
|
||||
|
||||
const templatesAvailable = useMemo(() => (
|
||||
templatesByProvisionSelected.filter(({ provider }) => providerSelected === provider)
|
||||
), [providerSelected])
|
||||
|
||||
useEffect(() => {
|
||||
// set first provision type
|
||||
!provisionSelected && setProvision(provisionTypes[0])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// set first provider type
|
||||
provisionSelected && !providerSelected && setProvider(providerTypes[0])
|
||||
}, [provisionSelected])
|
||||
|
||||
const { handleSelect, handleUnselect, handleClear } = useListForm({
|
||||
key: STEP_ID,
|
||||
list: data,
|
||||
setList: setFormData,
|
||||
getItemId: item => item.name
|
||||
})
|
||||
|
||||
const handleChangeProvision = evt => {
|
||||
setProvision(evt.target.value)
|
||||
setProvider(undefined)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
|
||||
const handleChangeProvider = evt => {
|
||||
setProvider(evt.target.value)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
|
||||
const handleClick = (template, isSelected) => {
|
||||
const { name, description } = template
|
||||
|
||||
// reset rest of form when change template
|
||||
setFormData({
|
||||
[CONFIGURATION_ID]: { name, description },
|
||||
[CONNECTION_ID]: {}
|
||||
})
|
||||
|
||||
isSelected
|
||||
? handleUnselect(name)
|
||||
: handleSelect(template)
|
||||
}
|
||||
|
||||
const RenderDescription = ({ description = '' }) => {
|
||||
const renderer = new Marked.Renderer()
|
||||
|
||||
renderer.link = (href, title, text) => (
|
||||
`<a class='MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorSecondary'
|
||||
target='_blank' rel='nofollow' title='${title}' href='${href}'>${text}</a>`
|
||||
)
|
||||
|
||||
const html = Marked(sanitize`${description}`, { renderer })
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* -- SELECTORS -- */}
|
||||
<Breadcrumbs separator={<NavArrowRight />}>
|
||||
<FormControl>
|
||||
<InputLabel color='secondary' shrink id='select-provision-type-label'>
|
||||
{'Provision type'}
|
||||
</InputLabel>
|
||||
<Select
|
||||
color='secondary'
|
||||
inputProps={{ 'data-cy': 'select-provision-type' }}
|
||||
labelId='select-provision-type-label'
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvision}
|
||||
value={provisionSelected}
|
||||
variant='outlined'
|
||||
>
|
||||
{provisionTypes.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel color='secondary' shrink id='select-provider-type-label'>
|
||||
{'Provider type'}
|
||||
</InputLabel>
|
||||
<Select
|
||||
color='secondary'
|
||||
inputProps={{ 'data-cy': 'select-provider-type' }}
|
||||
labelId='select-provider-type-label'
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvider}
|
||||
value={providerSelected}
|
||||
variant='outlined'
|
||||
>
|
||||
{providerTypes.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{providerConfig[type]?.name ?? type}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Breadcrumbs>
|
||||
|
||||
{/* -- DESCRIPTION -- */}
|
||||
{useMemo(() => description && <RenderDescription description={description} />, [description])}
|
||||
|
||||
<Divider style={{ margin: '1rem 0' }} />
|
||||
|
||||
{/* -- LIST -- */}
|
||||
<ListCards
|
||||
keyProp='name'
|
||||
list={templatesAvailable}
|
||||
gridProps={{ 'data-cy': 'providers-templates' }}
|
||||
CardComponent={ProvisionTemplateCard}
|
||||
cardsProps={({ value = {} }) => {
|
||||
const isSelected = data?.some(selected => selected.name === value.name)
|
||||
const isValid = isValidProviderTemplate(value, providerConfig)
|
||||
const image = providerConfig?.[value.provider]?.image
|
||||
|
||||
return {
|
||||
image,
|
||||
isProvider: true,
|
||||
isSelected,
|
||||
isValid,
|
||||
handleClick: () => handleClick(value, isSelected)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Template = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.ProviderTemplate,
|
||||
resolver: () => STEP_FORM_SCHEMA,
|
||||
content: useCallback(
|
||||
({ data, setFormData }) => {
|
||||
const provisionTemplates = useProvisionTemplate()
|
||||
const { providerConfig } = useAuth()
|
||||
const templateSelected = data?.[0]
|
||||
|
||||
const provisionTypes = useMemo(() => [
|
||||
...new Set(
|
||||
Object.values(providerConfig)
|
||||
.map(provider => provider?.provision_type).flat()
|
||||
)
|
||||
], [])
|
||||
|
||||
const provisionTypeSelected = useMemo(() => (
|
||||
getProvisionTypeFromTemplate(provisionTemplates, templateSelected)
|
||||
), [])
|
||||
|
||||
const [provisionSelected, setProvision] = useState(() => provisionTypeSelected ?? provisionTypes[0])
|
||||
const [providerSelected, setProvider] = useState(() => templateSelected?.provider)
|
||||
|
||||
const [templatesByProvisionSelected, providerTypes, description] = useMemo(() => {
|
||||
const templates = Object.values(provisionTemplates[provisionSelected]?.providers).flat()
|
||||
const types = [...new Set(templates.map(({ provider }) => provider))]
|
||||
const provisionDescription = provisionTemplates?.[provisionSelected]?.description
|
||||
|
||||
return [templates, types, provisionDescription]
|
||||
}, [provisionSelected])
|
||||
|
||||
const templatesAvailable = useMemo(() => (
|
||||
templatesByProvisionSelected.filter(({ provider }) => providerSelected === provider)
|
||||
), [providerSelected])
|
||||
|
||||
useEffect(() => {
|
||||
// set first provision type
|
||||
!provisionSelected && setProvision(provisionTypes[0])
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// set first provider type
|
||||
provisionSelected && !providerSelected && setProvider(providerTypes[0])
|
||||
}, [provisionSelected])
|
||||
|
||||
const { handleSelect, handleUnselect, handleClear } = useListForm({
|
||||
key: STEP_ID,
|
||||
list: data,
|
||||
setList: setFormData,
|
||||
getItemId: item => item.name
|
||||
})
|
||||
|
||||
const handleChangeProvision = evt => {
|
||||
setProvision(evt.target.value)
|
||||
setProvider(undefined)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
|
||||
const handleChangeProvider = evt => {
|
||||
setProvider(evt.target.value)
|
||||
templateSelected && handleClear()
|
||||
}
|
||||
|
||||
const handleClick = (template, isSelected) => {
|
||||
const { name, description } = template
|
||||
|
||||
// reset rest of form when change template
|
||||
setFormData({
|
||||
[CONFIGURATION_ID]: { name, description },
|
||||
[CONNECTION_ID]: {}
|
||||
})
|
||||
|
||||
isSelected
|
||||
? handleUnselect(name)
|
||||
: handleSelect(template)
|
||||
}
|
||||
|
||||
const RenderDescription = ({ description = '' }) => {
|
||||
const renderer = new Marked.Renderer()
|
||||
|
||||
renderer.link = (href, title, text) => (
|
||||
`<a class='MuiTypography-root MuiLink-root MuiLink-underlineHover MuiTypography-colorSecondary'
|
||||
target='_blank' rel='nofollow' title='${title}' href='${href}'>${text}</a>`
|
||||
)
|
||||
|
||||
const html = Marked(sanitize`${description}`, { renderer })
|
||||
return <div dangerouslySetInnerHTML={{ __html: html }} />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* -- SELECTORS -- */}
|
||||
<Breadcrumbs separator={<NavArrowRight />}>
|
||||
<FormControl>
|
||||
<InputLabel color='secondary' shrink id='select-provision-type-label'>
|
||||
{'Provision type'}
|
||||
</InputLabel>
|
||||
<Select
|
||||
color='secondary'
|
||||
inputProps={{ 'data-cy': 'select-provision-type' }}
|
||||
labelId='select-provision-type-label'
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvision}
|
||||
value={provisionSelected}
|
||||
variant='outlined'
|
||||
>
|
||||
{provisionTypes.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{type}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<InputLabel color='secondary' shrink id='select-provider-type-label'>
|
||||
{'Provider type'}
|
||||
</InputLabel>
|
||||
<Select
|
||||
color='secondary'
|
||||
inputProps={{ 'data-cy': 'select-provider-type' }}
|
||||
labelId='select-provider-type-label'
|
||||
native
|
||||
style={{ marginTop: '1em', minWidth: '8em' }}
|
||||
onChange={handleChangeProvider}
|
||||
value={providerSelected}
|
||||
variant='outlined'
|
||||
>
|
||||
{providerTypes.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{providerConfig[type]?.name ?? type}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Breadcrumbs>
|
||||
|
||||
{/* -- DESCRIPTION -- */}
|
||||
{useMemo(() => description && <RenderDescription description={description} />, [description])}
|
||||
|
||||
<Divider style={{ margin: '1rem 0' }} />
|
||||
|
||||
{/* -- LIST -- */}
|
||||
<ListCards
|
||||
keyProp='name'
|
||||
list={templatesAvailable}
|
||||
gridProps={{ 'data-cy': 'providers-templates' }}
|
||||
CardComponent={ProvisionTemplateCard}
|
||||
cardsProps={({ value = {} }) => {
|
||||
const isSelected = data?.some(selected => selected.name === value.name)
|
||||
const isValid = isValidProviderTemplate(value, providerConfig)
|
||||
const image = providerConfig?.[value.provider]?.image
|
||||
|
||||
return {
|
||||
image,
|
||||
isProvider: true,
|
||||
isSelected,
|
||||
isValid,
|
||||
handleClick: () => handleClick(value, isSelected)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}, [])
|
||||
content: Content
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func
|
||||
}
|
||||
|
||||
export * from 'client/components/Forms/Provider/CreateForm/Steps/Template/schema'
|
||||
export default Template
|
||||
|
@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import Template from 'client/components/Forms/Provider/CreateForm/Steps/Template'
|
||||
import BasicConfiguration from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration'
|
||||
import Connection from 'client/components/Forms/Provider/CreateForm/Steps/Connection'
|
||||
import Template, { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/Provider/CreateForm/Steps/Template'
|
||||
import BasicConfiguration, { STEP_ID as BASIC_ID } from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration'
|
||||
import Connection, { STEP_ID as CONNECTION_ID } from 'client/components/Forms/Provider/CreateForm/Steps/Connection'
|
||||
import { getConnectionEditable, getConnectionFixed } from 'client/models/ProviderTemplate'
|
||||
import { createSteps, deepmerge } from 'client/utils'
|
||||
|
||||
const Steps = createSteps(stepProps => {
|
||||
@ -28,11 +28,31 @@ const Steps = createSteps(stepProps => {
|
||||
Connection
|
||||
].filter(Boolean)
|
||||
}, {
|
||||
transformBeforeSubmit: formData => {
|
||||
const { template, configuration, connection } = formData
|
||||
const templateSelected = template?.[0]
|
||||
transformInitialValue: ({ provider, connection, providerConfig } = {}) => {
|
||||
const { description, ...currentBodyTemplate } = provider?.TEMPLATE?.PROVISION_BODY ?? {}
|
||||
|
||||
return deepmerge(templateSelected, { ...configuration, connection })
|
||||
// overwrite decrypted connection
|
||||
const fakeProviderTemplate = { ...currentBodyTemplate, connection }
|
||||
const connectionEditable = getConnectionEditable(fakeProviderTemplate, providerConfig)
|
||||
|
||||
return {
|
||||
[TEMPLATE_ID]: [fakeProviderTemplate],
|
||||
[CONNECTION_ID]: connectionEditable,
|
||||
[BASIC_ID]: { description }
|
||||
}
|
||||
},
|
||||
transformBeforeSubmit: (formData, providerConfig) => {
|
||||
const {
|
||||
[TEMPLATE_ID]: [templateSelected] = [],
|
||||
[CONNECTION_ID]: connection = {},
|
||||
[BASIC_ID]: configuration = {}
|
||||
} = formData ?? {}
|
||||
|
||||
const connectionFixed = getConnectionFixed(templateSelected, providerConfig)
|
||||
const allConnections = { ...connection, ...connectionFixed }
|
||||
const editedData = { ...configuration, connection: allConnections }
|
||||
|
||||
return deepmerge(templateSelected, editedData)
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -14,21 +14,26 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Redirect } from 'react-router'
|
||||
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import FormStepper from 'client/components/FormStepper'
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/Provider/CreateForm/Steps'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
|
||||
const CreateForm = ({ stepProps, initialValues, onSubmit }) => {
|
||||
const {
|
||||
steps,
|
||||
defaultValues,
|
||||
resolver,
|
||||
transformBeforeSubmit
|
||||
} = Steps(stepProps, initialValues)
|
||||
const CreateForm = ({ provider, providerConfig, connection, onSubmit }) => {
|
||||
const initialValues = provider && { provider, connection, providerConfig }
|
||||
const stepProps = { isUpdate: !!initialValues }
|
||||
|
||||
const stepsForm = useMemo(() => Steps(stepProps, initialValues), [])
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = stepsForm
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onSubmit',
|
||||
@ -41,18 +46,51 @@ const CreateForm = ({ stepProps, initialValues, onSubmit }) => {
|
||||
<FormStepper
|
||||
steps={steps}
|
||||
schema={resolver}
|
||||
onSubmit={data => onSubmit(transformBeforeSubmit?.(data) ?? data)}
|
||||
onSubmit={data =>
|
||||
onSubmit(transformBeforeSubmit?.(data, providerConfig) ?? data)
|
||||
}
|
||||
/>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
CreateForm.propTypes = {
|
||||
stepProps: PropTypes.shape({
|
||||
isUpdate: PropTypes.bool
|
||||
}),
|
||||
initialValues: PropTypes.object,
|
||||
const PreFetchingForm = ({ providerId, onSubmit }) => {
|
||||
const { providerConfig } = useAuth()
|
||||
const { getProvider, getProviderConnection } = useProviderApi()
|
||||
const { data, fetchRequestAll, error } = useFetchAll()
|
||||
const [provider, connection] = data ?? []
|
||||
|
||||
useEffect(() => {
|
||||
providerId && fetchRequestAll([
|
||||
getProvider(providerId),
|
||||
getProviderConnection(providerId)
|
||||
])
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return <Redirect to={PATH.PROVIDERS.LIST} />
|
||||
}
|
||||
|
||||
return (providerId && !data)
|
||||
? <SkeletonStepsForm />
|
||||
: <CreateForm
|
||||
provider={provider}
|
||||
providerConfig={providerConfig}
|
||||
connection={connection}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
}
|
||||
|
||||
PreFetchingForm.propTypes = {
|
||||
providerId: PropTypes.string,
|
||||
onSubmit: PropTypes.func
|
||||
}
|
||||
|
||||
export default CreateForm
|
||||
CreateForm.propTypes = {
|
||||
provider: PropTypes.object,
|
||||
connection: PropTypes.object,
|
||||
providerConfig: PropTypes.object,
|
||||
onSubmit: PropTypes.func
|
||||
}
|
||||
|
||||
export default PreFetchingForm
|
||||
|
@ -50,8 +50,9 @@ const ALIAS_FIELD = ({ nics = [] } = {}) => ({
|
||||
.filter(({ NAME }) => NAME !== name || !name) // filter it self
|
||||
.map(nic => {
|
||||
const { NAME, IP = '', NETWORK = '', NIC_ID = '' } = nic
|
||||
const text = [NAME ?? NIC_ID, NETWORK, IP].filter(Boolean).join(' - ')
|
||||
|
||||
return { text: `${NAME ?? NIC_ID} - ${NETWORK} ${IP}`, value: NAME }
|
||||
return { text, value: NAME }
|
||||
})
|
||||
],
|
||||
validation: yup
|
||||
|
@ -68,7 +68,7 @@ export const reorder = (newBootOrder, setFormData) => {
|
||||
const Booting = ({ data, setFormData }) => {
|
||||
const classes = useStyles()
|
||||
const { watch } = useFormContext()
|
||||
const bootOrder = data?.OS?.BOOT?.split(',').filter(Boolean)
|
||||
const bootOrder = data?.OS?.BOOT?.split(',').filter(Boolean) ?? []
|
||||
|
||||
const disks = useMemo(() => {
|
||||
const templateSeleted = watch(`${TEMPLATE_ID}[0]`)
|
||||
@ -95,12 +95,13 @@ const Booting = ({ data, setFormData }) => {
|
||||
}, [])
|
||||
|
||||
const nics = data?.[NIC_ID]
|
||||
?.map((nic, idx) => ({ ...nic, NAME: nic?.NAME ?? `NIC${idx}` }))
|
||||
?.map((nic, idx) => ({
|
||||
ID: `nic${idx}`,
|
||||
NAME: (
|
||||
<>
|
||||
<NetworkIcon size={16} />
|
||||
{`NIC ${idx}: ${nic.NETWORK}`}
|
||||
{[nic?.NAME ?? `NIC${idx}`, nic.NETWORK].filter(Boolean).join(': ')}
|
||||
</>
|
||||
)
|
||||
})) ?? []
|
||||
@ -173,7 +174,7 @@ const Booting = ({ data, setFormData }) => {
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
<Divider />
|
||||
{restOfItems.length > 0 && <Divider />}
|
||||
{restOfItems.map(({ ID, NAME }) => (
|
||||
<div key={ID} className={classes.item}>
|
||||
<Action
|
||||
|
@ -43,14 +43,14 @@ export const TAB_ID = 'NIC'
|
||||
const Networking = ({ data, setFormData }) => {
|
||||
const classes = useStyles()
|
||||
const nics = data?.[TAB_ID]
|
||||
?.map((nic, idx) => ({ ...nic, NAME: `NIC${idx}` }))
|
||||
?.map((nic, idx) => ({ ...nic, NAME: nic?.NAME ?? `NIC${idx}` }))
|
||||
|
||||
const { handleRemove, handleSave } = useListForm({
|
||||
parent: EXTRA_ID,
|
||||
key: TAB_ID,
|
||||
list: nics,
|
||||
setList: setFormData,
|
||||
getItemId: (item) => item.NAME,
|
||||
getItemId: (item) => item.NAME ?? `NIC${data.length + 1}`,
|
||||
addItemId: (item, id) => ({ ...item, NAME: id })
|
||||
})
|
||||
|
||||
@ -100,7 +100,7 @@ const Networking = ({ data, setFormData }) => {
|
||||
return (
|
||||
<SelectCard
|
||||
key={NAME}
|
||||
title={`${NAME} - ${NETWORK}`}
|
||||
title={[NAME, NETWORK].filter(Boolean).join(' - ')}
|
||||
subheader={<>
|
||||
{Object
|
||||
.entries({ RDP, SSH, ALIAS: PARENT, EXTERNAL })
|
||||
|
@ -20,14 +20,14 @@ import PropTypes from 'prop-types'
|
||||
import { useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useUserApi, useVmGroupApi, useVmTemplateApi } from 'client/features/One'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useUserApi, useVmGroupApi, useVmTemplateApi } from 'client/features/One'
|
||||
import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/VmTemplate/InstantiateForm/Steps'
|
||||
|
||||
const InstantiateForm = ({ template, onSubmit }) => {
|
||||
const stepProps = useMemo(() => Steps(template, template), [])
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = stepProps
|
||||
const stepsForm = useMemo(() => Steps(template, template), [])
|
||||
const { steps, defaultValues, resolver, transformBeforeSubmit } = stepsForm
|
||||
|
||||
const methods = useForm({
|
||||
mode: 'onSubmit',
|
||||
@ -46,7 +46,7 @@ const InstantiateForm = ({ template, onSubmit }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const PreFetchingForm = ({ templateId, ...props }) => {
|
||||
const PreFetchingForm = ({ templateId, onSubmit }) => {
|
||||
const { getUsers } = useUserApi()
|
||||
const { getVmGroups } = useVmGroupApi()
|
||||
const { getVmTemplate } = useVmTemplateApi()
|
||||
@ -62,7 +62,7 @@ const PreFetchingForm = ({ templateId, ...props }) => {
|
||||
|
||||
return (templateId && !data)
|
||||
? <SkeletonStepsForm />
|
||||
: <InstantiateForm {...props} template={data} />
|
||||
: <InstantiateForm template={data} onSubmit={onSubmit} />
|
||||
}
|
||||
|
||||
PreFetchingForm.propTypes = {
|
||||
|
@ -59,7 +59,7 @@ const Ownership = memo(({
|
||||
link: PATH.SYSTEM.USERS.DETAIL.replace(':id', userId),
|
||||
canEdit: actions?.includes?.(ACTIONS.CHANGE_OWNER),
|
||||
handleGetOptionList: getUserOptions,
|
||||
handleEdit: user => handleEdit?.({ user })
|
||||
handleEdit: (_, user) => handleEdit?.({ user })
|
||||
},
|
||||
{
|
||||
name: T.Group,
|
||||
@ -68,7 +68,7 @@ const Ownership = memo(({
|
||||
link: PATH.SYSTEM.GROUPS.DETAIL.replace(':id', groupId),
|
||||
canEdit: actions?.includes?.(ACTIONS.CHANGE_GROUP),
|
||||
handleGetOptionList: getGroupOptions,
|
||||
handleEdit: group => handleEdit?.({ group })
|
||||
handleEdit: (_, group) => handleEdit?.({ group })
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -58,7 +58,7 @@ const VmInfoTab = ({ tabProps = {} }) => {
|
||||
String(response) === String(ID) && await handleRefetch?.()
|
||||
}
|
||||
|
||||
const handleRename = async newName => {
|
||||
const handleRename = async (_, newName) => {
|
||||
const response = await rename(ID, newName)
|
||||
String(response) === String(ID) && await handleRefetch?.()
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ const VmTemplateInfoTab = ({ tabProps = {} }) => {
|
||||
String(response) === String(ID) && await handleRefetch?.()
|
||||
}
|
||||
|
||||
const handleRename = async newName => {
|
||||
const handleRename = async (_, newName) => {
|
||||
const response = await rename(ID, newName)
|
||||
String(response) === String(ID) && await handleRefetch?.()
|
||||
}
|
||||
|
@ -19,14 +19,19 @@ import PropTypes from 'prop-types'
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { T } from 'client/constants'
|
||||
import { T, VM_TEMPLATE_ACTIONS } from 'client/constants'
|
||||
|
||||
const InformationPanel = ({ template = {} }) => {
|
||||
const InformationPanel = ({ template = {}, handleRename, actions }) => {
|
||||
const { ID, NAME, REGTIME, LOCK } = template
|
||||
|
||||
const info = [
|
||||
{ name: T.ID, value: ID },
|
||||
{ name: T.Name, value: NAME },
|
||||
{
|
||||
name: T.Name,
|
||||
value: NAME,
|
||||
canEdit: actions?.includes?.(VM_TEMPLATE_ACTIONS.RENAME),
|
||||
handleEdit: handleRename
|
||||
},
|
||||
{
|
||||
name: T.StartTime,
|
||||
value: Helper.timeToString(REGTIME)
|
||||
@ -45,6 +50,8 @@ const InformationPanel = ({ template = {} }) => {
|
||||
InformationPanel.displayName = 'InformationPanel'
|
||||
|
||||
InformationPanel.propTypes = {
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
handleRename: PropTypes.func,
|
||||
template: PropTypes.object
|
||||
}
|
||||
|
||||
|
@ -14,50 +14,37 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Redirect, useParams, useHistory } from 'react-router'
|
||||
import { useParams, useHistory } from 'react-router'
|
||||
import { Container } from '@material-ui/core'
|
||||
|
||||
import { Container, LinearProgress } from '@material-ui/core'
|
||||
|
||||
import { useFetchAll } from 'client/hooks'
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import { CreateForm } from 'client/components/Forms/Provider'
|
||||
import { isValidProviderTemplate, getConnectionEditable, getConnectionFixed } from 'client/models/ProviderTemplate'
|
||||
import { isValidProviderTemplate } from 'client/models/ProviderTemplate'
|
||||
import { PATH } from 'client/apps/provision/routes'
|
||||
import { isDevelopment, deepmerge } from 'client/utils'
|
||||
import { isDevelopment } from 'client/utils'
|
||||
|
||||
function ProviderCreateForm () {
|
||||
const [initialValues, setInitialValues] = useState(null)
|
||||
const history = useHistory()
|
||||
const { id } = useParams()
|
||||
|
||||
const { providerConfig } = useAuth()
|
||||
const { enqueueSuccess, enqueueError } = useGeneralApi()
|
||||
const { getProvider, getProviderConnection, createProvider, updateProvider } = useProviderApi()
|
||||
const { data: preloadedData, fetchRequestAll, loading, error } = useFetchAll()
|
||||
const { createProvider, updateProvider } = useProviderApi()
|
||||
|
||||
const onSubmit = async formData => {
|
||||
try {
|
||||
if (id !== undefined) {
|
||||
const [provider = {}, connection = []] = preloadedData ?? []
|
||||
const providerId = provider?.ID
|
||||
|
||||
const formatData = deepmerge({ connection }, formData)
|
||||
|
||||
await updateProvider(id, formatData)
|
||||
enqueueSuccess(`Provider updated - ID: ${providerId}`)
|
||||
await updateProvider(id, formData)
|
||||
enqueueSuccess(`Provider updated - ID: ${id}`)
|
||||
} else {
|
||||
if (!isValidProviderTemplate(formData, providerConfig)) {
|
||||
enqueueError('The template selected has a bad format. Ask your cloud administrator')
|
||||
history.push(PATH.PROVIDERS.LIST)
|
||||
}
|
||||
|
||||
const connectionFixed = getConnectionFixed(formData, providerConfig)
|
||||
const formatData = deepmerge(formData, { connection: connectionFixed })
|
||||
|
||||
const responseId = await createProvider(formatData)
|
||||
const responseId = await createProvider(formData)
|
||||
enqueueSuccess(`Provider created - ID: ${responseId}`)
|
||||
}
|
||||
|
||||
@ -67,51 +54,9 @@ function ProviderCreateForm () {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const preloadFetchData = async () => {
|
||||
const data = await fetchRequestAll([
|
||||
getProvider(id),
|
||||
getProviderConnection(id)
|
||||
])
|
||||
|
||||
if (data) {
|
||||
const [provider = {}, connection = []] = data
|
||||
|
||||
const {
|
||||
PLAIN: { provider: plainProvider } = {},
|
||||
// remove encrypted connection from body template
|
||||
PROVISION_BODY: { description, connection: _, ...currentBodyTemplate }
|
||||
} = provider?.TEMPLATE
|
||||
|
||||
const connectionEditable = getConnectionEditable(
|
||||
{ provider: plainProvider, connection },
|
||||
providerConfig
|
||||
)
|
||||
|
||||
setInitialValues({
|
||||
template: [currentBodyTemplate],
|
||||
connection: connectionEditable,
|
||||
configuration: { description }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
id && preloadFetchData()
|
||||
}, [])
|
||||
|
||||
if (error) {
|
||||
return <Redirect to={PATH.PROVIDERS.LIST} />
|
||||
}
|
||||
|
||||
return (id && !initialValues) || loading ? (
|
||||
<LinearProgress color='secondary' />
|
||||
) : (
|
||||
return (
|
||||
<Container style={{ display: 'flex', flexFlow: 'column' }} disableGutters>
|
||||
<CreateForm
|
||||
stepProps={{ isUpdate: id !== undefined }}
|
||||
onSubmit={onSubmit}
|
||||
initialValues={initialValues}
|
||||
/>
|
||||
<CreateForm providerId={id} onSubmit={onSubmit} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -30,8 +30,6 @@ export const getProviderConfig = createAsyncThunk(
|
||||
|
||||
return { providerConfig: config }
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
|
||||
error?.status === httpCodes.unauthorized.id &&
|
||||
dispatch(logout(T.SessionExpired))
|
||||
}
|
||||
|
@ -20,7 +20,6 @@
|
||||
* @property {string} provider - Provider type
|
||||
* @property {object} plain - Information in plain format
|
||||
* @property {string} plain.provider - Provider type
|
||||
* @property {string|string[]} plain.location_key - Location key/s
|
||||
* @property {object} connection - Connections
|
||||
* @property {Array} inputs - Inputs to provision form
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user