mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
60c033b563
commit
67c626832f
@ -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 ? (
|
||||
<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
|
||||
|
@ -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 }) => (
|
||||
<FormWithSchema key={id} cy={id} {...section} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
OsSection.propTypes = { hypervisor: PropTypes.string }
|
||||
|
||||
export default OsSection
|
@ -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 }
|
@ -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: <Translate word={T.OSAndCpu} />,
|
||||
renderContent: () => <Booting hypervisor={hypervisor} />,
|
||||
error: !!errors?.OS,
|
||||
},
|
||||
{
|
||||
id: 'input_output',
|
||||
icon: IOIcon,
|
||||
label: <Translate word={T.InputOrOutput} />,
|
||||
renderContent: () => <InputOutput hypervisor={hypervisor} />,
|
||||
error: ['GRAPHICS', 'INPUT'].some((id) => errors?.[id]),
|
||||
},
|
||||
{
|
||||
id: 'context',
|
||||
icon: ContextIcon,
|
||||
label: <Translate word={T.Context} />,
|
||||
renderContent: () => <Context hypervisor={hypervisor} />,
|
||||
error: !!errors?.CONTEXT,
|
||||
},
|
||||
],
|
||||
[errors, hypervisor]
|
||||
)
|
||||
|
||||
return <Tabs tabs={tabs} />
|
||||
}
|
||||
|
||||
Content.propTypes = { hypervisor: PropTypes.string }
|
||||
|
||||
export default Content
|
@ -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 }) => (
|
||||
<>
|
||||
<ConfigurationSection hypervisor={hypervisor} />
|
||||
<FilesSection hypervisor={hypervisor} />
|
||||
<ContextVarsSection hypervisor={hypervisor} />
|
||||
</>
|
||||
)
|
||||
|
||||
ContextSection.propTypes = { hypervisor: PropTypes.string }
|
||||
|
||||
export default ContextSection
|
@ -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))
|
@ -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
|
@ -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 }) => (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={'io-graphics'}
|
||||
fields={useMemo(() => GRAPHICS_FIELDS({ hypervisor }), [hypervisor])}
|
||||
legend={T.Graphics}
|
||||
/>
|
||||
<InputsSection hypervisor={hypervisor} />
|
||||
</Stack>
|
||||
)
|
||||
|
||||
InputOutput.propTypes = { hypervisor: PropTypes.string }
|
||||
InputOutput.displayName = 'InputOutput'
|
||||
|
||||
export default InputOutput
|
@ -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 }))
|
@ -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 }
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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 },
|
||||
}
|
||||
|
@ -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 (
|
||||
<FormControl component="fieldset" sx={{ width: '100%' }}>
|
||||
@ -51,22 +64,22 @@ const ConfigurationSection = () => {
|
||||
sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-context-configuration-others`}
|
||||
id={stepId}
|
||||
cy={getCyPath('context-configuration-others')}
|
||||
fields={OTHER_FIELDS}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
<div>
|
||||
<section>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-context-ssh-public-key`}
|
||||
id={stepId}
|
||||
cy={getCyPath('context-ssh-public-key')}
|
||||
fields={[SSH_PUBLIC_KEY]}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
<Stack direction="row" gap="1em">
|
||||
<Button
|
||||
onClick={handleAddUserKey}
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
data-cy={`${EXTRA_ID}-add-context-ssh-public-key`}
|
||||
data-cy={getCyPath('add-context-ssh-public-key')}
|
||||
>
|
||||
{T.AddUserSshPublicKey}
|
||||
</Button>
|
||||
@ -78,11 +91,11 @@ const ConfigurationSection = () => {
|
||||
{T.Clear}
|
||||
</Button>
|
||||
</Stack>
|
||||
</div>
|
||||
</section>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-context-script`}
|
||||
id={stepId}
|
||||
cy={getCyPath('context-script')}
|
||||
fields={SCRIPT_FIELDS}
|
||||
id={EXTRA_ID}
|
||||
rootProps={{ sx: { width: '100%', gridColumn: '1 / -1' } }}
|
||||
/>
|
||||
</Stack>
|
||||
@ -90,4 +103,9 @@ const ConfigurationSection = () => {
|
||||
)
|
||||
}
|
||||
|
||||
ConfigurationSection.propTypes = {
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ConfigurationSection
|
||||
|
@ -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
|
||||
|
@ -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 }) => (
|
||||
<FormWithSchema
|
||||
accordion
|
||||
cy={`${EXTRA_ID}-context-files`}
|
||||
legend={T.Files}
|
||||
fields={() => 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
|
||||
|
@ -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) => (
|
||||
<>
|
||||
<ConfigurationSection />
|
||||
<ConfigurationSection stepId={EXTRA_ID} />
|
||||
<UserInputsSection />
|
||||
<FilesSection {...props} />
|
||||
<ContextVarsSection {...props} />
|
||||
<FilesSection stepId={EXTRA_ID} {...props} />
|
||||
<ContextVarsSection stepId={EXTRA_ID} {...props} />
|
||||
</>
|
||||
)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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 (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-io-graphics`}
|
||||
fields={GRAPHICS_FIELDS(hypervisor)}
|
||||
legend={T.Graphics}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
{inputsFields.length > 0 && <InputsSection fields={inputsFields} />}
|
||||
{pciDevicesFields.length > 0 && (
|
||||
<PciDevicesSection fields={pciDevicesFields} />
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
const InputOutput = ({ hypervisor }) => (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-io-graphics`}
|
||||
fields={GRAPHICS_FIELDS(hypervisor)}
|
||||
legend={T.Graphics}
|
||||
id={EXTRA_ID}
|
||||
/>
|
||||
<InputsSection stepId={EXTRA_ID} hypervisor={hypervisor} />
|
||||
<PciDevicesSection stepId={EXTRA_ID} hypervisor={hypervisor} />
|
||||
</Stack>
|
||||
)
|
||||
|
||||
InputOutput.propTypes = {
|
||||
data: PropTypes.any,
|
||||
|
@ -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 (
|
||||
<FormControl component="fieldset" sx={{ width: '100%' }}>
|
||||
<Legend title={T.Inputs} />
|
||||
<FormProvider {...methods}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
gap="0.5rem"
|
||||
component="form"
|
||||
onSubmit={methods.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-io-inputs`}
|
||||
fields={fields}
|
||||
rootProps={{ sx: { m: 0 } }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
color="secondary"
|
||||
startIcon={<AddCircledOutline />}
|
||||
sx={{ mt: '1em' }}
|
||||
data-cy={`${EXTRA_ID}-add-io-inputs`}
|
||||
const methods = useForm({
|
||||
defaultValues: INPUT_SCHEMA.default(),
|
||||
resolver: yupResolver(INPUT_SCHEMA),
|
||||
})
|
||||
|
||||
const onSubmit = (newInput) => {
|
||||
append(newInput)
|
||||
methods.reset()
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl component="fieldset" sx={{ width: '100%' }}>
|
||||
<Legend title={T.Inputs} />
|
||||
<FormProvider {...methods}>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="flex-start"
|
||||
gap="0.5rem"
|
||||
component="form"
|
||||
onSubmit={methods.handleSubmit(onSubmit)}
|
||||
>
|
||||
<Translate word={T.Add} />
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
<Divider />
|
||||
<List>
|
||||
{inputs?.map(({ id, TYPE, BUS }, index) => {
|
||||
const deviceIcon = deviceTypeIcons[TYPE]
|
||||
const deviceInfo = `${TYPE}`
|
||||
const busIcon = busTypeIcons[BUS]
|
||||
const busInfo = `${BUS}`
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={id}
|
||||
secondaryAction={
|
||||
<IconButton onClick={() => remove(index)}>
|
||||
<DeleteCircledOutline />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ '&:hover': { bgcolor: 'action.hover' } }}
|
||||
<FormWithSchema
|
||||
cy={getCyPath('io-inputs')}
|
||||
fields={fields}
|
||||
rootProps={{ sx: { m: 0 } }}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
color="secondary"
|
||||
startIcon={<AddCircledOutline />}
|
||||
data-cy={getCyPath('add-io-inputs')}
|
||||
sx={{ mt: '1em' }}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Stack
|
||||
component="span"
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{ '& > *': { width: 36 } }}
|
||||
>
|
||||
{deviceIcon}
|
||||
<span>{deviceInfo}</span>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
{busIcon}
|
||||
<span>{busInfo}</span>
|
||||
</Stack>
|
||||
<Translate word={T.Add} />
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
<Divider />
|
||||
<List>
|
||||
{inputs?.map(({ id, TYPE, BUS }, index) => {
|
||||
const deviceIcon = deviceTypeIcons[TYPE]
|
||||
const deviceInfo = `${TYPE}`
|
||||
const busIcon = busTypeIcons[BUS]
|
||||
const busInfo = `${BUS}`
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={id}
|
||||
secondaryAction={
|
||||
<IconButton onClick={() => remove(index)}>
|
||||
<DeleteCircledOutline />
|
||||
</IconButton>
|
||||
}
|
||||
primaryTypographyProps={{ variant: 'body1' }}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
sx={{ '&:hover': { bgcolor: 'action.hover' } }}
|
||||
>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Stack
|
||||
component="span"
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{ '& > *': { width: 36 } }}
|
||||
>
|
||||
{deviceIcon}
|
||||
<span>{deviceInfo}</span>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
{busIcon}
|
||||
<span>{busInfo}</span>
|
||||
</Stack>
|
||||
}
|
||||
primaryTypographyProps={{ variant: 'body1' }}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
InputsSection.propTypes = {
|
||||
fields: PropTypes.array,
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
}
|
||||
|
||||
InputsSection.displayName = 'InputsSection'
|
||||
|
@ -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)}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`${EXTRA_ID}-io-pci-devices`}
|
||||
cy={[stepId, 'io-pci-devices'].filter(Boolean).join('.')}
|
||||
fields={fields}
|
||||
rootProps={{ sx: { m: 0 } }}
|
||||
/>
|
||||
@ -130,7 +135,8 @@ const PciDevicesSection = ({ fields }) => {
|
||||
}
|
||||
|
||||
PciDevicesSection.propTypes = {
|
||||
fields: PropTypes.array,
|
||||
stepId: PropTypes.string,
|
||||
hypervisor: PropTypes.string,
|
||||
}
|
||||
|
||||
PciDevicesSection.displayName = 'PciDevicesSection'
|
||||
|
@ -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,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
@ -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 (
|
||||
<div>
|
||||
<Accordion variant="outlined">
|
||||
<AccordionSummary>
|
||||
<Translate word={T.UserTemplate} />
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<pre>
|
||||
<code style={{ whiteSpace: 'break-spaces' }}>
|
||||
{JSON.stringify(USER_TEMPLATE, null, 2)}
|
||||
</code>
|
||||
</pre>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Accordion variant="outlined">
|
||||
<AccordionSummary>
|
||||
<Translate word={T.Template} />
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<pre>
|
||||
<code style={{ whiteSpace: 'break-spaces' }}>
|
||||
{JSON.stringify(TEMPLATE, null, 2)}
|
||||
</code>
|
||||
</pre>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
<Box>
|
||||
{isUpdateConfEnabled && (
|
||||
<ButtonToTriggerForm
|
||||
buttonProps={{
|
||||
color: 'secondary',
|
||||
'data-cy': 'update-conf',
|
||||
label: T.UpdateVmConfiguration,
|
||||
variant: 'outlined',
|
||||
disabled: isFetching,
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: T.UpdateVmConfiguration,
|
||||
dataCy: 'modal-update-conf',
|
||||
},
|
||||
form: () =>
|
||||
UpdateConfigurationForm({
|
||||
stepProps: { hypervisor },
|
||||
initialValues: vm,
|
||||
}),
|
||||
onSubmit: handleUpdateConf,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(49%, 1fr))"
|
||||
marginTop="0.5em"
|
||||
>
|
||||
{osAttributes?.length > 0 && (
|
||||
<List title={T.OSAndCpu} list={osAttributes} />
|
||||
)}
|
||||
{featuresAttributes?.length > 0 && (
|
||||
<List title={T.Features} list={featuresAttributes} />
|
||||
)}
|
||||
{inputAttributes?.length > 0 && (
|
||||
<List title={T.Input} list={inputAttributes} />
|
||||
)}
|
||||
{graphicsAttributes?.length > 0 && (
|
||||
<List title={T.Graphics} list={graphicsAttributes} />
|
||||
)}
|
||||
{rawAttributes?.length > 0 && (
|
||||
<List title={T.Raw} list={rawAttributes} />
|
||||
)}
|
||||
{contextAttributes?.length > 0 && (
|
||||
<List title={T.Context} list={contextAttributes} />
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
|
83
src/fireedge/src/client/components/Tabs/Vm/Template.js
Normal file
83
src/fireedge/src/client/components/Tabs/Vm/Template.js
Normal file
@ -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 (
|
||||
<div>
|
||||
<Accordion variant="outlined">
|
||||
<AccordionSummary>
|
||||
<Translate word={T.UserTemplate} />
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box component="pre">
|
||||
<Box
|
||||
component="code"
|
||||
sx={{ whiteSpace: 'break-spaces', wordBreak: 'break-all' }}
|
||||
>
|
||||
{JSON.stringify(USER_TEMPLATE, null, 2)}
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Accordion variant="outlined">
|
||||
<AccordionSummary>
|
||||
<Translate word={T.Template} />
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box component="pre">
|
||||
<Box
|
||||
component="code"
|
||||
sx={{ whiteSpace: 'break-spaces', wordBreak: 'break-all' }}
|
||||
>
|
||||
{JSON.stringify(TEMPLATE, null, 2)}
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TemplateTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
TemplateTab.displayName = 'TemplateTab'
|
||||
|
||||
export default TemplateTab
|
@ -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 }) => {
|
||||
|
@ -99,16 +99,18 @@ const Tabs = ({
|
||||
}}
|
||||
{...tabsProps}
|
||||
>
|
||||
{tabs.map(({ id, value, name, label, error, icon: Icon }, idx) => (
|
||||
<MTab
|
||||
key={`tab-${name}`}
|
||||
id={`tab-${name}`}
|
||||
icon={error ? <WarningIcon /> : Icon && <Icon />}
|
||||
value={value ?? idx}
|
||||
label={label ?? name}
|
||||
data-cy={`tab-${id}`}
|
||||
/>
|
||||
))}
|
||||
{tabs.map(
|
||||
({ value, name, id = name, label, error, icon: Icon }, idx) => (
|
||||
<MTab
|
||||
key={`tab-${id}`}
|
||||
id={`tab-${id}`}
|
||||
icon={error ? <WarningIcon /> : Icon && <Icon />}
|
||||
value={value ?? idx}
|
||||
label={label ?? id}
|
||||
data-cy={`tab-${id}`}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</MTabs>
|
||||
),
|
||||
[tabs, tabSelected]
|
||||
|
@ -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',
|
||||
|
@ -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: '*',
|
||||
}
|
||||
|
@ -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 },
|
||||
}
|
||||
|
||||
|
@ -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 && (() => <ContentForm {...props} />),
|
||||
...ensuredExtraParams,
|
||||
}
|
||||
}
|
||||
|
@ -561,7 +561,7 @@ module.exports = {
|
||||
default: 0,
|
||||
},
|
||||
template: {
|
||||
from: resource,
|
||||
from: postBody,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user