1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00

F #5422: Fix instantiate template form (#1470)

This commit is contained in:
Sergio Betanzos 2021-09-21 13:11:27 +02:00 committed by GitHub
parent 1d83bd2de6
commit f16f214e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 356 additions and 326 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 })
}
]

View File

@ -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?.()
}

View File

@ -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?.()
}

View File

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

View File

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

View File

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

View File

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