mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
ebac963a14
commit
a3a2dc7a8b
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 },
|
||||
})
|
||||
|
@ -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))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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() {
|
||||
<GroupedTags tags={selectedRows} />
|
||||
) : (
|
||||
<InfoTabs
|
||||
id={selectedRows[0]?.original?.ID}
|
||||
vm={selectedRows[0]?.original}
|
||||
gotoPage={selectedRows[0]?.gotoPage}
|
||||
/>
|
||||
)}
|
||||
@ -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 (
|
||||
<Stack overflow="auto">
|
||||
<Stack direction="row" alignItems="center" gap={1} mb={1}>
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${id} | ${vm.NAME}`}
|
||||
</Typography>
|
||||
<SubmitButton
|
||||
data-cy="detail-refresh"
|
||||
icon={<RefreshDouble />}
|
||||
tooltip={Tr(T.Refresh)}
|
||||
isSubmitting={isFetching}
|
||||
onClick={() => getVm({ id: vm.ID })}
|
||||
/>
|
||||
{gotoPage && (
|
||||
<IconButton title={Tr(T.LocateOnTable)} onClick={gotoPage}>
|
||||
<BookmarkEmpty />
|
||||
<GotoIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<Typography color="text.primary" noWrap>
|
||||
{`#${vm.ID} | ${vm.NAME}`}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<VmTabs id={id} />
|
||||
<VmTabs id={vm.ID} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
||||
InfoTabs.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
vm: PropTypes.object.isRequired,
|
||||
gotoPage: PropTypes.func,
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user