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 8fdfddb2d5..affc3ec0d5 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 @@ -57,15 +57,21 @@ const Content = () => { ) } -const BasicConfiguration = () => ({ - id: STEP_ID, - label: T.Configuration, - resolver: formData => { - const hypervisor = formData?.[TEMPLATE_ID]?.[0]?.TEMPLATE?.HYPERVISOR - return SCHEMA(hypervisor) - }, - optionsValidate: { abortEarly: false }, - content: Content -}) +const BasicConfiguration = initialValues => { + const initialHypervisor = initialValues?.TEMPLATE?.HYPERVISOR + + return { + id: STEP_ID, + label: T.Configuration, + resolver: formData => { + const hypervisor = + formData?.[TEMPLATE_ID]?.[0]?.TEMPLATE?.HYPERVISOR ?? initialHypervisor + + return SCHEMA(hypervisor) + }, + optionsValidate: { abortEarly: false }, + content: Content + } +} export default BasicConfiguration diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting.js deleted file mode 100644 index 8cffeb7074..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting.js +++ /dev/null @@ -1,226 +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. * - * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import { SetStateAction } from 'react' -import PropTypes from 'prop-types' -import { useWatch } from 'react-hook-form' - -import { NetworkAlt as NetworkIcon, BoxIso as ImageIcon } from 'iconoir-react' -import { Stack, Checkbox, styled } from '@mui/material' -import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd' - -import { Translate } from 'client/components/HOC' -import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' -import { TAB_ID as STORAGE_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/storage' -import { TAB_ID as NIC_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/networking' -import { T } from 'client/constants' - -const BootItem = styled('div')(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: '0.5em', - border: `1px solid ${theme.palette.divider}`, - borderRadius: '0.5em', - padding: '1em', - marginBottom: '1em', - backgroundColor: theme.palette.background.default -})) - -const BootItemDraggable = styled(BootItem)(({ theme }) => ({ - '&:before': { - content: "'.'", - fontSize: 20, - color: theme.palette.action.active, - paddingBottom: 20, - textShadow: ` - 0 5px ${theme.palette.action.active}, - 0 10px ${theme.palette.action.active}, - 5px 0 ${theme.palette.action.active}, - 5px 5px ${theme.palette.action.active}, - 5px 10px ${theme.palette.action.active}, - 10px 0 ${theme.palette.action.active}, - 10px 5px ${theme.palette.action.active}, - 10px 10px ${theme.palette.action.active}` - } -})) - -export const TAB_ID = 'OS.BOOT' - -/** - * @param {string} id - Resource id: 'NIC' or 'DISK' - * @param {Array} list - List of resources - * @param {object} formData - Form data - * @param {SetStateAction} setFormData - React set state action - */ -export const reorderBootAfterRemove = (id, list, formData, setFormData) => { - const type = String(id).toLowerCase().replace(/\d+/g, '') // nic | disk - const getIndexFromId = id => String(id).toLowerCase().replace(type, '') - const idxToRemove = getIndexFromId(id) - - const ids = list - .filter(resource => resource.NAME !== id) - .map(resource => String(resource.NAME).toLowerCase()) - - const newBootOrder = [...formData?.OS?.BOOT?.split(',').filter(Boolean)] - .filter(bootId => !bootId.startsWith(type) || ids.includes(bootId)) - .map(bootId => { - if (!bootId.startsWith(type)) return bootId - - const resourceId = getIndexFromId(bootId) - - return resourceId < idxToRemove - ? bootId - : `${type}${resourceId - 1}` - }) - - reorder(newBootOrder, setFormData) -} - -/** - * @param {string[]} newBootOrder - New boot order - * @param {SetStateAction} setFormData - React set state action - */ -const reorder = (newBootOrder, setFormData) => { - setFormData(prev => ({ - ...prev, - [EXTRA_ID]: { - ...prev[EXTRA_ID], - OS: { BOOT: newBootOrder.join(',') } - } - })) -} - -const Booting = ({ data, setFormData, control }) => { - const booting = useWatch({ name: `${EXTRA_ID}.${TAB_ID}`, control }) - const bootOrder = booting?.split(',').filter(Boolean) ?? [] - - const disks = data?.[STORAGE_ID] - ?.map((disk, idx) => { - const isVolatile = !disk?.IMAGE && !disk?.IMAGE_ID - - return { - ID: `disk${idx}`, - NAME: ( - <> - - {isVolatile - ? <>{`${disk?.NAME}: `} - : [disk?.NAME, disk?.IMAGE].filter(Boolean).join(': ')} - - ) - } - }) ?? [] - - const nics = data?.[NIC_ID] - ?.map((nic, idx) => ({ - ID: `nic${idx}`, - NAME: ( - <> - - {[nic?.NAME, nic.NETWORK].filter(Boolean).join(': ')} - - ) - })) ?? [] - - const enabledItems = [...disks, ...nics] - .filter(item => bootOrder.includes(item.ID)) - .sort((a, b) => bootOrder.indexOf(a.ID) - bootOrder.indexOf(b.ID)) - - const restOfItems = [...disks, ...nics] - .filter(item => !bootOrder.includes(item.ID)) - - /** @param {DropResult} result - Drop result */ - const onDragEnd = result => { - const { destination, source, draggableId } = result - const newBootOrder = [...bootOrder] - - if ( - destination && - destination.index !== source.index && - newBootOrder.includes(draggableId) - ) { - newBootOrder.splice(source.index, 1) // remove current position - newBootOrder.splice(destination.index, 0, draggableId) // set in new position - - reorder(newBootOrder, setFormData) - } - } - - const handleEnable = itemId => { - const newBootOrder = [...bootOrder] - const itemIndex = bootOrder.indexOf(itemId) - - itemIndex >= 0 - ? newBootOrder.splice(itemIndex, 1) - : newBootOrder.push(itemId) - - reorder(newBootOrder, setFormData) - } - - return ( - - - - {({ droppableProps, innerRef, placeholder }) => ( - - {enabledItems.map(({ ID, NAME }, idx) => ( - - {({ draggableProps, dragHandleProps, innerRef }) => ( - - handleEnable(ID)} - /> - {NAME} - - )} - - ))} - {placeholder} - - )} - - {restOfItems.map(({ ID, NAME }) => ( - - handleEnable(ID)} - /> - {NAME} - - ))} - - - ) -} - -Booting.propTypes = { - data: PropTypes.any, - setFormData: PropTypes.func, - hypervisor: PropTypes.string, - control: PropTypes.object -} - -Booting.displayName = 'Booting' - -export default Booting diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js index 1ee8a88841..b4cde090f6 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/index.js @@ -16,20 +16,19 @@ /* eslint-disable jsdoc/require-jsdoc */ import { useMemo } from 'react' import PropTypes from 'prop-types' - import { useFormContext } from 'react-hook-form' -import { useTheme } from '@mui/material' -import { WarningCircledOutline as WarningIcon } from 'iconoir-react' +import { SystemShut as OsIcon } from 'iconoir-react' import { useAuth } from 'client/features/Auth' -import { Tr } from 'client/components/HOC' +import { Translate } from 'client/components/HOC' import Tabs from 'client/components/Tabs' -import Storage from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/storage' -import Networking from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/networking' -import Placement from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/placement' -import ScheduleAction from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/scheduleAction' -import Booting from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting' +import { TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' +import Storage from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage' +import Networking from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking' +import Placement from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/placement' +import Scheduling from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/scheduleAction' +import BootOrder from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting/bootOrder' import { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable' import { SCHEMA } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema' @@ -38,72 +37,69 @@ import { T } from 'client/constants' export const STEP_ID = 'extra' +/** @type {TabType[]} */ +export const TABS = [ + Storage, + Networking, + Placement, + Scheduling, + { + id: 'booting', + name: T.OSBooting, + icon: OsIcon, + Content: BootOrder, + getError: error => !!error?.OS + } +] + const Content = ({ data, setFormData }) => { - const theme = useTheme() const { watch, formState: { errors }, control } = useFormContext() const { view, getResourceView } = useAuth() - const tabs = useMemo(() => { - const hypervisor = watch(`${TEMPLATE_ID}[0].TEMPLATE.HYPERVISOR`) - const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.instantiate_dialog - const sectionsAvailable = getSectionsAvailable(dialog, hypervisor) + const hypervisor = useMemo(() => watch(`${TEMPLATE_ID}.0.HYPERVISOR`), []) - return [ - { - id: 'storage', - name: Tr(T.Storage), - renderContent: , - icon: errors[STEP_ID]?.[0] && ( - - ) - }, - { - id: 'network', - name: Tr(T.Network), - renderContent: , - icon: errors[STEP_ID]?.[1] && ( - - ) - }, - { - id: 'placement', - name: Tr(T.Placement), - renderContent: , - icon: errors[STEP_ID]?.[2] && ( - - ) - }, - { - id: 'sched_action', - name: Tr(T.ScheduledAction), - renderContent: , - icon: errors[STEP_ID]?.[3] && ( - - ) - }, - { - id: 'booting', - name: Tr(T.OSBooting), - renderContent: , - icon: errors[STEP_ID]?.[4] && ( - - ) - } - ].filter(({ id }) => sectionsAvailable.includes(id)) - }, [errors[STEP_ID], view, control]) + const sectionsAvailable = useMemo(() => { + const dialog = getResourceView('VM-TEMPLATE')?.dialogs?.instantiate_dialog + return getSectionsAvailable(dialog, hypervisor) + }, [view]) + + const totalErrors = Object.keys(errors[STEP_ID] ?? {}).length + + const tabs = useMemo( + () => TABS + .filter(({ id }) => sectionsAvailable.includes(id)) + .map(({ Content: TabContent, name, getError, ...section }) => ({ + ...section, + name, + label: , + // eslint-disable-next-line react/display-name + renderContent: () => , + error: getError?.(errors[STEP_ID]) + })), + [totalErrors, view, control] + ) return ( ) } -const ExtraConfiguration = () => ({ - id: STEP_ID, - label: T.AdvancedOptions, - resolver: SCHEMA, - optionsValidate: { abortEarly: false }, - content: Content -}) +const ExtraConfiguration = initialValues => { + const initialHypervisor = initialValues?.TEMPLATE?.HYPERVISOR + + return { + id: STEP_ID, + label: T.AdvancedOptions, + resolver: formData => { + const hypervisor = + formData?.[TEMPLATE_ID]?.[0]?.TEMPLATE?.HYPERVISOR ?? initialHypervisor + + return SCHEMA(hypervisor) + }, + optionsValidate: { abortEarly: false }, + content: Content + } +} Content.propTypes = { data: PropTypes.any, diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/networking.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/networking.js deleted file mode 100644 index 2e57e0ee20..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/networking.js +++ /dev/null @@ -1,148 +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. * - * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import PropTypes from 'prop-types' -import makeStyles from '@mui/styles/makeStyles' -import { Edit, Trash } from 'iconoir-react' -import { useWatch } from 'react-hook-form' - -import { useListForm } from 'client/hooks' -import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm' -import SelectCard, { Action } from 'client/components/Cards/SelectCard' -import { AttachNicForm } from 'client/components/Forms/Vm' -import { Translate } from 'client/components/HOC' - -import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' -import { SCHEMA as EXTRA_SCHEMA } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema' -import { reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting' -import { stringToBoolean } from 'client/models/Helper' -import { T } from 'client/constants' - -const useStyles = makeStyles({ - root: { - paddingBlock: '1em', - display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(300px, auto))', - gap: '1em' - } -}) - -export const TAB_ID = 'NIC' - -const Networking = ({ data, setFormData, control }) => { - const classes = useStyles() - const nics = useWatch({ name: `${EXTRA_ID}.${TAB_ID}`, control }) - - const { handleSetList, handleRemove, handleSave } = useListForm({ - parent: EXTRA_ID, - key: TAB_ID, - list: nics, - setList: setFormData, - getItemId: item => item.NAME, - addItemId: (item, _, itemIndex) => ({ ...item, NAME: `${TAB_ID}${itemIndex}` }) - }) - - const reorderNics = () => { - const diskSchema = EXTRA_SCHEMA.pick([TAB_ID]) - const { [TAB_ID]: newList } = diskSchema.cast({ [TAB_ID]: data?.[TAB_ID] }) - - handleSetList(newList) - } - - return ( - <> - AttachNicForm({ nics }), - onSubmit: handleSave - }]} - /> -
- {nics?.map(item => { - const { 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), - ALIAS: PARENT - }) - .map(([k, v]) => v ? `${k}` : '') - .filter(Boolean) - .join(' | ') - } - } - action={ - <> - {!hasAlias && - { - handleRemove(NAME) - reorderNics() - reorderBootAfterRemove(NAME, nics, data, setFormData) - }} - icon={} - /> - } - , - tooltip: - }} - options={[{ - dialogProps: { - title: - }, - form: () => AttachNicForm({ nics }, item), - onSubmit: newValues => handleSave(newValues, NAME) - }]} - /> - - } - /> - ) - })} -
- - ) -} - -Networking.propTypes = { - data: PropTypes.any, - setFormData: PropTypes.func, - hypervisor: PropTypes.string, - control: PropTypes.object -} - -Networking.displayName = 'Networking' - -export default Networking diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/placement.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/placement.js deleted file mode 100644 index 8edc3446ef..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/placement.js +++ /dev/null @@ -1,77 +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. * - * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import PropTypes from 'prop-types' -import makeStyles from '@mui/styles/makeStyles' - -import FormWithSchema from 'client/components/Forms/FormWithSchema' -import { Tr } from 'client/components/HOC' - -import { STEP_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' -import { - HOST_REQ_FIELD, - HOST_RANK_FIELD, - DS_REQ_FIELD, - DS_RANK_FIELD -} from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema' -import { T } from 'client/constants' - -const useStyles = makeStyles({ - root: { - paddingBlock: '1em', - display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(300px, auto))', - gap: '1em' - } -}) - -const Placement = () => { - const classes = useStyles() - - // TODO - Host requirements: add button to select HOST in list => ID="" - // TODO - Host policy options: Packing|Stripping|Load-aware - - // TODO - DS requirements: add button to select DATASTORE in list => ID="" - // TODO - DS policy options: Packing|Stripping - - return ( - <> - - - - ) -} - -Placement.propTypes = { - data: PropTypes.any, - setFormData: PropTypes.func -} - -Placement.displayName = 'Placement' - -export default Placement diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/scheduleAction.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/scheduleAction.js deleted file mode 100644 index 2c6f457454..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/scheduleAction.js +++ /dev/null @@ -1,130 +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. * - * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import PropTypes from 'prop-types' -import makeStyles from '@mui/styles/makeStyles' -import { Edit, Trash } from 'iconoir-react' -import { useWatch } from 'react-hook-form' - -import { useListForm } from 'client/hooks' -import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm' -import SelectCard, { Action } from 'client/components/Cards/SelectCard' -import { PunctualForm, RelativeForm } from 'client/components/Forms/Vm' -import { Translate } from 'client/components/HOC' - -import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' -import { T } from 'client/constants' - -const useStyles = makeStyles({ - root: { - paddingBlock: '1em', - display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(300px, auto))', - gap: '1em' - } -}) - -export const TAB_ID = 'SCHED_ACTION' - -const ScheduleAction = ({ setFormData, control }) => { - const classes = useStyles() - const scheduleActions = useWatch({ name: `${EXTRA_ID}.${TAB_ID}`, control }) - - const { handleRemove, handleSave } = useListForm({ - parent: EXTRA_ID, - key: TAB_ID, - list: scheduleActions, - setList: setFormData, - getItemId: item => item.NAME, - addItemId: (item, _, itemIndex) => ({ ...item, NAME: `${TAB_ID}${itemIndex}` }) - }) - - return ( - <> - PunctualForm(), - onSubmit: handleSave - }, - { - cy: 'add-sched-action-relative', - name: 'Relative action', - dialogProps: { title: T.ScheduledAction }, - form: () => RelativeForm(), - onSubmit: handleSave - }]} - /> -
- {scheduleActions?.map(item => { - const { NAME, ACTION, TIME } = item - const isRelative = String(TIME).includes('+') - - return ( - - handleRemove(NAME)} - icon={} - /> - , - tooltip: - }} - options={[{ - dialogProps: { - title: <>{`: ${NAME}`} - }, - form: () => isRelative - ? RelativeForm(undefined, item) - : PunctualForm(undefined, item), - onSubmit: newValues => handleSave(newValues, NAME) - }]} - /> - - } - /> - ) - })} -
- - ) -} - -ScheduleAction.propTypes = { - data: PropTypes.any, - setFormData: PropTypes.func, - hypervisor: PropTypes.string, - control: PropTypes.object -} - -ScheduleAction.displayName = 'ScheduleAction' - -export default ScheduleAction diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema.js index bdafab9bc3..ccc5d25be2 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema.js @@ -14,83 +14,14 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { array, object, string } from 'yup' +import { ObjectSchema } from 'yup' -import { INPUT_TYPES } from 'client/constants' -import { getValidationFromFields } from 'client/utils' +// get schemas from VmTemplate/CreateForm +import { SCHEMA as CREATE_EXTRA_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' +import { HYPERVISORS } from 'client/constants' -export const HOST_REQ_FIELD = { - name: 'SCHED_REQUIREMENTS', - label: 'Host requirements expression', - tooltip: ` - Boolean expression that rules out provisioning hosts - from list of machines suitable to run this VM`, - type: INPUT_TYPES.TEXT, - validation: string().trim().notRequired() -} - -export const HOST_RANK_FIELD = { - name: 'SCHED_RANK', - label: 'Host policy expression', - tooltip: ` - This field sets which attribute will be used - to sort the suitable hosts for this VM`, - type: INPUT_TYPES.TEXT, - validation: string().trim().notRequired() -} - -export const DS_REQ_FIELD = { - name: 'DS_SCHED_REQUIREMENTS', - label: 'Datastore requirements expression', - tooltip: ` - Boolean expression that rules out entries from - the pool of datastores suitable to run this VM.`, - type: INPUT_TYPES.TEXT, - validation: string().trim().notRequired() -} - -export const DS_RANK_FIELD = { - name: 'DS_SCHED_RANK', - label: 'Datastore policy expression', - tooltip: ` - This field sets which attribute will be used to - sort the suitable datastores for this VM`, - type: INPUT_TYPES.TEXT, - validation: string().trim().notRequired() -} - -export const SCHEMA = object({ - DISK: array() - .ensure() - .transform(disks => disks?.map((disk, idx) => ({ - ...disk, - NAME: disk?.NAME?.startsWith('DISK') || !disk?.NAME - ? `DISK${idx}` - : disk?.NAME - }))), - NIC: array() - .ensure() - .transform(nics => nics?.map((nic, idx) => ({ - ...nic, - NAME: nic?.NAME?.startsWith('NIC') || !nic?.NAME - ? `NIC${idx}` - : nic?.NAME - }))), - SCHED_ACTION: array() - .ensure() - .transform(actions => actions?.map((action, idx) => ({ - ...action, - NAME: action?.NAME?.startsWith('SCHED_ACTION') || !action?.NAME - ? `SCHED_ACTION${idx}` - : action?.NAME - }))), - OS: object({ - BOOT: string().trim().notRequired() - }), - ...getValidationFromFields([ - HOST_REQ_FIELD, - HOST_RANK_FIELD, - DS_REQ_FIELD, - DS_RANK_FIELD - ]) -}).noUnknown(false) +/** + * @param {HYPERVISORS} hypervisor - VM hypervisor + * @returns {ObjectSchema} Extra configuration schema + */ +export const SCHEMA = hypervisor => CREATE_EXTRA_SCHEMA(hypervisor).noUnknown(false) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/storage.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/storage.js deleted file mode 100644 index 7c40c9b1c7..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/storage.js +++ /dev/null @@ -1,192 +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. * - * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ -import PropTypes from 'prop-types' -import makeStyles from '@mui/styles/makeStyles' -import { Edit, Trash } from 'iconoir-react' -import { useWatch } from 'react-hook-form' - -import { useListForm } from 'client/hooks' -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, StatusChip } from 'client/components/Status' -import { Translate } from 'client/components/HOC' - -import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' -import { SCHEMA as EXTRA_SCHEMA } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema' -import { reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting' -import { getState, getDiskType } from 'client/models/Image' -import { stringToBoolean } from 'client/models/Helper' -import { prettyBytes } from 'client/utils' -import { T } from 'client/constants' - -const useStyles = makeStyles({ - root: { - paddingBlock: '1em', - display: 'grid', - gridTemplateColumns: 'repeat(auto-fit, minmax(300px, auto))', - gap: '1em' - } -}) - -export const TAB_ID = 'DISK' - -const Storage = ({ data, setFormData, hypervisor, control }) => { - const classes = useStyles() - const disks = useWatch({ name: `${EXTRA_ID}.${TAB_ID}`, control }) - - const { handleSetList, handleRemove, handleSave } = useListForm({ - parent: EXTRA_ID, - key: TAB_ID, - list: disks, - setList: setFormData, - getItemId: item => item.NAME, - addItemId: (item, _, itemIndex) => ({ ...item, NAME: `${TAB_ID}${itemIndex}` }) - }) - - const reorderDisks = () => { - const diskSchema = EXTRA_SCHEMA.pick([TAB_ID]) - const { [TAB_ID]: newList } = diskSchema.cast({ [TAB_ID]: data?.[TAB_ID] }) - - handleSetList(newList) - } - - return ( - <> - ImageSteps({ hypervisor }), - onSubmit: handleSave - }, - { - cy: 'attach-volatile-disk', - name: T.Volatile, - dialogProps: { title: T.AttachVolatile }, - form: () => VolatileSteps({ hypervisor }), - onSubmit: handleSave - } - ]} - /> -
- {disks?.map(item => { - const { - NAME, - TYPE, - IMAGE, - IMAGE_ID, - IMAGE_STATE, - ORIGINAL_SIZE, - SIZE = ORIGINAL_SIZE, - READONLY, - DATASTORE, - PERSISTENT - } = item - - const isVolatile = !IMAGE && !IMAGE_ID - const isPersistent = stringToBoolean(PERSISTENT) - 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}`} - {isPersistent && } - - )} - 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(NAME) - reorderDisks() - reorderBootAfterRemove(NAME, disks, data, setFormData) - }} - icon={} - /> - , - tooltip: - }} - options={[{ - dialogProps: { - title: - }, - form: () => isVolatile - ? VolatileSteps({ hypervisor }, item) - : ImageSteps({ hypervisor }, item), - onSubmit: newValues => handleSave(newValues, NAME) - }]} - /> - - } - /> - ) - })} -
- - ) -} - -Storage.propTypes = { - data: PropTypes.any, - setFormData: PropTypes.func, - hypervisor: PropTypes.string, - control: PropTypes.object -} - -Storage.displayName = 'Storage' - -export default Storage diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/index.js index e205413ba5..c2e41dc38b 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/index.js @@ -59,7 +59,7 @@ const Content = ({ data, setFormData }) => { // needs hypervisor to strip unknown attributes [CONFIGURATION_ID]: CONFIGURATION_SCHEMA?.({ [STEP_ID]: [extendedTemplate] }) .cast(extendedTemplate?.TEMPLATE, { stripUnknown: true }), - [EXTRA_ID]: EXTRA_SCHEMA + [EXTRA_ID]: EXTRA_SCHEMA(extendedTemplate?.TEMPLATE?.HYPERVISOR) .cast(extendedTemplate?.TEMPLATE, { stripUnknown: true }) })) diff --git a/src/fireedge/src/client/containers/VmTemplates/Instantiate.js b/src/fireedge/src/client/containers/VmTemplates/Instantiate.js index 099f341e6e..a8d8a2630a 100644 --- a/src/fireedge/src/client/containers/VmTemplates/Instantiate.js +++ b/src/fireedge/src/client/containers/VmTemplates/Instantiate.js @@ -25,12 +25,13 @@ import { isDevelopment } from 'client/utils' function InstantiateVmTemplate () { const history = useHistory() - const { state: { ID: templateId } = {} } = useLocation() + const { state: template = {} } = useLocation() + const { ID: templateId } = template ?? {} const { enqueueInfo } = useGeneralApi() const { instantiate } = useVmTemplateApi() - const onSubmit = async ([templateSelected, templates]) => { + const onSubmit = async ([templateSelected = template, templates]) => { try { const { ID, NAME } = templateSelected