diff --git a/src/fireedge/src/client/components/FormControl/ErrorHelper.js b/src/fireedge/src/client/components/FormControl/ErrorHelper.js index 5b14cab79e..e1d32c9227 100644 --- a/src/fireedge/src/client/components/FormControl/ErrorHelper.js +++ b/src/fireedge/src/client/components/FormControl/ErrorHelper.js @@ -28,7 +28,7 @@ const ErrorTypo = styled(Typography)(({ theme }) => ({ })) const ErrorHelper = memo(({ label, children, ...rest }) => { - const ensuredLabel = Array.isArray(label) ? label[0] : label + const ensuredLabel = Array.isArray(label) && label[0]?.word ? label[0] : label const translateProps = ensuredLabel?.word ? { ...ensuredLabel } diff --git a/src/fireedge/src/client/components/FormStepper/index.js b/src/fireedge/src/client/components/FormStepper/index.js index 67124489ee..17823a316d 100644 --- a/src/fireedge/src/client/components/FormStepper/index.js +++ b/src/fireedge/src/client/components/FormStepper/index.js @@ -36,7 +36,13 @@ const FIRST_STEP = 0 * @param {StepsForm} stepsForm - Steps form config * @returns {ReactElement} Stepper form component */ -const DefaultFormStepper = ({ onSubmit, steps, defaultValues, resolver }) => { +const DefaultFormStepper = ({ + onSubmit, + steps, + defaultValues, + resolver, + initialValues, +}) => { const methods = useForm({ mode: 'onSubmit', defaultValues, @@ -44,7 +50,7 @@ const DefaultFormStepper = ({ onSubmit, steps, defaultValues, resolver }) => { }) return ( - + ) @@ -54,6 +60,7 @@ DefaultFormStepper.propTypes = { onSubmit: PropTypes.func, steps: PropTypes.arrayOf(PropTypes.object), defaultValues: PropTypes.object, + initialValues: PropTypes.object, resolver: PropTypes.func, } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables.js similarity index 75% rename from src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/index.js rename to src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables.js index 31ed98bf05..a46f011e7e 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables.js @@ -13,22 +13,14 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import PropTypes from 'prop-types' -import { Box } from '@mui/material' +import { useCallback } from 'react' +import { object } from 'yup' import { useFormContext, useWatch } from 'react-hook-form' +import { Box } from '@mui/material' import { AttributePanel } from 'client/components/Tabs/Common' -import { SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/schema' import { cleanEmpty, cloneObject, set } from 'client/utils' -import { T, ACTIONS } from 'client/constants' - -const ALL_ACTIONS = [ - ACTIONS.COPY_ATTRIBUTE, - ACTIONS.ADD_ATTRIBUTE, - ACTIONS.EDIT_ATTRIBUTE, - ACTIONS.DELETE_ATTRIBUTE, -] +import { T } from 'client/constants' export const STEP_ID = 'custom-variables' @@ -36,37 +28,41 @@ const Content = () => { const { setValue } = useFormContext() const customVars = useWatch({ name: STEP_ID }) - const handleChangeAttribute = (path, newValue) => { - const newCustomVars = cloneObject(customVars) + const handleChangeAttribute = useCallback( + (path, newValue) => { + const newCustomVars = cloneObject(customVars) - set(newCustomVars, path, newValue) - setValue(STEP_ID, cleanEmpty(newCustomVars)) - } + set(newCustomVars, path, newValue) + setValue(STEP_ID, cleanEmpty(newCustomVars)) + }, + [customVars] + ) return ( ) } +/** + * Custom variables about VM Template. + * + * @returns {object} Custom configuration step + */ const CustomVariables = () => ({ id: STEP_ID, label: T.CustomVariables, - resolver: SCHEMA, + resolver: object(), optionsValidate: { abortEarly: false }, content: Content, }) -Content.propTypes = { - data: PropTypes.any, -} - export default CustomVariables diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/schema.js deleted file mode 100644 index 54c5f34d4b..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables/schema.js +++ /dev/null @@ -1,24 +0,0 @@ -/* ------------------------------------------------------------------------- * - * 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 {} from 'client/utils' -import {} from 'client/constants' - -/** @type {ObjectSchema} Step schema */ -const SCHEMA = object() - -export { SCHEMA } 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 new file mode 100644 index 0000000000..5573b4a02d --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/contextVarsSection.js @@ -0,0 +1,108 @@ +/* ------------------------------------------------------------------------- * + * 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, useCallback, useMemo } from 'react' +import PropTypes from 'prop-types' +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' +import { AttributePanel } from 'client/components/Tabs/Common' +import { getUnknownAttributes } from 'client/utils' +import { T } from 'client/constants' + +export const SECTION_ID = 'CONTEXT' + +/** + * Renders the context section of the extra configuration form. + * + * @param {object} props - Props passed to the component + * @param {string} props.hypervisor - VM hypervisor + * @returns {ReactElement} - Context vars section + */ +const ContextVarsSection = ({ hypervisor }) => { + const { enqueueError } = useGeneralApi() + const { setValue } = useFormContext() + const customVars = useWatch({ name: `${EXTRA_ID}.${SECTION_ID}` }) + + const unknownVars = useMemo(() => { + const knownVars = CONTEXT_SCHEMA(hypervisor).cast( + { [SECTION_ID]: customVars }, + { stripUnknown: true } + ) + + const currentContext = knownVars?.[SECTION_ID] || {} + + return getUnknownAttributes(customVars, currentContext) + }, [customVars]) + + const handleChangeAttribute = useCallback( + (path, newValue) => { + const contextPath = `${SECTION_ID}.${path}` + const formPath = `${EXTRA_ID}.${contextPath}` + + try { + // retrieve the schema for the given path + reach(CONTEXT_SCHEMA(hypervisor), contextPath) + enqueueError(T.ContextCustomVarErrorExists) + } catch (e) { + // When the path is not found, it means that + // the attribute is correct and we can set it + setValue(formPath, newValue) + } + }, + [hypervisor] + ) + + return ( + + + + + + + + + ) +} + +ContextVarsSection.propTypes = { + data: PropTypes.any, + setFormData: PropTypes.func, + hypervisor: PropTypes.string, + control: PropTypes.object, +} + +export default ContextVarsSection 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 3eef08fe7e..dbbb672401 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 @@ -22,6 +22,7 @@ import UserInputsSection, { } from './userInputsSection' import ConfigurationSection from './configurationSection' import FilesSection from './filesSection' +import ContextVarsSection from './contextVarsSection' import { T } from 'client/constants' @@ -32,6 +33,7 @@ const Context = (props) => ( + ) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js index a0cc222bfd..35cdf8e045 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/index.js @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ // eslint-disable-next-line no-unused-vars import { useMemo, ReactElement } from 'react' import PropTypes from 'prop-types' @@ -36,7 +35,7 @@ import Numa from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfi import { STEP_ID as GENERAL_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General' import { SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper' -import { T, RESOURCE_NAMES } from 'client/constants' +import { T, RESOURCE_NAMES, VmTemplate } from 'client/constants' /** * @typedef {object} TabType @@ -99,17 +98,27 @@ const Content = ({ data, setFormData }) => { return } -const ExtraConfiguration = () => ({ - id: STEP_ID, - label: T.AdvancedOptions, - resolver: (formData) => { - const hypervisor = formData?.[GENERAL_ID]?.HYPERVISOR +/** + * Optional configuration about VM Template. + * + * @param {VmTemplate} vmTemplate - VM Template + * @returns {object} Optional configuration step + */ +const ExtraConfiguration = (vmTemplate) => { + const initialHypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR - return SCHEMA(hypervisor) - }, - optionsValidate: { abortEarly: false }, - content: Content, -}) + return { + id: STEP_ID, + label: T.AdvancedOptions, + resolver: (formData) => { + const hypervisor = formData?.[GENERAL_ID]?.HYPERVISOR ?? initialHypervisor + + return SCHEMA(hypervisor) + }, + optionsValidate: { abortEarly: false }, + content: Content, + } +} Content.propTypes = { data: PropTypes.any, diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/styles.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/styles.js index 8382b46225..0129cba235 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/styles.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/styles.js @@ -20,6 +20,7 @@ export default makeStyles((theme) => ({ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: theme.spacing(1), + overflow: 'auto', [theme.breakpoints.down('md')]: { gridTemplateColumns: '1fr', }, 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 0d8e82cda0..9a13b0c7ed 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 @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ +import { reach } from 'yup' + import General, { STEP_ID as GENERAL_ID, } from 'client/components/Forms/VmTemplate/CreateForm/Steps/General' @@ -22,8 +24,14 @@ import ExtraConfiguration, { import CustomVariables, { STEP_ID as CUSTOM_ID, } from 'client/components/Forms/VmTemplate/CreateForm/Steps/CustomVariables' + import { jsonToXml, userInputsToArray } from 'client/models/Helper' -import { createSteps, isBase64, encodeBase64 } from 'client/utils' +import { + createSteps, + isBase64, + encodeBase64, + getUnknownAttributes, +} from 'client/utils' const Steps = createSteps([General, ExtraConfiguration, CustomVariables], { transformInitialValue: (vmTemplate, schema) => { @@ -39,19 +47,33 @@ const Steps = createSteps([General, ExtraConfiguration, CustomVariables], { { stripUnknown: true, context: { [EXTRA_ID]: vmTemplate.TEMPLATE } } ) - const customVars = {} - const knownAttributes = Object.getOwnPropertyNames({ + const knownAttributes = { ...knownTemplate[GENERAL_ID], ...knownTemplate[EXTRA_ID], - }) + } - Object.entries(vmTemplate?.TEMPLATE).forEach(([key, value]) => { - if (!knownAttributes.includes(key) && value) { - customVars[key] = value - } - }) + // Set the unknown attributes to the custom variables section + knownTemplate[CUSTOM_ID] = getUnknownAttributes( + vmTemplate?.TEMPLATE, + knownAttributes + ) - return { ...knownTemplate, [CUSTOM_ID]: customVars } + // Get the custom vars from the context + const knownContext = reach(schema, `${EXTRA_ID}.CONTEXT`).cast( + vmTemplate?.TEMPLATE?.CONTEXT, + { stripUnknown: true, context: { extra: 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 } } + ), + ...getUnknownAttributes(vmTemplate?.TEMPLATE?.CONTEXT, knownContext), + } + + return knownTemplate }, transformBeforeSubmit: (formData) => { const { diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles.js index 001ef7ac41..467adf0b69 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/styles.js @@ -20,6 +20,7 @@ export default makeStyles((theme) => ({ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2em', + overflow: 'auto', [theme.breakpoints.down('lg')]: { gridTemplateColumns: '1fr', }, diff --git a/src/fireedge/src/client/components/HOC/InternalLayout/styles.js b/src/fireedge/src/client/components/HOC/InternalLayout/styles.js index ef30164530..548787d78f 100644 --- a/src/fireedge/src/client/components/HOC/InternalLayout/styles.js +++ b/src/fireedge/src/client/components/HOC/InternalLayout/styles.js @@ -45,6 +45,8 @@ export default makeStyles((theme) => ({ paddingBottom: theme.spacing(2), height: '100%', overflow: 'auto', + display: 'flex', + flexDirection: 'column', }, /* ROUTES TRANSITIONS */ appear: {}, diff --git a/src/fireedge/src/client/components/Tabs/Common/AttributePanel.js b/src/fireedge/src/client/components/Tabs/Common/AttributePanel.js index e4139fb44e..ad1f1b7cf0 100644 --- a/src/fireedge/src/client/components/Tabs/Common/AttributePanel.js +++ b/src/fireedge/src/client/components/Tabs/Common/AttributePanel.js @@ -42,6 +42,8 @@ const { DELETE_ATTRIBUTE: DELETE, } = ACTIONS +const ALL_ACTIONS = [COPY, ADD, EDIT, DELETE] + // This attributes has special restrictions const SPECIAL_ATTRIBUTES = { VCENTER_CCR_REF: { @@ -87,7 +89,8 @@ const AttributePanel = memo( handleEdit, handleDelete, handleAdd, - actions = [], + allActionsEnabled = true, + actions = allActionsEnabled ? ALL_ACTIONS : [], filtersSpecialAttributes = true, collapse = false, }) => { @@ -136,6 +139,7 @@ AttributePanel.propTypes = { handleDelete: PropTypes.func, title: PropTypes.string, filtersSpecialAttributes: PropTypes.bool, + allActionsEnabled: PropTypes.bool, collapse: PropTypes.bool, } diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 325771999c..ad75c177c2 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -691,6 +691,10 @@ module.exports = { The contextualization package executes an init.sh file if it exists. If more than one script file is added, this list contains the scripts to run and their order`, + ContextCustomVariables: 'Context Custom Variables', + ContextCustomVariablesConcept: + 'Context information will be send to the VM at boot time', + ContextCustomVarErrorExists: 'Context Custom Variable already exists', /* VM Template schema - Input/Output */ InputOrOutput: 'Input / Output', Inputs: 'Inputs', diff --git a/src/fireedge/src/client/utils/helpers.js b/src/fireedge/src/client/utils/helpers.js index 43df45805b..97d7692129 100644 --- a/src/fireedge/src/client/utils/helpers.js +++ b/src/fireedge/src/client/utils/helpers.js @@ -478,3 +478,27 @@ export const intersperse = (arr, sep) => { .slice(1) .reduce((xs, x, i) => xs.concat([sep, x]), [ensuredArr[0]]) } + +/** + * Returns the unknown properties of an object. + * + * @param {object} obj - Object + * @param {string[]|object} knownAttributes - Attributes to check + * @returns {object} Returns object with unknown properties + */ +export const getUnknownAttributes = (obj, knownAttributes) => { + const unknown = {} + const entries = Object.entries(obj) + + const attributes = Array.isArray(knownAttributes) + ? knownAttributes + : Object.getOwnPropertyNames({ ...knownAttributes }) + + for (const [key, value] of entries) { + if (!attributes.includes(key) && value !== undefined) { + unknown[key] = obj[key] + } + } + + return unknown +} diff --git a/src/fireedge/src/client/utils/schema.js b/src/fireedge/src/client/utils/schema.js index 096be66c77..7483747155 100644 --- a/src/fireedge/src/client/utils/schema.js +++ b/src/fireedge/src/client/utils/schema.js @@ -475,6 +475,7 @@ export const createSteps = steps: performedSteps, defaultValues, resolver: generateSchema, + initialValues, ...extraParams, } }