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:
parent
8da274781b
commit
e147751bed
@ -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])
|
||||||
|
|
||||||
|
@ -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])
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
])
|
])
|
||||||
|
@ -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
|
||||||
|
@ -52,6 +52,7 @@ const FilesSection = ({ stepId, hypervisor, oneConfig, adminGroup }) => (
|
|||||||
),
|
),
|
||||||
[hypervisor]
|
[hypervisor]
|
||||||
)}
|
)}
|
||||||
|
saveState={true}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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 }),
|
||||||
|
@ -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)
|
||||||
|
)
|
||||||
|
@ -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'
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
|
@ -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 }
|
||||||
|
@ -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))
|
||||||
|
@ -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 = {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
@ -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 />}
|
||||||
|
@ -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
|
||||||
|
@ -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 '<'
|
return '<'
|
||||||
case '>':
|
case '>':
|
||||||
return '>'
|
return '>'
|
||||||
|
case '&':
|
||||||
|
return '&'
|
||||||
default:
|
default:
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user