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 ( - - - - - - - - - - - {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' } }} + + + + + + + {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: '', }, },