diff --git a/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js b/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js
index 0f10416631..c7cf622682 100644
--- a/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js
+++ b/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js
@@ -27,9 +27,7 @@ import {
DialogPropTypes,
} from 'client/components/Dialogs'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
-import SubmitButton, {
- SubmitButtonPropTypes,
-} from 'client/components/FormControl/SubmitButton'
+import SubmitButton from 'client/components/FormControl/SubmitButton'
import FormStepper from 'client/components/FormStepper'
import { Translate } from 'client/components/HOC'
import { isDevelopment } from 'client/utils'
@@ -130,6 +128,7 @@ const ButtonToTriggerForm = ({ buttonProps = {}, options = [] }) => {
resolver,
description,
fields,
+ ContentForm,
onSubmit,
}) =>
resolver && (
@@ -145,6 +144,8 @@ const ButtonToTriggerForm = ({ buttonProps = {}, options = [] }) => {
schema={resolver}
onSubmit={onSubmit}
/>
+ ) : ContentForm ? (
+
) : (
<>
{description}
@@ -161,7 +162,7 @@ const ButtonToTriggerForm = ({ buttonProps = {}, options = [] }) => {
}
export const ButtonToTriggerFormPropTypes = {
- buttonProps: PropTypes.shape(SubmitButtonPropTypes),
+ buttonProps: PropTypes.shape({ ...SubmitButton.propTypes }),
options: PropTypes.arrayOf(
PropTypes.shape({
cy: PropTypes.string,
@@ -176,7 +177,6 @@ export const ButtonToTriggerFormPropTypes = {
}
ButtonToTriggerForm.propTypes = ButtonToTriggerFormPropTypes
-
ButtonToTriggerForm.displayName = 'ButtonToTriggerForm'
export default ButtonToTriggerForm
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/booting/index.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/booting/index.js
new file mode 100644
index 0000000000..a8b0aa622e
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/booting/index.js
@@ -0,0 +1,42 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { ReactElement, useMemo } from 'react'
+import PropTypes from 'prop-types'
+
+import FormWithSchema from 'client/components/Forms/FormWithSchema'
+import { SECTIONS } from 'client/components/Forms/Vm/UpdateConfigurationForm/booting/schema'
+import { HYPERVISORS } from 'client/constants'
+
+/**
+ * @param {object} props - Component props
+ * @param {HYPERVISORS} props.hypervisor - VM hypervisor
+ * @returns {ReactElement} OS section component
+ */
+const OsSection = ({ hypervisor }) => {
+ const sections = useMemo(() => SECTIONS(hypervisor), [hypervisor])
+
+ return (
+ <>
+ {sections.map(({ id, ...section }) => (
+
+ ))}
+ >
+ )
+}
+
+OsSection.propTypes = { hypervisor: PropTypes.string }
+
+export default OsSection
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/booting/schema.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/booting/schema.js
new file mode 100644
index 0000000000..da7a114637
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/booting/schema.js
@@ -0,0 +1,79 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { ObjectSchema } from 'yup'
+
+import * as bootingSchema from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/schema'
+
+import {
+ Field,
+ Section,
+ getObjectSchemaFromFields,
+ filterFieldsByHypervisor,
+} from 'client/utils'
+import { T, HYPERVISORS, ATTR_CONF_CAN_BE_UPDATED } from 'client/constants'
+
+const getFields = (section) =>
+ section.map((attr) => bootingSchema[attr]).filter(Boolean)
+
+// Supported fields
+const OS_FIELDS = getFields(ATTR_CONF_CAN_BE_UPDATED.OS)
+const FEATURES_FIELDS = getFields(ATTR_CONF_CAN_BE_UPDATED.FEATURES)
+const RAW_FIELDS = getFields(ATTR_CONF_CAN_BE_UPDATED.RAW)
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {Section[]} Sections
+ */
+const SECTIONS = ({ hypervisor }) => [
+ {
+ id: 'os-boot',
+ legend: T.Boot,
+ fields: filterFieldsByHypervisor(OS_FIELDS, hypervisor),
+ },
+ {
+ id: 'os-features',
+ legend: T.Features,
+ fields: filterFieldsByHypervisor(FEATURES_FIELDS, hypervisor),
+ },
+ {
+ id: 'os-raw',
+ legend: T.RawData,
+ legendTooltip: T.RawDataConcept,
+ fields: filterFieldsByHypervisor(RAW_FIELDS, hypervisor),
+ },
+]
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {Field[]} OS fields
+ */
+const FIELDS = ({ hypervisor }) => [
+ ...SECTIONS({ hypervisor })
+ .map(({ fields }) => fields)
+ .flat(),
+]
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {ObjectSchema} Step schema
+ */
+const SCHEMA = ({ hypervisor }) =>
+ getObjectSchemaFromFields(FIELDS({ hypervisor }))
+
+export { SECTIONS, FIELDS, SCHEMA }
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/content.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/content.js
new file mode 100644
index 0000000000..5a8d72b79d
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/content.js
@@ -0,0 +1,75 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { ReactElement, useMemo } from 'react'
+import PropTypes from 'prop-types'
+import { useFormContext } from 'react-hook-form'
+import {
+ SystemShut as OsIcon,
+ DataTransferBoth as IOIcon,
+ Folder as ContextIcon,
+} from 'iconoir-react'
+
+import InputOutput from 'client/components/Forms/Vm/UpdateConfigurationForm/inputOutput'
+import Booting from 'client/components/Forms/Vm/UpdateConfigurationForm/booting'
+import Context from 'client/components/Forms/Vm/UpdateConfigurationForm/context'
+
+import Tabs from 'client/components/Tabs'
+import { Translate } from 'client/components/HOC'
+import { T, HYPERVISORS } from 'client/constants'
+
+/**
+ * @param {object} props - Component props
+ * @param {HYPERVISORS} props.hypervisor - VM hypervisor
+ * @returns {ReactElement} Form content component
+ */
+const Content = ({ hypervisor }) => {
+ const {
+ formState: { errors },
+ } = useFormContext()
+
+ const tabs = useMemo(
+ () => [
+ {
+ id: 'booting',
+ icon: OsIcon,
+ label: ,
+ renderContent: () => ,
+ error: !!errors?.OS,
+ },
+ {
+ id: 'input_output',
+ icon: IOIcon,
+ label: ,
+ renderContent: () => ,
+ error: ['GRAPHICS', 'INPUT'].some((id) => errors?.[id]),
+ },
+ {
+ id: 'context',
+ icon: ContextIcon,
+ label: ,
+ renderContent: () => ,
+ error: !!errors?.CONTEXT,
+ },
+ ],
+ [errors, hypervisor]
+ )
+
+ return
+}
+
+Content.propTypes = { hypervisor: PropTypes.string }
+
+export default Content
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/context/index.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/context/index.js
new file mode 100644
index 0000000000..41e7adae77
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/context/index.js
@@ -0,0 +1,40 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { ReactElement } from 'react'
+import PropTypes from 'prop-types'
+
+import ConfigurationSection from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection'
+import FilesSection from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection'
+import ContextVarsSection from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/contextVarsSection'
+
+import { HYPERVISORS } from 'client/constants'
+
+/**
+ * @param {object} props - Component props
+ * @param {HYPERVISORS} props.hypervisor - VM hypervisor
+ * @returns {ReactElement} Context section component
+ */
+const ContextSection = ({ hypervisor }) => (
+ <>
+
+
+
+ >
+)
+
+ContextSection.propTypes = { hypervisor: PropTypes.string }
+
+export default ContextSection
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/context/schema.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/context/schema.js
new file mode 100644
index 0000000000..eee20f3b05
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/context/schema.js
@@ -0,0 +1,28 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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, ObjectSchema } from 'yup'
+
+import {
+ CONFIGURATION_SCHEMA,
+ FILES_SCHEMA,
+} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema'
+
+/**
+ * @param {string} [hypervisor] - VM hypervisor
+ * @returns {ObjectSchema} Context schema
+ */
+export const SCHEMA = (hypervisor) =>
+ object().concat(CONFIGURATION_SCHEMA).concat(FILES_SCHEMA(hypervisor))
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/index.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/index.js
new file mode 100644
index 0000000000..f53507c52f
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/index.js
@@ -0,0 +1,51 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { reach } from 'yup'
+
+import { SCHEMA } from 'client/components/Forms/Vm/UpdateConfigurationForm/schema'
+import ContentForm from 'client/components/Forms/Vm/UpdateConfigurationForm/content'
+import { ensureContextWithScript } from 'client/components/Forms/VmTemplate/CreateForm/Steps'
+import { createForm, getUnknownAttributes } from 'client/utils'
+
+const UpdateConfigurationForm = createForm(SCHEMA, undefined, {
+ ContentForm,
+ transformInitialValue: (vmTemplate, schema) => {
+ const template = vmTemplate?.TEMPLATE ?? {}
+ const context = template?.CONTEXT ?? {}
+
+ const knownTemplate = schema.cast(
+ { ...vmTemplate, ...template },
+ { stripUnknown: true, context: { ...template } }
+ )
+
+ // Get the custom vars from the context
+ const knownContext = reach(schema, 'CONTEXT').cast(context, {
+ stripUnknown: true,
+ context: { ...template },
+ })
+
+ // Merge known and unknown context custom vars
+ knownTemplate.CONTEXT = {
+ ...knownContext,
+ ...getUnknownAttributes(context, knownContext),
+ }
+
+ return knownTemplate
+ },
+ transformBeforeSubmit: (formData) => ensureContextWithScript(formData),
+})
+
+export default UpdateConfigurationForm
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/index.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/index.js
new file mode 100644
index 0000000000..787b617fa8
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/index.js
@@ -0,0 +1,49 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { ReactElement, useMemo } from 'react'
+import PropTypes from 'prop-types'
+import { Stack } from '@mui/material'
+
+import { FormWithSchema } from 'client/components/Forms'
+
+import InputsSection from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/inputsSection'
+import { GRAPHICS_FIELDS } from 'client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/schema'
+import { T, HYPERVISORS } from 'client/constants'
+
+/**
+ * @param {object} props - Component props
+ * @param {HYPERVISORS} props.hypervisor - VM hypervisor
+ * @returns {ReactElement} IO section component
+ */
+const InputOutput = ({ hypervisor }) => (
+
+ GRAPHICS_FIELDS({ hypervisor }), [hypervisor])}
+ legend={T.Graphics}
+ />
+
+
+)
+
+InputOutput.propTypes = { hypervisor: PropTypes.string }
+InputOutput.displayName = 'InputOutput'
+
+export default InputOutput
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/schema.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/schema.js
new file mode 100644
index 0000000000..fe5062a189
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/inputOutput/schema.js
@@ -0,0 +1,57 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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, ObjectSchema } from 'yup'
+
+import * as ioSchema from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema'
+
+import {
+ Field,
+ filterFieldsByHypervisor,
+ getObjectSchemaFromFields,
+} from 'client/utils'
+import { HYPERVISORS, ATTR_CONF_CAN_BE_UPDATED } from 'client/constants'
+
+const getFields = (section) =>
+ section.map((attr) => ioSchema[attr]).filter(Boolean)
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {Field[]} List of Graphics editable fields
+ */
+export const GRAPHICS_FIELDS = ({ hypervisor }) =>
+ filterFieldsByHypervisor(
+ getFields(ATTR_CONF_CAN_BE_UPDATED.GRAPHICS),
+ hypervisor
+ )
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {ObjectSchema} Graphics schema
+ */
+export const GRAPHICS_SCHEMA = ({ hypervisor }) =>
+ getObjectSchemaFromFields(GRAPHICS_FIELDS(hypervisor))
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {ObjectSchema} I/O schema
+ */
+export const SCHEMA = ({ hypervisor }) =>
+ object()
+ .concat(ioSchema.INPUTS_SCHEMA)
+ .concat(GRAPHICS_SCHEMA({ hypervisor }))
diff --git a/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/schema.js b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/schema.js
new file mode 100644
index 0000000000..9e7a9cd6a2
--- /dev/null
+++ b/src/fireedge/src/client/components/Forms/Vm/UpdateConfigurationForm/schema.js
@@ -0,0 +1,34 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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, ObjectSchema } from 'yup'
+
+import { HYPERVISORS } from 'client/constants'
+import { SCHEMA as OS_SCHEMA } from './booting/schema'
+import { SCHEMA as IO_SCHEMA } from './inputOutput/schema'
+import { SCHEMA as CONTEXT_SCHEMA } from './context/schema'
+
+/**
+ * @param {object} [formProps] - Form props
+ * @param {HYPERVISORS} [formProps.hypervisor] - VM hypervisor
+ * @returns {ObjectSchema} Configuration schema
+ */
+export const SCHEMA = ({ hypervisor }) =>
+ object()
+ .concat(IO_SCHEMA({ hypervisor }))
+ .concat(OS_SCHEMA({ hypervisor }))
+ .concat(CONTEXT_SCHEMA({ hypervisor }))
+
+export { IO_SCHEMA, OS_SCHEMA, CONTEXT_SCHEMA }
diff --git a/src/fireedge/src/client/components/Forms/Vm/index.js b/src/fireedge/src/client/components/Forms/Vm/index.js
index eb941602c0..8fc5091bf3 100644
--- a/src/fireedge/src/client/components/Forms/Vm/index.js
+++ b/src/fireedge/src/client/components/Forms/Vm/index.js
@@ -149,6 +149,13 @@ const CreateRelativeCharterForm = (configProps) =>
configProps
)
+/**
+ * @param {ConfigurationProps} configProps - Configuration
+ * @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
+ */
+const UpdateConfigurationForm = (configProps) =>
+ AsyncLoadForm({ formPath: 'Vm/UpdateConfigurationForm' }, configProps)
+
export {
AttachNicForm,
AttachSecGroupForm,
@@ -167,5 +174,6 @@ export {
ResizeDiskForm,
SaveAsDiskForm,
SaveAsTemplateForm,
+ UpdateConfigurationForm,
VolatileSteps,
}
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootSchema.js
index 109509e595..025b1c91df 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootSchema.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootSchema.js
@@ -163,13 +163,13 @@ export const FIRMWARE = {
label: T.Firmware,
tooltip: T.FirmwareConcept,
notOnHypervisors: [firecracker, lxc],
- type: ([_, custom] = []) => (custom ? INPUT_TYPES.TEXT : INPUT_TYPES.SELECT),
+ type: ([, , custom] = []) => (custom ? INPUT_TYPES.TEXT : INPUT_TYPES.SELECT),
validation: string()
.trim()
.notRequired()
.default(() => undefined),
- dependOf: ['$general.HYPERVISOR', FEATURE_CUSTOM_ENABLED.name],
- values: ([hypervisor] = []) => {
+ dependOf: ['HYPERVISOR', '$general.HYPERVISOR', FEATURE_CUSTOM_ENABLED.name],
+ values: ([templateHyperv, hypervisor = templateHyperv] = []) => {
const types =
{
[vcenter]: VCENTER_FIRMWARE_TYPES,
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/schema.js
index 2be66246db..a6864ca29f 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/schema.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/schema.js
@@ -90,3 +90,8 @@ const FIELDS = (hypervisor) => [
const SCHEMA = (hypervisor) => getObjectSchemaFromFields(FIELDS(hypervisor))
export { SECTIONS, FIELDS, BOOT_ORDER_FIELD, SCHEMA }
+export * from './bootSchema'
+export * from './kernelSchema'
+export * from './ramdiskSchema'
+export * from './featuresSchema'
+export * from './rawSchema'
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSchema.js
index 9ad8b7018a..c29ce93a2e 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSchema.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSchema.js
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { string, boolean, ref, ObjectSchema } from 'yup'
+import { string, boolean, lazy, ObjectSchema } from 'yup'
import { T, INPUT_TYPES } from 'client/constants'
import { Field, getObjectSchemaFromFields, decodeBase64 } from 'client/utils'
@@ -64,9 +64,9 @@ export const ENCODE_START_SCRIPT = {
name: 'CONTEXT.ENCODE_START_SCRIPT',
label: T.EncodeScriptInBase64,
...switchField,
- validation: boolean()
- .transform((value) => Boolean(value))
- .default(() => ref('$extra.CONTEXT.START_SCRIPT_BASE64')),
+ validation: lazy((_, { context }) =>
+ boolean().default(() => !!context?.CONTEXT?.START_SCRIPT_BASE64)
+ ),
}
/** @type {Field} Start script field */
@@ -76,13 +76,21 @@ export const START_SCRIPT = {
tooltip: T.StartScriptConcept,
type: INPUT_TYPES.TEXT,
multiline: true,
- validation: string()
- .trim()
- .notRequired()
- .ensure()
- .when('$extra.CONTEXT.START_SCRIPT_BASE64', (scriptEncoded, schema) =>
- scriptEncoded ? schema.default(() => decodeBase64(scriptEncoded)) : schema
- ),
+ validation: lazy((value, { context }) =>
+ string()
+ .trim()
+ .notRequired()
+ .ensure()
+ .default(() => {
+ try {
+ const base64 = context?.CONTEXT?.START_SCRIPT_BASE64
+
+ return value ?? decodeBase64(base64 ?? '')
+ } catch {
+ return value
+ }
+ })
+ ),
grid: { md: 12 },
fieldProps: { rows: 4 },
}
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js
index face7cf9d9..411826b3b3 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js
@@ -13,34 +13,47 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { useMemo, JSXElementConstructor } from 'react'
+import { ReactElement, useCallback, useMemo } from 'react'
+import PropTypes from 'prop-types'
import { Stack, FormControl, Button } from '@mui/material'
import { useFormContext } from 'react-hook-form'
import { FormWithSchema, Legend } from 'client/components/Forms'
-
-import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
import { SSH_PUBLIC_KEY, SCRIPT_FIELDS, OTHER_FIELDS } from './schema'
import { T } from 'client/constants'
const SSH_KEY_USER = '$USER[SSH_PUBLIC_KEY]'
-/** @returns {JSXElementConstructor} - Configuration section */
-const ConfigurationSection = () => {
+/**
+ * Renders the configuration section to VM Template form.
+ *
+ * @param {object} props - Props passed to the component
+ * @param {string} [props.stepId] - ID of the step the section belongs to
+ * @returns {ReactElement} - Configuration section
+ */
+const ConfigurationSection = ({ stepId }) => {
const { setValue, getValues } = useFormContext()
const SSH_PUBLIC_KEY_PATH = useMemo(
- () => `${EXTRA_ID}.${SSH_PUBLIC_KEY.name}`,
- []
+ () => [stepId, SSH_PUBLIC_KEY.name].filter(Boolean).join('.'),
+ [stepId]
)
- const handleClearKey = () => setValue(SSH_PUBLIC_KEY_PATH)
+ const getCyPath = useCallback(
+ (cy) => [stepId, cy].filter(Boolean).join('-'),
+ [stepId]
+ )
- const handleAddUserKey = () => {
+ const handleClearKey = useCallback(
+ () => setValue(SSH_PUBLIC_KEY_PATH),
+ [setValue, SSH_PUBLIC_KEY_PATH]
+ )
+
+ const handleAddUserKey = useCallback(() => {
let currentKey = getValues(SSH_PUBLIC_KEY_PATH)
currentKey &&= currentKey + '\n'
setValue(SSH_PUBLIC_KEY_PATH, `${currentKey ?? ''}${SSH_KEY_USER}`)
- }
+ }, [getValues, setValue, SSH_PUBLIC_KEY_PATH])
return (
@@ -51,22 +64,22 @@ const ConfigurationSection = () => {
sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }}
>
-
+
@@ -78,11 +91,11 @@ const ConfigurationSection = () => {
{T.Clear}
-
+
@@ -90,4 +103,9 @@ const ConfigurationSection = () => {
)
}
+ConfigurationSection.propTypes = {
+ stepId: PropTypes.string,
+ hypervisor: PropTypes.string,
+}
+
export default ConfigurationSection
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/contextVarsSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/contextVarsSection.js
index 86dbd038bd..f9b440ca61 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/contextVarsSection.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/contextVarsSection.js
@@ -19,9 +19,7 @@ import { reach } from 'yup'
import { useFormContext, useWatch } from 'react-hook-form'
import { Accordion, AccordionSummary, Box } from '@mui/material'
-import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
import { SCHEMA as CONTEXT_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema'
-
import { useGeneralApi } from 'client/features/General'
import { Legend } from 'client/components/Forms'
@@ -32,16 +30,19 @@ import { T } from 'client/constants'
export const SECTION_ID = 'CONTEXT'
/**
- * Renders the context section of the extra configuration form.
+ * Renders the context section to VM Template form.
*
* @param {object} props - Props passed to the component
+ * @param {string} [props.stepId] - ID of the step the section belongs to
* @param {string} props.hypervisor - VM hypervisor
* @returns {ReactElement} - Context vars section
*/
-const ContextVarsSection = ({ hypervisor }) => {
+const ContextVarsSection = ({ stepId, hypervisor }) => {
const { enqueueError } = useGeneralApi()
const { setValue } = useFormContext()
- const customVars = useWatch({ name: `${EXTRA_ID}.${SECTION_ID}` })
+ const customVars = useWatch({
+ name: [stepId, SECTION_ID].filter(Boolean).join('.'),
+ })
const unknownVars = useMemo(() => {
const knownVars = CONTEXT_SCHEMA(hypervisor).cast(
@@ -57,7 +58,7 @@ const ContextVarsSection = ({ hypervisor }) => {
const handleChangeAttribute = useCallback(
(path, newValue) => {
const contextPath = `${SECTION_ID}.${path}`
- const formPath = `${EXTRA_ID}.${contextPath}`
+ const formPath = [stepId, contextPath].filter(Boolean).join('.')
try {
// retrieve the schema for the given path
@@ -100,10 +101,8 @@ const ContextVarsSection = ({ hypervisor }) => {
}
ContextVarsSection.propTypes = {
- data: PropTypes.any,
- setFormData: PropTypes.func,
+ stepId: PropTypes.string,
hypervisor: PropTypes.string,
- control: PropTypes.object,
}
export default ContextVarsSection
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js
index 129778f9d2..41b605dc3f 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js
@@ -13,12 +13,11 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { JSXElementConstructor } from 'react'
+import { ReactElement, useMemo } 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'
@@ -26,24 +25,26 @@ export const SECTION_ID = 'CONTEXT'
/**
* @param {object} props - Props
+ * @param {string} [props.stepId] - ID of the step the section belongs to
* @param {string} props.hypervisor - VM hypervisor
- * @returns {JSXElementConstructor} - Files section
+ * @returns {ReactElement} - Files section
*/
-const FilesSection = ({ hypervisor }) => (
+const FilesSection = ({ stepId, hypervisor }) => (
FILES_FIELDS(hypervisor)}
- id={EXTRA_ID}
+ id={stepId}
+ cy={useMemo(
+ () => [stepId, 'context-files'].filter(Boolean).join('-'),
+ [stepId]
+ )}
+ fields={useMemo(() => FILES_FIELDS(hypervisor), [hypervisor])}
/>
)
FilesSection.propTypes = {
- data: PropTypes.any,
- setFormData: PropTypes.func,
+ stepId: PropTypes.string,
hypervisor: PropTypes.string,
- control: PropTypes.object,
}
export default FilesSection
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js
index dbbb672401..108069f64e 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js
@@ -16,7 +16,10 @@
import PropTypes from 'prop-types'
import { Folder as ContextIcon } from 'iconoir-react'
-import { TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
+import {
+ TabType,
+ STEP_ID as EXTRA_ID,
+} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
import UserInputsSection, {
SECTION_ID as USER_INPUTS_ID,
} from './userInputsSection'
@@ -30,10 +33,10 @@ export const TAB_ID = ['CONTEXT', USER_INPUTS_ID]
const Context = (props) => (
<>
-
+
-
-
+
+
>
)
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js
index 52298300d1..03e558ceae 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/graphicsSchema.js
@@ -26,11 +26,11 @@ import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants'
const { vcenter, lxc, kvm } = HYPERVISORS
/** @type {Field} Type field */
-const TYPE = {
+export const TYPE = {
name: 'GRAPHICS.TYPE',
type: INPUT_TYPES.TOGGLE,
- dependOf: '$general.HYPERVISOR',
- values: (hypervisor = kvm) => {
+ dependOf: ['HYPERVISOR', '$general.HYPERVISOR'],
+ values: ([templateHyperv = kvm, hypervisor = templateHyperv] = []) => {
const types = {
[vcenter]: [T.Vmrc],
[lxc]: [T.Vnc],
@@ -41,12 +41,13 @@ const TYPE = {
validation: string()
.trim()
.notRequired()
+ .uppercase()
.default(() => undefined),
grid: { md: 12 },
}
/** @type {Field} Listen field */
-const LISTEN = {
+export const LISTEN = {
name: 'GRAPHICS.LISTEN',
label: T.ListenOnIp,
type: INPUT_TYPES.TEXT,
@@ -61,7 +62,7 @@ const LISTEN = {
}
/** @type {Field} Port field */
-const PORT = {
+export const PORT = {
name: 'GRAPHICS.PORT',
label: T.ServerPort,
tooltip: T.ServerPortConcept,
@@ -75,7 +76,7 @@ const PORT = {
}
/** @type {Field} Keymap field */
-const KEYMAP = {
+export const KEYMAP = {
name: 'GRAPHICS.KEYMAP',
label: T.Keymap,
type: INPUT_TYPES.TEXT,
@@ -89,7 +90,7 @@ const KEYMAP = {
}
/** @type {Field} Password random field */
-const RANDOM_PASSWD = {
+export const RANDOM_PASSWD = {
name: 'GRAPHICS.RANDOM_PASSWD',
label: T.GenerateRandomPassword,
type: INPUT_TYPES.CHECKBOX,
@@ -100,7 +101,7 @@ const RANDOM_PASSWD = {
}
/** @type {Field} Password field */
-const PASSWD = {
+export const PASSWD = {
name: 'GRAPHICS.PASSWD',
label: T.Password,
type: INPUT_TYPES.PASSWORD,
@@ -115,7 +116,7 @@ const PASSWD = {
}
/** @type {Field} Command field */
-const COMMAND = {
+export const COMMAND = {
name: 'GRAPHICS.COMMAND',
label: T.Command,
notOnHypervisors: [lxc],
@@ -139,6 +140,6 @@ export const GRAPHICS_FIELDS = (hypervisor) =>
hypervisor
)
-/** @type {ObjectSchema} Context Files schema */
+/** @type {ObjectSchema} Graphics schema */
export const GRAPHICS_SCHEMA = (hypervisor) =>
getObjectSchemaFromFields(GRAPHICS_FIELDS(hypervisor))
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js
index f8ea77fc61..758eece4b1 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/index.js
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { useMemo } from 'react'
import PropTypes from 'prop-types'
import { Stack } from '@mui/material'
import { DataTransferBoth as IOIcon } from 'iconoir-react'
@@ -26,34 +25,27 @@ import {
} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
import InputsSection, { SECTION_ID as INPUT_ID } from './inputsSection'
import PciDevicesSection, { SECTION_ID as PCI_ID } from './pciDevicesSection'
-import { GRAPHICS_FIELDS, INPUTS_FIELDS, PCI_FIELDS } from './schema'
+import { GRAPHICS_FIELDS } from './schema'
import { T } from 'client/constants'
export const TAB_ID = ['GRAPHICS', INPUT_ID, PCI_ID]
-const InputOutput = ({ hypervisor }) => {
- const inputsFields = useMemo(() => INPUTS_FIELDS(hypervisor), [hypervisor])
- const pciDevicesFields = useMemo(() => PCI_FIELDS(hypervisor), [hypervisor])
-
- return (
-
-
- {inputsFields.length > 0 && }
- {pciDevicesFields.length > 0 && (
-
- )}
-
- )
-}
+const InputOutput = ({ hypervisor }) => (
+
+
+
+
+
+)
InputOutput.propTypes = {
data: PropTypes.any,
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/inputsSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/inputsSection.js
index 122e1b1fdb..dc50e4ad73 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/inputsSection.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/inputsSection.js
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { JSXElementConstructor } from 'react'
+import { ReactElement, useCallback, memo, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Stack, FormControl, Divider, Button, IconButton } from '@mui/material'
import List from '@mui/material/List'
@@ -26,109 +26,131 @@ import { yupResolver } from '@hookform/resolvers/yup'
import { FormWithSchema, Legend } from 'client/components/Forms'
import { Translate } from 'client/components/HOC'
-import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
-import { INPUT_SCHEMA, deviceTypeIcons, busTypeIcons } from './schema'
-import { T } from 'client/constants'
+import {
+ INPUTS_FIELDS,
+ INPUT_SCHEMA,
+ deviceTypeIcons,
+ busTypeIcons,
+} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema'
+import { T, HYPERVISORS } from 'client/constants'
export const SECTION_ID = 'INPUT'
-/**
- * @param {object} props - Props
- * @param {Array} props.fields - Fields
- * @returns {JSXElementConstructor} - Inputs section
- */
-const InputsSection = ({ fields }) => {
- const {
- fields: inputs,
- append,
- remove,
- } = useFieldArray({
- name: `${EXTRA_ID}.${SECTION_ID}`,
- })
+const InputsSection = memo(
+ /**
+ * @param {object} props - Props
+ * @param {string} [props.stepId] - ID of the step the section belongs to
+ * @param {HYPERVISORS} props.hypervisor - VM hypervisor
+ * @returns {ReactElement} - Inputs section
+ */
+ ({ stepId, hypervisor }) => {
+ const fields = useMemo(() => INPUTS_FIELDS(hypervisor), [hypervisor])
- const methods = useForm({
- defaultValues: INPUT_SCHEMA.default(),
- resolver: yupResolver(INPUT_SCHEMA),
- })
+ const {
+ fields: inputs,
+ append,
+ remove,
+ } = useFieldArray({
+ name: useMemo(
+ () => [stepId, SECTION_ID].filter(Boolean).join('.'),
+ [stepId]
+ ),
+ })
- const onSubmit = (newInput) => {
- append(newInput)
- methods.reset()
- }
+ const getCyPath = useCallback(
+ (cy) => [stepId, cy].filter(Boolean).join('-'),
+ [stepId]
+ )
- return (
-
-
-
-
-
- }
- sx={{ mt: '1em' }}
- data-cy={`${EXTRA_ID}-add-io-inputs`}
+ const methods = useForm({
+ defaultValues: INPUT_SCHEMA.default(),
+ resolver: yupResolver(INPUT_SCHEMA),
+ })
+
+ const onSubmit = (newInput) => {
+ append(newInput)
+ methods.reset()
+ }
+
+ if (fields.length === 0) {
+ return null
+ }
+
+ return (
+
+
+
+
-
-
-
-
-
-
- {inputs?.map(({ id, TYPE, BUS }, index) => {
- const deviceIcon = deviceTypeIcons[TYPE]
- const deviceInfo = `${TYPE}`
- const busIcon = busTypeIcons[BUS]
- const busInfo = `${BUS}`
-
- return (
- remove(index)}>
-
-
- }
- sx={{ '&:hover': { bgcolor: 'action.hover' } }}
+
+ }
+ data-cy={getCyPath('add-io-inputs')}
+ sx={{ mt: '1em' }}
>
- *': { width: 36 } }}
- >
- {deviceIcon}
- {deviceInfo}
-
- {busIcon}
- {busInfo}
-
+
+
+
+
+
+
+ {inputs?.map(({ id, TYPE, BUS }, index) => {
+ const deviceIcon = deviceTypeIcons[TYPE]
+ const deviceInfo = `${TYPE}`
+ const busIcon = busTypeIcons[BUS]
+ const busInfo = `${BUS}`
+
+ return (
+ remove(index)}>
+
+
}
- primaryTypographyProps={{ variant: 'body1' }}
- />
-
- )
- })}
-
-
- )
-}
+ sx={{ '&:hover': { bgcolor: 'action.hover' } }}
+ >
+ *': { width: 36 } }}
+ >
+ {deviceIcon}
+ {deviceInfo}
+
+ {busIcon}
+ {busInfo}
+
+ }
+ primaryTypographyProps={{ variant: 'body1' }}
+ />
+
+ )
+ })}
+
+
+ )
+ }
+)
InputsSection.propTypes = {
- fields: PropTypes.array,
+ stepId: PropTypes.string,
+ hypervisor: PropTypes.string,
}
InputsSection.displayName = 'InputsSection'
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js
index 31b68aac15..eca6505716 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/pciDevicesSection.js
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { JSXElementConstructor, useMemo } from 'react'
+import { ReactElement, useMemo } from 'react'
import PropTypes from 'prop-types'
import { Stack, FormControl, Divider, Button, IconButton } from '@mui/material'
import List from '@mui/material/List'
@@ -28,18 +28,23 @@ import { FormWithSchema, Legend } from 'client/components/Forms'
import { Translate } from 'client/components/HOC'
import { getPciDevices } from 'client/models/Host'
-import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
-import { PCI_SCHEMA } from './schema'
-import { T } from 'client/constants'
+import {
+ PCI_FIELDS,
+ PCI_SCHEMA,
+} from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema'
+import { T, HYPERVISORS } from 'client/constants'
export const SECTION_ID = 'PCI'
/**
* @param {object} props - Props
- * @param {Array} props.fields - Fields
- * @returns {JSXElementConstructor} - Inputs section
+ * @param {string} [props.stepId] - ID of the step the section belongs to
+ * @param {HYPERVISORS} props.hypervisor - VM hypervisor
+ * @returns {ReactElement} - Inputs section
*/
-const PciDevicesSection = ({ fields }) => {
+const PciDevicesSection = ({ stepId, hypervisor }) => {
+ const fields = useMemo(() => PCI_FIELDS(hypervisor))
+
const { data: hosts = [] } = useGetHostsQuery()
const pciDevicesAvailable = useMemo(
() => hosts.map(getPciDevices).flat(),
@@ -51,7 +56,7 @@ const PciDevicesSection = ({ fields }) => {
append,
remove,
} = useFieldArray({
- name: `${EXTRA_ID}.${SECTION_ID}`,
+ name: [stepId, SECTION_ID].filter(Boolean).join('.'),
})
const methods = useForm({
@@ -79,7 +84,7 @@ const PciDevicesSection = ({ fields }) => {
onSubmit={methods.handleSubmit(onSubmit)}
>
@@ -130,7 +135,8 @@ const PciDevicesSection = ({ fields }) => {
}
PciDevicesSection.propTypes = {
- fields: PropTypes.array,
+ stepId: PropTypes.string,
+ hypervisor: PropTypes.string,
}
PciDevicesSection.displayName = 'PciDevicesSection'
diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js
index 9a13b0c7ed..d510496b08 100644
--- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js
+++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js
@@ -33,6 +33,30 @@ import {
getUnknownAttributes,
} from 'client/utils'
+/**
+ * Encodes the start script value to base64 if it is not already encoded.
+ *
+ * @param {object} template - VM template
+ * @returns {object} Context with the start script value encoded
+ */
+export const ensureContextWithScript = (template = {}) => {
+ template.CONTEXT = ((context = {}) => {
+ const { START_SCRIPT, ENCODE_START_SCRIPT, ...restOfContext } = context
+
+ if (!START_SCRIPT) return { ...restOfContext }
+ if (!ENCODE_START_SCRIPT) return { ...restOfContext, START_SCRIPT }
+
+ // encode the script if it is not already encoded
+ const encodedScript = isBase64(START_SCRIPT)
+ ? START_SCRIPT
+ : encodeBase64(START_SCRIPT)
+
+ return { ...restOfContext, START_SCRIPT_BASE64: encodedScript }
+ })(template.CONTEXT)
+
+ return { ...template }
+}
+
const Steps = createSteps([General, ExtraConfiguration, CustomVariables], {
transformInitialValue: (vmTemplate, schema) => {
const userInputs = userInputsToArray(vmTemplate?.TEMPLATE?.USER_INPUTS, {
@@ -44,7 +68,10 @@ const Steps = createSteps([General, ExtraConfiguration, CustomVariables], {
[GENERAL_ID]: { ...vmTemplate, ...vmTemplate?.TEMPLATE },
[EXTRA_ID]: { ...vmTemplate?.TEMPLATE, USER_INPUTS: userInputs },
},
- { stripUnknown: true, context: { [EXTRA_ID]: vmTemplate.TEMPLATE } }
+ {
+ stripUnknown: true,
+ context: { ...vmTemplate, [EXTRA_ID]: vmTemplate.TEMPLATE },
+ }
)
const knownAttributes = {
@@ -61,15 +88,18 @@ const Steps = createSteps([General, ExtraConfiguration, CustomVariables], {
// Get the custom vars from the context
const knownContext = reach(schema, `${EXTRA_ID}.CONTEXT`).cast(
vmTemplate?.TEMPLATE?.CONTEXT,
- { stripUnknown: true, context: { extra: vmTemplate.TEMPLATE } }
+ {
+ stripUnknown: true,
+ context: {
+ ...vmTemplate,
+ [EXTRA_ID]: vmTemplate.TEMPLATE,
+ },
+ }
)
// Merge known and unknown context custom vars
knownTemplate[EXTRA_ID].CONTEXT = {
- ...reach(schema, `${EXTRA_ID}.CONTEXT`).cast(
- vmTemplate?.TEMPLATE?.CONTEXT,
- { stripUnknown: true, context: { extra: vmTemplate.TEMPLATE } }
- ),
+ ...knownContext,
...getUnknownAttributes(vmTemplate?.TEMPLATE?.CONTEXT, knownContext),
}
@@ -77,38 +107,25 @@ const Steps = createSteps([General, ExtraConfiguration, CustomVariables], {
},
transformBeforeSubmit: (formData) => {
const {
- [GENERAL_ID]: { MODIFICATION: _, ...general } = {},
+ [GENERAL_ID]: general = {},
[CUSTOM_ID]: customVariables = {},
- [EXTRA_ID]: {
- CONTEXT: { START_SCRIPT, ENCODE_START_SCRIPT, ...restOfContext },
- TOPOLOGY: { ENABLE_NUMA, ...restOfTopology },
- ...extraTemplate
- } = {},
+ [EXTRA_ID]: extraTemplate = {},
} = formData ?? {}
- const context = {
- ...restOfContext,
- // transform start script to base64 if needed
- [ENCODE_START_SCRIPT ? 'START_SCRIPT_BASE64' : 'START_SCRIPT']:
- ENCODE_START_SCRIPT && !isBase64(START_SCRIPT)
- ? encodeBase64(START_SCRIPT)
- : START_SCRIPT,
- }
- const topology = ENABLE_NUMA ? { TOPOLOGY: restOfTopology } : {}
+ ensureContextWithScript(extraTemplate)
// add user inputs to context
Object.keys(extraTemplate?.USER_INPUTS ?? {}).forEach((name) => {
const isCapacity = ['MEMORY', 'CPU', 'VCPU'].includes(name)
const upperName = String(name).toUpperCase()
- !isCapacity && (context[upperName] = `$${upperName}`)
+
+ !isCapacity && (extraTemplate.CONTEXT[upperName] = `$${upperName}`)
})
return jsonToXml({
...customVariables,
...extraTemplate,
...general,
- ...topology,
- CONTEXT: context,
})
},
})
diff --git a/src/fireedge/src/client/components/Tabs/Vm/Configuration.js b/src/fireedge/src/client/components/Tabs/Vm/Configuration.js
index b75b808a57..16b82c6a1b 100644
--- a/src/fireedge/src/client/components/Tabs/Vm/Configuration.js
+++ b/src/fireedge/src/client/components/Tabs/Vm/Configuration.js
@@ -13,52 +13,147 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
-import { ReactElement } from 'react'
+import { ReactElement, useMemo } from 'react'
import PropTypes from 'prop-types'
-import { Accordion, AccordionSummary, AccordionDetails } from '@mui/material'
+import { Box, Stack } from '@mui/material'
-import { useGetVmQuery } from 'client/features/OneApi/vm'
-import { Translate } from 'client/components/HOC'
-import { T } from 'client/constants'
+import {
+ useGetVmQuery,
+ useUpdateConfigurationMutation,
+} from 'client/features/OneApi/vm'
+import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
+import { UpdateConfigurationForm } from 'client/components/Forms/Vm'
+import { List } from 'client/components/Tabs/Common'
+
+import { getHypervisor, isAvailableAction } from 'client/models/VirtualMachine'
+import { getActionsAvailable, jsonToXml } from 'client/models/Helper'
+import { T, VM_ACTIONS, ATTR_CONF_CAN_BE_UPDATED } from 'client/constants'
+
+const { UPDATE_CONF } = VM_ACTIONS
/**
* Renders configuration tab.
*
* @param {object} props - Props
+ * @param {object|boolean} props.tabProps - Tab properties
+ * @param {object} [props.tabProps.actions] - Actions from tab view yaml
* @param {string} props.id - Virtual machine id
* @returns {ReactElement} Configuration tab
*/
-const VmConfigurationTab = ({ id }) => {
- const { data: vm = {} } = useGetVmQuery({ id })
- const { TEMPLATE, USER_TEMPLATE } = vm
+const VmConfigurationTab = ({ tabProps: { actions } = {}, id }) => {
+ const [updateConf] = useUpdateConfigurationMutation()
+ const { data: vm = {}, isFetching } = useGetVmQuery({ id })
+ const { TEMPLATE } = vm
+
+ const hypervisor = useMemo(() => getHypervisor(vm), [vm])
+
+ const isUpdateConfEnabled = useMemo(() => {
+ const actionsByHypervisor = getActionsAvailable(actions, hypervisor)
+ const actionsByState = actionsByHypervisor.filter((action) =>
+ isAvailableAction(action, vm)
+ )
+
+ return actionsByState.includes?.(UPDATE_CONF)
+ }, [vm])
+
+ const [
+ osAttributes,
+ featuresAttributes,
+ inputAttributes,
+ graphicsAttributes,
+ rawAttributes,
+ contextAttributes,
+ ] = useMemo(() => {
+ const filterSection = (section) => {
+ const supported = ATTR_CONF_CAN_BE_UPDATED[section] || '*'
+ const attributes = TEMPLATE[section] || {}
+ const sectionAttributes = []
+
+ const getAttrFromEntry = (key, value, idx) => {
+ const isSupported = supported === '*' || supported.includes(key)
+ const hasValue = typeof value === 'string' && value !== ''
+
+ if (isSupported && hasValue) {
+ const name = idx ? `${idx}.${key}` : key
+ sectionAttributes.push({ name, value, dataCy: name })
+ }
+ }
+
+ const addAttrFromAttributes = (attrs, keyAsIndex) => {
+ for (const [key, value] of Object.entries(attrs)) {
+ typeof value === 'object'
+ ? addAttrFromAttributes(value, key)
+ : getAttrFromEntry(key, value, keyAsIndex)
+ }
+ }
+
+ addAttrFromAttributes(attributes)
+
+ return sectionAttributes
+ }
+
+ return Object.keys(ATTR_CONF_CAN_BE_UPDATED).map(filterSection)
+ }, [TEMPLATE])
+
+ const handleUpdateConf = async (newConfiguration) => {
+ const xml = jsonToXml(newConfiguration)
+ await updateConf({ id, template: xml })
+ }
return (
-
-
-
-
-
-
-
-
- {JSON.stringify(USER_TEMPLATE, null, 2)}
-
-
-
-
-
-
-
-
-
-
-
- {JSON.stringify(TEMPLATE, null, 2)}
-
-
-
-
-
+
+ {isUpdateConfEnabled && (
+
+ UpdateConfigurationForm({
+ stepProps: { hypervisor },
+ initialValues: vm,
+ }),
+ onSubmit: handleUpdateConf,
+ },
+ ]}
+ />
+ )}
+
+
+ {osAttributes?.length > 0 && (
+
+ )}
+ {featuresAttributes?.length > 0 && (
+
+ )}
+ {inputAttributes?.length > 0 && (
+
+ )}
+ {graphicsAttributes?.length > 0 && (
+
+ )}
+ {rawAttributes?.length > 0 && (
+
+ )}
+ {contextAttributes?.length > 0 && (
+
+ )}
+
+
)
}
diff --git a/src/fireedge/src/client/components/Tabs/Vm/Template.js b/src/fireedge/src/client/components/Tabs/Vm/Template.js
new file mode 100644
index 0000000000..8ec6e4f58b
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/Template.js
@@ -0,0 +1,83 @@
+/* ------------------------------------------------------------------------- *
+ * Copyright 2002-2022, 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 { ReactElement } from 'react'
+import PropTypes from 'prop-types'
+import {
+ Box,
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+} from '@mui/material'
+
+import { useGetVmQuery } from 'client/features/OneApi/vm'
+import { Translate } from 'client/components/HOC'
+import { T } from 'client/constants'
+
+/**
+ * Renders template tab.
+ *
+ * @param {object} props - Props
+ * @param {string} props.id - Virtual machine id
+ * @returns {ReactElement} Template tab
+ */
+const TemplateTab = ({ id }) => {
+ const { data: vm = {} } = useGetVmQuery({ id })
+ const { TEMPLATE, USER_TEMPLATE } = vm
+
+ return (
+
+
+
+
+
+
+
+
+ {JSON.stringify(USER_TEMPLATE, null, 2)}
+
+
+
+
+
+
+
+
+
+
+
+ {JSON.stringify(TEMPLATE, null, 2)}
+
+
+
+
+
+ )
+}
+
+TemplateTab.propTypes = {
+ tabProps: PropTypes.object,
+ id: PropTypes.string,
+}
+
+TemplateTab.displayName = 'TemplateTab'
+
+export default TemplateTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/index.js b/src/fireedge/src/client/components/Tabs/Vm/index.js
index e0ae2e0b0c..387d5fbfd3 100644
--- a/src/fireedge/src/client/components/Tabs/Vm/index.js
+++ b/src/fireedge/src/client/components/Tabs/Vm/index.js
@@ -23,23 +23,25 @@ import { getAvailableInfoTabs } from 'client/models/Helper'
import { RESOURCE_NAMES } from 'client/constants'
import Tabs from 'client/components/Tabs'
-import Configuration from 'client/components/Tabs/Vm/Configuration'
import Info from 'client/components/Tabs/Vm/Info'
import Network from 'client/components/Tabs/Vm/Network'
import History from 'client/components/Tabs/Vm/History'
import SchedActions from 'client/components/Tabs/Vm/SchedActions'
import Snapshot from 'client/components/Tabs/Vm/Snapshot'
import Storage from 'client/components/Tabs/Vm/Storage'
+import Configuration from 'client/components/Tabs/Vm/Configuration'
+import Template from 'client/components/Tabs/Vm/Template'
const getTabComponent = (tabName) =>
({
- configuration: Configuration,
info: Info,
network: Network,
history: History,
schedActions: SchedActions,
snapshot: Snapshot,
storage: Storage,
+ configuration: Configuration,
+ template: Template,
}[tabName])
const VmTabs = memo(({ id }) => {
diff --git a/src/fireedge/src/client/components/Tabs/index.js b/src/fireedge/src/client/components/Tabs/index.js
index 3f8fd58641..f8f7e53ee7 100644
--- a/src/fireedge/src/client/components/Tabs/index.js
+++ b/src/fireedge/src/client/components/Tabs/index.js
@@ -99,16 +99,18 @@ const Tabs = ({
}}
{...tabsProps}
>
- {tabs.map(({ id, value, name, label, error, icon: Icon }, idx) => (
- : Icon && }
- value={value ?? idx}
- label={label ?? name}
- data-cy={`tab-${id}`}
- />
- ))}
+ {tabs.map(
+ ({ value, name, id = name, label, error, icon: Icon }, idx) => (
+ : Icon && }
+ value={value ?? idx}
+ label={label ?? id}
+ data-cy={`tab-${id}`}
+ />
+ )
+ )}
),
[tabs, tabSelected]
diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js
index e3d4fbe555..cc84e5f460 100644
--- a/src/fireedge/src/client/constants/translates.js
+++ b/src/fireedge/src/client/constants/translates.js
@@ -157,6 +157,7 @@ module.exports = {
UnReschedule: 'Un-Reschedule',
Unshare: 'Unshare',
Update: 'Update',
+ UpdateVmConfiguration: 'Update VM Configuration',
UpdateProvider: 'Update Provider',
UpdateScheduleAction: 'Update schedule action: %s',
UpdateVmTemplate: 'Update VM Template',
@@ -663,6 +664,7 @@ module.exports = {
Number of iothreads for virtio disks.
By default threads will be assign to disk by round robin algorithm.
Disk thread id can be forced by disk IOTHREAD attribute`,
+ Raw: 'Raw',
RawData: 'Raw data',
RawDataConcept: 'Raw data to be passed directly to the hypervisor',
RawValidateConcept: `
@@ -701,6 +703,7 @@ module.exports = {
ContextCustomVarErrorExists: 'Context Custom Variable already exists',
/* VM Template schema - Input/Output */
InputOrOutput: 'Input / Output',
+ Input: 'Input',
Inputs: 'Inputs',
PciDevices: 'PCI Devices',
DeviceName: 'Device name',
diff --git a/src/fireedge/src/client/constants/vm.js b/src/fireedge/src/client/constants/vm.js
index 79a4f258a3..fec6a63cc6 100644
--- a/src/fireedge/src/client/constants/vm.js
+++ b/src/fireedge/src/client/constants/vm.js
@@ -1151,3 +1151,22 @@ export const EXTERNAL_IP_ATTRS = [
'AZ_IPADDRESS',
'SL_PRIMARYIPADDRESS',
]
+
+/** @enum {string[]} Supported configuration attributes in the VM */
+export const ATTR_CONF_CAN_BE_UPDATED = {
+ OS: [
+ 'ARCH',
+ 'MACHINE',
+ 'KERNEL',
+ 'INITRD',
+ 'BOOTLOADER',
+ 'BOOT',
+ 'SD_DISK_BUS',
+ 'UUID',
+ ],
+ FEATURES: ['ACPI', 'PAE', 'APIC', 'LOCALTIME', 'HYPERV', 'GUEST_AGENT'],
+ INPUT: ['TYPE', 'BUS'],
+ GRAPHICS: ['TYPE', 'LISTEN', 'PASSWD', 'KEYMAP'],
+ RAW: ['DATA', 'DATA_VMX', 'TYPE'],
+ CONTEXT: '*',
+}
diff --git a/src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js b/src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js
index 39dd4b586c..54059ac1bd 100644
--- a/src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js
+++ b/src/fireedge/src/client/containers/Settings/ConfigurationUI/schema.js
@@ -61,9 +61,7 @@ const DISABLE_ANIMATIONS_FIELD = {
name: 'DISABLE_ANIMATIONS',
label: T.DisableDashboardAnimations,
type: INPUT_TYPES.CHECKBOX,
- validation: boolean()
- .yesOrNo()
- .default(() => false),
+ validation: boolean(),
grid: { md: 12 },
}
diff --git a/src/fireedge/src/client/utils/schema.js b/src/fireedge/src/client/utils/schema.js
index 7483747155..cccf58241f 100644
--- a/src/fireedge/src/client/utils/schema.js
+++ b/src/fireedge/src/client/utils/schema.js
@@ -17,6 +17,7 @@
// eslint-disable-next-line no-unused-vars
import { ReactElement, SetStateAction } from 'react'
+
import {
// eslint-disable-next-line no-unused-vars
GridProps,
@@ -174,6 +175,7 @@ import { stringToBoolean } from 'client/models/Helper'
* @typedef {object} ExtraParams
* @property {function(object):object} [transformBeforeSubmit] - Transform validated form data after submit
* @property {function(object, BaseSchema):object} [transformInitialValue] - Transform initial value after load form
+ * @property {ReactElement} [ContentForm] - Render content of form
*/
/**
@@ -500,6 +502,7 @@ export const createForm =
const {
transformBeforeSubmit,
transformInitialValue = defaultTransformInitialValue,
+ ContentForm,
...restOfParams
} = extraParams
@@ -519,6 +522,7 @@ export const createForm =
fields: () => fieldsCallback,
defaultValues,
transformBeforeSubmit,
+ ContentForm: ContentForm && (() => ),
...ensuredExtraParams,
}
}
diff --git a/src/fireedge/src/server/utils/constants/commands/vm.js b/src/fireedge/src/server/utils/constants/commands/vm.js
index e34d38bd8c..5b28486886 100644
--- a/src/fireedge/src/server/utils/constants/commands/vm.js
+++ b/src/fireedge/src/server/utils/constants/commands/vm.js
@@ -561,7 +561,7 @@ module.exports = {
default: 0,
},
template: {
- from: resource,
+ from: postBody,
default: '',
},
},