1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-10 01:17:40 +03:00

M #-: VM template default values (#2982)

Signed-off-by: David Carracedo <dcarracedo@opennebula.io>
Co-authored-by: Tino Vázquez <cvazquez@opennebula.io>
This commit is contained in:
David 2024-03-14 16:27:11 +01:00 committed by GitHub
parent 8da274781b
commit e147751bed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 263 additions and 75 deletions

View File

@ -76,7 +76,7 @@ const SwitchController = memo(
useEffect(() => { useEffect(() => {
if (!watcher || !dependencies || !watch) return if (!watcher || !dependencies || !watch) return
const watcherValue = watcher(watch, name) const watcherValue = watcher(watch, { name })
watcherValue !== undefined && onChange(watcherValue) watcherValue !== undefined && onChange(watcherValue)
}, [watch, watcher, dependencies]) }, [watch, watcher, dependencies])

View File

@ -17,7 +17,7 @@ import { memo, useCallback, useEffect } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { TextField } from '@mui/material' import { TextField } from '@mui/material'
import { useController, useWatch } from 'react-hook-form' import { useController, useWatch, useFormContext } from 'react-hook-form'
import { ErrorHelper, Tooltip } from 'client/components/FormControl' import { ErrorHelper, Tooltip } from 'client/components/FormControl'
import { Tr, labelCanBeTranslated } from 'client/components/HOC' import { Tr, labelCanBeTranslated } from 'client/components/HOC'
@ -49,10 +49,13 @@ const TextController = memo(
fieldState: { error }, fieldState: { error },
} = useController({ name, control }) } = useController({ name, control })
const formContext = useFormContext()
useEffect(() => { useEffect(() => {
if (!watcher || !dependencies || !watch) return if (!watcher || !dependencies || !watch) return
const watcherValue = watcher(watch) const watcherValue = watcher(watch, { name, formContext })
watcherValue !== undefined && onChange(watcherValue) watcherValue !== undefined && onChange(watcherValue)
}, [watch, watcher, dependencies]) }, [watch, watcher, dependencies])

View File

@ -202,8 +202,21 @@ const FormWithSchema = ({
set(fieldsHiddenMerge, id ? `${id}.${element}` : `${element}`, true) set(fieldsHiddenMerge, id ? `${id}.${element}` : `${element}`, true)
}) })
const fieldsToMergeinSchema = {}
// Add only the fields of the FormWithSchema component that is being checking
fields.forEach(
(field) =>
get(fieldsToMerge, `${id}.${field.name}`) &&
set(
fieldsToMergeinSchema,
`${id}.${field.name}`,
get(fieldsToMerge, `${id}.${field.name}`)
)
)
// Set modified fields // Set modified fields
const mix = merge({}, fieldsToMerge, fieldsHiddenMerge) const mix = merge({}, fieldsToMergeinSchema, fieldsHiddenMerge)
setModifiedFields(mix) setModifiedFields(mix)
// If fieldPath exists, set in the store // If fieldPath exists, set in the store

View File

