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)