From a3a2dc7a8bd13583402395de4a99a94fc851e6c6 Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Tue, 31 May 2022 17:35:13 +0200 Subject: [PATCH] F #5422: Improves to VM Template form (#2114) --- .../Steps/ExtraConfiguration/numa/schema.js | 159 ++++++++++-------- .../Steps/ExtraConfiguration/schema.js | 9 +- .../CreateForm/Steps/General/capacityUtils.js | 13 +- .../components/Tables/VmTemplates/actions.js | 31 ++-- .../containers/VirtualMachines/index.js | 36 ++-- .../containers/VmTemplates/Instantiate.js | 3 +- 6 files changed, 135 insertions(+), 116 deletions(-) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema.js index 55ee8602e0..5b6ecbd3d0 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { string, number, boolean, lazy } from 'yup' +import { string, number, boolean, lazy, ObjectSchema } from 'yup' import { useGetHostsQuery } from 'client/features/OneApi/host' import { getHugepageSizes } from 'client/models/Host' @@ -31,12 +31,11 @@ import { sentenceCase, prettyBytes, arrayToOptions, + getObjectSchemaFromFields, } from 'client/utils' const { vcenter, firecracker } = HYPERVISORS -const threadsValidation = number().nullable().notRequired().integer() - const ENABLE_NUMA = { name: 'TOPOLOGY.ENABLE_NUMA', label: T.NumaTopology, @@ -48,104 +47,105 @@ const ENABLE_NUMA = { grid: { md: 12 }, } -/** - * @param {HYPERVISORS} hypervisor - VM hypervisor - * @returns {Field} Pin policy field - */ -const PIN_POLICY = (hypervisor) => { - const isVCenter = hypervisor === vcenter - const isFirecracker = hypervisor === firecracker - - return { - name: 'TOPOLOGY.PIN_POLICY', - label: T.PinPolicy, - tooltip: [T.PinPolicyConcept, NUMA_PIN_POLICIES.join(', ')], - type: INPUT_TYPES.SELECT, - values: arrayToOptions(NUMA_PIN_POLICIES, { - addEmpty: false, - getText: sentenceCase, - }), - validation: string() +/** @type {Field} Pin policy field */ +const PIN_POLICY = { + name: 'TOPOLOGY.PIN_POLICY', + label: T.PinPolicy, + tooltip: [T.PinPolicyConcept, NUMA_PIN_POLICIES.join(', ')], + type: INPUT_TYPES.SELECT, + values: arrayToOptions(NUMA_PIN_POLICIES, { + addEmpty: false, + getText: sentenceCase, + }), + dependOf: '$general.HYPERVISOR', + validation: lazy((_, { context }) => + string() .trim() .notRequired() .default( () => - isFirecracker + context?.general?.HYPERVISOR === firecracker ? NUMA_PIN_POLICIES[2] // SHARED : NUMA_PIN_POLICIES[0] // NONE - ), - fieldProps: { disabled: isVCenter || isFirecracker }, - } + ) + ), + fieldProps: (hypervisor) => ({ + disabled: [vcenter, firecracker].includes(hypervisor), + }), } -/** - * @param {HYPERVISORS} hypervisor - VM hypervisor - * @returns {Field} Cores field - */ -const CORES = (hypervisor) => ({ +/** @type {Field} Cores field */ +const CORES = { name: 'TOPOLOGY.CORES', label: T.Cores, tooltip: T.NumaCoresConcept, - dependOf: '$general.VCPU', - type: hypervisor === vcenter ? INPUT_TYPES.SELECT : INPUT_TYPES.TEXT, + dependOf: ['$general.VCPU', '$general.HYPERVISOR'], + type: ([, hypervisor] = []) => + hypervisor === vcenter ? INPUT_TYPES.SELECT : INPUT_TYPES.TEXT, htmlType: 'number', - values: (vcpu) => arrayToOptions(getFactorsOfNumber(vcpu ?? 0)), + values: ([vcpu] = []) => arrayToOptions(getFactorsOfNumber(vcpu ?? 0)), validation: number() .notRequired() .integer() .default(() => undefined), -}) +} -/** - * @param {HYPERVISORS} hypervisor - VM hypervisor - * @returns {Field} Sockets field - */ -const SOCKETS = (hypervisor) => ({ +/** @type {Field} Sockets field */ +const SOCKETS = { name: 'TOPOLOGY.SOCKETS', label: T.Sockets, tooltip: T.NumaSocketsConcept, type: INPUT_TYPES.TEXT, htmlType: 'number', + dependOf: ['$general.HYPERVISOR', '$general.VCPU', 'TOPOLOGY.CORES'], validation: number() .notRequired() .integer() .default(() => 1), - fieldProps: { - disabled: hypervisor === firecracker, - }, - ...(hypervisor === vcenter && { - fieldProps: { disabled: true }, - dependOf: ['$general.VCPU', 'TOPOLOGY.CORES'], - watcher: ([vcpu, cores] = []) => { - if (!isNaN(+vcpu) && !isNaN(+cores) && +cores !== 0) { - return vcpu / cores - } - }, + fieldProps: (hypervisor) => ({ + disabled: [vcenter, firecracker].includes(hypervisor), }), -}) + watcher: ([hypervisor, vcpu, cores] = []) => { + if (hypervisor === vcenter) return -/** - * @param {HYPERVISORS} hypervisor - VM hypervisor - * @returns {Field} Threads field - */ -const THREADS = (hypervisor) => ({ + if (!isNaN(+vcpu) && !isNaN(+cores) && +cores !== 0) { + return vcpu / cores + } + }, +} + +const emptyStringToNull = (value, originalValue) => + originalValue === '' ? null : value + +const threadsValidation = number() + .nullable() + .integer() + .transform(emptyStringToNull) + +/** @type {Field} Threads field */ +const THREADS = { name: 'TOPOLOGY.THREADS', label: T.Threads, tooltip: T.ThreadsConcept, - type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: threadsValidation, - ...(hypervisor === firecracker && { - type: INPUT_TYPES.SELECT, - values: arrayToOptions([1, 2]), - validation: threadsValidation.min(1).max(2), - }), - ...(hypervisor === vcenter && { - type: INPUT_TYPES.SELECT, - values: arrayToOptions([1]), - validation: threadsValidation.min(1).max(1), - }), -}) + dependOf: '$general.HYPERVISOR', + type: (hypervisor) => + [firecracker, vcenter].includes(hypervisor) + ? INPUT_TYPES.SELECT + : INPUT_TYPES.TEXT, + values: (hypervisor) => + ({ + [firecracker]: arrayToOptions([1, 2]), + [vcenter]: arrayToOptions([1]), + }[hypervisor]), + validation: lazy( + (_, { context }) => + ({ + [firecracker]: threadsValidation.min(1).max(2), + [vcenter]: threadsValidation.min(1).max(1), + }[context?.general?.HYPERVISOR] || threadsValidation) + ), +} /** @type {Field} Hugepage size field */ const HUGEPAGES = { @@ -200,4 +200,23 @@ const NUMA_FIELDS = (hypervisor) => */ const SCHEMA_FIELDS = (hypervisor) => [ENABLE_NUMA, ...NUMA_FIELDS(hypervisor)] -export { NUMA_FIELDS, SCHEMA_FIELDS as FIELDS, ENABLE_NUMA } +/** + * @param {string} [hypervisor] - VM hypervisor + * @returns {ObjectSchema} Schema for NUMA fields + */ +const NUMA_SCHEMA = (hypervisor) => + getObjectSchemaFromFields(SCHEMA_FIELDS(hypervisor)).afterSubmit((result) => { + const { TOPOLOGY, ...ensuredResult } = result + const { ENABLE_NUMA: isEnabled, ...restOfTopology } = TOPOLOGY + + isEnabled && (ensuredResult.TOPOLOGY = { ...restOfTopology }) + + return { ...ensuredResult } + }) + +export { + NUMA_FIELDS, + SCHEMA_FIELDS as FIELDS, + NUMA_SCHEMA as SCHEMA, + ENABLE_NUMA, +} diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js index dbdd2d8191..e98103754c 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js @@ -19,7 +19,7 @@ import { HYPERVISORS } from 'client/constants' import { getObjectSchemaFromFields } from 'client/utils' import { FIELDS as PLACEMENT_FIELDS } from './placement/schema' import { FIELDS as OS_FIELDS, BOOT_ORDER_FIELD } from './booting/schema' -import { FIELDS as NUMA_FIELDS } from './numa/schema' +import { SCHEMA as NUMA_SCHEMA, FIELDS as NUMA_FIELDS } from './numa/schema' import { SCHEMA as IO_SCHEMA } from './inputOutput/schema' import { SCHEMA as CONTEXT_SCHEMA } from './context/schema' import { SCHEMA as STORAGE_SCHEMA } from './storage/schema' @@ -57,12 +57,9 @@ export const SCHEMA = (hypervisor) => .concat(CONTEXT_SCHEMA(hypervisor)) .concat(IO_SCHEMA(hypervisor)) .concat( - getObjectSchemaFromFields([ - ...PLACEMENT_FIELDS, - ...OS_FIELDS(hypervisor), - ...NUMA_FIELDS(hypervisor), - ]) + getObjectSchemaFromFields([...PLACEMENT_FIELDS, ...OS_FIELDS(hypervisor)]) ) + .concat(NUMA_SCHEMA(hypervisor)) export { mapNameByIndex, diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacityUtils.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacityUtils.js index 43f8b2c1b4..9a31bc345c 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacityUtils.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacityUtils.js @@ -78,12 +78,15 @@ const modificationTypeInput = (fieldName, { type: typeId }) => ({ getText: (type) => sentenceCase(type), }), validation: lazy((_, { context }) => - string().default(() => { - const capacityUserInput = context.extra?.USER_INPUTS?.[fieldName] - const { type } = getUserInputParams(capacityUserInput) + string() + .default(() => { + const capacityUserInput = context.extra?.USER_INPUTS?.[fieldName] + const { type } = getUserInputParams(capacityUserInput) - return type - }) + return type + }) + // Modification type is not required in template + .afterSubmit(() => undefined) ), grid: { md: 3 }, }) diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js index c026f0e077..c576c59bea 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js @@ -174,28 +174,23 @@ const Actions = () => { }, }, form: (rows) => { - const vmTemplates = rows?.map(({ original }) => original) - const stepProps = { isMultiple: vmTemplates.length > 1 } - const initialValues = { - name: `Copy of ${vmTemplates?.[0]?.NAME}`, - } + const names = rows?.map(({ original }) => original?.NAME) + const stepProps = { isMultiple: names.length > 1 } + const initialValues = { name: `Copy of ${names?.[0]}` } return CloneForm({ stepProps, initialValues }) }, - onSubmit: (rows) => async (formData) => { - const { prefix, ...restOfData } = formData + onSubmit: + (rows) => + async ({ prefix, name } = {}) => { + const vmTemplates = rows?.map?.( + ({ original: { ID, NAME } = {} }) => + // overwrite all names with prefix+NAME + ({ id: ID, name: prefix ? `${prefix} ${NAME}` : name }) + ) - const vmTemplates = rows?.map?.( - ({ original: { ID, NAME } = {} }) => { - // overwrite all names with prefix+NAME - const name = prefix ? `${prefix} ${NAME}` : NAME - - return { id: ID, ...restOfData, name } - } - ) - - await Promise.all(vmTemplates.map(clone)) - }, + await Promise.all(vmTemplates.map(clone)) + }, }, ], }, diff --git a/src/fireedge/src/client/containers/VirtualMachines/index.js b/src/fireedge/src/client/containers/VirtualMachines/index.js index 6452bacd2b..5a5393bd68 100644 --- a/src/fireedge/src/client/containers/VirtualMachines/index.js +++ b/src/fireedge/src/client/containers/VirtualMachines/index.js @@ -15,18 +15,19 @@ * ------------------------------------------------------------------------- */ import { ReactElement, useState, memo } from 'react' import PropTypes from 'prop-types' -import { BookmarkEmpty } from 'iconoir-react' +import { Pin as GotoIcon, RefreshDouble } from 'iconoir-react' import { Typography, Box, Stack, Chip, IconButton } from '@mui/material' import { Row } from 'react-table' -import vmApi from 'client/features/OneApi/vm' +import { useLazyGetVmQuery } from 'client/features/OneApi/vm' import { VmsTable } from 'client/components/Tables' import VmActions from 'client/components/Tables/Vms/actions' import VmTabs from 'client/components/Tabs/Vm' import SplitPane from 'client/components/SplitPane' import MultipleTags from 'client/components/MultipleTags' +import { SubmitButton } from 'client/components/FormControl' import { Tr } from 'client/components/HOC' -import { T } from 'client/constants' +import { T, VM } from 'client/constants' /** * Displays a list of VMs with a split pane between the list and selected row(s). @@ -56,7 +57,7 @@ function VirtualMachines() { ) : ( )} @@ -71,34 +72,39 @@ function VirtualMachines() { /** * Displays details of a VM. * - * @param {string} id - VM id to display + * @param {VM} vm - VM to display * @param {Function} [gotoPage] - Function to navigate to a page of a VM * @returns {ReactElement} VM details */ -const InfoTabs = memo(({ id, gotoPage }) => { - const vm = vmApi.endpoints.getVms.useQueryState(undefined, { - selectFromResult: ({ data = [] }) => data.find((item) => +item.ID === +id), - }) +const InfoTabs = memo(({ vm, gotoPage }) => { + const [getVm, { isFetching }] = useLazyGetVmQuery() return ( - - {`#${id} | ${vm.NAME}`} - + } + tooltip={Tr(T.Refresh)} + isSubmitting={isFetching} + onClick={() => getVm({ id: vm.ID })} + /> {gotoPage && ( - + )} + + {`#${vm.ID} | ${vm.NAME}`} + - + ) }) InfoTabs.propTypes = { - id: PropTypes.string.isRequired, + vm: PropTypes.object.isRequired, gotoPage: PropTypes.func, } diff --git a/src/fireedge/src/client/containers/VmTemplates/Instantiate.js b/src/fireedge/src/client/containers/VmTemplates/Instantiate.js index 3debab1f87..908c2fceda 100644 --- a/src/fireedge/src/client/containers/VmTemplates/Instantiate.js +++ b/src/fireedge/src/client/containers/VmTemplates/Instantiate.js @@ -53,8 +53,7 @@ function InstantiateVmTemplate() { const onSubmit = async (templates) => { try { - const promises = await Promise.all(templates.map(instantiate)) - promises.map((res) => res.unwrap?.()) + await Promise.all(templates.map((t) => instantiate(t).unwrap())) history.push(PATH.INSTANCE.VMS.LIST)