@ -31,7 +31,7 @@ const VIEW = (name, admin) => ({
label: name, label: name,
type: INPUT_TYPES.SWITCH, type: INPUT_TYPES.SWITCH,
dependOf: admin ? `GROUP_ADMIN_DEFAULT_VIEW` : `DEFAULT_VIEW`, dependOf: admin ? `GROUP_ADMIN_DEFAULT_VIEW` : `DEFAULT_VIEW`,
watcher: (value, nameField) => { watcher: (value, { name: nameField }) => {
// Check the switch if it is the default view // Check the switch if it is the default view
const view = const view =
nameField.split('.').length === 3 ? nameField.split('.')[2] : undefined nameField.split('.').length === 3 ? nameField.split('.')[2] : undefined

View File

@ -40,16 +40,16 @@ export const SSH_PUBLIC_KEY = (isUpdate) => ({
}) })
/** @type {Field} Network context field */ /** @type {Field} Network context field */
const NETWORK = { const NETWORK = (isUpdate) => ({
name: 'CONTEXT.NETWORK', name: 'CONTEXT.NETWORK',
label: T.AddNetworkContextualization, label: T.AddNetworkContextualization,
tooltip: T.AddNetworkContextualizationConcept, tooltip: T.AddNetworkContextualizationConcept,
type: INPUT_TYPES.SWITCH, type: INPUT_TYPES.SWITCH,
validation: boolean() validation: boolean()
.yesOrNo() .yesOrNo()
.default(() => true), .default(() => (isUpdate ? undefined : true)),
grid: { md: 12 }, grid: { md: 12 },
} })
/** @type {Field} Token OneGate token field */ /** @type {Field} Token OneGate token field */
const TOKEN = { const TOKEN = {
@ -112,7 +112,11 @@ export const START_SCRIPT_BASE64 = {
export const SCRIPT_FIELDS = [START_SCRIPT, ENCODE_START_SCRIPT] export const SCRIPT_FIELDS = [START_SCRIPT, ENCODE_START_SCRIPT]
/** @type {Field[]} List of other fields */ /** @type {Field[]} List of other fields */
export const OTHER_FIELDS = [NETWORK, TOKEN, REPORT_READY] export const OTHER_FIELDS = (isUpdate) => [
NETWORK(isUpdate),
TOKEN,
REPORT_READY,
]
/** @type {ObjectSchema} User context configuration schema */ /** @type {ObjectSchema} User context configuration schema */
export const CONFIGURATION_SCHEMA = (isUpdate) => export const CONFIGURATION_SCHEMA = (isUpdate) =>
@ -120,5 +124,5 @@ export const CONFIGURATION_SCHEMA = (isUpdate) =>
SSH_PUBLIC_KEY(isUpdate), SSH_PUBLIC_KEY(isUpdate),
START_SCRIPT_BASE64, START_SCRIPT_BASE64,
...SCRIPT_FIELDS, ...SCRIPT_FIELDS,
...OTHER_FIELDS, ...OTHER_FIELDS(isUpdate),
]) ])

View File

@ -93,7 +93,12 @@ const ConfigurationSection = ({ stepId, oneConfig, adminGroup, isUpdate }) => {
id={stepId} id={stepId}
saveState={true} saveState={true}
cy={getCyPath('context-configuration-others')} cy={getCyPath('context-configuration-others')}
fields={disableFields(OTHER_FIELDS, 'CONTEXT', oneConfig, adminGroup)} fields={disableFields(
OTHER_FIELDS(isUpdate),
'CONTEXT',
oneConfig,
adminGroup
)}
/> />
<section> <section>
<FormWithSchema <FormWithSchema

View File

@ -52,6 +52,7 @@ const FilesSection = ({ stepId, hypervisor, oneConfig, adminGroup }) => (
), ),
[hypervisor] [hypervisor]
)} )}
saveState={true}
/> />
) )

View File

