diff --git a/src/fireedge/src/client/components/Cards/DiskCard.js b/src/fireedge/src/client/components/Cards/DiskCard.js index 8ca670318f..45f6d023bd 100644 --- a/src/fireedge/src/client/components/Cards/DiskCard.js +++ b/src/fireedge/src/client/components/Cards/DiskCard.js @@ -22,144 +22,116 @@ import DiskSnapshotCard from 'client/components/Cards/DiskSnapshotCard' import { StatusChip } from 'client/components/Status' import { rowStyles } from 'client/components/Tables/styles' +import { getDiskName, getDiskType } from 'client/models/Image' import { stringToBoolean } from 'client/models/Helper' import { prettyBytes } from 'client/utils' import { Disk } from 'client/constants' -const DiskCard = memo( - ({ - disk = {}, - actions = [], - extraActionProps = {}, - snapshotActions = [], - extraSnapshotActionProps = {}, - }) => { - const classes = rowStyles() +const DiskCard = memo(({ disk = {}, actions = [], snapshotActions = [] }) => { + const classes = rowStyles() - /** @type {Disk} */ - const { - DISK_ID, - DATASTORE, - TARGET, - IMAGE, - TYPE, - FORMAT, - SIZE, - MONITOR_SIZE, - READONLY, - PERSISTENT, - SAVE, - CLONE, - IS_CONTEXT, - SNAPSHOTS, - } = disk + /** @type {Disk} */ + const { + DISK_ID, + DATASTORE, + TARGET, + TYPE, + SIZE, + MONITOR_SIZE, + READONLY, + PERSISTENT, + SAVE, + CLONE, + IS_CONTEXT, + SNAPSHOTS, + } = disk - const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-' - const monitorSize = +MONITOR_SIZE ? prettyBytes(+MONITOR_SIZE, 'MB') : '-' + const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-' + const monitorSize = +MONITOR_SIZE ? prettyBytes(+MONITOR_SIZE, 'MB') : '-' - const type = String(TYPE).toLowerCase() + const labels = useMemo( + () => + [ + { label: getDiskType(disk), dataCy: 'type' }, + { + label: stringToBoolean(PERSISTENT) && 'PERSISTENT', + dataCy: 'persistent', + }, + { + label: stringToBoolean(READONLY) && 'READONLY', + dataCy: 'readonly', + }, + { + label: stringToBoolean(SAVE) && 'SAVE', + dataCy: 'save', + }, + { + label: stringToBoolean(CLONE) && 'CLONE', + dataCy: 'clone', + }, + ].filter(({ label } = {}) => Boolean(label)), + [TYPE, PERSISTENT, READONLY, SAVE, CLONE] + ) - const image = - IMAGE ?? - { - fs: `${FORMAT} - ${size}`, - swap: size, - }[type] - - const labels = useMemo( - () => - [ - { label: TYPE, dataCy: 'type' }, - { - label: stringToBoolean(PERSISTENT) && 'PERSISTENT', - dataCy: 'persistent', - }, - { - label: stringToBoolean(READONLY) && 'READONLY', - dataCy: 'readonly', - }, - { - label: stringToBoolean(SAVE) && 'SAVE', - dataCy: 'save', - }, - { - label: stringToBoolean(CLONE) && 'CLONE', - dataCy: 'clone', - }, - ].filter(({ label } = {}) => Boolean(label)), - [TYPE, PERSISTENT, READONLY, SAVE, CLONE] - ) - - return ( - -
-
- - {image} - - - {labels.map(({ label, dataCy }) => ( - - ))} - -
-
- {`#${DISK_ID}`} - {TARGET && ( - - - {` ${TARGET}`} - - )} - {DATASTORE && ( - - - {` ${DATASTORE}`} - - )} - - - {` ${monitorSize}/${size}`} - -
+ return ( + +
+
+ + {getDiskName(disk)} + + + {labels.map(({ label, dataCy }) => ( + + ))} +
- {!IS_CONTEXT && !!actions.length && ( -
- {actions.map((Action, idx) => ( - - ))} -
- )} - {!!SNAPSHOTS?.length && ( - - {SNAPSHOTS?.map((snapshot) => ( - - ))} - - )} - - ) - } -) +
+ {`#${DISK_ID}`} + {TARGET && ( + + + {` ${TARGET}`} + + )} + {DATASTORE && ( + + + {` ${DATASTORE}`} + + )} + + + {` ${monitorSize}/${size}`} + +
+
+ {!IS_CONTEXT && !!actions && ( +
{actions}
+ )} + {!!SNAPSHOTS?.length && ( + + {SNAPSHOTS?.map((snapshot) => ( + + ))} + + )} +
+ ) +}) DiskCard.propTypes = { disk: PropTypes.object.isRequired, diff --git a/src/fireedge/src/client/components/Cards/DiskSnapshotCard.js b/src/fireedge/src/client/components/Cards/DiskSnapshotCard.js index b69730c05f..95eaf8fbcd 100644 --- a/src/fireedge/src/client/components/Cards/DiskSnapshotCard.js +++ b/src/fireedge/src/client/components/Cards/DiskSnapshotCard.js @@ -26,62 +26,52 @@ import * as Helper from 'client/models/Helper' import { prettyBytes } from 'client/utils' import { T, DiskSnapshot } from 'client/constants' -const DiskSnapshotCard = memo( - ({ snapshot = {}, actions = [], extraActionProps = {} }) => { - const classes = rowStyles() +const DiskSnapshotCard = memo(({ snapshot = {}, actions = [] }) => { + const classes = rowStyles() - /** @type {DiskSnapshot} */ - const { - ID, - NAME, - ACTIVE, - DATE, - SIZE: SNAPSHOT_SIZE, - MONITOR_SIZE: SNAPSHOT_MONITOR_SIZE, - } = snapshot + /** @type {DiskSnapshot} */ + const { + ID, + NAME, + ACTIVE, + DATE, + SIZE: SNAPSHOT_SIZE, + MONITOR_SIZE: SNAPSHOT_MONITOR_SIZE, + } = snapshot - const isActive = Helper.stringToBoolean(ACTIVE) - const time = Helper.timeFromMilliseconds(+DATE) - const timeAgo = `created ${time.toRelative()}` + const isActive = Helper.stringToBoolean(ACTIVE) + const time = Helper.timeFromMilliseconds(+DATE) + const timeAgo = `created ${time.toRelative()}` - const size = +SNAPSHOT_SIZE ? prettyBytes(+SNAPSHOT_SIZE, 'MB') : '-' - const monitorSize = +SNAPSHOT_MONITOR_SIZE - ? prettyBytes(+SNAPSHOT_MONITOR_SIZE, 'MB') - : '-' + const size = +SNAPSHOT_SIZE ? prettyBytes(+SNAPSHOT_SIZE, 'MB') : '-' + const monitorSize = +SNAPSHOT_MONITOR_SIZE + ? prettyBytes(+SNAPSHOT_MONITOR_SIZE, 'MB') + : '-' - return ( - -
-
- {NAME} - - {isActive && } />} - } /> - -
-
- {`#${ID} ${timeAgo}`} - - - {` ${monitorSize}/${size}`} - -
+ return ( + +
+
+ {NAME} + + {isActive && } />} + } /> +
- {!!actions.length && ( -
- {actions.map((Action, idx) => ( - - ))} -
- )} - - ) - } -) +
+ {`#${ID} ${timeAgo}`} + + + {` ${monitorSize}/${size}`} + +
+
+ {typeof actions === 'function' && ( +
{actions({ snapshot })}
+ )} +
+ ) +}) DiskSnapshotCard.propTypes = { snapshot: PropTypes.object.isRequired, diff --git a/src/fireedge/src/client/components/Cards/NicCard.js b/src/fireedge/src/client/components/Cards/NicCard.js index f41fe48b77..0a878f4a71 100644 --- a/src/fireedge/src/client/components/Cards/NicCard.js +++ b/src/fireedge/src/client/components/Cards/NicCard.js @@ -26,19 +26,21 @@ import { } from '@mui/material' import { rowStyles } from 'client/components/Tables/styles' +import { StatusChip } from 'client/components/Status' import MultipleTags from 'client/components/MultipleTags' import SecurityGroupCard from 'client/components/Cards/SecurityGroupCard' import { Translate } from 'client/components/HOC' +import { stringToBoolean } from 'client/models/Helper' import { T, Nic, NicAlias } from 'client/constants' const NicCard = memo( ({ nic = {}, actions = [], - extraActionProps = {}, aliasActions = [], - extraAliasActionProps = {}, + showParents = false, + clipboardOnTags = true, }) => { const classes = rowStyles() const isMobile = useMediaQuery((theme) => theme.breakpoints.down('md')) @@ -50,6 +52,8 @@ const NicCard = memo( IP, MAC, PCI_ID, + RDP, + SSH, PARENT, ADDRESS, ALIAS, @@ -60,6 +64,16 @@ const NicCard = memo( const isPciDevice = PCI_ID !== undefined const dataCy = isAlias ? 'alias' : 'nic' + + const noClipboardTags = [ + { text: stringToBoolean(RDP) && 'RDP', dataCy: `${dataCy}-rdp` }, + { text: stringToBoolean(SSH) && 'SSH', dataCy: `${dataCy}-ssh` }, + showParents && { + text: isAlias ? `PARENT: ${PARENT}` : false, + dataCy: `${dataCy}-parent`, + }, + ].filter(({ text } = {}) => Boolean(text)) + const tags = [ { text: IP, dataCy: `${dataCy}-ip` }, { text: MAC, dataCy: `${dataCy}-mac` }, @@ -73,32 +87,34 @@ const NicCard = memo( data-cy={`${dataCy}-${NIC_ID}`} sx={{ flexWrap: 'wrap', - ...(isAlias && { boxShadow: 'none !important' }), + boxShadow: 'none !important', }} > - +
{`${NIC_ID} | ${NETWORK}`} + {noClipboardTags.map((tag) => ( + + ))}
- {!isMobile && - !isPciDevice && - actions.map((Action, idx) => ( - - ))} + {!isPciDevice &&
{actions}
} {!!ALIAS?.length && ( {ALIAS?.map((alias) => ( @@ -106,7 +122,7 @@ const NicCard = memo( key={alias.NIC_ID} nic={alias} actions={aliasActions} - extraActionProps={extraAliasActionProps} + showParents={showParents} /> ))} @@ -148,14 +164,12 @@ const NicCard = memo( NicCard.propTypes = { nic: PropTypes.object, - actions: PropTypes.array, - extraActionProps: PropTypes.object, - aliasActions: PropTypes.array, - extraAliasActionProps: PropTypes.object, + actions: PropTypes.node, + aliasActions: PropTypes.node, + showParents: PropTypes.bool, + clipboardOnTags: PropTypes.bool, } NicCard.displayName = 'NicCard' -NicCard.displayName = 'NicCard' - export default NicCard diff --git a/src/fireedge/src/client/components/Dialogs/DialogForm.js b/src/fireedge/src/client/components/Dialogs/DialogForm.js index 81324485ac..663cdcbcb3 100644 --- a/src/fireedge/src/client/components/Dialogs/DialogForm.js +++ b/src/fireedge/src/client/components/Dialogs/DialogForm.js @@ -32,9 +32,6 @@ const useStyles = makeStyles((theme) => ({ height: '60vh', maxWidth: '100%', maxHeight: '100%', - overflow: 'hidden', - display: 'flex', - flexDirection: 'column', [theme.breakpoints.only('xs')]: { width: '100vw', height: '100vh', diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/CommonFields.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/CommonFields.js index ce9d4f7ba8..d43167b66c 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/CommonFields.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/CommonFields.js @@ -13,121 +13,363 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import * as yup from 'yup' -import { INPUT_TYPES, T, HYPERVISORS } from 'client/constants' +import { string, number } from 'yup' +import { INPUT_TYPES, T, HYPERVISORS, Field } from 'client/constants' const { kvm, vcenter, firecracker, lxc } = HYPERVISORS -export const TARGET = { - name: 'TARGET', - label: 'Target device', - notOnHypervisors: [vcenter, firecracker, lxc], - tooltip: ` - Device to map image disk. - If set, it will overwrite the default device mapping.`, - type: INPUT_TYPES.TEXT, - validation: yup.string().trim().notRequired().default(undefined), -} +/** @type {Field[]} List of general fields */ +export const GENERAL_FIELDS = [ + { + name: 'TARGET', + label: T.TargetDevice, + tooltip: T.TargetDeviceConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [vcenter, firecracker, lxc], + validation: string().trim().notRequired().default(undefined), + fieldProps: { placeholder: 'sdc' }, + }, + { + name: 'READONLY', + label: T.ReadOnly, + notOnHypervisors: [vcenter], + type: INPUT_TYPES.SELECT, + values: [ + { text: T.Yes, value: 'YES' }, + { text: T.No, value: 'NO' }, + ], + validation: string() + .trim() + .notRequired() + .default(() => 'NO'), + }, + { + name: 'DEV_PREFIX', + label: T.Bus, + notOnHypervisors: [vcenter, firecracker, lxc], + type: INPUT_TYPES.SELECT, + values: [ + { text: '', value: '' }, + { text: 'Virtio', value: 'vd' }, + { text: 'CSI/SATA', value: 'sd' }, + { text: 'Parallel ATA (IDE)', value: 'hd' }, + { text: 'Custom', value: 'custom' }, + ], + validation: string().trim().notRequired().default(undefined), + }, + { + name: 'CACHE', + label: T.Cache, + notOnHypervisors: [vcenter, firecracker, lxc], + type: INPUT_TYPES.SELECT, + values: [ + { text: '', value: '' }, + { text: 'Default', value: 'default' }, + { text: 'Writethrough', value: 'writethrough' }, + { text: 'Writeback', value: 'writeback' }, + { text: 'Directsync', value: 'directsync' }, + { text: 'Unsafe', value: 'unsafe' }, + ], + validation: string().trim().notRequired().default(undefined), + }, + { + name: 'IO', + label: T.IoPolicy, + notOnHypervisors: [vcenter, firecracker, lxc], + type: INPUT_TYPES.SELECT, + values: [ + { text: '', value: '' }, + { text: 'Threads', value: 'threads' }, + { text: 'Native', value: 'native' }, + ], + validation: string().trim().notRequired().default(undefined), + }, + { + name: 'DISCARD', + label: T.Discard, + notOnHypervisors: [vcenter, firecracker, lxc], + type: INPUT_TYPES.SELECT, + values: [ + { text: '', value: '' }, + { text: 'Ignore', value: 'ignore' }, + { text: 'Unmap', value: 'unmap' }, + ], + validation: string().trim().notRequired().default(undefined), + }, + { + name: 'SIZE_IOPS_SEC', + label: T.IopsSize, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'IOTHREADS', + label: T.IoThreadId, + tooltip: T.IoThreadIdConcept, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, +] -export const READONLY = { - name: 'READONLY', - label: 'Read only', - notOnHypervisors: [vcenter], - type: INPUT_TYPES.SELECT, - values: [ - { text: T.Yes, value: 'YES' }, - { text: T.No, value: 'NO' }, - ], - validation: yup - .string() - .trim() - .notRequired() - .default(() => 'NO'), -} +/** @type {Field[]} List of vCenter fields */ +export const VCENTER_FIELDS = [ + { + name: 'VCENTER_ADAPTER_TYPE', + label: T.BusAdapterController, + notOnHypervisors: [kvm, firecracker], + type: INPUT_TYPES.SELECT, + values: [ + { text: '', value: '' }, + { text: 'lsiLogic', value: 'lsiLogic' }, + { text: 'ide', value: 'ide' }, + { text: 'busLogic', value: 'busLogic' }, + { text: 'Custom', value: 'custom' }, + ], + validation: string().trim().notRequired().default(undefined), + }, + { + name: 'VCENTER_DISK_TYPE', + label: T.DiskProvisioningType, + notOnHypervisors: [kvm, firecracker], + type: INPUT_TYPES.SELECT, + values: [ + { text: '', value: '' }, + { text: 'Thin', value: 'thin' }, + { text: 'Thick', value: 'thick' }, + { text: 'Eager Zeroed Thick', value: 'eagerZeroedThick' }, + { text: 'Custom', value: 'custom' }, + ], + validation: string().trim().notRequired().default(undefined), + }, +] -export const DEV_PREFIX = { - name: 'DEV_PREFIX', - label: 'BUS', - notOnHypervisors: [vcenter, firecracker, lxc], - type: INPUT_TYPES.SELECT, - values: [ - { text: '', value: '' }, - { text: 'Virtio', value: 'vd' }, - { text: 'CSI/SATA', value: 'sd' }, - { text: 'Parallel ATA (IDE)', value: 'hd' }, - { text: 'Custom', value: 'custom' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -} +/** @type {Field[]} List of throttling bytes fields */ +export const THROTTLING_BYTES_FIELDS = [ + { + name: 'TOTAL_BYTES_SEC', + label: T.TotalValue, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'TOTAL_BYTES_SEC_MAX', + label: T.TotalMaximum, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'TOTAL_BYTES_SEC_MAX_LENGTH', + label: T.TotalMaximumLength, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'READ_BYTES_SEC', + label: T.ReadValue, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'READ_BYTES_SEC_MAX', + label: T.ReadMaximum, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'READ_BYTES_SEC_MAX_LENGTH', + label: T.ReadMaximumLength, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'WRITE_BYTES_SEC', + label: T.WriteValue, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'WRITE_BYTES_SEC_MAX', + label: T.WriteMaximum, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'WRITE_BYTES_SEC_MAX_LENGTH', + label: T.WriteMaximumLength, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, +] -export const VCENTER_ADAPTER_TYPE = { - name: 'VCENTER_ADAPTER_TYPE', - label: 'Bus adapter controller', - notOnHypervisors: [kvm, firecracker], - type: INPUT_TYPES.SELECT, - values: [ - { text: '', value: '' }, - { text: 'lsiLogic', value: 'lsiLogic' }, - { text: 'ide', value: 'ide' }, - { text: 'busLogic', value: 'busLogic' }, - { text: 'Custom', value: 'custom' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -} +/** @type {Field[]} List of throttling IOPS fields */ +export const THROTTLING_IOPS_FIELDS = [ + { + name: 'TOTAL_IOPS_SEC', + label: T.TotalValue, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'TOTAL_IOPS_SEC_MAX', + label: T.TotalMaximum, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'TOTAL_IOPS_SEC_MAX_LENGTH', + label: T.TotalMaximumLength, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'READ_IOPS_SEC', + label: T.ReadValue, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'READ_IOPS_SEC_MAX', + label: T.ReadMaximum, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'READ_IOPS_SEC_MAX_LENGTH', + label: T.ReadMaximumLength, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'WRITE_IOPS_SEC', + label: T.WriteValue, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'WRITE_IOPS_SEC_MAX', + label: T.WriteMaximum, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, + { + name: 'WRITE_IOPS_SEC_MAX_LENGTH', + label: T.WriteMaximumLength, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [lxc, firecracker, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, +] -export const VCENTER_DISK_TYPE = { - name: 'VCENTER_DISK_TYPE', - label: 'Disk provisioning type', - notOnHypervisors: [kvm, firecracker], - type: INPUT_TYPES.SELECT, - values: [ - { text: '', value: '' }, - { text: 'Thin', value: 'thin' }, - { text: 'Thick', value: 'thick' }, - { text: 'Eager Zeroed Thick', value: 'eagerZeroedThick' }, - { text: 'Custom', value: 'custom' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -} - -export const CACHE = { - name: 'CACHE', - label: 'Cache', - notOnHypervisors: [vcenter, firecracker, lxc], - type: INPUT_TYPES.SELECT, - values: [ - { text: '', value: '' }, - { text: 'Default', value: 'default' }, - { text: 'Writethrough', value: 'writethrough' }, - { text: 'Writeback', value: 'writeback' }, - { text: 'Directsync', value: 'directsync' }, - { text: 'Unsafe', value: 'unsafe' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -} - -export const IO = { - name: 'IO', - label: 'IO Policy', - notOnHypervisors: [vcenter, firecracker, lxc], - type: INPUT_TYPES.SELECT, - values: [ - { text: '', value: '' }, - { text: 'Threads', value: 'threads' }, - { text: 'Native', value: 'native' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -} - -export const DISCARD = { - name: 'DISCARD', - label: 'Discard', - notOnHypervisors: [vcenter, firecracker, lxc], - type: INPUT_TYPES.SELECT, - values: [ - { text: '', value: '' }, - { text: 'Ignore', value: 'ignore' }, - { text: 'Unmap', value: 'unmap' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -} +/** @type {Field[]} List of edge cluster fields */ +export const EDGE_CLUSTER_FIELDS = [ + { + name: 'RECOVERY_SNAPSHOT_FREQ', + label: T.SnapshotFrequency, + type: INPUT_TYPES.TEXT, + htmlType: 'number', + notOnHypervisors: [firecracker, lxc, vcenter], + validation: number() + .min(0) + .notRequired() + .default(() => undefined), + }, +] diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/index.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/index.js index f6275d02d7..235c1b0906 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/index.js @@ -13,22 +13,50 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ +import { useMemo } from 'react' import PropTypes from 'prop-types' +import { Box } from '@mui/material' import FormWithSchema from 'client/components/Forms/FormWithSchema' import { SCHEMA, - FIELDS, + SECTIONS, } from 'client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/schema' -import { T } from 'client/constants' +import { Step } from 'client/utils' +import { T, HYPERVISORS } from 'client/constants' export const STEP_ID = 'advanced' -const Content = ({ hypervisor }) => ( - -) +const Content = ({ hypervisor }) => { + const sections = useMemo(() => SECTIONS(hypervisor), []) + return ( + + {sections.map(({ id, legend, fields }) => ( + + ))} + + ) +} + +/** + * Renders advanced options to disk. + * + * @param {object} props - Props + * @param {HYPERVISORS} props.hypervisor - Hypervisor + * @returns {Step} Advance options step + */ const AdvancedOptions = ({ hypervisor } = {}) => ({ id: STEP_ID, label: T.AdvancedOptions, diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/schema.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/schema.js index af3fe6a4a8..c2b5f00ae1 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/schema.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/AdvancedOptions/schema.js @@ -13,37 +13,83 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import { number, ObjectSchema } from 'yup' -import * as COMMON_FIELDS from 'client/components/Forms/Vm/AttachDiskForm/CommonFields' -import { INPUT_TYPES, HYPERVISORS } from 'client/constants' -import { getValidationFromFields } from 'client/utils' +import { + GENERAL_FIELDS, + VCENTER_FIELDS, + EDGE_CLUSTER_FIELDS, + THROTTLING_BYTES_FIELDS, + THROTTLING_IOPS_FIELDS, +} from 'client/components/Forms/Vm/AttachDiskForm/CommonFields' +import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants' +import { + Field, + Section, + getValidationFromFields, + filterFieldsByHypervisor, +} from 'client/utils' const { vcenter } = HYPERVISORS +/** @type {Field} Size field */ const SIZE = { name: 'SIZE', - label: 'Size on instantiate', - tooltip: ` - The size of the disk will be modified to match - this size when the template is instantiated`, + label: T.SizeOnInstantiate, + tooltip: T.SizeOnInstantiateConcept, notOnHypervisors: [vcenter], type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: yup - .number() - .typeError('Size value must be a number') + validation: number() .notRequired() - .default(undefined), + .default(() => undefined), } -export const FIELDS = (hypervisor) => - [SIZE, ...Object.values(COMMON_FIELDS)] - .map((field) => (typeof field === 'function' ? field(hypervisor) : field)) - .filter( - ({ notOnHypervisors } = {}) => !notOnHypervisors?.includes?.(hypervisor) - ) +/** + * @param {HYPERVISORS} hypervisor - Hypervisor + * @returns {Section[]} Sections + */ +const SECTIONS = (hypervisor) => [ + { + id: 'general', + legend: T.General, + fields: filterFieldsByHypervisor([SIZE, ...GENERAL_FIELDS], hypervisor), + }, + { + id: 'vcenter', + legend: 'vCenter', + fields: filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor), + }, + { + id: 'edge-cluster', + legend: T.EdgeCluster, + fields: filterFieldsByHypervisor(EDGE_CLUSTER_FIELDS, hypervisor), + }, + { + id: 'throttling-bytes', + legend: T.ThrottlingBytes, + fields: filterFieldsByHypervisor(THROTTLING_BYTES_FIELDS, hypervisor), + }, + { + id: 'throttling-iops', + legend: T.ThrottlingIOPS, + fields: filterFieldsByHypervisor(THROTTLING_IOPS_FIELDS, hypervisor), + }, +] -export const SCHEMA = (hypervisor) => - yup.object(getValidationFromFields(FIELDS(hypervisor))) +/** + * @param {HYPERVISORS} hypervisor - Hypervisor + * @returns {Field[]} Advanced options fields + */ +const FIELDS = (hypervisor) => + SECTIONS(hypervisor) + .map(({ fields }) => fields) + .flat() + +/** + * @param {HYPERVISORS} hypervisor - Hypervisor + * @returns {ObjectSchema} Advanced options schema + */ +const SCHEMA = (hypervisor) => getValidationFromFields(FIELDS(hypervisor)) + +export { SECTIONS, FIELDS, SCHEMA } diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/ImagesTable/index.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/ImagesTable/index.js index 737cb85fde..e7fece0ff0 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/ImagesTable/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/ImageSteps/ImagesTable/index.js @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ import PropTypes from 'prop-types' import { useListForm } from 'client/hooks' import { ImagesTable } from 'client/components/Tables' import { SCHEMA } from 'client/components/Forms/Vm/AttachDiskForm/ImageSteps/ImagesTable/schema' +import { Step } from 'client/utils' import { T } from 'client/constants' export const STEP_ID = 'image' @@ -48,6 +48,11 @@ const Content = ({ data, setFormData }) => { ) } +/** + * Renders datatable to select an image form pool. + * + * @returns {Step} Image step + */ const ImageStep = () => ({ id: STEP_ID, label: T.Image, diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/index.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/index.js index f83a22c766..06d87c7e2f 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/index.js @@ -13,22 +13,50 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ +import { useMemo } from 'react' import PropTypes from 'prop-types' +import { Box } from '@mui/material' import FormWithSchema from 'client/components/Forms/FormWithSchema' import { SCHEMA, - FIELDS, + SECTIONS, } from 'client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/schema' -import { T } from 'client/constants' +import { Step } from 'client/utils' +import { T, HYPERVISORS } from 'client/constants' export const STEP_ID = 'advanced' -const Content = ({ hypervisor }) => ( - -) +const Content = ({ hypervisor }) => { + const sections = useMemo(() => SECTIONS(hypervisor), []) + return ( + + {sections.map(({ id, legend, fields }) => ( + + ))} + + ) +} + +/** + * Renders advanced options to volatile disk. + * + * @param {object} props - Props + * @param {HYPERVISORS} props.hypervisor - Hypervisor + * @returns {Step} Advance options step + */ const AdvancedOptions = ({ hypervisor } = {}) => ({ id: STEP_ID, label: T.AdvancedOptions, diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/schema.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/schema.js index 2c1a0bbeca..f96ce76263 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/schema.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/AdvancedOptions/schema.js @@ -13,24 +13,68 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import { ObjectSchema } from 'yup' import { - TARGET, - READONLY, - CACHE, - IO, - DISCARD, - DEV_PREFIX, - VCENTER_DISK_TYPE, + GENERAL_FIELDS, + VCENTER_FIELDS, + EDGE_CLUSTER_FIELDS, + THROTTLING_BYTES_FIELDS, + THROTTLING_IOPS_FIELDS, } from 'client/components/Forms/Vm/AttachDiskForm/CommonFields' -import { getValidationFromFields } from 'client/utils' +import { T, HYPERVISORS } from 'client/constants' +import { + Field, + Section, + getValidationFromFields, + filterFieldsByHypervisor, +} from 'client/utils' -export const FIELDS = (hypervisor) => - [TARGET, READONLY, CACHE, IO, DISCARD, DEV_PREFIX, VCENTER_DISK_TYPE].filter( - ({ notOnHypervisors } = {}) => !notOnHypervisors?.includes?.(hypervisor) - ) +/** + * @param {HYPERVISORS} hypervisor - Hypervisor + * @returns {Section[]} Sections + */ +const SECTIONS = (hypervisor) => [ + { + id: 'general', + legend: T.General, + fields: filterFieldsByHypervisor(GENERAL_FIELDS, hypervisor), + }, + { + id: 'vcenter', + legend: 'vCenter', + fields: filterFieldsByHypervisor(VCENTER_FIELDS, hypervisor), + }, + { + id: 'edge-cluster', + legend: T.EdgeCluster, + fields: filterFieldsByHypervisor(EDGE_CLUSTER_FIELDS, hypervisor), + }, + { + id: 'throttling-bytes', + legend: T.ThrottlingBytes, + fields: filterFieldsByHypervisor(THROTTLING_BYTES_FIELDS, hypervisor), + }, + { + id: 'throttling-iops', + legend: T.ThrottlingIOPS, + fields: filterFieldsByHypervisor(THROTTLING_IOPS_FIELDS, hypervisor), + }, +] -export const SCHEMA = (hypervisor) => - yup.object(getValidationFromFields(FIELDS(hypervisor))) +/** + * @param {HYPERVISORS} hypervisor - Hypervisor + * @returns {Field[]} Advanced options fields + */ +const FIELDS = (hypervisor) => + SECTIONS(hypervisor) + .map(({ fields }) => fields) + .flat() + +/** + * @param {HYPERVISORS} hypervisor - Hypervisor + * @returns {ObjectSchema} Advanced options schema + */ +const SCHEMA = (hypervisor) => getValidationFromFields(FIELDS(hypervisor)) + +export { SECTIONS, FIELDS, SCHEMA } diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/index.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/index.js index ffaa17afe9..15cc37f4cd 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/index.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ +import { useMemo } from 'react' import PropTypes from 'prop-types' import FormWithSchema from 'client/components/Forms/FormWithSchema' @@ -21,14 +21,24 @@ import { SCHEMA, FIELDS, } from 'client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/schema' -import { T } from 'client/constants' +import { Step } from 'client/utils' +import { T, HYPERVISORS } from 'client/constants' export const STEP_ID = 'configuration' -const Content = ({ hypervisor }) => ( - -) +const Content = ({ hypervisor }) => { + const memoFields = useMemo(() => FIELDS(hypervisor), []) + return +} + +/** + * Renders configuration to volatile disk. + * + * @param {object} props - Props + * @param {HYPERVISORS} props.hypervisor - Hypervisor + * @returns {Step} Basic configuration step + */ const BasicConfiguration = ({ hypervisor } = {}) => ({ id: STEP_ID, label: T.Configuration, diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/schema.js b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/schema.js index 83beb55ff7..41e504b2e5 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/schema.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachDiskForm/VolatileSteps/BasicConfiguration/schema.js @@ -13,26 +13,34 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import { number, string, object, ObjectSchema } from 'yup' -import { INPUT_TYPES, HYPERVISORS } from 'client/constants' -import { getValidationFromFields } from 'client/utils' +import { useGetSunstoneConfigQuery } from 'client/features/OneApi/system' +import { + Field, + getValidationFromFields, + filterFieldsByHypervisor, + arrayToOptions, +} from 'client/utils' +import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants' const { vcenter } = HYPERVISORS +/** @type {Field} Size field */ const SIZE = { name: 'SIZE', - label: 'Size', + label: T.Size, type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: yup - .number() - .typeError('Size value must be a number') - .required('Size field is required') - .default(undefined), + validation: number() + .required() + .default(() => undefined), } +/** + * @param {HYPERVISORS} hypervisor - hypervisor + * @returns {Field} Disk type field + */ const TYPE = (hypervisor) => ({ name: 'TYPE', label: 'Disk type', @@ -44,61 +52,60 @@ const TYPE = (hypervisor) => ({ { text: 'FS', value: 'fs' }, { text: 'Swap', value: 'swap' }, ], - validation: yup.string().trim().notRequired().default('fs'), + validation: string().trim().notRequired().default('fs'), }) -const FORMAT = (hypervisor) => { - const typeFieldName = TYPE(hypervisor).name +/** + * @param {HYPERVISORS} hypervisor - hypervisor + * @returns {Field} Format field + */ +const FORMAT = (hypervisor) => ({ + name: 'FORMAT', + label: T.Format, + type: INPUT_TYPES.SELECT, + dependOf: 'TYPE', + htmlType: (type) => type === 'swap' && INPUT_TYPES.HIDDEN, + values: + hypervisor === vcenter + ? [{ text: 'Raw', value: 'raw' }] + : [ + { text: 'Raw', value: 'raw' }, + { text: 'qcow2', value: 'qcow2' }, + ], + validation: string() + .trim() + .when('TYPE', (type, schema) => + type === 'swap' ? schema.notRequired() : schema.required() + ) + .default('raw'), +}) - return { - name: 'FORMAT', - label: 'Format', - type: INPUT_TYPES.SELECT, - dependOf: typeFieldName, - htmlType: (type) => (type === 'swap' ? INPUT_TYPES.HIDDEN : undefined), - values: - hypervisor === vcenter - ? [{ text: 'Raw', value: 'raw' }] - : [ - { text: 'Raw', value: 'raw' }, - { text: 'qcow2', value: 'qcow2' }, - ], - validation: yup - .string() - .trim() - .when(typeFieldName, (type, schema) => - type === 'swap' - ? schema.notRequired() - : schema.required('Format field is required') - ) - .default('raw'), - } -} - -const FILESYSTEM = (hypervisor) => ({ +/** @type {Field} Filesystem field */ +const FILESYSTEM = { name: 'FS', - label: 'Filesystem', + label: T.FileSystemType, notOnHypervisors: [vcenter], type: INPUT_TYPES.SELECT, - dependOf: TYPE(hypervisor).name, - htmlType: (type) => (type === 'swap' ? INPUT_TYPES.HIDDEN : undefined), - values: [ - // TODO: sunstone-config => support_fs ??? - { text: '', value: '' }, - { text: 'ext4', value: 'ext4' }, - { text: 'ext3', value: 'ext3' }, - { text: 'ext2', value: 'ext2' }, - { text: 'xfs', value: 'xfs' }, - ], - validation: yup.string().trim().notRequired().default(undefined), -}) + dependOf: 'TYPE', + htmlType: (type) => type === 'swap' && INPUT_TYPES.HIDDEN, + values: () => { + const { data: config } = useGetSunstoneConfigQuery() + return arrayToOptions(config?.supported_fs) + }, + validation: string().trim().notRequired().default(undefined), +} + +/** + * @param {HYPERVISORS} hypervisor - hypervisor + * @returns {Field[]} List of fields + */ export const FIELDS = (hypervisor) => - [SIZE, TYPE, FORMAT, FILESYSTEM] - .map((field) => (typeof field === 'function' ? field(hypervisor) : field)) - .filter( - ({ notOnHypervisors } = {}) => !notOnHypervisors?.includes?.(hypervisor) - ) + filterFieldsByHypervisor([SIZE, TYPE, FORMAT, FILESYSTEM], hypervisor) +/** + * @param {HYPERVISORS} hypervisor - hypervisor + * @returns {ObjectSchema} Schema + */ export const SCHEMA = (hypervisor) => - yup.object(getValidationFromFields(FIELDS(hypervisor))) + object(getValidationFromFields(FIELDS(hypervisor))) diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/index.js b/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/index.js index 80e10d2f51..74fe82bbf6 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/index.js @@ -13,31 +13,55 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ +import { useMemo } from 'react' import PropTypes from 'prop-types' - -import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { Box } from '@mui/material' import { SCHEMA, - FIELDS, + SECTIONS, } from 'client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/schema' -import { T } from 'client/constants' +import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { Step } from 'client/utils' +import { T, Nic, HYPERVISORS } from 'client/constants' export const STEP_ID = 'advanced' -const Content = (props) => ( - -) +const Content = (props) => { + const sections = useMemo(() => SECTIONS(props), []) + return ( + + {sections.map(({ id, legend, fields }) => ( + + ))} + + ) +} + +/** + * Renders advanced options to nic. + * + * @param {object} props - Props + * @param {Nic[]} props.nics - Current nics + * @param {HYPERVISORS} props.hypervisor - Hypervisor + * @returns {Step} Advance options step + */ const AdvancedOptions = (props) => ({ id: STEP_ID, label: T.AdvancedOptions, - resolver: SCHEMA, + resolver: () => SCHEMA(props), optionsValidate: { abortEarly: false }, content: () => Content(props), }) @@ -46,6 +70,7 @@ Content.propTypes = { data: PropTypes.any, setFormData: PropTypes.func, nics: PropTypes.array, + hypervisor: PropTypes.string, } export default AdvancedOptions diff --git a/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/schema.js b/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/schema.js index 8e90f7c8f3..79dbb0026e 100644 --- a/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/schema.js +++ b/src/fireedge/src/client/components/Forms/Vm/AttachNicForm/Steps/AdvancedOptions/schema.js @@ -13,82 +13,378 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { boolean, string, object, ObjectSchema } from 'yup' +import { boolean, number, string, ObjectSchema } from 'yup' -import { Field, getValidationFromFields } from 'client/utils' -import { T, INPUT_TYPES } from 'client/constants' +import { + Field, + Section, + filterFieldsByHypervisor, + filterFieldsByDriver, + getObjectSchemaFromFields, +} from 'client/utils' +import { T, INPUT_TYPES, HYPERVISORS, VN_DRIVERS, Nic } from 'client/constants' -/** @type {Field} RDP connection field */ -const RDP_FIELD = { - name: 'RDP', - label: T.RdpConnection, - type: INPUT_TYPES.SWITCH, - validation: boolean().yesOrNo(), - grid: { md: 12 }, -} +const { firecracker } = HYPERVISORS +const { ovswitch, vcenter } = VN_DRIVERS -/** @type {Field} SSH connection field */ -const SSH_FIELD = { - name: 'SSH', - label: T.SshConnection, - type: INPUT_TYPES.SWITCH, - validation: boolean().yesOrNo(), - grid: { md: 12 }, -} +const filterByHypAndDriver = (fields, { hypervisor, driver }) => + filterFieldsByDriver(filterFieldsByHypervisor(fields, hypervisor), driver) /** - * @param {object} currentFormData - Current form data - * @param {object[]} currentFormData.nics - Nics - * @returns {Field} Alias field + * @param {object} [data] - VM or VM Template data + * @param {Nic[]} [data.nics] - Current NICs + * @returns {Field[]} List of general fields */ -const ALIAS_FIELD = ({ nics = [] }) => ({ - name: 'PARENT', - label: T.AsAnAlias, - dependOf: 'NAME', - type: (name) => { - const hasAlias = nics?.some((nic) => nic.PARENT === name) +const GENERAL_FIELDS = ({ nics = [] } = {}) => + [ + { + name: 'RDP', + label: T.RdpConnection, + type: INPUT_TYPES.SWITCH, + validation: boolean().yesOrNo(), + grid: { sm: 6 }, + }, + { + name: 'SSH', + label: T.SshConnection, + type: INPUT_TYPES.SWITCH, + validation: boolean().yesOrNo(), + grid: { sm: 6 }, + }, + !!nics?.length && { + name: 'PARENT', + label: T.AsAnAlias, + dependOf: 'NAME', + type: (name) => { + const hasAlias = nics?.some((nic) => nic.PARENT === name) - return name && hasAlias ? INPUT_TYPES.HIDDEN : INPUT_TYPES.SELECT + return name && hasAlias ? INPUT_TYPES.HIDDEN : INPUT_TYPES.SELECT + }, + values: (name) => [ + { text: '', value: '' }, + ...nics + .filter(({ PARENT }) => !PARENT) // filter nic alias + .filter(({ NAME }) => NAME !== name || !name) // filter it self + .map((nic) => { + const { NAME, IP = '', NETWORK = '', NIC_ID = '' } = nic + const text = [NAME ?? NIC_ID, NETWORK, IP] + .filter(Boolean) + .join(' - ') + + return { text, value: NAME } + }), + ], + validation: string() + .trim() + .notRequired() + .default(() => undefined), + grid: { sm: 6 }, + }, + { + name: 'EXTERNAL', + label: T.External, + tooltip: T.ExternalConcept, + type: INPUT_TYPES.SWITCH, + dependOf: 'PARENT', + htmlType: (parent) => !parent?.length && INPUT_TYPES.HIDDEN, + validation: boolean().yesOrNo(), + grid: { sm: 6 }, + }, + ].filter(Boolean) + +/** @type {Field[]} List of IPv4 fields */ +const OVERRIDE_IPV4_FIELDS = [ + { + name: 'IP', + label: T.IP, + tooltip: T.IPv4Concept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'MAC', + label: T.MAC, + tooltip: T.MACConcept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'NETWORK_MASK', + label: T.NetworkMask, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + fieldProps: { placeholder: '255.255.255.0' }, + }, + { + name: 'NETWORK_ADDRESS', + label: T.NetworkAddress, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + fieldProps: { placeholder: '192.168.1.0' }, + }, + { + name: 'GATEWAY', + label: T.Gateway, + tooltip: T.GatewayConcept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'SEARCH_DOMAIN', + label: T.SearchDomainForDNSResolution, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'METHOD', + label: T.NetworkMethod, + tooltip: T.NetworkMethod4Concept, + type: INPUT_TYPES.SELECT, + values: [ + { text: 'static (Based on context)', value: 'static' }, + { text: 'dhcp (DHCPv4)', value: 'dhcp' }, + { text: 'skip (Do not configure IPv4)', value: 'skip' }, + ], + validation: string().trim().notRequired().default('static'), }, - values: (name) => [ - { text: '', value: '' }, - ...nics - .filter(({ PARENT }) => !PARENT) // filter nic alias - .filter(({ NAME }) => NAME !== name || !name) // filter it self - .map((nic) => { - const { NAME, IP = '', NETWORK = '', NIC_ID = '' } = nic - const text = [NAME ?? NIC_ID, NETWORK, IP].filter(Boolean).join(' - ') - - return { text, value: NAME } - }), - ], - validation: string() - .trim() - .notRequired() - .default(() => undefined), -}) - -/** @type {Field} External field */ -const EXTERNAL_FIELD = { - name: 'EXTERNAL', - label: T.External, - tooltip: T.ExternalConcept, - type: INPUT_TYPES.SWITCH, - dependOf: 'PARENT', - htmlType: (parent) => !parent?.length && INPUT_TYPES.HIDDEN, - validation: boolean().yesOrNo(), -} - -/** - * @param {object} [currentFormData] - Current form data - * @returns {Field[]} List of Graphics fields - */ -export const FIELDS = (currentFormData = {}) => [ - RDP_FIELD, - SSH_FIELD, - ALIAS_FIELD(currentFormData), - EXTERNAL_FIELD, ] -/** @type {ObjectSchema} Advanced options schema */ -export const SCHEMA = object(getValidationFromFields(FIELDS())) +/** @type {Field[]} List of IPv6 fields */ +const OVERRIDE_IPV6_FIELDS = [ + { + name: 'IP6', + label: T.IP, + tooltip: T.IPv6Concept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'GATEWAY6', + label: T.Gateway, + tooltip: T.Gateway6Concept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'IP6_METHOD', + label: T.NetworkMethod, + tooltip: T.NetworkMethod6Concept, + type: INPUT_TYPES.SELECT, + values: [ + { text: 'static (Based on context)', value: 'static' }, + { text: 'auto (SLAAC)', value: 'auto' }, + { text: 'dhcp (SLAAC and DHCPv6)', value: 'dhcp' }, + { text: 'disable (Do not use IPv6)', value: 'disable' }, + { text: 'skip (Do not configure IPv4)', value: 'skip' }, + ], + validation: string().trim().notRequired().default('static'), + }, +] + +/** @type {Field[]} List of Inbound traffic QoS fields */ +const OVERRIDE_IN_QOS_FIELDS = [ + { + name: 'INBOUND_AVG_BW', + label: T.AverageBandwidth, + tooltip: T.InboundAverageBandwidthConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, + { + name: 'INBOUND_PEAK_BW', + label: T.PeakBandwidth, + tooltip: T.InboundPeakBandwidthConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, + { + name: 'INBOUND_PEAK_KB', + label: T.PeakBurst, + tooltip: T.PeakBurstConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + notOnDrivers: [vcenter], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, +] + +/** @type {Field[]} List of Outbound traffic QoS fields */ +const OVERRIDE_OUT_QOS_FIELDS = [ + { + name: 'OUTBOUND_AVG_BW', + label: T.AverageBandwidth, + tooltip: T.OutboundAverageBandwidthConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + notOnDrivers: [ovswitch], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, + { + name: 'OUTBOUND_PEAK_BW', + label: T.PeakBandwidth, + tooltip: T.OutboundPeakBandwidthConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + notOnDrivers: [ovswitch], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, + { + name: 'OUTBOUND_PEAK_KB', + label: T.PeakBurst, + tooltip: T.PeakBurstConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + notOnDrivers: [ovswitch, vcenter], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, +] + +/** @type {Field[]} List of hardware fields */ +const HARDWARE_FIELDS = [ + { + name: 'MODEL', + label: T.HardwareModelToEmulate, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, + { + name: 'VIRTIO_QUEUES', + label: T.TransmissionQueue, + tooltip: T.OnlySupportedForVirtioDriver, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + htmlType: 'number', + validation: number() + .notRequired() + .default(() => undefined), + }, +] + +/** @type {Field[]} List of guest option fields */ +const GUEST_FIELDS = [ + { + name: 'GUEST_MTU', + label: T.GuestMTU, + tooltip: T.GuestMTUConcept, + type: INPUT_TYPES.TEXT, + notOnHypervisors: [firecracker], + validation: string() + .trim() + .notRequired() + .default(() => undefined), + }, +] + +/** + * @param {object} data - VM or VM Template data + * @param {Nic[]} [data.nics] - Current nics on resource + * @param {VN_DRIVERS} [data.driver] - Virtual network driver + * @param {HYPERVISORS} [data.hypervisor] - VM Hypervisor + * @returns {Section[]} Sections + */ +const SECTIONS = ({ nics, driver, hypervisor = HYPERVISORS.kvm } = {}) => { + const filters = { driver, hypervisor } + + return [ + { + id: 'general', + legend: T.General, + fields: filterByHypAndDriver(GENERAL_FIELDS({ nics }), filters), + }, + { + id: 'override-ipv4', + legend: T.OverrideNetworkValuesIPv4, + fields: filterByHypAndDriver(OVERRIDE_IPV4_FIELDS, filters), + }, + { + id: 'override-ipv6', + legend: T.OverrideNetworkValuesIPv6, + fields: filterByHypAndDriver(OVERRIDE_IPV6_FIELDS, filters), + }, + { + id: 'override-in-qos', + legend: T.OverrideNetworkInboundTrafficQos, + fields: filterByHypAndDriver(OVERRIDE_IN_QOS_FIELDS, filters), + }, + { + id: 'override-out-qos', + legend: T.OverrideNetworkOutboundTrafficQos, + fields: filterByHypAndDriver(OVERRIDE_OUT_QOS_FIELDS, filters), + }, + { + id: 'hardware', + legend: T.Hardware, + fields: filterByHypAndDriver(HARDWARE_FIELDS, filters), + }, + { + id: 'guest', + legend: T.GuestOptions, + fields: filterByHypAndDriver(GUEST_FIELDS, filters), + }, + ] +} + +/** + * @param {object} data - VM or VM Template data + * @returns {Field[]} Advanced options schema + */ +const FIELDS = (data) => + SECTIONS(data) + .map(({ fields }) => fields) + .flat() + +/** + * @param {object} data - VM or VM Template data + * @returns {ObjectSchema} Advanced options schema + */ +const SCHEMA = (data) => getObjectSchemaFromFields(FIELDS(data)) + +export { SECTIONS, FIELDS, SCHEMA } diff --git a/src/fireedge/src/client/components/Forms/Vm/CreateCharterForm/index.js b/src/fireedge/src/client/components/Forms/Vm/CreateCharterForm/index.js index 57133e0a76..b96714ee6a 100644 --- a/src/fireedge/src/client/components/Forms/Vm/CreateCharterForm/index.js +++ b/src/fireedge/src/client/components/Forms/Vm/CreateCharterForm/index.js @@ -38,7 +38,7 @@ const FixedLeases = ({ leases }) => { return ( <> - + {transformChartersToSchedActions(fixedLeases, true)?.map((action) => { const { ACTION, TIME, PERIOD, WARNING, WARNING_PERIOD } = action diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootOrder.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootOrder.js index 8b027283a1..08074345ac 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootOrder.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootOrder.js @@ -69,7 +69,7 @@ const BootItemDraggable = styled('div')(({ theme, disabled }) => ({ * @returns {string} Updated boot order after remove */ export const reorderBootAfterRemove = (id, list, currentBootOrder) => { - const type = String(id).toLowerCase().replace(/\d+/g, '') // nic | disk + const type = String(id).toLowerCase().replace(/\d+/g, '') // nic | nic_alias | disk const getIndexFromId = (bootId) => `${bootId}`.toLowerCase().replace(type, '') @@ -129,9 +129,13 @@ const BootOrder = () => { [] ) - const nics = useMemo( - () => - getValues(`${EXTRA_ID}.${NIC_ID}`)?.map((nic, idx) => ({ + const nics = useMemo(() => { + const nicId = `${EXTRA_ID}.${NIC_ID[0]}` + const nicAliasId = `${EXTRA_ID}.${NIC_ID[1]}` + const nicValues = getValues([nicId, nicAliasId]).flat() + + return ( + nicValues?.map((nic, idx) => ({ ID: `nic${idx}`, NAME: ( <> @@ -139,9 +143,9 @@ const BootOrder = () => { {[nic?.NAME, nic.NETWORK].filter(Boolean).join(': ')} ), - })) ?? [], - [] - ) + })) ?? [] + ) + }, []) const enabledItems = [...disks, ...nics] .filter((item) => bootOrder.includes(item.ID)) @@ -230,5 +234,4 @@ const BootOrder = () => { } BootOrder.displayName = 'BootOrder' - export default BootOrder diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/index.js index c09a5b1012..c50b485683 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/index.js @@ -47,7 +47,8 @@ const Booting = ({ hypervisor, ...props }) => { sx={{ gridTemplateColumns: { sm: '1fr', md: '1fr 1fr' } }} > {(!!props.data?.[STORAGE_ID]?.length || - !!props.data?.[NIC_ID]?.length) && ( + !!props.data?.[NIC_ID[0]]?.length || + !!props.data?.[NIC_ID[1]]?.length) && ( { - const { id, NAME, RDP, SSH, NETWORK, PARENT, EXTERNAL } = item - const hasAlias = nics?.some((nic) => nic.PARENT === NAME) - - return ( - - {Object.entries({ - RDP: stringToBoolean(RDP), - SSH: stringToBoolean(SSH), - EXTERNAL: stringToBoolean(EXTERNAL), - [`PARENT: ${PARENT}`]: PARENT, - }) - .map(([k, v]) => (v ? `${k}` : '')) - .filter(Boolean) - .join(' | ')} - - } - action={ - <> - {!hasAlias && ( - } - handleClick={handleRemove} - color="error" - icon={} - /> - )} - , - tooltip: , - }} - options={[ - { - dialogProps: { - title: ( - - ), - }, - form: () => AttachNicForm({ nics }, item), - onSubmit: handleUpdate, - }, - ]} - /> - - } - /> - ) -}) - -NicItem.propTypes = { - index: PropTypes.number, - item: PropTypes.object, - nics: PropTypes.array, - handleRemove: PropTypes.func, - handleUpdate: PropTypes.func, -} - -NicItem.displayName = 'NicItem' - -export default NicItem diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js index 7a2364ed8e..3310db5611 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js @@ -18,9 +18,12 @@ import { Stack } from '@mui/material' import { ServerConnection as NetworkIcon } from 'iconoir-react' import { useFormContext, useFieldArray } from 'react-hook-form' -import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm' -import { AttachNicForm } from 'client/components/Forms/Vm' import { FormWithSchema } from 'client/components/Forms' +import NicCard from 'client/components/Cards/NicCard' +import { + AttachAction, + DetachAction, +} from 'client/components/Tabs/Vm/Network/Actions' import { STEP_ID as EXTRA_ID, @@ -33,58 +36,78 @@ import { } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting' import { FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema' import { T } from 'client/constants' -import NicItem from './NicItem' -export const TAB_ID = 'NIC' +export const TAB_ID = ['NIC', 'NIC_ALIAS'] -const mapNameFunction = mapNameByIndex('NIC') +const mapNicNameFunction = mapNameByIndex('NIC') +const mapAliasNameFunction = mapNameByIndex('NIC_ALIAS') -const Networking = () => { +const Networking = ({ hypervisor }) => { const { setValue, getValues } = useFormContext() + const { - fields: nics, - replace, - update, - append, + fields: nics = [], + replace: replaceNic, + update: updateNic, + append: appendNic, } = useFieldArray({ - name: `${EXTRA_ID}.${TAB_ID}`, + name: `${EXTRA_ID}.${TAB_ID[0]}`, }) - const removeAndReorder = (nicName) => { - const updatedNics = nics + const { + fields: alias = [], + replace: replaceAlias, + update: updateAlias, + append: appendAlias, + } = useFieldArray({ + name: `${EXTRA_ID}.${TAB_ID[1]}`, + }) + + const removeAndReorder = (nic) => { + const nicName = nic?.NAME + const isAlias = !!nic?.PARENT?.length + const list = isAlias ? alias : nics + + const updatedList = list .filter(({ NAME }) => NAME !== nicName) - .map(mapNameFunction) + .map(isAlias ? mapAliasNameFunction : mapNicNameFunction) + const currentBootOrder = getValues(BOOT_ORDER_NAME()) const updatedBootOrder = reorderBootAfterRemove( nicName, - nics, + list, currentBootOrder ) - replace(updatedNics) + isAlias ? replaceAlias(updatedList) : replaceNic(updatedList) setValue(BOOT_ORDER_NAME(), updatedBootOrder) } - const handleUpdate = (updatedNic, index) => { - update(index, mapNameFunction(updatedNic, index)) + const handleUpdate = ({ NAME: _, ...updatedNic }, id) => { + const isAlias = !!updatedNic?.PARENT?.length + const index = isAlias + ? alias.findIndex((nic) => nic.id === id) + : nics.findIndex((nic) => nic.id === id) + + isAlias + ? updateAlias(index, mapAliasNameFunction(updatedNic, index)) + : updateNic(index, mapNicNameFunction(updatedNic, index)) + } + + const handleAppend = (newNic) => { + const isAlias = !!newNic?.PARENT?.length + + isAlias + ? appendAlias(mapAliasNameFunction(newNic, alias.length)) + : appendNic(mapNicNameFunction(newNic, nics.length)) } return ( - <> - AttachNicForm({ nics }), - onSubmit: (nic) => append(mapNameFunction(nic, nics.length)), - }, - ]} +
+ { sx={{ gridTemplateColumns: { sm: '1fr', - md: 'repeat(auto-fit, minmax(300px, 0.5fr))', + md: 'repeat(auto-fit, minmax(400px, 0.5fr))', }, }} > - {nics?.map(({ id, ...item }, index) => ( - removeAndReorder(item?.NAME)} - handleUpdate={(updatedNic) => handleUpdate(updatedNic, index)} - /> - ))} + {[...nics, ...alias]?.map(({ id, ...item }, index) => { + const hasAlias = alias?.some((nic) => nic.PARENT === item.NAME) + item.NIC_ID = index + + return ( + + {!hasAlias && ( + removeAndReorder(item)} + /> + )} + { + const wasAlias = !!item?.PARENT?.length + const isAlias = !!updatedNic?.PARENT?.length + + if (wasAlias === isAlias) { + return handleUpdate(updatedNic, id) + } + + removeAndReorder(item) + handleAppend(updatedNic) + }} + /> + + } + /> + ) + })} { legendTooltip={T.NetworkDefaultsConcept} id={EXTRA_ID} /> - +
) } @@ -132,7 +185,7 @@ const TAB = { name: T.Network, icon: NetworkIcon, Content: Networking, - getError: (error) => !!error?.[TAB_ID], + getError: (error) => TAB_ID.some((id) => error?.[id]), } export default TAB diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema.js index 226df1696c..54d9279613 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema.js @@ -49,6 +49,9 @@ const SCHEMA = object({ NIC: array() .ensure() .transform((nics) => nics.map(mapNameByIndex('NIC'))), + NIC_ALIAS: array() + .ensure() + .transform((nics) => nics.map(mapNameByIndex('NIC_ALIAS'))), }).concat(getObjectSchemaFromFields(FIELDS)) export { FIELDS, SCHEMA } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/DiskItem.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/DiskItem.js deleted file mode 100644 index 7034503f26..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/DiskItem.js +++ /dev/null @@ -1,143 +0,0 @@ -/* ------------------------------------------------------------------------- * - * 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 { memo, JSXElementConstructor } from 'react' -import PropTypes from 'prop-types' -import { Stack } from '@mui/material' -import { Edit, Trash } from 'iconoir-react' - -import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm' -import SelectCard, { Action } from 'client/components/Cards/SelectCard' -import { ImageSteps, VolatileSteps } from 'client/components/Forms/Vm' -import { StatusCircle } from 'client/components/Status' -import { Translate } from 'client/components/HOC' - -import { getState, getDiskType } from 'client/models/Image' -import { stringToBoolean } from 'client/models/Helper' -import { prettyBytes } from 'client/utils' -import { T } from 'client/constants' - -/** - * The disk item will be included in the VM Template. - * - * @param {object} props - Props - * @param {number} props.index - Index in list - * @param {object} props.item - Disk - * @param {string} props.hypervisor - VM hypervisor - * @param {string} props.handleRemove - Remove function - * @param {string} props.handleUpdate - Update function - * @returns {JSXElementConstructor} - Disk card - */ -const DiskItem = memo(({ item, hypervisor, handleRemove, handleUpdate }) => { - const { - NAME, - TYPE, - IMAGE, - IMAGE_ID, - IMAGE_STATE, - ORIGINAL_SIZE, - SIZE = ORIGINAL_SIZE, - READONLY, - DATASTORE, - PERSISTENT, - } = item - - const isVolatile = !IMAGE && !IMAGE_ID - const state = !isVolatile && getState({ STATE: IMAGE_STATE }) - const type = isVolatile ? TYPE : getDiskType(item) - const originalSize = +ORIGINAL_SIZE ? prettyBytes(+ORIGINAL_SIZE, 'MB') : '-' - const size = prettyBytes(+SIZE, 'MB') - - return ( - - {`${NAME} - `} - - - ) : ( - - - {`${NAME}: ${IMAGE}`} - - ) - } - subheader={ - <> - {Object.entries({ - [DATASTORE]: DATASTORE, - READONLY: stringToBoolean(READONLY), - PERSISTENT: stringToBoolean(PERSISTENT), - [isVolatile || ORIGINAL_SIZE === SIZE - ? size - : `${originalSize}/${size}`]: true, - [type]: type, - }) - .map(([k, v]) => (v ? `${k}` : '')) - .filter(Boolean) - .join(' | ')} - - } - action={ - <> - } - handleClick={handleRemove} - color="error" - icon={} - /> - , - tooltip: , - }} - options={[ - { - dialogProps: { - title: , - }, - form: () => - isVolatile - ? VolatileSteps({ hypervisor }, item) - : ImageSteps({ hypervisor }, item), - onSubmit: handleUpdate, - }, - ]} - /> - - } - /> - ) -}) - -DiskItem.propTypes = { - index: PropTypes.number, - item: PropTypes.object, - hypervisor: PropTypes.string, - handleRemove: PropTypes.func, - handleUpdate: PropTypes.func, -} - -DiskItem.displayName = 'DiskItem' - -export default DiskItem diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/index.js index 2e61265a26..5075fc2d95 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/index.js @@ -18,9 +18,12 @@ import { Stack } from '@mui/material' import { Db as DatastoreIcon } from 'iconoir-react' import { useFieldArray, useFormContext } from 'react-hook-form' -import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm' -import { ImageSteps, VolatileSteps } from 'client/components/Forms/Vm' import { FormWithSchema } from 'client/components/Forms' +import DiskCard from 'client/components/Cards/DiskCard' +import { + AttachAction, + DetachAction, +} from 'client/components/Tabs/Vm/Storage/Actions' import { STEP_ID as EXTRA_ID, @@ -32,8 +35,8 @@ import { reorderBootAfterRemove, } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting' import { FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema' +import { getDiskName } from 'client/models/Image' import { T } from 'client/constants' -import DiskItem from './DiskItem' export const TAB_ID = 'DISK' @@ -70,33 +73,10 @@ const Storage = ({ hypervisor }) => { } return ( - <> - ImageSteps({ hypervisor }), - onSubmit: (image) => append(mapNameFunction(image, disks.length)), - }, - { - cy: 'attach-volatile', - name: T.Volatile, - dialogProps: { - title: T.AttachVolatile, - dataCy: 'modal-attach-volatile', - }, - form: () => VolatileSteps({ hypervisor }), - onSubmit: (image) => append(mapNameFunction(image, disks.length)), - }, - ]} +
+ append(mapNameFunction(image, disks.length))} /> { }, }} > - {disks?.map(({ id, ...item }, index) => ( - removeAndReorder(item?.NAME)} - handleUpdate={(updatedDisk) => handleUpdate(updatedDisk, index)} - /> - ))} + {disks?.map(({ id, ...item }, index) => { + item.DISK_ID ??= index + + return ( + + removeAndReorder(item?.NAME)} + /> + handleUpdate(updatedDisk, index)} + /> + + } + /> + ) + })} { legend={T.StorageOptions} id={EXTRA_ID} /> - +
) } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js index d53530ef1d..5cc3af69ee 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/capacitySchema.js @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ import { number } from 'yup' +import { Field } from 'client/utils' import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants' +const commonValidation = number() + .positive() + .default(() => undefined) + +/** @type {Field} Memory field */ const MEMORY = (hypervisor) => { - let validation = number() - .integer('Memory should be integer number') - .positive('Memory should be positive number') - .typeError('Memory must be a number') - .required('Memory field is required') - .default(() => undefined) + let validation = commonValidation.required() if (hypervisor === HYPERVISORS.vcenter) { validation = validation.isDivisibleBy(4) @@ -33,7 +33,7 @@ const MEMORY = (hypervisor) => { return { name: 'MEMORY', label: T.Memory, - tooltip: 'Amount of RAM required for the VM.', + tooltip: T.MemoryConcept, type: INPUT_TYPES.TEXT, htmlType: 'number', validation, @@ -41,34 +41,25 @@ const MEMORY = (hypervisor) => { } } +/** @type {Field} Physical CPU field */ const PHYSICAL_CPU = { name: 'CPU', label: T.PhysicalCpu, - tooltip: ` - Percentage of CPU divided by 100 required for - the Virtual Machine. Half a processor is written 0.5.`, + tooltip: T.PhysicalCpuConcept, type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: number() - .positive('CPU should be positive number') - .typeError('CPU must be a number') - .required('CPU field is required') - .default(() => undefined), + validation: commonValidation.required(), grid: { md: 12 }, } +/** @type {Field} Virtual CPU field */ const VIRTUAL_CPU = { name: 'VCPU', label: T.VirtualCpu, - tooltip: ` - Number of virtual cpus. This value is optional, the default - hypervisor behavior is used, usually one virtual CPU`, + tooltip: T.VirtualCpuConcept, type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: number() - .positive('Virtual CPU should be positive number') - .notRequired() - .default(() => undefined), + validation: commonValidation.notRequired(), grid: { md: 12 }, } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js index 2fa4591e7e..953905c4c4 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/index.js @@ -36,7 +36,7 @@ const Content = () => { const { view, getResourceView } = useAuth() const { watch } = useFormContext() - const groups = useMemo(() => { + const sections = useMemo(() => { const hypervisor = watch(`${TEMPLATE_ID}[0].TEMPLATE.HYPERVISOR`) const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.instantiate_dialog const sectionsAvailable = getSectionsAvailable(dialog, hypervisor) @@ -46,7 +46,7 @@ const Content = () => { return (
- {groups.map(({ id, legend, fields }) => ( + {sections.map(({ id, legend, fields }) => ( { - const [attachNic] = useAttachNicMutation() +const AttachAction = memo( + ({ vmId, hypervisor, nic, currentNics, onSubmit, sx }) => { + const [attachNic] = useAttachNicMutation() - const handleAttachNic = async (formData) => { - const isAlias = !!formData?.PARENT?.length - const data = { [isAlias ? 'NIC_ALIAS' : 'NIC']: formData } + const handleAttachNic = async (formData) => { + if (onSubmit && typeof onSubmit === 'function') { + return await onSubmit(formData) + } - const template = jsonToXml(data) - await attachNic({ id: vmId, template }) + const isAlias = !!formData?.PARENT?.length + const data = { [isAlias ? 'NIC_ALIAS' : 'NIC']: formData } + + const template = jsonToXml(data) + await attachNic({ id: vmId, template }) + } + + return ( + , + tooltip: Tr(T.Edit), + sx, + } + : { + color: 'secondary', + 'data-cy': 'add-nic', + label: T.AttachNic, + variant: 'outlined', + sx, + } + } + options={[ + { + dialogProps: { title: T.AttachNic, dataCy: 'modal-attach-nic' }, + form: () => AttachNicForm({ hypervisor, nics: currentNics }, nic), + onSubmit: handleAttachNic, + }, + ]} + /> + ) } +) - return ( - AttachNicForm({ nics: currentNics }), - onSubmit: handleAttachNic, - }, - ]} - /> - ) -}) - -const DetachAction = memo(({ vmId, nic }) => { +const DetachAction = memo(({ vmId, nic, onSubmit, sx }) => { const [detachNic] = useDetachNicMutation() const { NIC_ID, PARENT } = nic const isAlias = !!PARENT?.length const handleDetach = async () => { - await detachNic({ id: vmId, nic: NIC_ID }) + const handleDetachNic = onSubmit ?? detachNic + await handleDetachNic({ id: vmId, nic: NIC_ID }) } return ( @@ -73,6 +90,7 @@ const DetachAction = memo(({ vmId, nic }) => { 'data-cy': `detach-nic-${NIC_ID}`, icon: , tooltip: Tr(T.Detach), + sx, }} options={[ { @@ -81,7 +99,7 @@ const DetachAction = memo(({ vmId, nic }) => { title: ( ), children:

{Tr(T.DoYouWantProceed)}

, @@ -94,9 +112,12 @@ const DetachAction = memo(({ vmId, nic }) => { }) const ActionPropTypes = { - vmId: PropTypes.string.isRequired, - currentNics: PropTypes.object, + vmId: PropTypes.string, + hypervisor: PropTypes.string, + currentNics: PropTypes.array, nic: PropTypes.object, + onSubmit: PropTypes.func, + sx: PropTypes.object, } AttachAction.propTypes = ActionPropTypes diff --git a/src/fireedge/src/client/components/Tabs/Vm/Network/index.js b/src/fireedge/src/client/components/Tabs/Vm/Network/index.js index efc4870667..97133f858b 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Network/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Network/index.js @@ -46,24 +46,24 @@ const { ATTACH_NIC, DETACH_NIC } = VM_ACTIONS const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => { const { data: vm } = useGetVmQuery(id) - const [nics, actionsAvailable] = useMemo(() => { + const [nics, hypervisor, actionsAvailable] = useMemo(() => { const groupedNics = getNics(vm, { groupAlias: true, securityGroupsFromTemplate: true, }) - const hypervisor = getHypervisor(vm) - const actionsByHypervisor = getActionsAvailable(actions, hypervisor) + const hyperV = getHypervisor(vm) + const actionsByHypervisor = getActionsAvailable(actions, hyperV) const actionsByState = actionsByHypervisor.filter( (action) => !isAvailableAction(action)(vm) ) - return [groupedNics, actionsByState] + return [groupedNics, hyperV, actionsByState] }, [vm]) return ( - <> +
{actionsAvailable?.includes?.(ATTACH_NIC) && ( - + )} @@ -75,15 +75,16 @@ const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => { + ) + } /> ) })} - +
) } diff --git a/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js b/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js index 60878af4e2..67d37ab5e5 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js @@ -58,7 +58,7 @@ const VmSnapshotTab = ({ tabProps: { actions } = {}, id }) => { }, [vm]) return ( - <> +
{actionsAvailable?.includes(SNAPSHOT_CREATE) && ( )} @@ -75,7 +75,7 @@ const VmSnapshotTab = ({ tabProps: { actions } = {}, id }) => { /> ))} - +
) } diff --git a/src/fireedge/src/client/components/Tabs/Vm/Storage/Actions.js b/src/fireedge/src/client/components/Tabs/Vm/Storage/Actions.js index 6237f2877e..2d01695cc2 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Storage/Actions.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Storage/Actions.js @@ -48,48 +48,86 @@ import { jsonToXml } from 'client/models/Helper' import { Tr, Translate } from 'client/components/HOC' import { T, VM_ACTIONS } from 'client/constants' -const AttachAction = memo(({ vmId, hypervisor }) => { +const AttachAction = memo(({ vmId, disk, hypervisor, onSubmit, sx }) => { const [attachDisk] = useAttachDiskMutation() const handleAttachDisk = async (formData) => { + if (onSubmit && typeof onSubmit === 'function') { + return await onSubmit(formData) + } + const template = jsonToXml({ DISK: formData }) await attachDisk({ id: vmId, template }) } return ( ImageSteps({ hypervisor }), - onSubmit: handleAttachDisk, - }, - { - cy: 'attach-volatile', - name: T.Volatile, - dialogProps: { title: T.AttachVolatile }, - form: () => VolatileSteps({ hypervisor }), - onSubmit: handleAttachDisk, - }, - ]} + buttonProps={ + disk + ? { + 'data-cy': `edit-${disk.DISK_ID}`, + icon: , + tooltip: Tr(T.Edit), + sx, + } + : { + color: 'secondary', + 'data-cy': 'add-disk', + label: T.AttachDisk, + variant: 'outlined', + sx, + } + } + options={ + disk + ? [ + { + dialogProps: { + title: ( + + ), + }, + form: () => + !disk?.IMAGE && !disk?.IMAGE_ID // is volatile + ? VolatileSteps({ hypervisor }, disk) + : ImageSteps({ hypervisor }, disk), + onSubmit: handleAttachDisk, + }, + ] + : [ + { + cy: 'attach-image', + name: T.Image, + dialogProps: { + title: T.AttachImage, + dataCy: 'modal-attach-image', + }, + form: () => ImageSteps({ hypervisor }, disk), + onSubmit: handleAttachDisk, + }, + { + cy: 'attach-volatile', + name: T.Volatile, + dialogProps: { + title: T.AttachVolatile, + dataCy: 'modal-attach-volatile', + }, + form: () => VolatileSteps({ hypervisor }, disk), + onSubmit: handleAttachDisk, + }, + ] + } /> ) }) -const DetachAction = memo(({ vmId, disk, name: imageName }) => { +const DetachAction = memo(({ vmId, disk, name: imageName, onSubmit, sx }) => { const [detachDisk] = useDetachDiskMutation() const { DISK_ID } = disk const handleDetach = async () => { - await detachDisk({ id: vmId, disk: DISK_ID }) + const handleDetachDisk = onSubmit ?? detachDisk + await handleDetachDisk({ id: vmId, disk: DISK_ID }) } return ( @@ -98,6 +136,7 @@ const DetachAction = memo(({ vmId, disk, name: imageName }) => { 'data-cy': `${VM_ACTIONS.DETACH_DISK}-${DISK_ID}`, icon: , tooltip: Tr(T.Detach), + sx, }} options={[ { @@ -118,7 +157,7 @@ const DetachAction = memo(({ vmId, disk, name: imageName }) => { ) }) -const SaveAsAction = memo(({ vmId, disk, snapshot, name: imageName }) => { +const SaveAsAction = memo(({ vmId, disk, snapshot, name: imageName, sx }) => { const [saveAsDisk] = useSaveAsDiskMutation() const { DISK_ID: diskId } = disk const { ID: snapshotId, NAME: snapshotName } = snapshot ?? {} @@ -138,6 +177,7 @@ const SaveAsAction = memo(({ vmId, disk, snapshot, name: imageName }) => { 'data-cy': `${VM_ACTIONS.DISK_SAVEAS}-${diskId}`, icon: , tooltip: Tr(T.SaveAs), + sx, }} options={[ { @@ -163,7 +203,7 @@ const SaveAsAction = memo(({ vmId, disk, snapshot, name: imageName }) => { ) }) -const ResizeAction = memo(({ vmId, disk, name: imageName }) => { +const ResizeAction = memo(({ vmId, disk, name: imageName, sx }) => { const [resizeDisk] = useResizeDiskMutation() const { DISK_ID } = disk @@ -177,6 +217,7 @@ const ResizeAction = memo(({ vmId, disk, name: imageName }) => { 'data-cy': `${VM_ACTIONS.RESIZE_DISK}-${DISK_ID}`, icon: , tooltip: Tr(T.Resize), + sx, }} options={[ { @@ -196,7 +237,7 @@ const ResizeAction = memo(({ vmId, disk, name: imageName }) => { ) }) -const SnapshotCreateAction = memo(({ vmId, disk, name: imageName }) => { +const SnapshotCreateAction = memo(({ vmId, disk, name: imageName, sx }) => { const [createDiskSnapshot] = useCreateDiskSnapshotMutation() const { DISK_ID } = disk @@ -210,6 +251,7 @@ const SnapshotCreateAction = memo(({ vmId, disk, name: imageName }) => { 'data-cy': `${VM_ACTIONS.SNAPSHOT_DISK_CREATE}-${DISK_ID}`, icon: , tooltip: Tr(T.TakeSnapshot), + sx, }} options={[ { @@ -229,7 +271,7 @@ const SnapshotCreateAction = memo(({ vmId, disk, name: imageName }) => { ) }) -const SnapshotRenameAction = memo(({ vmId, disk, snapshot }) => { +const SnapshotRenameAction = memo(({ vmId, disk, snapshot, sx }) => { const [renameDiskSnapshot] = useRenameDiskSnapshotMutation() const { DISK_ID } = disk const { ID, NAME = '' } = snapshot @@ -249,6 +291,7 @@ const SnapshotRenameAction = memo(({ vmId, disk, snapshot }) => { 'data-cy': `${VM_ACTIONS.SNAPSHOT_DISK_RENAME}-${DISK_ID}-${ID}`, icon: , tooltip: Tr(T.Edit), + sx, }} options={[ { @@ -266,7 +309,7 @@ const SnapshotRenameAction = memo(({ vmId, disk, snapshot }) => { ) }) -const SnapshotRevertAction = memo(({ vmId, disk, snapshot }) => { +const SnapshotRevertAction = memo(({ vmId, disk, snapshot, sx }) => { const [revertDiskSnapshot] = useRevertDiskSnapshotMutation() const { DISK_ID } = disk const { ID, NAME = T.Snapshot } = snapshot @@ -281,6 +324,7 @@ const SnapshotRevertAction = memo(({ vmId, disk, snapshot }) => { 'data-cy': `${VM_ACTIONS.SNAPSHOT_DISK_REVERT}-${DISK_ID}-${ID}`, icon: , tooltip: Tr(T.Revert), + sx, }} options={[ { @@ -298,7 +342,7 @@ const SnapshotRevertAction = memo(({ vmId, disk, snapshot }) => { ) }) -const SnapshotDeleteAction = memo(({ vmId, disk, snapshot }) => { +const SnapshotDeleteAction = memo(({ vmId, disk, snapshot, sx }) => { const [deleteDiskSnapshot] = useDeleteDiskSnapshotMutation() const { DISK_ID } = disk const { ID, NAME = T.Snapshot } = snapshot @@ -313,6 +357,7 @@ const SnapshotDeleteAction = memo(({ vmId, disk, snapshot }) => { 'data-cy': `${VM_ACTIONS.SNAPSHOT_DISK_DELETE}-${DISK_ID}-${ID}`, icon: , tooltip: Tr(T.Delete), + sx, }} options={[ { @@ -331,11 +376,13 @@ const SnapshotDeleteAction = memo(({ vmId, disk, snapshot }) => { }) const ActionPropTypes = { - vmId: PropTypes.string.isRequired, + vmId: PropTypes.string, hypervisor: PropTypes.string, disk: PropTypes.object, snapshot: PropTypes.object, name: PropTypes.string, + onSubmit: PropTypes.func, + sx: PropTypes.object, } AttachAction.propTypes = ActionPropTypes diff --git a/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js b/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js index f12adc9093..623223bf92 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js @@ -18,6 +18,7 @@ import PropTypes from 'prop-types' import { Stack } from '@mui/material' import { useGetVmQuery } from 'client/features/OneApi/vm' +import DiskCard from 'client/components/Cards/DiskCard' import { AttachAction, SaveAsAction, @@ -28,13 +29,13 @@ import { SnapshotRenameAction, SnapshotDeleteAction, } from 'client/components/Tabs/Vm/Storage/Actions' -import DiskCard from 'client/components/Cards/DiskCard' import { getDisks, getHypervisor, isAvailableAction, } from 'client/models/VirtualMachine' +import { getDiskName } from 'client/models/Image' import { getActionsAvailable } from 'client/models/Helper' import { VM_ACTIONS } from 'client/constants' @@ -71,11 +72,8 @@ const VmStorageTab = ({ tabProps: { actions } = {}, id }) => { return [getDisks(vm), hyperV, actionsByState] }, [vm]) - const filterByAvailable = (action, button) => - actionsAvailable.includes(action) && button - return ( - <> +
{actionsAvailable?.includes?.(ATTACH_DISK) && ( )} @@ -83,31 +81,60 @@ const VmStorageTab = ({ tabProps: { actions } = {}, id }) => { {disks.map((disk) => { const isImage = disk.IMAGE_ID !== undefined + const imageName = getDiskName(disk) + const diskActionProps = { vmId: id, disk, name: imageName } return ( + {isImage && actionsAvailable.includes(DISK_SAVEAS) && ( + + )} + {actionsAvailable.includes(SNAPSHOT_DISK_CREATE) && ( + + )} + {actionsAvailable.includes(RESIZE_DISK) && ( + + )} + {actionsAvailable.includes(DETACH_DISK) && ( + + )} + + } + snapshotActions={({ snapshot }) => ( + <> + {isImage && actionsAvailable.includes(DISK_SAVEAS) && ( + + )} + {actionsAvailable.includes(SNAPSHOT_DISK_RENAME) && ( + + )} + {actionsAvailable.includes(SNAPSHOT_DISK_REVERT) && ( + + )} + {actionsAvailable.includes(SNAPSHOT_DISK_DELETE) && ( + + )} + + )} /> ) })} - +
) } diff --git a/src/fireedge/src/client/constants/network.js b/src/fireedge/src/client/constants/network.js index 22acab2e93..38e3357535 100644 --- a/src/fireedge/src/client/constants/network.js +++ b/src/fireedge/src/client/constants/network.js @@ -108,6 +108,21 @@ export const AR_TYPES = { IP4_6_STATIC: 'IP4_6_STATIC', } +/** @enum {string} Virtual Network Drivers */ +export const VN_DRIVERS = { + dummy: 'dummy', + dot1Q: '802.1Q', + ebtables: 'ebtables', + fw: 'fw', + ovswitch: 'ovswitch', + vxlan: 'vxlan', + vcenter: 'vcenter', + ovswitch_vxlan: 'ovswitch_vxlan', + bridge: 'bridge', + elastic: 'elastic', + nodeport: 'nodeport', +} + /** @enum {string} Virtual network actions */ export const VN_ACTIONS = { CREATE_DIALOG: 'create_dialog', diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index f40ff0cbd5..dcfadf376e 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -347,11 +347,11 @@ module.exports = { Secondary: 'Secondary', /* instances schema */ - IP: 'IP', DeployID: 'Deploy ID', vCenterDeployment: 'vCenter Deployment', Deployment: 'Deployment', Monitoring: 'Monitoring', + EdgeCluster: 'Edge Cluster', /* flow schema */ Strategy: 'Strategy', @@ -391,6 +391,10 @@ module.exports = { SshConnection: 'SSH connection', External: 'External', ExternalConcept: 'The NIC will be attached as an external alias of the VM', + OverrideNetworkValuesIPv4: 'Override Network Values IPv4', + OverrideNetworkValuesIPv6: 'Override Network Values IPv6', + OverrideNetworkInboundTrafficQos: 'Override Network Inbound Traffic QoS', + OverrideNetworkOutboundTrafficQos: 'Override Network Outbound Traffic QoS', /* VM Template schema */ /* VM Template schema - general */ @@ -572,7 +576,7 @@ module.exports = { Keymap: 'Keymap', GenerateRandomPassword: 'Generate random password', Command: 'Command', - Bus: 'Bus', + Bus: 'BUS', /* VM Template schema - NUMA */ PinPolicy: 'Pin Policy', PinPolicyConcept: 'Virtual CPU pinning preference: %s', @@ -590,6 +594,45 @@ module.exports = { Number of virtual CPUs. This value is optional, the default hypervisor behavior is used, usually one virtual CPU`, + /* Virtual Network schema - network */ + IP: 'IP', + IPv4Concept: 'First IP in the range in dot notation', + IPv6Concept: 'First IP6 (full 128 bits) in the range', + MAC: 'MAC', + MACConcept: ` + First MAC, if not provided it will be generated + using the IP and the MAC_PREFIX in oned.conf`, + NetworkAddress: 'Network address', + NetworkMask: 'Network mask', + Gateway: 'Gateway', + GatewayConcept: 'Default gateway for the network', + Gateway6Concept: 'IPv6 router for this network', + SearchDomainForDNSResolution: 'Search domains for DNS resolution', + NetworkMethod: 'Network method', + NetworkMethod4Concept: 'Sets IPv4 guest conf. method for NIC in this network', + NetworkMethod6Concept: 'Sets IPv6 guest conf. method for NIC in this network', + DNS: 'DNS', + DNSConcept: 'DNS servers, a space separated list of servers', + AverageBandwidth: 'Average bandwidth (KBytes/s)', + PeakBandwidth: 'Peak bandwidth (KBytes/s)', + PeakBurst: 'Peak burst (KBytes)', + InboundAverageBandwidthConcept: + 'Average bitrate for the interface in kilobytes/second for inbound traffic', + InboundPeakBandwidthConcept: + 'Maximum bitrate for the interface in kilobytes/second for inbound traffic', + OutboundAverageBandwidthConcept: + 'Average bitrate for the interface in kilobytes/second for outbound traffic', + OutboundPeakBandwidthConcept: + 'Maximum bitrate for the interface in kilobytes/second for outbound traffic', + PeakBurstConcept: 'Data that can be transmitted at peak speed in kilobytes', + Hardware: 'Hardware', + HardwareModelToEmulate: 'Hardware model to emulate', + TransmissionQueue: 'Transmission queue', + OnlySupportedForVirtioDriver: 'Only supported for virtio driver', + GuestOptions: 'Guest options', + GuestMTU: 'GuestMTU', + GuestMTUConcept: 'Sets the MTU for the NICs in this network', + /* security group schema */ TCP: 'TCP', UDP: 'UDP', @@ -629,19 +672,53 @@ module.exports = { DownloadAppToOpenNebula: 'Download App to OpenNebula', ExportAppNameConcept: 'Name that the resource will get for description purposes', - ExportTemplateNameConcept: - 'The following template will be created in OpenNebula and the previous images will be referenced in the disks', + ExportTemplateNameConcept: ` + The following template will be created in OpenNebula + and the previous images will be referenced in the disks`, ExportAssociateApp: 'Export associated VM templates/images', ImportAssociateApp: 'Import associated VM templates/images', /* Image schema */ + /* Image - general */ Limit: 'Limit', BasePath: 'Base path', - - /* Image schema */ FileSystemType: 'Filesystem type', Persistent: 'Persistent', RunningVMs: 'Running VMs', + /* Disk - general */ + DiskType: 'Disk type', + SizeOnInstantiate: 'Size on instantiate', + SizeOnInstantiateConcept: ` + The size of the disk will be modified to match + this size when the template is instantiated`, + TargetDevice: 'Target device', + TargetDeviceConcept: ` + Device to map image disk. + If set, it will overwrite the default device mapping`, + ReadOnly: 'Read-only', + BusAdapterController: 'Bus adapter controller', + DiskProvisioningType: 'Disk provisioning type', + Cache: 'Cache', + IoPolicy: 'IO Policy', + Discard: 'Discard', + IopsSize: 'Size of IOPS per second', + ThrottlingBytes: 'Throttling (Bytes/s)', + ThrottlingIOPS: 'Throttling (IOPS)', + TotalValue: 'Total value', + TotalMaximum: 'Total maximum', + TotalMaximumLength: 'Total maximum length', + ReadValue: 'Read value', + ReadMaximum: 'Read maximum', + ReadMaximumLength: 'Read maximum length', + WriteValue: 'Write value', + WriteMaximum: 'Write maximum', + WriteMaximumLength: 'Write maximum length', + SnapshotFrequency: 'Snapshot Frequency in seconds', + IoThreadId: 'IOTHREAD id', + IoThreadIdConcept: ` + Iothread id used by this disk. Default is round robin. + Can be used only if IOTHREADS > 0. If this input is disabled + please first configure IOTHREADS value on OS & CPU -> Features`, /* User inputs */ UserInputs: 'User Inputs', diff --git a/src/fireedge/src/client/features/OneApi/index.js b/src/fireedge/src/client/features/OneApi/index.js index 1b942400b9..24bd035024 100644 --- a/src/fireedge/src/client/features/OneApi/index.js +++ b/src/fireedge/src/client/features/OneApi/index.js @@ -73,8 +73,10 @@ const oneApi = createApi({ return { data: response.data ?? {} } } catch (axiosError) { - const { message, data, status, statusText } = axiosError - const error = message ?? data?.message ?? statusText + const { message, data = {}, status, statusText } = axiosError + const { message: messageFromServer, data: errorFromOned } = data + + const error = message ?? errorFromOned ?? messageFromServer ?? statusText status === httpCodes.unauthorized.id ? dispatch(logout(T.SessionExpired)) diff --git a/src/fireedge/src/client/models/Image.js b/src/fireedge/src/client/models/Image.js index 7f4cb1cb20..043d16f857 100644 --- a/src/fireedge/src/client/models/Image.js +++ b/src/fireedge/src/client/models/Image.js @@ -20,6 +20,7 @@ import { StateInfo, Image, } from 'client/constants' +import { prettyBytes } from 'client/utils' /** * Returns the image type. @@ -46,3 +47,16 @@ export const getState = ({ STATE } = {}) => IMAGE_STATES[+STATE] */ export const getDiskType = ({ DISK_TYPE } = {}) => isNaN(+DISK_TYPE) ? DISK_TYPE : DISK_TYPES[+DISK_TYPE] + +/** + * Returns the disk name. + * + * @param {Image} image - Image + * @returns {string} - Disk name + */ +export const getDiskName = ({ IMAGE, SIZE, TYPE, FORMAT } = {}) => { + const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-' + const type = String(TYPE).toLowerCase() + + return IMAGE ?? { fs: `${FORMAT} - ${size}`, swap: size }[type] +} diff --git a/src/fireedge/src/client/theme/defaults.js b/src/fireedge/src/client/theme/defaults.js index f7d1924109..a648bbfae5 100644 --- a/src/fireedge/src/client/theme/defaults.js +++ b/src/fireedge/src/client/theme/defaults.js @@ -350,6 +350,13 @@ export default (appTheme, mode = SCHEMES.DARK) => { }, }, }, + MuiFormLabel: { + styleOverrides: { + root: { + padding: '0 2em 0 0', + }, + }, + }, MuiTextField: { defaultProps: { variant: 'outlined', diff --git a/src/fireedge/src/client/utils/helpers.js b/src/fireedge/src/client/utils/helpers.js index fa3d14a3ad..601217b0d7 100644 --- a/src/fireedge/src/client/utils/helpers.js +++ b/src/fireedge/src/client/utils/helpers.js @@ -16,7 +16,8 @@ import DOMPurify from 'dompurify' import { object, reach, ObjectSchema, BaseSchema } from 'yup' import { isMergeableObject } from 'client/utils/merge' -import { HYPERVISORS } from 'client/constants' +import { Field } from 'client/utils/schema' +import { HYPERVISORS, VN_DRIVERS } from 'client/constants' /** * Simulate a delay in a function. @@ -198,9 +199,9 @@ export const getObjectSchemaFromFields = (fields) => }, object()) /** - * @param {Array} fields - Fields + * @param {Field[]} fields - Fields * @param {HYPERVISORS} hypervisor - Hypervisor - * @returns {Array} Filtered fields + * @returns {Field[]} Filtered fields */ export const filterFieldsByHypervisor = ( fields, @@ -212,6 +213,18 @@ export const filterFieldsByHypervisor = ( ({ notOnHypervisors } = {}) => !notOnHypervisors?.includes?.(hypervisor) ) +/** + * @param {Field[]} fields - Fields + * @param {VN_DRIVERS} driver - Driver + * @returns {Field[]} Filtered fields + */ +export const filterFieldsByDriver = (fields, driver = false) => + fields + .map((field) => (typeof field === 'function' ? field(driver) : field)) + .filter( + ({ notOnDrivers } = {}) => !driver || !notOnDrivers?.includes?.(driver) + ) + /** * Filter an object list by property. * diff --git a/src/fireedge/src/client/utils/schema.js b/src/fireedge/src/client/utils/schema.js index 30a248a9a4..a91da72b3e 100644 --- a/src/fireedge/src/client/utils/schema.js +++ b/src/fireedge/src/client/utils/schema.js @@ -16,7 +16,7 @@ /* eslint-disable jsdoc/valid-types */ // eslint-disable-next-line no-unused-vars -import { JSXElementConstructor, SetStateAction } from 'react' +import { ReactElement, SetStateAction } from 'react' import { // eslint-disable-next-line no-unused-vars GridProps, @@ -34,6 +34,10 @@ import { Row } from 'react-table' import { UserInputObject, T, + // eslint-disable-next-line no-unused-vars + HYPERVISORS, + // eslint-disable-next-line no-unused-vars + VN_DRIVERS, INPUT_TYPES, USER_INPUT_TYPES, } from 'client/constants' @@ -70,7 +74,7 @@ import { /** * @typedef {object} SelectOption - Option of select field - * @property {string|JSXElementConstructor} text - Text to display on select list + * @property {string|ReactElement} text - Text to display on select list * @property {any} value - Value to option */ @@ -104,12 +108,16 @@ import { * - Default: { xs: 12, md: 6 } * @property {BaseSchema|DependOfCallback} [validation] * - Schema to validate the field value + * @property {HYPERVISORS[]|DependOfCallback} [notOnHypervisors] + * - Filters the field when the hypervisor is not include on list + * @property {VN_DRIVERS[]|DependOfCallback} [notOnDrivers] + * - Filters the field when the driver is not include on list * @property {TextFieldProps|CheckboxProps|InputBaseComponentProps} [fieldProps] * - Extra properties to material-ui field * @property {function(string|number):any} [renderValue] * - Render the current selected value inside selector input * - **Only for select inputs.** - * @property {JSXElementConstructor} [Table] + * @property {ReactElement} [Table] * - Table component. One of table defined in: `client/components/Tables` * - **Only for table inputs.** * @property {boolean|DependOfCallback} [singleSelect] @@ -148,7 +156,7 @@ import { * @property {string} id - Id * @property {string} label - Label * @property {BaseSchema|function(object):BaseSchema} resolver - Schema - * @property {function(object, SetStateAction):JSXElementConstructor} content - Content + * @property {function(object, SetStateAction):ReactElement} content - Content * @property {ValidateOptions|undefined} optionsValidate - Validate options */ diff --git a/src/fireedge/src/server/routes/api/system/functions.js b/src/fireedge/src/server/routes/api/system/functions.js index 8d437cb4ae..30eccb6295 100644 --- a/src/fireedge/src/server/routes/api/system/functions.js +++ b/src/fireedge/src/server/routes/api/system/functions.js @@ -32,6 +32,7 @@ const ALLOWED_KEYS_ONED_CONF = [ 'DS_MAD_CONF', 'MARKET_MAD_CONF', 'VM_MAD', + 'VN_MAD_CONF', 'IM_MAD', 'AUTH_MAD', ]