mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
51af5a24a0
commit
c9cc9a29a8
@ -50,7 +50,7 @@ export const KERNEL_DS = {
|
||||
?.sort((a, b) => {
|
||||
const compareOptions = { numeric: true, ignorePunctuation: true }
|
||||
|
||||
return a.value.localeCompare(b.value, undefined, compareOptions)
|
||||
return a.text.localeCompare(b.text, undefined, compareOptions)
|
||||
})
|
||||
},
|
||||
validation: kernelValidation.when(
|
||||
|
@ -50,7 +50,7 @@ export const RAMDISK_DS = {
|
||||
?.sort((a, b) => {
|
||||
const compareOptions = { numeric: true, ignorePunctuation: true }
|
||||
|
||||
return a.value.localeCompare(b.value, undefined, compareOptions)
|
||||
return a.text.localeCompare(b.text, undefined, compareOptions)
|
||||
})
|
||||
},
|
||||
validation: ramdiskValidation.when(
|
||||
|
@ -22,13 +22,15 @@ import { FormWithSchema } from 'client/components/Forms'
|
||||
|
||||
import { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import InputsSection, { SECTION_ID as INPUT_ID } from './inputsSection'
|
||||
import { INPUT_OUTPUT_FIELDS, INPUTS_FIELDS } from './schema'
|
||||
import PciDevicesSection, { SECTION_ID as PCI_ID } from './pciDevicesSection'
|
||||
import { INPUT_OUTPUT_FIELDS, INPUTS_FIELDS, PCI_FIELDS } from './schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = ['GRAPHICS', INPUT_ID]
|
||||
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
|
||||
@ -45,6 +47,9 @@ const InputOutput = ({ hypervisor }) => {
|
||||
{inputsFields.length > 0 && (
|
||||
<InputsSection fields={inputsFields} />
|
||||
)}
|
||||
{pciDevicesFields.length > 0 && (
|
||||
<PciDevicesSection fields={pciDevicesFields} />
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, 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 { string, array, object, ObjectSchema, ArraySchema } from 'yup'
|
||||
|
||||
import { useHost } from 'client/features/One'
|
||||
import { getPciDevices } from 'client/models/Host'
|
||||
import { Field, arrayToOptions, filterFieldsByHypervisor, getValidationFromFields } from 'client/utils'
|
||||
import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants'
|
||||
|
||||
const { vcenter, lxc, firecracker } = HYPERVISORS
|
||||
|
||||
const transformPciToString = (pciDevice = {}) => {
|
||||
const { DEVICE = '', VENDOR = '', CLASS = '' } = pciDevice
|
||||
return [DEVICE, VENDOR, CLASS].join(',')
|
||||
}
|
||||
|
||||
const getPciAttributes = (pciDevice = '') => {
|
||||
const [DEVICE, VENDOR, CLASS] = pciDevice.split(',')
|
||||
return { DEVICE, VENDOR, CLASS }
|
||||
}
|
||||
|
||||
/** @type {Field} Name PCI device field */
|
||||
const DEVICE_NAME = {
|
||||
name: 'DEVICE_NAME',
|
||||
label: T.DeviceName,
|
||||
notOnHypervisors: [vcenter, lxc, firecracker],
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: () => {
|
||||
const hosts = useHost()
|
||||
const pciDevices = hosts.map(getPciDevices).flat()
|
||||
|
||||
return arrayToOptions(pciDevices, {
|
||||
getText: ({ DEVICE_NAME } = {}) => DEVICE_NAME,
|
||||
getValue: transformPciToString
|
||||
})
|
||||
},
|
||||
validation: string().trim().notRequired(),
|
||||
grid: { sm: 12, md: 3 }
|
||||
}
|
||||
|
||||
/** @type {Field} Common field properties */
|
||||
const commonFieldProps = name => ({
|
||||
name,
|
||||
notOnHypervisors: [vcenter, lxc, firecracker],
|
||||
type: INPUT_TYPES.TEXT,
|
||||
dependOf: DEVICE_NAME.name,
|
||||
watcher: pciDevice => {
|
||||
if (pciDevice) {
|
||||
const { [name]: attribute } = getPciAttributes(pciDevice)
|
||||
|
||||
return attribute
|
||||
}
|
||||
},
|
||||
validation: string().trim().required(),
|
||||
fieldProps: { disabled: true },
|
||||
grid: { xs: 12, sm: 4, md: 3 }
|
||||
})
|
||||
|
||||
/** @type {Field} PCI device field */
|
||||
const DEVICE = { label: T.Device, ...commonFieldProps('DEVICE') }
|
||||
|
||||
/** @type {Field} PCI device field */
|
||||
const VENDOR = { label: T.Vendor, ...commonFieldProps('VENDOR') }
|
||||
|
||||
/** @type {Field} PCI device field */
|
||||
const CLASS = { label: T.Class, ...commonFieldProps('CLASS') }
|
||||
|
||||
/**
|
||||
* @param {string} [hypervisor] - VM hypervisor
|
||||
* @returns {Field[]} List of Graphic inputs fields
|
||||
*/
|
||||
export const PCI_FIELDS = (hypervisor) =>
|
||||
filterFieldsByHypervisor([DEVICE_NAME, DEVICE, VENDOR, CLASS], hypervisor)
|
||||
|
||||
/** @type {ObjectSchema} PCI devices object schema */
|
||||
export const PCI_SCHEMA = object(getValidationFromFields([DEVICE, VENDOR, CLASS]))
|
||||
|
||||
/** @type {ArraySchema} PCI devices schema */
|
||||
export const PCI_DEVICES_SCHEMA = array(PCI_SCHEMA).ensure()
|
@ -0,0 +1,122 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2021, 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 { JSXElementConstructor, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack, FormControl, Divider, Button, IconButton } from '@mui/material'
|
||||
import List from '@mui/material/List'
|
||||
import ListItem from '@mui/material/ListItem'
|
||||
import ListItemText from '@mui/material/ListItemText'
|
||||
import { DeleteCircledOutline, AddCircledOutline } from 'iconoir-react'
|
||||
import { useFieldArray, useForm, FormProvider } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
|
||||
import { useHost } from 'client/features/One'
|
||||
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'
|
||||
|
||||
export const SECTION_ID = 'PCI'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {Array} props.fields - Fields
|
||||
* @returns {JSXElementConstructor} - Inputs section
|
||||
*/
|
||||
const PciDevicesSection = ({ fields }) => {
|
||||
const hosts = useHost()
|
||||
const pciDevicesAvailable = useMemo(() => hosts.map(getPciDevices).flat(), [hosts.length])
|
||||
|
||||
const { fields: pciDevices, append, remove } = useFieldArray({
|
||||
name: `${EXTRA_ID}.${SECTION_ID}`
|
||||
})
|
||||
|
||||
const methods = useForm({
|
||||
defaultValues: PCI_SCHEMA.default(),
|
||||
resolver: yupResolver(PCI_SCHEMA)
|
||||
})
|
||||
|
||||
const onSubmit = newInput => {
|
||||
append(newInput)
|
||||
methods.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl component='fieldset' sx={{ width: '100%', gridColumn: '1 / -1' }}>
|
||||
<Legend title={T.PciDevices} />
|
||||
<FormProvider {...methods}>
|
||||
<Stack
|
||||
direction='row' alignItems='flex-start' gap='0.5rem'
|
||||
component='form'
|
||||
onSubmit={methods.handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormWithSchema
|
||||
cy={`create-vm-template-${EXTRA_ID}.io-pci-devices`}
|
||||
fields={fields}
|
||||
rootProps={{ sx: { m: 0 } }}
|
||||
/>
|
||||
<Button
|
||||
variant='outlined'
|
||||
type='submit'
|
||||
startIcon={<AddCircledOutline />}
|
||||
sx={{ mt: '1em' }}
|
||||
>
|
||||
<Translate word={T.Add} />
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
<Divider />
|
||||
<List>
|
||||
{pciDevices?.map(({ id, DEVICE, VENDOR, CLASS }, index) => {
|
||||
const deviceName = pciDevicesAvailable
|
||||
.find(pciDevice => pciDevice?.DEVICE === DEVICE)?.DEVICE_NAME
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={id}
|
||||
secondaryAction={
|
||||
<IconButton onClick={() => remove(index)}>
|
||||
<DeleteCircledOutline />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{ '&:hover': { bgcolor: 'action.hover' } }}
|
||||
>
|
||||
<ListItemText
|
||||
primary={deviceName}
|
||||
primaryTypographyProps={{ variant: 'body1' }}
|
||||
secondary={[
|
||||
`#${DEVICE}`,
|
||||
`Vendor: ${VENDOR}`,
|
||||
`Class: ${CLASS}`].join(' | ')}
|
||||
/>
|
||||
</ListItem>
|
||||
)
|
||||
})}
|
||||
</List>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
PciDevicesSection.propTypes = {
|
||||
fields: PropTypes.array
|
||||
}
|
||||
|
||||
PciDevicesSection.displayName = 'PciDevicesSection'
|
||||
|
||||
export default PciDevicesSection
|
@ -17,6 +17,7 @@ import { object, ObjectSchema } from 'yup'
|
||||
|
||||
import { GRAPHICS_FIELDS } from './graphicsSchema'
|
||||
import { INPUTS_SCHEMA } from './inputsSchema'
|
||||
import { PCI_DEVICES_SCHEMA } from './pciDevicesSchema'
|
||||
import { Field, getObjectSchemaFromFields } from 'client/utils'
|
||||
|
||||
/**
|
||||
@ -31,10 +32,12 @@ export const INPUT_OUTPUT_FIELDS = hypervisor =>
|
||||
* @returns {ObjectSchema} I/O schema
|
||||
*/
|
||||
export const SCHEMA = hypervisor => object({
|
||||
INPUT: INPUTS_SCHEMA
|
||||
INPUT: INPUTS_SCHEMA,
|
||||
PCI: PCI_DEVICES_SCHEMA
|
||||
}).concat(getObjectSchemaFromFields([
|
||||
...GRAPHICS_FIELDS(hypervisor)
|
||||
]))
|
||||
|
||||
export * from './graphicsSchema'
|
||||
export * from './inputsSchema'
|
||||
export * from './pciDevicesSchema'
|
||||
|
@ -28,7 +28,7 @@ export const VM_GROUP_FIELD = {
|
||||
const vmGroups = useVmGroup()
|
||||
|
||||
return vmGroups
|
||||
?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: ID }))
|
||||
?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: String(ID) }))
|
||||
?.sort((a, b) => {
|
||||
const compareOptions = { numeric: true, ignorePunctuation: true }
|
||||
|
||||
|
@ -27,7 +27,7 @@ export const VM_GROUP_FIELD = {
|
||||
const vmGroups = useVmGroup()
|
||||
|
||||
return vmGroups
|
||||
?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: ID }))
|
||||
?.map(({ ID, NAME }) => ({ text: `#${ID} ${NAME}`, value: String(ID) }))
|
||||
?.sort((a, b) => {
|
||||
const compareOptions = { numeric: true, ignorePunctuation: true }
|
||||
|
||||
|
@ -16,6 +16,22 @@
|
||||
import * as STATES from 'client/constants/states'
|
||||
import COLOR from 'client/constants/color'
|
||||
|
||||
/**
|
||||
* @typedef {object} PciDevice - PCI device
|
||||
* @property {string} DOMAIN - PCI address domain
|
||||
* @property {string} BUS - PCI address bus
|
||||
* @property {string} SLOT - PCI address slot
|
||||
* @property {string} FUNCTION - PCI address function
|
||||
* @property {string} ADDRESS - PCI address, bus, slot and function
|
||||
* @property {string} DEVICE - Id of PCI device
|
||||
* @property {string} CLASS - Id of PCI device class
|
||||
* @property {string} VENDOR - Id of PCI device vendor
|
||||
* @property {string} VMID - Id using this device, -1 if free
|
||||
* @property {string} [DEVICE_NAME] - Name of PCI device
|
||||
* @property {string} [VENDOR_NAME] - Name of PCI device vendor
|
||||
* @property {string} [CLASS_NAME] - Name of PCI device class
|
||||
*/
|
||||
|
||||
/** @type {STATES.StateInfo[]} Host states */
|
||||
export const HOST_STATES = [
|
||||
{
|
||||
|
@ -473,6 +473,11 @@ module.exports = {
|
||||
/* VM Template schema - Input/Output */
|
||||
InputOrOutput: 'Input / Output',
|
||||
Inputs: 'Inputs',
|
||||
PciDevices: 'PCI Devices',
|
||||
DeviceName: 'Device name',
|
||||
Device: 'Device',
|
||||
Vendor: 'Vendor',
|
||||
Class: 'Class',
|
||||
/* VM Template schema - Input/Output - graphics */
|
||||
Graphics: 'Graphics',
|
||||
VMRC: 'VMRC',
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { DEFAULT_CPU_MODELS, HOST_STATES, HYPERVISORS, StateInfo } from 'client/constants'
|
||||
import { DEFAULT_CPU_MODELS, HOST_STATES, HYPERVISORS, PciDevice, StateInfo } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Returns information about the host state.
|
||||
@ -79,6 +79,15 @@ export const getHugepageSizes = host => {
|
||||
.flat()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns list of PCI devices from the host.
|
||||
*
|
||||
* @param {object} host - Host
|
||||
* @returns {PciDevice[]} List of PCI devices from resource
|
||||
*/
|
||||
export const getPciDevices = host =>
|
||||
[host?.HOST_SHARE?.PCI_DEVICES?.PCI ?? []].flat().filter(Boolean)
|
||||
|
||||
/**
|
||||
* Returns list of KVM CPU Models available from the host pool.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user