mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
4463493b12
commit
ab1893257b
@ -69,7 +69,6 @@ const ButtonToTriggerForm = ({
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
aria-haspopup={isGroupButton ? 'true' : false}
|
||||
disabled={!options.length}
|
||||
|
||||
endicon={isGroupButton ? <NavArrowDown /> : undefined}
|
||||
onClick={evt => !isGroupButton
|
||||
? openDialogForm(options[0])
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { JSXElementConstructor } from 'react'
|
||||
import { useMemo, JSXElementConstructor } from 'react'
|
||||
import { Stack, FormControl, Button } from '@mui/material'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
@ -23,21 +23,20 @@ import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateFo
|
||||
import { SSH_PUBLIC_KEY, SCRIPT_FIELDS, OTHER_FIELDS } from './schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const SECTION_ID = 'CONTEXT'
|
||||
|
||||
const SSH_KEY_USER = '$USER[SSH_PUBLIC_KEY]'
|
||||
|
||||
/** @returns {JSXElementConstructor} - Configuration section */
|
||||
const ConfigurationSection = () => {
|
||||
const { setValue, getValues } = useFormContext()
|
||||
const SSH_PUBLIC_KEY_PATH = `${EXTRA_ID}.${SSH_PUBLIC_KEY.name}`
|
||||
const SSH_PUBLIC_KEY_PATH = useMemo(() => `${EXTRA_ID}.${SSH_PUBLIC_KEY.name}`, [])
|
||||
|
||||
const handleClearKey = () => setValue(SSH_PUBLIC_KEY_PATH)
|
||||
|
||||
const handleAddUserKey = () => {
|
||||
const currentSshPublicKey = getValues(SSH_PUBLIC_KEY_PATH) ?? ''
|
||||
let currentKey = getValues(SSH_PUBLIC_KEY_PATH)
|
||||
currentKey &&= currentKey + '\n'
|
||||
|
||||
setValue(SSH_PUBLIC_KEY_PATH, currentSshPublicKey + `\n${SSH_KEY_USER}`)
|
||||
setValue(SSH_PUBLIC_KEY_PATH, `${currentKey ?? ''}${SSH_KEY_USER}`)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -0,0 +1,54 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { string, ObjectSchema } from 'yup'
|
||||
|
||||
import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants'
|
||||
import { Field, filterFieldsByHypervisor, getObjectSchemaFromFields } from 'client/utils'
|
||||
|
||||
const { vcenter } = HYPERVISORS
|
||||
|
||||
/** @type {Field} Files field */
|
||||
export const FILES_DS = {
|
||||
name: 'CONTEXT.FILES_DS',
|
||||
label: T.ContextFiles,
|
||||
tooltip: T.ContextFilesConcept,
|
||||
notOnHypervisors: [vcenter],
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired(),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
/** @type {Field} Init scripts field */
|
||||
export const INIT_SCRIPTS = {
|
||||
name: 'CONTEXT.INIT_SCRIPTS',
|
||||
label: T.InitScripts,
|
||||
tooltip: T.InitScriptsConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired(),
|
||||
grid: { md: 12 }
|
||||
}
|
||||
|
||||
/** @type {Field[]} List of Context Files fields */
|
||||
export const FILES_FIELDS = hypervisor =>
|
||||
filterFieldsByHypervisor([FILES_DS, INIT_SCRIPTS], hypervisor)
|
||||
|
||||
/** @type {ObjectSchema} Context Files schema */
|
||||
export const FILES_SCHEMA = hypervisor =>
|
||||
getObjectSchemaFromFields(FILES_FIELDS(hypervisor))
|
@ -0,0 +1,48 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { FormWithSchema } from 'client/components/Forms'
|
||||
|
||||
import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { FILES_FIELDS } from './schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const SECTION_ID = 'CONTEXT'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.hypervisor - VM hypervisor
|
||||
* @returns {JSXElementConstructor} - Files section
|
||||
*/
|
||||
const FilesSection = ({ hypervisor }) => (
|
||||
<FormWithSchema
|
||||
cy={`create-vm-template-${EXTRA_ID}.context-files`}
|
||||
legend={T.Files}
|
||||
fields={() => FILES_FIELDS(hypervisor)}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
)
|
||||
|
||||
FilesSection.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object
|
||||
}
|
||||
|
||||
export default FilesSection
|
@ -17,17 +17,19 @@ import PropTypes from 'prop-types'
|
||||
import { Folder as ContextIcon } from 'iconoir-react'
|
||||
|
||||
import { TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import ConfigurationSection, { SECTION_ID as CONFIGURATION_ID } from './configurationSection'
|
||||
import UserInputsSection, { SECTION_ID as USER_INPUTS_ID } from './userInputsSection'
|
||||
import ConfigurationSection from './configurationSection'
|
||||
import FilesSection from './filesSection'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = [CONFIGURATION_ID, USER_INPUTS_ID]
|
||||
export const TAB_ID = ['CONTEXT', USER_INPUTS_ID]
|
||||
|
||||
const Context = () => {
|
||||
const Context = props => {
|
||||
return (
|
||||
<>
|
||||
<ConfigurationSection />
|
||||
<FilesSection {...props} />
|
||||
<UserInputsSection />
|
||||
</>
|
||||
)
|
||||
|
@ -13,14 +13,21 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { object } from 'yup'
|
||||
import { object, ObjectSchema } from 'yup'
|
||||
|
||||
import { USER_INPUTS_SCHEMA } from './userInputsSchema'
|
||||
import { CONFIGURATION_SCHEMA } from './configurationSchema'
|
||||
import { FILES_SCHEMA } from './filesSchema'
|
||||
|
||||
export const SCHEMA = object()
|
||||
/**
|
||||
* @param {string} [hypervisor] - VM hypervisor
|
||||
* @returns {ObjectSchema} Context schema
|
||||
*/
|
||||
export const SCHEMA = hypervisor => object()
|
||||
.concat(CONFIGURATION_SCHEMA)
|
||||
.concat(USER_INPUTS_SCHEMA)
|
||||
.concat(FILES_SCHEMA(hypervisor))
|
||||
|
||||
export * from './userInputsSchema'
|
||||
export * from './configurationSchema'
|
||||
export * from './filesSchema'
|
||||
|
@ -183,5 +183,6 @@ export const USER_INPUT_SCHEMA = getObjectSchemaFromFields(USER_INPUT_FIELDS)
|
||||
|
||||
/** @type {ObjectSchema} User Inputs schema */
|
||||
export const USER_INPUTS_SCHEMA = object({
|
||||
USER_INPUTS: array(USER_INPUT_SCHEMA).ensure()
|
||||
USER_INPUTS: array(USER_INPUT_SCHEMA).ensure(),
|
||||
INPUTS_ORDER: string().trim().strip()
|
||||
})
|
||||
|
@ -0,0 +1,108 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { memo, JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Edit, Trash } from 'iconoir-react'
|
||||
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { AttachNicForm } from 'client/components/Forms/Vm'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {number} props.index - Index in list
|
||||
* @param {object} props.item - NIC
|
||||
* @param {string} props.handleRemove - Remove function
|
||||
* @param {string} props.handleUpdate - Update function
|
||||
* @returns {JSXElementConstructor} - NIC card
|
||||
*/
|
||||
const NicItem = memo(({
|
||||
item,
|
||||
nics,
|
||||
handleRemove,
|
||||
handleUpdate
|
||||
}) => {
|
||||
const { id, NAME, RDP, SSH, NETWORK, PARENT, EXTERNAL } = item
|
||||
const hasAlias = nics?.some(nic => nic.PARENT === NAME)
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
key={id ?? NAME}
|
||||
title={[NAME, NETWORK].filter(Boolean).join(' - ')}
|
||||
subheader={<>
|
||||
{Object
|
||||
.entries({
|
||||
RDP: stringToBoolean(RDP),
|
||||
SSH: stringToBoolean(SSH),
|
||||
EXTERNAL: stringToBoolean(EXTERNAL),
|
||||
[`PARENT: ${PARENT}`]: PARENT
|
||||
})
|
||||
.map(([k, v]) => v ? `${k}` : '')
|
||||
.filter(Boolean)
|
||||
.join(' | ')
|
||||
}
|
||||
</>}
|
||||
action={
|
||||
<>
|
||||
{!hasAlias &&
|
||||
<Action
|
||||
data-cy={`remove-${NAME}`}
|
||||
tooltip={<Translate word={T.Remove} />}
|
||||
handleClick={handleRemove}
|
||||
color='error'
|
||||
icon={<Trash />}
|
||||
/>
|
||||
}
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `edit-${NAME}`,
|
||||
icon: <Edit />,
|
||||
tooltip: <Translate word={T.Edit} />
|
||||
}}
|
||||
options={[{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.EditSomething}
|
||||
values={[`${NAME} - ${NETWORK}`]}
|
||||
/>
|
||||
)
|
||||
},
|
||||
form: () => AttachNicForm({ nics }, item),
|
||||
onSubmit: handleUpdate
|
||||
}]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) => prev.item?.NAME === next.item?.NAME)
|
||||
|
||||
NicItem.propTypes = {
|
||||
index: PropTypes.number,
|
||||
item: PropTypes.object,
|
||||
nics: PropTypes.array,
|
||||
handleRemove: PropTypes.func,
|
||||
handleUpdate: PropTypes.func
|
||||
}
|
||||
|
||||
NicItem.displayName = 'NicItem'
|
||||
|
||||
export default NicItem
|
@ -15,19 +15,19 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
import { ServerConnection as NetworkIcon, Edit, Trash } from 'iconoir-react'
|
||||
import { ServerConnection as NetworkIcon } from 'iconoir-react'
|
||||
import { useFormContext, useFieldArray } from 'react-hook-form'
|
||||
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { AttachNicForm } from 'client/components/Forms/Vm'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { FormWithSchema } from 'client/components/Forms'
|
||||
|
||||
import { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema'
|
||||
import { BOOT_ORDER_NAME, reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting'
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
import { FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema'
|
||||
import { T } from 'client/constants'
|
||||
import NicItem from './NicItem'
|
||||
|
||||
export const TAB_ID = 'NIC'
|
||||
|
||||
@ -48,6 +48,10 @@ const Networking = () => {
|
||||
setValue(BOOT_ORDER_NAME(), updatedBootOrder)
|
||||
}
|
||||
|
||||
const handleUpdate = (updatedNic, index) => {
|
||||
update(index, mapNameFunction(updatedNic, index))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonToTriggerForm
|
||||
@ -63,69 +67,31 @@ const Networking = () => {
|
||||
onSubmit: nic => append(mapNameFunction(nic, nics.length))
|
||||
}]}
|
||||
/>
|
||||
<Stack
|
||||
pb='1em'
|
||||
display='grid'
|
||||
gridTemplateColumns='repeat(auto-fit, minmax(300px, 0.5fr))'
|
||||
gap='1em'
|
||||
mt='1em'
|
||||
<Stack pb='1em' display='grid' gap='1em' mt='1em'
|
||||
sx={{
|
||||
gridTemplateColumns: {
|
||||
sm: '1fr',
|
||||
md: 'repeat(auto-fit, minmax(300px, 0.5fr))'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{nics?.map((item, index) => {
|
||||
const { id, NAME, RDP, SSH, NETWORK, PARENT, EXTERNAL } = item
|
||||
const hasAlias = nics?.some(nic => nic.PARENT === NAME)
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
key={id ?? NAME}
|
||||
title={[NAME, NETWORK].filter(Boolean).join(' - ')}
|
||||
subheader={<>
|
||||
{Object
|
||||
.entries({
|
||||
RDP: stringToBoolean(RDP),
|
||||
SSH: stringToBoolean(SSH),
|
||||
EXTERNAL: stringToBoolean(EXTERNAL),
|
||||
[`PARENT: ${PARENT}`]: PARENT
|
||||
})
|
||||
.map(([k, v]) => v ? `${k}` : '')
|
||||
.filter(Boolean)
|
||||
.join(' | ')
|
||||
}
|
||||
</>}
|
||||
action={
|
||||
<>
|
||||
{!hasAlias &&
|
||||
<Action
|
||||
data-cy={`remove-${NAME}`}
|
||||
handleClick={() => removeAndReorder(NAME)}
|
||||
icon={<Trash />}
|
||||
/>
|
||||
}
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `edit-${NAME}`,
|
||||
icon: <Edit />,
|
||||
tooltip: <Translate word={T.Edit} />
|
||||
}}
|
||||
options={[{
|
||||
dialogProps: {
|
||||
title: (
|
||||
<Translate
|
||||
word={T.EditSomething}
|
||||
values={[`${NAME} - ${NETWORK}`]}
|
||||
/>
|
||||
)
|
||||
},
|
||||
form: () => AttachNicForm({ nics }, item),
|
||||
onSubmit: updatedNic =>
|
||||
update(index, mapNameFunction(updatedNic, index))
|
||||
}]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{nics?.map(({ id, ...item }, index) => (
|
||||
<NicItem
|
||||
key= {id ?? item?.NAME}
|
||||
item={item}
|
||||
nics={nics}
|
||||
handleRemove={() => removeAndReorder(item?.NAME)}
|
||||
handleUpdate={updatedNic => handleUpdate(updatedNic, index)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<FormWithSchema
|
||||
cy={`create-vm-template-${EXTRA_ID}.network-options`}
|
||||
fields={FIELDS}
|
||||
legend={T.NetworkDefaults}
|
||||
legendTooltip={T.NetworkDefaultsConcept}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { object, string, array, ObjectSchema } from 'yup'
|
||||
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { Field, getObjectSchemaFromFields } from 'client/utils'
|
||||
import { mapNameByIndex } from '../schema'
|
||||
|
||||
/** @returns {Field} NIC filter field */
|
||||
const FILTER = {
|
||||
name: 'NIC_DEFAULT.FILTER',
|
||||
label: T.DefaultNicFilter,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.default(() => undefined)
|
||||
}
|
||||
|
||||
/** @returns {Field} NIC model field */
|
||||
const MODEL = {
|
||||
name: 'NIC_DEFAULT.MODEL',
|
||||
label: T.DefaultNicModel,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.default(() => undefined)
|
||||
}
|
||||
|
||||
/** @type {Field[]} List of Network defaults fields */
|
||||
const FIELDS = [FILTER, MODEL]
|
||||
|
||||
/** @type {ObjectSchema} Network schema */
|
||||
const SCHEMA = object({
|
||||
NIC: array()
|
||||
.ensure()
|
||||
.transform(nics => nics.map(mapNameByIndex('NIC')))
|
||||
}).concat(getObjectSchemaFromFields(FIELDS))
|
||||
|
||||
export { FIELDS, SCHEMA }
|
@ -21,6 +21,8 @@ import { FIELDS as OS_FIELDS } from 'client/components/Forms/VmTemplate/CreateFo
|
||||
import { FIELDS as NUMA_FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema'
|
||||
import { SCHEMA as IO_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema'
|
||||
import { SCHEMA as CONTEXT_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema'
|
||||
import { SCHEMA as STORAGE_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema'
|
||||
import { SCHEMA as NETWORK_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema'
|
||||
import { getObjectSchemaFromFields } from 'client/utils'
|
||||
|
||||
export const mapNameByIndex = (prefixName) => (resource, idx) => ({
|
||||
@ -30,28 +32,19 @@ export const mapNameByIndex = (prefixName) => (resource, idx) => ({
|
||||
: resource?.NAME
|
||||
})
|
||||
|
||||
export const DISK_SCHEMA = array()
|
||||
.ensure()
|
||||
.transform(disks => disks.map(mapNameByIndex('DISK')))
|
||||
|
||||
export const NIC_SCHEMA = array()
|
||||
.ensure()
|
||||
.transform(nics => nics.map(mapNameByIndex('NIC')))
|
||||
|
||||
export const SCHED_ACTION_SCHEMA = array()
|
||||
.ensure()
|
||||
.transform(actions => actions.map(mapNameByIndex('SCHED_ACTION')))
|
||||
|
||||
export const SCHEMA = hypervisor => object({
|
||||
DISK: DISK_SCHEMA,
|
||||
NIC: NIC_SCHEMA,
|
||||
SCHED_ACTION: SCHED_ACTION_SCHEMA
|
||||
})
|
||||
.concat(CONTEXT_SCHEMA)
|
||||
.concat(NETWORK_SCHEMA)
|
||||
.concat(STORAGE_SCHEMA)
|
||||
.concat(CONTEXT_SCHEMA(hypervisor))
|
||||
.concat(IO_SCHEMA(hypervisor))
|
||||
.concat(getObjectSchemaFromFields([
|
||||
...PLACEMENT_FIELDS,
|
||||
...OS_FIELDS(hypervisor),
|
||||
...NUMA_FIELDS(hypervisor)
|
||||
]))
|
||||
.noUnknown(false)
|
||||
|
@ -1,191 +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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
import { Db as DatastoreIcon, Edit, Trash } from 'iconoir-react'
|
||||
import { useFormContext, useFieldArray } from 'react-hook-form'
|
||||
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { ImageSteps, VolatileSteps } from 'client/components/Forms/Vm'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema'
|
||||
import { BOOT_ORDER_NAME, reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting'
|
||||
import { getState, getDiskType } from 'client/models/Image'
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = 'DISK'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('DISK')
|
||||
|
||||
const Storage = ({ hypervisor }) => {
|
||||
const { getValues, setValue } = useFormContext()
|
||||
const { fields: disks, replace, update, append } = useFieldArray({
|
||||
name: `${EXTRA_ID}.${TAB_ID}`
|
||||
})
|
||||
|
||||
const removeAndReorder = diskName => {
|
||||
const updatedDisks = disks.filter(({ NAME }) => NAME !== diskName).map(mapNameFunction)
|
||||
const currentBootOrder = getValues(BOOT_ORDER_NAME())
|
||||
const updatedBootOrder = reorderBootAfterRemove(diskName, disks, currentBootOrder)
|
||||
|
||||
replace(updatedDisks)
|
||||
setValue(BOOT_ORDER_NAME(), updatedBootOrder)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-disk',
|
||||
label: T.AttachDisk,
|
||||
variant: 'outlined'
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'attach-image-disk',
|
||||
name: T.Image,
|
||||
dialogProps: { title: T.AttachImage },
|
||||
form: () => ImageSteps({ hypervisor }),
|
||||
onSubmit: image => append(mapNameFunction(image, disks.length))
|
||||
},
|
||||
{
|
||||
cy: 'attach-volatile-disk',
|
||||
name: T.Volatile,
|
||||
dialogProps: { title: T.AttachVolatile },
|
||||
form: () => VolatileSteps({ hypervisor }),
|
||||
onSubmit: image => append(mapNameFunction(image, disks.length))
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<Stack
|
||||
pb='1em'
|
||||
display='grid'
|
||||
gridTemplateColumns='repeat(auto-fit, minmax(300px, 0.5fr))'
|
||||
gap='1em'
|
||||
mt='1em'
|
||||
>
|
||||
{disks?.map((item, index) => {
|
||||
const {
|
||||
id,
|
||||
NAME,
|
||||
TYPE,
|
||||
IMAGE,
|
||||
IMAGE_ID,
|
||||
IMAGE_STATE,
|
||||
ORIGINAL_SIZE,
|
||||
SIZE = ORIGINAL_SIZE,
|
||||
READONLY,
|
||||
DATASTORE,
|
||||
PERSISTENT
|
||||
} = item
|
||||
|
||||
const isVolatile = !IMAGE && !IMAGE_ID
|
||||
const isPersistent = stringToBoolean(PERSISTENT)
|
||||
const state = !isVolatile && getState({ STATE: IMAGE_STATE })
|
||||
const type = isVolatile ? TYPE : getDiskType(item)
|
||||
const originalSize = +ORIGINAL_SIZE ? prettyBytes(+ORIGINAL_SIZE, 'MB') : '-'
|
||||
const size = prettyBytes(+SIZE, 'MB')
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
key={id ?? NAME}
|
||||
title={isVolatile ? (
|
||||
<>
|
||||
{`${NAME} - `}
|
||||
<Translate word={T.VolatileDisk} />
|
||||
</>
|
||||
) : (
|
||||
<Stack component='span' alignItems='center' gap='0.5em'>
|
||||
<StatusCircle color={state?.color} tooltip={state?.name} />
|
||||
{`${NAME}: ${IMAGE}`}
|
||||
{isPersistent && <StatusChip text='PERSISTENT' />}
|
||||
</Stack>
|
||||
)}
|
||||
subheader={<>
|
||||
{Object
|
||||
.entries({
|
||||
[DATASTORE]: DATASTORE,
|
||||
READONLY: stringToBoolean(READONLY),
|
||||
PERSISTENT: stringToBoolean(PERSISTENT),
|
||||
[isVolatile || ORIGINAL_SIZE === SIZE
|
||||
? size : `${originalSize}/${size}`]: true,
|
||||
[type]: type
|
||||
})
|
||||
.map(([k, v]) => v ? `${k}` : '')
|
||||
.filter(Boolean)
|
||||
.join(' | ')
|
||||
}
|
||||
</>}
|
||||
action={
|
||||
<>
|
||||
<Action
|
||||
data-cy={`remove-${NAME}`}
|
||||
tooltip={<Translate word={T.Remove} />}
|
||||
handleClick={() => removeAndReorder(NAME, index)}
|
||||
icon={<Trash />}
|
||||
/>
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `edit-${NAME}`,
|
||||
icon: <Edit />,
|
||||
tooltip: <Translate word={T.Edit} />
|
||||
}}
|
||||
options={[{
|
||||
dialogProps: {
|
||||
title: <Translate word={T.EditSomething} values={[NAME]} />
|
||||
},
|
||||
form: () => isVolatile
|
||||
? VolatileSteps({ hypervisor }, item)
|
||||
: ImageSteps({ hypervisor }, item),
|
||||
onSubmit: updatedDisk =>
|
||||
update(index, mapNameFunction(updatedDisk, index))
|
||||
}]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Storage.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'storage',
|
||||
name: T.Storage,
|
||||
icon: DatastoreIcon,
|
||||
Content: Storage,
|
||||
getError: error => !!error?.[TAB_ID]
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,139 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { memo, JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
import { Edit, Trash } from 'iconoir-react'
|
||||
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { ImageSteps, VolatileSteps } from 'client/components/Forms/Vm'
|
||||
import { StatusCircle, StatusChip } from 'client/components/Status'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import { getState, getDiskType } from 'client/models/Image'
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* The disk item will be included in the VM Template.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {number} props.index - Index in list
|
||||
* @param {object} props.item - Disk
|
||||
* @param {string} props.hypervisor - VM hypervisor
|
||||
* @param {string} props.handleRemove - Remove function
|
||||
* @param {string} props.handleUpdate - Update function
|
||||
* @returns {JSXElementConstructor} - Disk card
|
||||
*/
|
||||
const DiskItem = memo(({
|
||||
item,
|
||||
hypervisor,
|
||||
handleRemove,
|
||||
handleUpdate
|
||||
}) => {
|
||||
const {
|
||||
NAME,
|
||||
TYPE,
|
||||
IMAGE,
|
||||
IMAGE_ID,
|
||||
IMAGE_STATE,
|
||||
ORIGINAL_SIZE,
|
||||
SIZE = ORIGINAL_SIZE,
|
||||
READONLY,
|
||||
DATASTORE,
|
||||
PERSISTENT
|
||||
} = item
|
||||
|
||||
const isVolatile = !IMAGE && !IMAGE_ID
|
||||
const isPersistent = stringToBoolean(PERSISTENT)
|
||||
const state = !isVolatile && getState({ STATE: IMAGE_STATE })
|
||||
const type = isVolatile ? TYPE : getDiskType(item)
|
||||
const originalSize = +ORIGINAL_SIZE ? prettyBytes(+ORIGINAL_SIZE, 'MB') : '-'
|
||||
const size = prettyBytes(+SIZE, 'MB')
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
title={isVolatile ? (
|
||||
<>
|
||||
{`${NAME} - `}
|
||||
<Translate word={T.VolatileDisk} />
|
||||
</>
|
||||
) : (
|
||||
<Stack component='span' alignItems='center' gap='0.5em'>
|
||||
<StatusCircle color={state?.color} tooltip={state?.name} />
|
||||
{`${NAME}: ${IMAGE}`}
|
||||
{isPersistent && <StatusChip text='PERSISTENT' />}
|
||||
</Stack>
|
||||
)}
|
||||
subheader={<>
|
||||
{Object
|
||||
.entries({
|
||||
[DATASTORE]: DATASTORE,
|
||||
READONLY: stringToBoolean(READONLY),
|
||||
PERSISTENT: stringToBoolean(PERSISTENT),
|
||||
[isVolatile || ORIGINAL_SIZE === SIZE
|
||||
? size : `${originalSize}/${size}`]: true,
|
||||
[type]: type
|
||||
})
|
||||
.map(([k, v]) => v ? `${k}` : '')
|
||||
.filter(Boolean)
|
||||
.join(' | ')
|
||||
}
|
||||
</>}
|
||||
action={
|
||||
<>
|
||||
<Action
|
||||
data-cy={`remove-${NAME}`}
|
||||
tooltip={<Translate word={T.Remove} />}
|
||||
handleClick={handleRemove}
|
||||
color='error'
|
||||
icon={<Trash />}
|
||||
/>
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
'data-cy': `edit-${NAME}`,
|
||||
icon: <Edit />,
|
||||
tooltip: <Translate word={T.Edit} />
|
||||
}}
|
||||
options={[{
|
||||
dialogProps: {
|
||||
title: <Translate word={T.EditSomething} values={[NAME]} />
|
||||
},
|
||||
form: () => isVolatile
|
||||
? VolatileSteps({ hypervisor }, item)
|
||||
: ImageSteps({ hypervisor }, item),
|
||||
onSubmit: handleUpdate
|
||||
}]}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}, (prev, next) => prev.item?.NAME === next.item?.NAME)
|
||||
|
||||
DiskItem.propTypes = {
|
||||
index: PropTypes.number,
|
||||
item: PropTypes.object,
|
||||
hypervisor: PropTypes.string,
|
||||
handleRemove: PropTypes.func,
|
||||
handleUpdate: PropTypes.func
|
||||
}
|
||||
|
||||
DiskItem.displayName = 'DiskItem'
|
||||
|
||||
export default DiskItem
|
@ -0,0 +1,124 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
import { Db as DatastoreIcon } from 'iconoir-react'
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form'
|
||||
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import { ImageSteps, VolatileSteps } from 'client/components/Forms/Vm'
|
||||
import { FormWithSchema } from 'client/components/Forms'
|
||||
|
||||
import { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema'
|
||||
import { BOOT_ORDER_NAME, reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting'
|
||||
import { FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema'
|
||||
import { T } from 'client/constants'
|
||||
import DiskItem from './DiskItem'
|
||||
|
||||
export const TAB_ID = 'DISK'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('DISK')
|
||||
|
||||
const Storage = ({ hypervisor }) => {
|
||||
const { getValues, setValue } = useFormContext()
|
||||
const { fields: disks, append, update, replace } = useFieldArray({
|
||||
name: `${EXTRA_ID}.${TAB_ID}`
|
||||
})
|
||||
|
||||
const removeAndReorder = diskName => {
|
||||
const updatedDisks = disks.filter(({ NAME }) => NAME !== diskName).map(mapNameFunction)
|
||||
const currentBootOrder = getValues(BOOT_ORDER_NAME())
|
||||
const updatedBootOrder = reorderBootAfterRemove(diskName, disks, currentBootOrder)
|
||||
|
||||
replace(updatedDisks)
|
||||
setValue(BOOT_ORDER_NAME(), updatedBootOrder)
|
||||
}
|
||||
|
||||
const handleUpdate = (updatedDisk, index) => {
|
||||
update(index, mapNameFunction(updatedDisk, index))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'add-disk',
|
||||
label: T.AttachDisk,
|
||||
variant: 'outlined'
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
cy: 'attach-image-disk',
|
||||
name: T.Image,
|
||||
dialogProps: { title: T.AttachImage },
|
||||
form: () => ImageSteps({ hypervisor }),
|
||||
onSubmit: image => append(mapNameFunction(image, disks.length))
|
||||
},
|
||||
{
|
||||
cy: 'attach-volatile-disk',
|
||||
name: T.Volatile,
|
||||
dialogProps: { title: T.AttachVolatile },
|
||||
form: () => VolatileSteps({ hypervisor }),
|
||||
onSubmit: image => append(mapNameFunction(image, disks.length))
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<Stack pb='1em' display='grid' gap='1em' mt='1em'
|
||||
sx={{
|
||||
gridTemplateColumns: {
|
||||
sm: '1fr',
|
||||
md: 'repeat(auto-fit, minmax(300px, 0.5fr))'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{disks?.map(({ id, ...item }, index) => (
|
||||
<DiskItem
|
||||
key={id ?? item?.NAME}
|
||||
item={item}
|
||||
handleRemove={() => removeAndReorder(item?.NAME)}
|
||||
handleUpdate={updatedDisk => handleUpdate(updatedDisk, index)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
<FormWithSchema
|
||||
cy={`create-vm-template-${EXTRA_ID}.storage-options`}
|
||||
fields={FIELDS}
|
||||
legend={T.StorageOptions}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Storage.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'storage',
|
||||
name: T.Storage,
|
||||
icon: DatastoreIcon,
|
||||
Content: Storage,
|
||||
getError: error => !!error?.[TAB_ID]
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,53 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { object, string, array, ObjectSchema } from 'yup'
|
||||
|
||||
import { useDatastore } from 'client/features/One'
|
||||
import { getDeployMode } from 'client/models/Datastore'
|
||||
import { T, INPUT_TYPES } from 'client/constants'
|
||||
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
||||
import { mapNameByIndex } from '../schema'
|
||||
|
||||
/** @returns {Field} Deploy mode field */
|
||||
const TM_MAD_SYSTEM = {
|
||||
name: 'TM_MAD_SYSTEM',
|
||||
label: T.DeployMode,
|
||||
tooltip: T.DeployModeConcept,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const datastores = useDatastore()
|
||||
const modes = datastores?.map(getDeployMode)?.flat()
|
||||
|
||||
return arrayToOptions([...new Set(modes)], { addEmpty: 'Default' })
|
||||
},
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.default(() => undefined)
|
||||
}
|
||||
|
||||
/** @type {Field[]} List of Storage fields */
|
||||
const FIELDS = [TM_MAD_SYSTEM]
|
||||
|
||||
/** @type {ObjectSchema} Storage schema */
|
||||
const SCHEMA = object({
|
||||
...getValidationFromFields(FIELDS),
|
||||
DISK: array()
|
||||
.ensure()
|
||||
.transform(disks => disks.map(mapNameByIndex('DISK')))
|
||||
})
|
||||
|
||||
export { FIELDS, SCHEMA }
|
@ -40,10 +40,16 @@ export const MEMORY = {
|
||||
|
||||
/** @type {Field} Hot reloading on memory field */
|
||||
export const ENABLE_HR_MEMORY = {
|
||||
name: 'ENABLE_HR_MEMORY',
|
||||
name: 'HOT_RESIZE.MEMORY_HOT_ADD_ENABLED',
|
||||
label: T.EnableHotResize,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().default(() => false),
|
||||
validation: boolean()
|
||||
.transform(value => {
|
||||
if (typeof value === 'boolean') return value
|
||||
|
||||
return String(value).toUpperCase() === 'YES'
|
||||
})
|
||||
.default(() => false),
|
||||
grid: { xs: 4, md: 6 }
|
||||
}
|
||||
|
||||
@ -85,10 +91,16 @@ export const VIRTUAL_CPU = {
|
||||
|
||||
/** @type {Field} Hot reloading on virtual CPU field */
|
||||
export const ENABLE_HR_VCPU = {
|
||||
name: 'ENABLE_HR_VCPU',
|
||||
name: 'HOT_RESIZE.CPU_HOT_ADD_ENABLED',
|
||||
label: T.EnableHotResize,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
validation: boolean().default(() => false),
|
||||
validation: boolean()
|
||||
.transform(value => {
|
||||
if (typeof value === 'boolean') return value
|
||||
|
||||
return String(value).toUpperCase() === 'YES'
|
||||
})
|
||||
.default(() => false),
|
||||
grid: { xs: 4, md: 6 }
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ const Steps = createSteps(
|
||||
...vmTemplate?.TEMPLATE,
|
||||
USER_INPUTS: userInputsToArray(vmTemplate?.TEMPLATE?.USER_INPUTS)
|
||||
}
|
||||
}, { context: { [EXTRA_ID]: vmTemplate.TEMPLATE } })
|
||||
}, { stripUnknown: true, context: { [EXTRA_ID]: vmTemplate.TEMPLATE } })
|
||||
}),
|
||||
transformBeforeSubmit: formData => {
|
||||
const {
|
||||
@ -42,22 +42,28 @@ const Steps = createSteps(
|
||||
// const templateXML = jsonToXml({ ...general, ...extraTemplate })
|
||||
// return { template: templateXML }
|
||||
|
||||
const userInputs = userInputsToObject(USER_INPUTS)
|
||||
const inputsOrder = USER_INPUTS.map(({ name }) => name).join(',')
|
||||
const { START_SCRIPT, ENCODE_START_SCRIPT, ...restOfContext } = CONTEXT
|
||||
|
||||
const context = {
|
||||
...restOfContext,
|
||||
// transform start script to base64 if needed
|
||||
[ENCODE_START_SCRIPT ? 'START_SCRIPT_BASE64' : 'START_SCRIPT']:
|
||||
ENCODE_START_SCRIPT && !isBase64(START_SCRIPT)
|
||||
? btoa(unescape(encodeURIComponent(START_SCRIPT)))
|
||||
: START_SCRIPT
|
||||
}
|
||||
|
||||
const userInputs = userInputsToObject(USER_INPUTS)
|
||||
const inputsOrder = USER_INPUTS.map(({ name }) => name).join(',')
|
||||
// add user inputs to context
|
||||
for (const { name } of USER_INPUTS) {
|
||||
const upperName = String(name).toUpperCase()
|
||||
context[upperName] = `$${upperName}`
|
||||
}
|
||||
|
||||
return {
|
||||
...general,
|
||||
...extraTemplate,
|
||||
...general,
|
||||
CONTEXT: context,
|
||||
USER_INPUTS: userInputs,
|
||||
INPUTS_ORDER: inputsOrder
|
||||
|
@ -22,7 +22,7 @@ import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useFetch } from 'client/hooks'
|
||||
// import { useUserApi, useVmGroupApi, useVmTemplateApi } from 'client/features/One'
|
||||
import { useVmTemplateApi, useHostApi, useImageApi } from 'client/features/One'
|
||||
import { useVmTemplateApi, useHostApi, useImageApi, useDatastoreApi } from 'client/features/One'
|
||||
import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper'
|
||||
import Steps from 'client/components/Forms/VmTemplate/CreateForm/Steps'
|
||||
|
||||
@ -52,6 +52,7 @@ const PreFetchingForm = ({ templateId, onSubmit }) => {
|
||||
// const { getVmGroups } = useVmGroupApi()
|
||||
const { getHosts } = useHostApi()
|
||||
const { getImages } = useImageApi()
|
||||
const { getDatastores } = useDatastoreApi()
|
||||
const { getVmTemplate } = useVmTemplateApi()
|
||||
const { fetchRequest, data } = useFetch(
|
||||
() => getVmTemplate(templateId, { extended: true })
|
||||
@ -61,6 +62,7 @@ const PreFetchingForm = ({ templateId, onSubmit }) => {
|
||||
templateId && fetchRequest()
|
||||
getHosts()
|
||||
getImages()
|
||||
getDatastores()
|
||||
// getUsers()
|
||||
// getVmGroups()
|
||||
}, [])
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useState, memo, JSXElementConstructor } from 'react'
|
||||
import { useState, memo, useEffect, JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { DEFAULT_IMAGE, IMAGE_FORMATS } from 'client/constants'
|
||||
@ -46,6 +46,10 @@ const Image = memo(
|
||||
...imgProps
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
error && setError(INITIAL_STATE)
|
||||
}, [src])
|
||||
|
||||
/** Increment retries by one in error state. */
|
||||
const addRetry = () => {
|
||||
setError(prev => ({ ...prev, retries: prev.retries + 1 }))
|
||||
|
@ -37,7 +37,7 @@ export default [
|
||||
{
|
||||
Header: 'Type',
|
||||
id: 'TYPE',
|
||||
accessor: row => DatastoreModel.getType(row)
|
||||
accessor: row => DatastoreModel.getType(row)?.name
|
||||
},
|
||||
{
|
||||
Header: 'Clusters IDs',
|
||||
|
@ -16,10 +16,7 @@
|
||||
import * as STATES from 'client/constants/states'
|
||||
import COLOR from 'client/constants/color'
|
||||
|
||||
/**
|
||||
* @type {{name: string, shortName: string}}
|
||||
* Datastore type information
|
||||
*/
|
||||
/** @type {{name: string, shortName: string}} Datastore type information */
|
||||
export const DATASTORE_TYPES = [
|
||||
{
|
||||
name: 'IMAGE',
|
||||
|
@ -354,6 +354,17 @@ module.exports = {
|
||||
/* VM schema - ownership */
|
||||
InstantiateAsUser: 'Instantiate as different User',
|
||||
InstantiateAsGroup: 'Instantiate as different Group',
|
||||
/* VM Template schema - storage */
|
||||
StorageOptions: 'Storage Options',
|
||||
DeployMode: 'Deploy Mode',
|
||||
DeployModeConcept: 'Set an alternative mode to deploy VM disks to the hosts',
|
||||
/* VM Template schema - network */
|
||||
NetworkDefaults: 'Network Defaults',
|
||||
NetworkDefaultsConcept: `
|
||||
Values that will be copied to each new NIC.
|
||||
Final users may not be aware of this`,
|
||||
DefaultNicModel: 'Default hardware model to emulate for all NICs',
|
||||
DefaultNicFilter: 'Default network filtering rule for all NICs',
|
||||
/* VM Template schema - capacity */
|
||||
MaxMemory: 'Max memory',
|
||||
MemoryModification: 'Memory modification',
|
||||
@ -485,9 +496,16 @@ module.exports = {
|
||||
ReportReadyToOneGateConcept: 'Sends READY=YES to OneGate, useful for OneFlow',
|
||||
StartScript: 'Start script',
|
||||
StartScriptConcept: `
|
||||
Text of the script executed when the machine starts up. It can contain
|
||||
shebang in case it is not shell script`,
|
||||
Text of the script executed when the machine starts up. It can contain
|
||||
shebang in case it is not shell script`,
|
||||
EncodeScriptInBase64: 'Encode script in Base64',
|
||||
ContextFiles: 'Files Datastores',
|
||||
ContextFilesConcept: 'List of File images to include in context device',
|
||||
InitScripts: 'Init scripts',
|
||||
InitScriptsConcept: `
|
||||
The contextualization package executes an init.sh file if it exists.
|
||||
If more than one script file is added, this list contains the scripts
|
||||
to run and their order`,
|
||||
/* VM Template schema - Input/Output */
|
||||
InputOrOutput: 'Input / Output',
|
||||
Inputs: 'Inputs',
|
||||
|
@ -34,6 +34,21 @@ export const getType = ({ TYPE } = {}) => DATASTORE_TYPES[TYPE]
|
||||
*/
|
||||
export const getState = ({ STATE = 0 } = {}) => DATASTORE_STATES[STATE]
|
||||
|
||||
/**
|
||||
* Return the TM_MAD_SYSTEM attribute.
|
||||
*
|
||||
* @param {object} datastore - Datastore
|
||||
* @returns {string[]} - The list of deploy modes available
|
||||
*/
|
||||
export const getDeployMode = (datastore = {}) => {
|
||||
const { TEMPLATE = {} } = datastore
|
||||
const isImage = getType(datastore)?.name === DATASTORE_TYPES[0]?.name
|
||||
|
||||
return isImage
|
||||
? TEMPLATE?.TM_MAD_SYSTEM?.split(',')?.filter(Boolean) ?? []
|
||||
: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns information about datastore capacity.
|
||||
*
|
||||
|
@ -328,7 +328,7 @@ export const mapUserInputs = (userInputs = {}) =>
|
||||
*
|
||||
* @param {any[]} array - List of option values
|
||||
* @param {object} [options] - Options to conversion
|
||||
* @param {boolean} [options.addEmpty] - If `true`, add an empty option
|
||||
* @param {boolean|string} [options.addEmpty] - If `true`, add an empty option
|
||||
* @param {function(any):any} [options.getText] - Function to get the text option
|
||||
* @param {function(any):any} [options.getValue] - Function to get the value option
|
||||
* @returns {SelectOption} Options
|
||||
@ -338,7 +338,11 @@ export const arrayToOptions = (array = [], options = {}) => {
|
||||
|
||||
const values = array.map(item => ({ text: getText(item), value: getValue(item) }))
|
||||
|
||||
addEmpty && values.unshift({ text: '-', value: '' })
|
||||
if (addEmpty) {
|
||||
typeof addEmpty === 'string'
|
||||
? values.unshift({ text: addEmpty, value: '' })
|
||||
: values.unshift({ text: '-', value: '' })
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user