mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-22 18:50:08 +03:00
parent
77556f1b8f
commit
d91510862e
@ -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 }
|
||||
|
@ -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 (
|
||||
<FormProvider {...methods}>
|
||||
<FormProvider {...methods} initialValues={initialValues}>
|
||||
<FormStepper steps={steps} schema={resolver} onSubmit={onSubmit} />
|
||||
</FormProvider>
|
||||
)
|
||||
@ -54,6 +60,7 @@ DefaultFormStepper.propTypes = {
|
||||
onSubmit: PropTypes.func,
|
||||
steps: PropTypes.arrayOf(PropTypes.object),
|
||||
defaultValues: PropTypes.object,
|
||||
initialValues: PropTypes.object,
|
||||
resolver: PropTypes.func,
|
||||
}
|
||||
|
||||
|
@ -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 (
|
||||
<Box display="grid" gap="1em">
|
||||
<AttributePanel
|
||||
allActionsEnabled
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={customVars}
|
||||
actions={ALL_ACTIONS}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
@ -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 }
|
@ -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 (
|
||||
<Box display="grid" gap="1em">
|
||||
<Accordion
|
||||
variant="transparent"
|
||||
TransitionProps={{ unmountOnExit: false }}
|
||||
>
|
||||
<AccordionSummary>
|
||||
<Legend
|
||||
disableGutters
|
||||
title={T.ContextCustomVariables}
|
||||
tooltip={T.ContextCustomVariablesConcept}
|
||||
/>
|
||||
</AccordionSummary>
|
||||
<AttributePanel
|
||||
allActionsEnabled
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={unknownVars}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
</Accordion>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
ContextVarsSection.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
hypervisor: PropTypes.string,
|
||||
control: PropTypes.object,
|
||||
}
|
||||
|
||||
export default ContextVarsSection
|
@ -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) => (
|
||||
<ConfigurationSection />
|
||||
<UserInputsSection />
|
||||
<FilesSection {...props} />
|
||||
<ContextVarsSection {...props} />
|
||||
</>
|
||||
)
|
||||
|
||||
|
@ -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 <Tabs tabs={tabs} />
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -20,6 +20,7 @@ export default makeStyles((theme) => ({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '2em',
|
||||
overflow: 'auto',
|
||||
[theme.breakpoints.down('lg')]: {
|
||||
gridTemplateColumns: '1fr',
|
||||
},
|
||||
|
@ -45,6 +45,8 @@ export default makeStyles((theme) => ({
|
||||
paddingBottom: theme.spacing(2),
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
/* ROUTES TRANSITIONS */
|
||||
appear: {},
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -475,6 +475,7 @@ export const createSteps =
|
||||
steps: performedSteps,
|
||||
defaultValues,
|
||||
resolver: generateSchema,
|
||||
initialValues,
|
||||
...extraParams,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user