@ -120,9 +120,10 @@ const ExtraConfiguration = ({
apiTemplateDataExtended: vmTemplate, apiTemplateDataExtended: vmTemplate,
oneConfig, oneConfig,
adminGroup, adminGroup,
store,
}) => { }) => {
const initialHypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR const initialHypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
const isUpdate = vmTemplate?.NAME const isUpdate = !!vmTemplate?.NAME
return { return {
id: STEP_ID, id: STEP_ID,
@ -130,7 +131,10 @@ const ExtraConfiguration = ({
resolver: (formData) => { resolver: (formData) => {
const hypervisor = formData?.[GENERAL_ID]?.HYPERVISOR ?? initialHypervisor const hypervisor = formData?.[GENERAL_ID]?.HYPERVISOR ?? initialHypervisor
return SCHEMA(hypervisor, isUpdate) const currentState = store.getState()
const modifiedFields = currentState.general?.modifiedFields
return SCHEMA(hypervisor, oneConfig, adminGroup, isUpdate, modifiedFields)
}, },
optionsValidate: { abortEarly: false }, optionsValidate: { abortEarly: false },
content: (props) => Content({ ...props, oneConfig, adminGroup, isUpdate }), content: (props) => Content({ ...props, oneConfig, adminGroup, isUpdate }),

View File

@ -66,7 +66,7 @@ const KEYMAP_VALUES = {
} }
/** @type {Field} Type field */ /** @type {Field} Type field */
export const TYPE = { export const TYPE = (isUpdate) => ({
name: 'GRAPHICS.TYPE', name: 'GRAPHICS.TYPE',
type: INPUT_TYPES.TOGGLE, type: INPUT_TYPES.TOGGLE,
dependOf: ['HYPERVISOR', '$general.HYPERVISOR'], dependOf: ['HYPERVISOR', '$general.HYPERVISOR'],
@ -82,24 +82,24 @@ export const TYPE = {
.trim() .trim()
.notRequired() .notRequired()
.uppercase() .uppercase()
.default(() => undefined), .default(() => (isUpdate ? undefined : T.Vnc)),
grid: { md: 12 }, grid: { md: 12 },
} })
/** @type {Field} Listen field */ /** @type {Field} Listen field */
export const LISTEN = { export const LISTEN = (isUpdate) => ({
name: 'GRAPHICS.LISTEN', name: 'GRAPHICS.LISTEN',
label: T.ListenOnIp, label: T.ListenOnIp,
type: INPUT_TYPES.TEXT, type: INPUT_TYPES.TEXT,
dependOf: TYPE.name, dependOf: TYPE().name,
htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN, htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN,
validation: string() validation: string()
.trim() .trim()
.notRequired() .notRequired()
.default(() => undefined), .default(() => (isUpdate ? undefined : '0.0.0.0')),
fieldProps: { placeholder: '0.0.0.0' }, fieldProps: { placeholder: '0.0.0.0' },
grid: { md: 12 }, grid: { md: 12 },
} })
/** @type {Field} Port field */ /** @type {Field} Port field */
export const PORT = { export const PORT = {
@ -107,7 +107,7 @@ export const PORT = {
label: T.ServerPort, label: T.ServerPort,
tooltip: T.ServerPortConcept, tooltip: T.ServerPortConcept,
type: INPUT_TYPES.TEXT, type: INPUT_TYPES.TEXT,
dependOf: TYPE.name, dependOf: TYPE().name,
htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN, htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN,
validation: string() validation: string()
.trim() .trim()
@ -120,7 +120,7 @@ export const KEYMAP = {
name: 'GRAPHICS.KEYMAP', name: 'GRAPHICS.KEYMAP',
label: T.Keymap, label: T.Keymap,
type: INPUT_TYPES.AUTOCOMPLETE, type: INPUT_TYPES.AUTOCOMPLETE,
dependOf: TYPE.name, dependOf: TYPE().name,
values: arrayToOptions(Object.entries(KEYMAP_VALUES), { values: arrayToOptions(Object.entries(KEYMAP_VALUES), {
addEmpty: false, addEmpty: false,
getText: ([_, label]) => label, getText: ([_, label]) => label,
@ -177,7 +177,7 @@ export const RANDOM_PASSWD = {
name: 'GRAPHICS.RANDOM_PASSWD', name: 'GRAPHICS.RANDOM_PASSWD',
label: T.GenerateRandomPassword, label: T.GenerateRandomPassword,
type: INPUT_TYPES.CHECKBOX, type: INPUT_TYPES.CHECKBOX,
dependOf: TYPE.name, dependOf: TYPE().name,
htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN, htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN,
validation: boolean().yesOrNo(), validation: boolean().yesOrNo(),
grid: { md: 12 }, grid: { md: 12 },
@ -188,7 +188,7 @@ export const PASSWD = {
name: 'GRAPHICS.PASSWD', name: 'GRAPHICS.PASSWD',
label: T.Password, label: T.Password,
type: INPUT_TYPES.PASSWORD, type: INPUT_TYPES.PASSWORD,
dependOf: [TYPE.name, RANDOM_PASSWD.name], dependOf: [TYPE().name, RANDOM_PASSWD.name],
htmlType: ([noneType, random] = []) => htmlType: ([noneType, random] = []) =>
(!noneType || random) && INPUT_TYPES.HIDDEN, (!noneType || random) && INPUT_TYPES.HIDDEN,
validation: string() validation: string()
@ -204,7 +204,7 @@ export const COMMAND = {
label: T.Command, label: T.Command,
notOnHypervisors: [lxc], notOnHypervisors: [lxc],
type: INPUT_TYPES.TEXT, type: INPUT_TYPES.TEXT,
dependOf: TYPE.name, dependOf: TYPE().name,
htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN, htmlType: (noneType) => !noneType && INPUT_TYPES.HIDDEN,
validation: string() validation: string()
.trim() .trim()
@ -217,14 +217,15 @@ export const COMMAND = {
* @param {string} [hypervisor] - VM hypervisor * @param {string} [hypervisor] - VM hypervisor
* @param {object} oneConfig - Config of oned.conf * @param {object} oneConfig - Config of oned.conf
* @param {boolean} adminGroup - User is admin or not * @param {boolean} adminGroup - User is admin or not
* @param {boolean} isUpdate - The form is being updated
* @returns {Field[]} List of Graphics fields * @returns {Field[]} List of Graphics fields
*/ */
export const GRAPHICS_FIELDS = (hypervisor, oneConfig, adminGroup) => export const GRAPHICS_FIELDS = (hypervisor, oneConfig, adminGroup, isUpdate) =>
disableFields( disableFields(
filterFieldsByHypervisor( filterFieldsByHypervisor(
[ [
TYPE, TYPE(isUpdate),
LISTEN, LISTEN(isUpdate),
PORT, PORT,
KEYMAP, KEYMAP,
CUSTOM_KEYMAP, CUSTOM_KEYMAP,
@ -240,5 +241,7 @@ export const GRAPHICS_FIELDS = (hypervisor, oneConfig, adminGroup) =>
) )
/** @type {ObjectSchema} Graphics schema */ /** @type {ObjectSchema} Graphics schema */
export const GRAPHICS_SCHEMA = (hypervisor) => export const GRAPHICS_SCHEMA = (hypervisor, oneConfig, adminGroup, isUpdate) =>
getObjectSchemaFromFields(GRAPHICS_FIELDS(hypervisor)) getObjectSchemaFromFields(
GRAPHICS_FIELDS(hypervisor, oneConfig, adminGroup, isUpdate)
)

View File

@ -33,7 +33,7 @@ import { useGeneralApi } from 'client/features/General'
export const TAB_ID = ['GRAPHICS', INPUT_ID, PCI_ID, VIDEO_ID] export const TAB_ID = ['GRAPHICS', INPUT_ID, PCI_ID, VIDEO_ID]
const InputOutput = ({ hypervisor, oneConfig, adminGroup }) => { const InputOutput = ({ hypervisor, oneConfig, adminGroup, isUpdate }) => {
const { setFieldPath } = useGeneralApi() const { setFieldPath } = useGeneralApi()
useEffect(() => { useEffect(() => {
setFieldPath(`extra.InputOutput`) setFieldPath(`extra.InputOutput`)
@ -47,7 +47,7 @@ const InputOutput = ({ hypervisor, oneConfig, adminGroup }) => {
> >
<FormWithSchema <FormWithSchema
cy={`${EXTRA_ID}-io-graphics`} cy={`${EXTRA_ID}-io-graphics`}
fields={GRAPHICS_FIELDS(hypervisor, oneConfig, adminGroup)} fields={GRAPHICS_FIELDS(hypervisor, oneConfig, adminGroup, isUpdate)}
legend={T.Graphics} legend={T.Graphics}
id={EXTRA_ID} id={EXTRA_ID}
saveState={true} saveState={true}
@ -81,6 +81,7 @@ InputOutput.propTypes = {
control: PropTypes.object, control: PropTypes.object,
oneConfig: PropTypes.object, oneConfig: PropTypes.object,
adminGroup: PropTypes.bool, adminGroup: PropTypes.bool,
isUpdate: PropTypes.bool,
} }
InputOutput.displayName = 'InputOutput' InputOutput.displayName = 'InputOutput'

View File

@ -22,13 +22,16 @@ import { VIDEO_SCHEMA } from './videoSchema'
/** /**
* @param {string} [hypervisor] - VM hypervisor * @param {string} [hypervisor] - VM hypervisor
* @param {object} oneConfig - Config of oned.conf
* @param {boolean} adminGroup - User is admin or not
* @param {boolean} isUpdate - The form is being updated
* @returns {ObjectSchema} I/O schema * @returns {ObjectSchema} I/O schema
*/ */
export const SCHEMA = (hypervisor) => export const SCHEMA = (hypervisor, oneConfig, adminGroup, isUpdate) =>
object() object()
.concat(INPUTS_SCHEMA) .concat(INPUTS_SCHEMA)
.concat(PCI_DEVICES_SCHEMA) .concat(PCI_DEVICES_SCHEMA)
.concat(GRAPHICS_SCHEMA(hypervisor)) .concat(GRAPHICS_SCHEMA(hypervisor, oneConfig, adminGroup, isUpdate))
.concat(VIDEO_SCHEMA(hypervisor)) .concat(VIDEO_SCHEMA(hypervisor))
export * from './graphicsSchema' export * from './graphicsSchema'

View File

@ -30,8 +30,14 @@ import {
import { T } from 'client/constants' import { T } from 'client/constants'
import { useGeneralApi } from 'client/features/General' import { useGeneralApi } from 'client/features/General'
const Placement = ({ oneConfig, adminGroup }) => { import { useSelector } from 'react-redux'
const Placement = ({ oneConfig, adminGroup, isUpdate }) => {
const { setFieldPath } = useGeneralApi() const { setFieldPath } = useGeneralApi()
// Get modified fields by the user
const modifiedFields = useSelector((state) => state.general.modifiedFields)
useEffect(() => { useEffect(() => {
setFieldPath(`extra.Placement`) setFieldPath(`extra.Placement`)
}, []) }, [])
@ -44,15 +50,17 @@ const Placement = ({ oneConfig, adminGroup }) => {
// TODO - DS policy options: Packing|Stripping // TODO - DS policy options: Packing|Stripping
<> <>
{SECTIONS(oneConfig, adminGroup).map(({ id, ...section }) => ( {SECTIONS(oneConfig, adminGroup, isUpdate, modifiedFields).map(
<FormWithSchema ({ id, ...section }) => (
key={id} <FormWithSchema
id={EXTRA_ID} key={id}
cy={`${EXTRA_ID}-${id}`} id={EXTRA_ID}
saveState={true} cy={`${EXTRA_ID}-${id}`}
{...section} saveState={true}
/> {...section}
))} />
)
)}
</> </>
) )
} }
@ -62,6 +70,7 @@ Placement.propTypes = {
setFormData: PropTypes.func, setFormData: PropTypes.func,
oneConfig: PropTypes.object, oneConfig: PropTypes.object,
adminGroup: PropTypes.bool, adminGroup: PropTypes.bool,
isUpdate: PropTypes.bool,
} }
Placement.displayName = 'Placement' Placement.displayName = 'Placement'
@ -72,7 +81,7 @@ const TAB = {
name: T.Placement, name: T.Placement,
icon: PlacementIcon, icon: PlacementIcon,
Content: Placement, Content: Placement,
getError: (error) => FIELDS.some(({ name }) => error?.[name]), getError: (error) => FIELDS({}).some(({ name }) => error?.[name]),
} }
export default TAB export default TAB

View File

@ -17,15 +17,89 @@ import { string } from 'yup'
import { Field, Section, disableFields } from 'client/utils' import { Field, Section, disableFields } from 'client/utils'
import { T, INPUT_TYPES } from 'client/constants' import { T, INPUT_TYPES } from 'client/constants'
import { transformXmlString } from 'client/models/Helper'
/**
* Add or replace the hypervisor type in SCHED_REQUIREMENTS attribute.
*
* @param {string} schedRequirements - Actual value
* @param {string} hypervisor - Value of the hypervisor
* @returns {string} - The result after replace or add the new hypervsior
*/
const addHypervisorRequirement = (schedRequirements, hypervisor) => {
// Regular expression pattern to match (HYPERVISOR=VALUE)
const regexPattern = /\(HYPERVISOR=(kvm|dummy|lxc|vcenter|firecracker|qemu)\)/
// If exists a condition with hypervisor, replace the type. If not, add the hypervisor type.
if (regexPattern.test(schedRequirements)) {
// Replace the matched pattern with the new hypervisor
return schedRequirements.replace(
regexPattern,
'(HYPERVISOR=' + hypervisor + ')'
)
} else {
// Add the condition only
return schedRequirements
? `(${schedRequirements}) & (HYPERVISOR=${hypervisor})`
: `(HYPERVISOR=${hypervisor})`
}
}
/** @type {Field} Host requirement field */ /** @type {Field} Host requirement field */
const HOST_REQ_FIELD = { const HOST_REQ_FIELD = (isUpdate, modifiedFields, instantiate) => ({
name: 'SCHED_REQUIREMENTS', name: 'SCHED_REQUIREMENTS',
label: T.HostReqExpression, label: T.HostReqExpression,
tooltip: T.HostReqExpressionConcept, tooltip: T.HostReqExpressionConcept,
type: INPUT_TYPES.TEXT, type: INPUT_TYPES.TEXT,
validation: string().trim().notRequired(), dependOf: '$general.HYPERVISOR',
} watcher: (hypervisor, { formContext }) => {
// Value of SCHED_REQUIREMENTS
const actualValue = formContext.getValues('extra.SCHED_REQUIREMENTS')
// Check if the hypervisor was changed by the user
const hypervisorHasChanged = modifiedFields?.general?.HYPERVISOR
// Add condition only if the hypervisor was changed by the user or if we are in the create form
if (hypervisorHasChanged || !isUpdate) {
// Return SCHED_REQUIREMENTS with the condition of hypervisor
return addHypervisorRequirement(actualValue, hypervisor)
} else {
// Return SCHED_REQUIREMENTS without the condition of hypervisor
return actualValue
}
},
validation: string()
.trim()
.notRequired()
.afterSubmit((value, { context }) => {
// After submit case exists because if the user don't enter on Placement section, the watcher function will not be executed
// Instantiate not use default values
if (instantiate) return transformXmlString(value)
// Check if SCHED_REQUIREMENTS was changed by the user
const schedRequirementsHasChanged =
modifiedFields?.extra?.Placement?.SCHED_REQUIREMENTS
// Check if the hypervisor was change by the user
const hypervisorHasChanged = modifiedFields?.general?.HYPERVISOR
// Replace or add hyperviosr condition
if (
(!isUpdate && !schedRequirementsHasChanged) ||
(isUpdate && hypervisorHasChanged && !schedRequirementsHasChanged)
) {
const result = addHypervisorRequirement(
value,
context.general.HYPERVISOR
)
return transformXmlString(result)
} else {
return transformXmlString(value)
}
}),
})
/** @type {Field} Host rank requirement field */ /** @type {Field} Host rank requirement field */
const HOST_RANK_FIELD = { const HOST_RANK_FIELD = {
@ -55,12 +129,12 @@ const DS_RANK_FIELD = {
} }
/** @type {Section[]} Sections */ /** @type {Section[]} Sections */
const SECTIONS = (oneConfig, adminGroup) => [ const SECTIONS = (oneConfig, adminGroup, isUpdate, modifiedFields) => [
{ {
id: 'placement-host', id: 'placement-host',
legend: T.Host, legend: T.Host,
fields: disableFields( fields: disableFields(
[HOST_REQ_FIELD, HOST_RANK_FIELD], [HOST_REQ_FIELD(isUpdate, modifiedFields), HOST_RANK_FIELD],
'', '',
oneConfig, oneConfig,
adminGroup adminGroup
@ -79,6 +153,11 @@ const SECTIONS = (oneConfig, adminGroup) => [
] ]
/** @type {Field[]} List of Placement fields */ /** @type {Field[]} List of Placement fields */
const FIELDS = [HOST_REQ_FIELD, HOST_RANK_FIELD, DS_REQ_FIELD, DS_RANK_FIELD] const FIELDS = ({ isUpdate, modifiedFields, instantiate }) => [
HOST_REQ_FIELD(isUpdate, modifiedFields, instantiate),
HOST_RANK_FIELD,
DS_REQ_FIELD,
DS_RANK_FIELD,
]
export { SECTIONS, FIELDS } export { SECTIONS, FIELDS }

View File

@ -48,18 +48,30 @@ const SCHED_ACTION_SCHEMA = object({
/** /**
* @param {HYPERVISORS} hypervisor - VM hypervisor * @param {HYPERVISORS} hypervisor - VM hypervisor
* @param {boolean} isUpdate - If it's an update of the form * @param {object} oneConfig - Config of oned.conf
* @param {boolean} adminGroup - User is admin or not
* @param {boolean} isUpdate - The form is being updated
* @param {object} modifiedFields - Map with the fields modified by the user
* @returns {ObjectSchema} Extra configuration schema * @returns {ObjectSchema} Extra configuration schema
*/ */
export const SCHEMA = (hypervisor, isUpdate) => export const SCHEMA = (
hypervisor,
oneConfig,
adminGroup,
isUpdate,
modifiedFields
) =>
object() object()
.concat(SCHED_ACTION_SCHEMA) .concat(SCHED_ACTION_SCHEMA)
.concat(NETWORK_SCHEMA) .concat(NETWORK_SCHEMA)
.concat(STORAGE_SCHEMA) .concat(STORAGE_SCHEMA)
.concat(CONTEXT_SCHEMA(hypervisor, isUpdate)) .concat(CONTEXT_SCHEMA(hypervisor, isUpdate))
.concat(IO_SCHEMA(hypervisor)) .concat(IO_SCHEMA(hypervisor, oneConfig, adminGroup, isUpdate))
.concat( .concat(
getObjectSchemaFromFields([...PLACEMENT_FIELDS, ...OS_FIELDS(hypervisor)]) getObjectSchemaFromFields([
...PLACEMENT_FIELDS({ isUpdate, modifiedFields }),
...OS_FIELDS(hypervisor),
])
) )
.concat(getObjectSchemaFromFields([...BACKUP_FIELDS])) .concat(getObjectSchemaFromFields([...BACKUP_FIELDS]))
.concat(NUMA_SCHEMA(hypervisor)) .concat(NUMA_SCHEMA(hypervisor))

View File

@ -56,7 +56,7 @@ export const DESCRIPTION = {
} }
/** @type {Field} Hypervisor field */ /** @type {Field} Hypervisor field */
export const HYPERVISOR_FIELD = { export const HYPERVISOR_FIELD = (isUpdate) => ({
name: 'HYPERVISOR', name: 'HYPERVISOR',
type: INPUT_TYPES.TOGGLE, type: INPUT_TYPES.TOGGLE,
values: arrayToOptions(Object.values(HYPERVISORS), { values: arrayToOptions(Object.values(HYPERVISORS), {
@ -66,9 +66,10 @@ export const HYPERVISOR_FIELD = {
validation: string() validation: string()
.trim() .trim()
.required() .required()
.default(() => HYPERVISORS.kvm), .default(() => (isUpdate ? undefined : HYPERVISORS.kvm)),
grid: { md: 12 }, grid: { md: 12 },
} })
/** @type {Field} Logo field */ /** @type {Field} Logo field */
export const LOGO = { export const LOGO = {

View File

@ -65,7 +65,7 @@ const SECTIONS = (hypervisor, isUpdate, features, oneConfig, adminGroup) =>
legend: T.Hypervisor, legend: T.Hypervisor,
required: true, required: true,
fields: disableFields( fields: disableFields(
[HYPERVISOR_FIELD, VROUTER_FIELD], [HYPERVISOR_FIELD(isUpdate), VROUTER_FIELD],
'', '',
oneConfig, oneConfig,
adminGroup adminGroup

View File

@ -109,7 +109,13 @@ const ExtraConfiguration = ({ data: vmTemplate, oneConfig, adminGroup }) => {
resolver: SCHEMA, resolver: SCHEMA,
optionsValidate: { abortEarly: false }, optionsValidate: { abortEarly: false },
content: (props) => content: (props) =>
Content({ ...props, hypervisor, oneConfig, adminGroup }), Content({
...props,
hypervisor,
oneConfig,
adminGroup,
instantiate: true,
}),
} }
} }

View File

@ -34,4 +34,9 @@ export const SCHEMA = object()
.concat(SCHED_ACTION_SCHEMA) .concat(SCHED_ACTION_SCHEMA)
.concat(NETWORK_SCHEMA) .concat(NETWORK_SCHEMA)
.concat(STORAGE_SCHEMA) .concat(STORAGE_SCHEMA)
.concat(getObjectSchemaFromFields([...PLACEMENT_FIELDS, BOOT_ORDER_FIELD])) .concat(
getObjectSchemaFromFields([
...PLACEMENT_FIELDS({ instantiate: true }),
BOOT_ORDER_FIELD,
])
)

View File

@ -96,7 +96,11 @@ function CreateVmTemplate() {
rawTemplate, rawTemplate,
modifiedFields, modifiedFields,
existingTemplate, existingTemplate,
TAB_FORM_MAP TAB_FORM_MAP,
{
instantiate: false,
update: !!templateId,
}
) )
// Every action that is not an human action // Every action that is not an human action
@ -131,6 +135,7 @@ function CreateVmTemplate() {
apiTemplateDataExtended, apiTemplateDataExtended,
oneConfig, oneConfig,
adminGroup, adminGroup,
store,
}} }}
onSubmit={onSubmit} onSubmit={onSubmit}
fallback={<SkeletonStepsForm />} fallback={<SkeletonStepsForm />}

View File

@ -86,7 +86,10 @@ function InstantiateVmTemplate() {
rawTemplate, rawTemplate,
modifiedFields, modifiedFields,
existingTemplate, existingTemplate,
TAB_FORM_MAP TAB_FORM_MAP,
{
instantiate: true,
}
) )
// Every action that is not an human action // Every action that is not an human action

View File

@ -625,13 +625,15 @@ export const getErrorMessage = (resource) => {
* @param {string} xmlString - The string with xml value * @param {string} xmlString - The string with xml value
* @returns {string} - A string with the same value but with the replace characters * @returns {string} - A string with the same value but with the replace characters
*/ */
export const transformXmlString = (xmlString) => export const transformXmlString = (xmlString = '') =>
xmlString.replace(/[<>"']/g, function (c) { xmlString.replace(/[<>&"']/g, function (c) {
switch (c) { switch (c) {
case '<': case '<':
return '&lt;' return '&lt;'
case '>': case '>':
return '&gt;' return '&gt;'
case '&':
return '&amp;'
default: default:
return c return c
} }

View File

@ -20,9 +20,6 @@ import { transformXmlString } from 'client/models/Helper'
// Attributes that will be always modify with the value of the form (except Storage, Network and PCI sections) // Attributes that will be always modify with the value of the form (except Storage, Network and PCI sections)
const alwaysIncludeAttributes = { const alwaysIncludeAttributes = {
general: {
HYPERVISOR: true,
},
extra: { extra: {
OsCpu: { OsCpu: {
OS: { OS: {
@ -36,10 +33,9 @@ const alwaysIncludeAttributes = {
Context: { Context: {
INPUTS_ORDER: true, INPUTS_ORDER: true,
USER_INPUTS: true, USER_INPUTS: true,
CONTEXT: { },
SSH_PUBLIC_KEY: true, Placement: {
NETWORK: true, SCHED_REQUIREMENTS: true,
},
}, },
}, },
} }
@ -54,6 +50,31 @@ const alwaysIncludeNic = {
NAME: true, NAME: true,
} }
const defaultValuesCreate = {
general: {
HYPERVISOR: true,
},
extra: {
Context: {
CONTEXT: {
NETWORK: true,
SSH_PUBLIC_KEY: true,
},
},
InputOutput: {
GRAPHICS: {
LISTEN: true,
TYPE: true,
},
},
Placement: {
SCHED_REQUIREMENTS: true,
},
},
}
const defaultValuesUpdate = {}
// Attributes that will be always modify with the value of the form in the Nic alias section // Attributes that will be always modify with the value of the form in the Nic alias section
const alwaysIncludeNicAlias = { const alwaysIncludeNicAlias = {
PARENT: true, PARENT: true,
@ -67,23 +88,31 @@ const alwaysIncludeNicAlias = {
* @param {object} modifiedFields - Touched/Dirty fields object * @param {object} modifiedFields - Touched/Dirty fields object
* @param {object} existingTemplate - Existing data * @param {object} existingTemplate - Existing data
* @param {object} tabFormMap - Maps formData fields to tabs * @param {object} tabFormMap - Maps formData fields to tabs
* @param {boolean} update - If the form is being updated
* @returns {object} - Filtered template data * @returns {object} - Filtered template data
*/ */
const filterTemplateData = ( const filterTemplateData = (
formData, formData,
modifiedFields, modifiedFields,
existingTemplate, existingTemplate,
tabFormMap tabFormMap,
{ update = true, instantiate = false }
) => { ) => {
// Generate a form from the original data // Generate a form from the original data
const normalizedTemplate = normalizeTemplate(existingTemplate, tabFormMap) const normalizedTemplate = normalizeTemplate(existingTemplate, tabFormMap)
const includeAtributes = !instantiate
? update
? merge({}, alwaysIncludeAttributes, defaultValuesUpdate)
: merge({}, alwaysIncludeAttributes, defaultValuesCreate)
: alwaysIncludeAttributes
// Filter data of formData.general // Filter data of formData.general
const newGeneral = reduceGeneral( const newGeneral = reduceGeneral(
formData?.general, formData?.general,
modifiedFields?.general, modifiedFields?.general,
normalizedTemplate?.general, normalizedTemplate?.general,
alwaysIncludeAttributes includeAtributes
) )
// Filter data of formData.extra // Filter data of formData.extra
@ -92,7 +121,7 @@ const filterTemplateData = (
modifiedFields, modifiedFields,
normalizedTemplate, normalizedTemplate,
tabFormMap, tabFormMap,
alwaysIncludeAttributes includeAtributes
) )
// Add custom variables // Add custom variables