diff --git a/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js b/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js index 7b2f12dba7..9c49efea8e 100644 --- a/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js +++ b/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js @@ -69,7 +69,6 @@ const ButtonToTriggerForm = ({ aria-expanded={open ? 'true' : undefined} aria-haspopup={isGroupButton ? 'true' : false} disabled={!options.length} - endicon={isGroupButton ? : undefined} onClick={evt => !isGroupButton ? openDialogForm(options[0]) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js index 76e66c0b2c..2c3b9717c5 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/configurationSection.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { JSXElementConstructor } from 'react' +import { useMemo, JSXElementConstructor } from 'react' import { Stack, FormControl, Button } from '@mui/material' import { useFormContext } from 'react-hook-form' @@ -23,21 +23,20 @@ import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateFo import { SSH_PUBLIC_KEY, SCRIPT_FIELDS, OTHER_FIELDS } from './schema' import { T } from 'client/constants' -export const SECTION_ID = 'CONTEXT' - const SSH_KEY_USER = '$USER[SSH_PUBLIC_KEY]' /** @returns {JSXElementConstructor} - Configuration section */ const ConfigurationSection = () => { const { setValue, getValues } = useFormContext() - const SSH_PUBLIC_KEY_PATH = `${EXTRA_ID}.${SSH_PUBLIC_KEY.name}` + const SSH_PUBLIC_KEY_PATH = useMemo(() => `${EXTRA_ID}.${SSH_PUBLIC_KEY.name}`, []) const handleClearKey = () => setValue(SSH_PUBLIC_KEY_PATH) const handleAddUserKey = () => { - const currentSshPublicKey = getValues(SSH_PUBLIC_KEY_PATH) ?? '' + let currentKey = getValues(SSH_PUBLIC_KEY_PATH) + currentKey &&= currentKey + '\n' - setValue(SSH_PUBLIC_KEY_PATH, currentSshPublicKey + `\n${SSH_KEY_USER}`) + setValue(SSH_PUBLIC_KEY_PATH, `${currentKey ?? ''}${SSH_KEY_USER}`) } return ( diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSchema.js new file mode 100644 index 0000000000..0d2546f68b --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSchema.js @@ -0,0 +1,54 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { string, ObjectSchema } from 'yup' + +import { T, INPUT_TYPES, HYPERVISORS } from 'client/constants' +import { Field, filterFieldsByHypervisor, getObjectSchemaFromFields } from 'client/utils' + +const { vcenter } = HYPERVISORS + +/** @type {Field} Files field */ +export const FILES_DS = { + name: 'CONTEXT.FILES_DS', + label: T.ContextFiles, + tooltip: T.ContextFilesConcept, + notOnHypervisors: [vcenter], + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired(), + grid: { md: 12 } +} + +/** @type {Field} Init scripts field */ +export const INIT_SCRIPTS = { + name: 'CONTEXT.INIT_SCRIPTS', + label: T.InitScripts, + tooltip: T.InitScriptsConcept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired(), + grid: { md: 12 } +} + +/** @type {Field[]} List of Context Files fields */ +export const FILES_FIELDS = hypervisor => + filterFieldsByHypervisor([FILES_DS, INIT_SCRIPTS], hypervisor) + +/** @type {ObjectSchema} Context Files schema */ +export const FILES_SCHEMA = hypervisor => + getObjectSchemaFromFields(FILES_FIELDS(hypervisor)) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js new file mode 100644 index 0000000000..7093a3b2fc --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/filesSection.js @@ -0,0 +1,48 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { JSXElementConstructor } from 'react' +import PropTypes from 'prop-types' + +import { FormWithSchema } from 'client/components/Forms' + +import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' +import { FILES_FIELDS } from './schema' +import { T } from 'client/constants' + +export const SECTION_ID = 'CONTEXT' + +/** + * @param {object} props - Props + * @param {string} props.hypervisor - VM hypervisor + * @returns {JSXElementConstructor} - Files section + */ +const FilesSection = ({ hypervisor }) => ( + FILES_FIELDS(hypervisor)} + id={EXTRA_ID} + /> +) + +FilesSection.propTypes = { + data: PropTypes.any, + setFormData: PropTypes.func, + hypervisor: PropTypes.string, + control: PropTypes.object +} + +export default FilesSection diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js index a032ebad49..cd164ed162 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/index.js @@ -17,17 +17,19 @@ import PropTypes from 'prop-types' import { Folder as ContextIcon } from 'iconoir-react' import { TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' -import ConfigurationSection, { SECTION_ID as CONFIGURATION_ID } from './configurationSection' import UserInputsSection, { SECTION_ID as USER_INPUTS_ID } from './userInputsSection' +import ConfigurationSection from './configurationSection' +import FilesSection from './filesSection' import { T } from 'client/constants' -export const TAB_ID = [CONFIGURATION_ID, USER_INPUTS_ID] +export const TAB_ID = ['CONTEXT', USER_INPUTS_ID] -const Context = () => { +const Context = props => { return ( <> + ) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema.js index 129b20ac26..e74d328fd6 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema.js @@ -13,14 +13,21 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { object } from 'yup' +import { object, ObjectSchema } from 'yup' import { USER_INPUTS_SCHEMA } from './userInputsSchema' import { CONFIGURATION_SCHEMA } from './configurationSchema' +import { FILES_SCHEMA } from './filesSchema' -export const SCHEMA = object() +/** + * @param {string} [hypervisor] - VM hypervisor + * @returns {ObjectSchema} Context schema + */ +export const SCHEMA = hypervisor => object() .concat(CONFIGURATION_SCHEMA) .concat(USER_INPUTS_SCHEMA) + .concat(FILES_SCHEMA(hypervisor)) export * from './userInputsSchema' export * from './configurationSchema' +export * from './filesSchema' diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/userInputsSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/userInputsSchema.js index 0a816df081..620feaf2f7 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/userInputsSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/userInputsSchema.js @@ -183,5 +183,6 @@ export const USER_INPUT_SCHEMA = getObjectSchemaFromFields(USER_INPUT_FIELDS) /** @type {ObjectSchema} User Inputs schema */ export const USER_INPUTS_SCHEMA = object({ - USER_INPUTS: array(USER_INPUT_SCHEMA).ensure() + USER_INPUTS: array(USER_INPUT_SCHEMA).ensure(), + INPUTS_ORDER: string().trim().strip() }) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/NicItem.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/NicItem.js new file mode 100644 index 0000000000..5a33bf13e2 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/NicItem.js @@ -0,0 +1,108 @@ +/* ------------------------------------------------------------------------- * + * 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 { Edit, Trash } from 'iconoir-react' + +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 { stringToBoolean } from 'client/models/Helper' +import { T } from 'client/constants' + +/** + * @param {object} props - Props + * @param {number} props.index - Index in list + * @param {object} props.item - NIC + * @param {string} props.handleRemove - Remove function + * @param {string} props.handleUpdate - Update function + * @returns {JSXElementConstructor} - NIC card + */ +const NicItem = memo(({ + item, + nics, + handleRemove, + handleUpdate +}) => { + 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 + }]} + /> + + } + /> + ) +}, (prev, next) => prev.item?.NAME === next.item?.NAME) + +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.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js similarity index 57% rename from src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking.js rename to src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js index ee8ec52413..561e963760 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/index.js @@ -15,19 +15,19 @@ * ------------------------------------------------------------------------- */ import PropTypes from 'prop-types' import { Stack } from '@mui/material' -import { ServerConnection as NetworkIcon, Edit, Trash } from 'iconoir-react' +import { ServerConnection as NetworkIcon } from 'iconoir-react' import { useFormContext, useFieldArray } from 'react-hook-form' 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 { FormWithSchema } from 'client/components/Forms' import { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' import { BOOT_ORDER_NAME, reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting' -import { stringToBoolean } from 'client/models/Helper' +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' @@ -48,6 +48,10 @@ const Networking = () => { setValue(BOOT_ORDER_NAME(), updatedBootOrder) } + const handleUpdate = (updatedNic, index) => { + update(index, mapNameFunction(updatedNic, index)) + } + return ( <> { onSubmit: nic => append(mapNameFunction(nic, nics.length)) }]} /> - - {nics?.map((item, index) => { - 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 && - removeAndReorder(NAME)} - icon={} - /> - } - , - tooltip: - }} - options={[{ - dialogProps: { - title: ( - - ) - }, - form: () => AttachNicForm({ nics }, item), - onSubmit: updatedNic => - update(index, mapNameFunction(updatedNic, index)) - }]} - /> - - } - /> - ) - })} + {nics?.map(({ id, ...item }, index) => ( + removeAndReorder(item?.NAME)} + handleUpdate={updatedNic => handleUpdate(updatedNic, index)} + /> + ))} + ) } 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 new file mode 100644 index 0000000000..32a52ce64a --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema.js @@ -0,0 +1,54 @@ +/* ------------------------------------------------------------------------- * + * 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 { object, string, array, ObjectSchema } from 'yup' + +import { T, INPUT_TYPES } from 'client/constants' +import { Field, getObjectSchemaFromFields } from 'client/utils' +import { mapNameByIndex } from '../schema' + +/** @returns {Field} NIC filter field */ +const FILTER = { + name: 'NIC_DEFAULT.FILTER', + label: T.DefaultNicFilter, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined) +} + +/** @returns {Field} NIC model field */ +const MODEL = { + name: 'NIC_DEFAULT.MODEL', + label: T.DefaultNicModel, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .notRequired() + .default(() => undefined) +} + +/** @type {Field[]} List of Network defaults fields */ +const FIELDS = [FILTER, MODEL] + +/** @type {ObjectSchema} Network schema */ +const SCHEMA = object({ + NIC: array() + .ensure() + .transform(nics => nics.map(mapNameByIndex('NIC'))) +}).concat(getObjectSchemaFromFields(FIELDS)) + +export { FIELDS, SCHEMA } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js index 3e9852fa2b..c031d9af84 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema.js @@ -21,6 +21,8 @@ import { FIELDS as OS_FIELDS } from 'client/components/Forms/VmTemplate/CreateFo import { FIELDS as NUMA_FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/numa/schema' import { SCHEMA as IO_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/inputOutput/schema' import { SCHEMA as CONTEXT_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/context/schema' +import { SCHEMA as STORAGE_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema' +import { SCHEMA as NETWORK_SCHEMA } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/networking/schema' import { getObjectSchemaFromFields } from 'client/utils' export const mapNameByIndex = (prefixName) => (resource, idx) => ({ @@ -30,28 +32,19 @@ export const mapNameByIndex = (prefixName) => (resource, idx) => ({ : resource?.NAME }) -export const DISK_SCHEMA = array() - .ensure() - .transform(disks => disks.map(mapNameByIndex('DISK'))) - -export const NIC_SCHEMA = array() - .ensure() - .transform(nics => nics.map(mapNameByIndex('NIC'))) - export const SCHED_ACTION_SCHEMA = array() .ensure() .transform(actions => actions.map(mapNameByIndex('SCHED_ACTION'))) export const SCHEMA = hypervisor => object({ - DISK: DISK_SCHEMA, - NIC: NIC_SCHEMA, SCHED_ACTION: SCHED_ACTION_SCHEMA }) - .concat(CONTEXT_SCHEMA) + .concat(NETWORK_SCHEMA) + .concat(STORAGE_SCHEMA) + .concat(CONTEXT_SCHEMA(hypervisor)) .concat(IO_SCHEMA(hypervisor)) .concat(getObjectSchemaFromFields([ ...PLACEMENT_FIELDS, ...OS_FIELDS(hypervisor), ...NUMA_FIELDS(hypervisor) ])) - .noUnknown(false) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage.js deleted file mode 100644 index 167f5e31b1..0000000000 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage.js +++ /dev/null @@ -1,191 +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 { Stack } from '@mui/material' -import { Db as DatastoreIcon, Edit, Trash } from 'iconoir-react' -import { useFormContext, useFieldArray } from 'react-hook-form' - -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, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' -import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' -import { BOOT_ORDER_NAME, reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/CreateForm/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' - -export const TAB_ID = 'DISK' - -const mapNameFunction = mapNameByIndex('DISK') - -const Storage = ({ hypervisor }) => { - const { getValues, setValue } = useFormContext() - const { fields: disks, replace, update, append } = useFieldArray({ - name: `${EXTRA_ID}.${TAB_ID}` - }) - - const removeAndReorder = diskName => { - const updatedDisks = disks.filter(({ NAME }) => NAME !== diskName).map(mapNameFunction) - const currentBootOrder = getValues(BOOT_ORDER_NAME()) - const updatedBootOrder = reorderBootAfterRemove(diskName, disks, currentBootOrder) - - replace(updatedDisks) - setValue(BOOT_ORDER_NAME(), updatedBootOrder) - } - - return ( - <> - ImageSteps({ hypervisor }), - onSubmit: image => append(mapNameFunction(image, disks.length)) - }, - { - cy: 'attach-volatile-disk', - name: T.Volatile, - dialogProps: { title: T.AttachVolatile }, - form: () => VolatileSteps({ hypervisor }), - onSubmit: image => append(mapNameFunction(image, disks.length)) - } - ]} - /> - - {disks?.map((item, index) => { - const { - id, - 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={() => removeAndReorder(NAME, index)} - icon={} - /> - , - tooltip: - }} - options={[{ - dialogProps: { - title: - }, - form: () => isVolatile - ? VolatileSteps({ hypervisor }, item) - : ImageSteps({ hypervisor }, item), - onSubmit: updatedDisk => - update(index, mapNameFunction(updatedDisk, index)) - }]} - /> - - } - /> - ) - })} - - - ) -} - -Storage.propTypes = { - data: PropTypes.any, - setFormData: PropTypes.func, - hypervisor: PropTypes.string, - control: PropTypes.object -} - -/** @type {TabType} */ -const TAB = { - id: 'storage', - name: T.Storage, - icon: DatastoreIcon, - Content: Storage, - getError: error => !!error?.[TAB_ID] -} - -export default TAB 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 new file mode 100644 index 0000000000..4e74cd06a3 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/DiskItem.js @@ -0,0 +1,139 @@ +/* ------------------------------------------------------------------------- * + * 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, StatusChip } 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 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} + color='error' + icon={} + /> + , + tooltip: + }} + options={[{ + dialogProps: { + title: + }, + form: () => isVolatile + ? VolatileSteps({ hypervisor }, item) + : ImageSteps({ hypervisor }, item), + onSubmit: handleUpdate + }]} + /> + + } + /> + ) +}, (prev, next) => prev.item?.NAME === next.item?.NAME) + +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 new file mode 100644 index 0000000000..f8baf6f340 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/index.js @@ -0,0 +1,124 @@ +/* ------------------------------------------------------------------------- * + * 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 PropTypes from 'prop-types' +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 { STEP_ID as EXTRA_ID, TabType } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration' +import { mapNameByIndex } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/schema' +import { BOOT_ORDER_NAME, reorderBootAfterRemove } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/booting' +import { FIELDS } from 'client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema' +import { T } from 'client/constants' +import DiskItem from './DiskItem' + +export const TAB_ID = 'DISK' + +const mapNameFunction = mapNameByIndex('DISK') + +const Storage = ({ hypervisor }) => { + const { getValues, setValue } = useFormContext() + const { fields: disks, append, update, replace } = useFieldArray({ + name: `${EXTRA_ID}.${TAB_ID}` + }) + + const removeAndReorder = diskName => { + const updatedDisks = disks.filter(({ NAME }) => NAME !== diskName).map(mapNameFunction) + const currentBootOrder = getValues(BOOT_ORDER_NAME()) + const updatedBootOrder = reorderBootAfterRemove(diskName, disks, currentBootOrder) + + replace(updatedDisks) + setValue(BOOT_ORDER_NAME(), updatedBootOrder) + } + + const handleUpdate = (updatedDisk, index) => { + update(index, mapNameFunction(updatedDisk, index)) + } + + return ( + <> + ImageSteps({ hypervisor }), + onSubmit: image => append(mapNameFunction(image, disks.length)) + }, + { + cy: 'attach-volatile-disk', + name: T.Volatile, + dialogProps: { title: T.AttachVolatile }, + form: () => VolatileSteps({ hypervisor }), + onSubmit: image => append(mapNameFunction(image, disks.length)) + } + ]} + /> + + {disks?.map(({ id, ...item }, index) => ( + removeAndReorder(item?.NAME)} + handleUpdate={updatedDisk => handleUpdate(updatedDisk, index)} + /> + ))} + + + + ) +} + +Storage.propTypes = { + data: PropTypes.any, + setFormData: PropTypes.func, + hypervisor: PropTypes.string, + control: PropTypes.object +} + +/** @type {TabType} */ +const TAB = { + id: 'storage', + name: T.Storage, + icon: DatastoreIcon, + Content: Storage, + getError: error => !!error?.[TAB_ID] +} + +export default TAB diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema.js new file mode 100644 index 0000000000..e6dee55ec6 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/ExtraConfiguration/storage/schema.js @@ -0,0 +1,53 @@ +/* ------------------------------------------------------------------------- * + * 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 { object, string, array, ObjectSchema } from 'yup' + +import { useDatastore } from 'client/features/One' +import { getDeployMode } from 'client/models/Datastore' +import { T, INPUT_TYPES } from 'client/constants' +import { Field, arrayToOptions, getValidationFromFields } from 'client/utils' +import { mapNameByIndex } from '../schema' + +/** @returns {Field} Deploy mode field */ +const TM_MAD_SYSTEM = { + name: 'TM_MAD_SYSTEM', + label: T.DeployMode, + tooltip: T.DeployModeConcept, + type: INPUT_TYPES.SELECT, + values: () => { + const datastores = useDatastore() + const modes = datastores?.map(getDeployMode)?.flat() + + return arrayToOptions([...new Set(modes)], { addEmpty: 'Default' }) + }, + validation: string() + .trim() + .notRequired() + .default(() => undefined) +} + +/** @type {Field[]} List of Storage fields */ +const FIELDS = [TM_MAD_SYSTEM] + +/** @type {ObjectSchema} Storage schema */ +const SCHEMA = object({ + ...getValidationFromFields(FIELDS), + DISK: array() + .ensure() + .transform(disks => disks.map(mapNameByIndex('DISK'))) +}) + +export { FIELDS, SCHEMA } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js index b9fba8b646..014b479631 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/General/capacitySchema.js @@ -40,10 +40,16 @@ export const MEMORY = { /** @type {Field} Hot reloading on memory field */ export const ENABLE_HR_MEMORY = { - name: 'ENABLE_HR_MEMORY', + name: 'HOT_RESIZE.MEMORY_HOT_ADD_ENABLED', label: T.EnableHotResize, type: INPUT_TYPES.SWITCH, - validation: boolean().default(() => false), + validation: boolean() + .transform(value => { + if (typeof value === 'boolean') return value + + return String(value).toUpperCase() === 'YES' + }) + .default(() => false), grid: { xs: 4, md: 6 } } @@ -85,10 +91,16 @@ export const VIRTUAL_CPU = { /** @type {Field} Hot reloading on virtual CPU field */ export const ENABLE_HR_VCPU = { - name: 'ENABLE_HR_VCPU', + name: 'HOT_RESIZE.CPU_HOT_ADD_ENABLED', label: T.EnableHotResize, type: INPUT_TYPES.SWITCH, - validation: boolean().default(() => false), + validation: boolean() + .transform(value => { + if (typeof value === 'boolean') return value + + return String(value).toUpperCase() === 'YES' + }) + .default(() => false), grid: { xs: 4, md: 6 } } diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js index 7b07f00e08..e63aea4590 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/Steps/index.js @@ -31,7 +31,7 @@ const Steps = createSteps( ...vmTemplate?.TEMPLATE, USER_INPUTS: userInputsToArray(vmTemplate?.TEMPLATE?.USER_INPUTS) } - }, { context: { [EXTRA_ID]: vmTemplate.TEMPLATE } }) + }, { stripUnknown: true, context: { [EXTRA_ID]: vmTemplate.TEMPLATE } }) }), transformBeforeSubmit: formData => { const { @@ -42,22 +42,28 @@ const Steps = createSteps( // const templateXML = jsonToXml({ ...general, ...extraTemplate }) // return { template: templateXML } + const userInputs = userInputsToObject(USER_INPUTS) + const inputsOrder = USER_INPUTS.map(({ name }) => name).join(',') const { START_SCRIPT, ENCODE_START_SCRIPT, ...restOfContext } = CONTEXT const context = { ...restOfContext, + // transform start script to base64 if needed [ENCODE_START_SCRIPT ? 'START_SCRIPT_BASE64' : 'START_SCRIPT']: ENCODE_START_SCRIPT && !isBase64(START_SCRIPT) ? btoa(unescape(encodeURIComponent(START_SCRIPT))) : START_SCRIPT } - const userInputs = userInputsToObject(USER_INPUTS) - const inputsOrder = USER_INPUTS.map(({ name }) => name).join(',') + // add user inputs to context + for (const { name } of USER_INPUTS) { + const upperName = String(name).toUpperCase() + context[upperName] = `$${upperName}` + } return { - ...general, ...extraTemplate, + ...general, CONTEXT: context, USER_INPUTS: userInputs, INPUTS_ORDER: inputsOrder diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/index.js index 388f06b412..ab7318d680 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/CreateForm/index.js @@ -22,7 +22,7 @@ import { yupResolver } from '@hookform/resolvers/yup' import { useFetch } from 'client/hooks' // import { useUserApi, useVmGroupApi, useVmTemplateApi } from 'client/features/One' -import { useVmTemplateApi, useHostApi, useImageApi } from 'client/features/One' +import { useVmTemplateApi, useHostApi, useImageApi, useDatastoreApi } from 'client/features/One' import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper' import Steps from 'client/components/Forms/VmTemplate/CreateForm/Steps' @@ -52,6 +52,7 @@ const PreFetchingForm = ({ templateId, onSubmit }) => { // const { getVmGroups } = useVmGroupApi() const { getHosts } = useHostApi() const { getImages } = useImageApi() + const { getDatastores } = useDatastoreApi() const { getVmTemplate } = useVmTemplateApi() const { fetchRequest, data } = useFetch( () => getVmTemplate(templateId, { extended: true }) @@ -61,6 +62,7 @@ const PreFetchingForm = ({ templateId, onSubmit }) => { templateId && fetchRequest() getHosts() getImages() + getDatastores() // getUsers() // getVmGroups() }, []) diff --git a/src/fireedge/src/client/components/Image/index.js b/src/fireedge/src/client/components/Image/index.js index fe5e3a318e..106ec5f9e7 100644 --- a/src/fireedge/src/client/components/Image/index.js +++ b/src/fireedge/src/client/components/Image/index.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { useState, memo, JSXElementConstructor } from 'react' +import { useState, memo, useEffect, JSXElementConstructor } from 'react' import PropTypes from 'prop-types' import { DEFAULT_IMAGE, IMAGE_FORMATS } from 'client/constants' @@ -46,6 +46,10 @@ const Image = memo( ...imgProps } + useEffect(() => { + error && setError(INITIAL_STATE) + }, [src]) + /** Increment retries by one in error state. */ const addRetry = () => { setError(prev => ({ ...prev, retries: prev.retries + 1 })) diff --git a/src/fireedge/src/client/components/Tables/Datastores/columns.js b/src/fireedge/src/client/components/Tables/Datastores/columns.js index 33e2c11944..62ff659f88 100644 --- a/src/fireedge/src/client/components/Tables/Datastores/columns.js +++ b/src/fireedge/src/client/components/Tables/Datastores/columns.js @@ -37,7 +37,7 @@ export default [ { Header: 'Type', id: 'TYPE', - accessor: row => DatastoreModel.getType(row) + accessor: row => DatastoreModel.getType(row)?.name }, { Header: 'Clusters IDs', diff --git a/src/fireedge/src/client/constants/datastore.js b/src/fireedge/src/client/constants/datastore.js index 228fd7d221..ed5e0d467b 100644 --- a/src/fireedge/src/client/constants/datastore.js +++ b/src/fireedge/src/client/constants/datastore.js @@ -16,10 +16,7 @@ import * as STATES from 'client/constants/states' import COLOR from 'client/constants/color' -/** - * @type {{name: string, shortName: string}} - * Datastore type information - */ +/** @type {{name: string, shortName: string}} Datastore type information */ export const DATASTORE_TYPES = [ { name: 'IMAGE', diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 9fe1e1d763..8fc1b5f34e 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -354,6 +354,17 @@ module.exports = { /* VM schema - ownership */ InstantiateAsUser: 'Instantiate as different User', InstantiateAsGroup: 'Instantiate as different Group', + /* VM Template schema - storage */ + StorageOptions: 'Storage Options', + DeployMode: 'Deploy Mode', + DeployModeConcept: 'Set an alternative mode to deploy VM disks to the hosts', + /* VM Template schema - network */ + NetworkDefaults: 'Network Defaults', + NetworkDefaultsConcept: ` + Values that will be copied to each new NIC. + Final users may not be aware of this`, + DefaultNicModel: 'Default hardware model to emulate for all NICs', + DefaultNicFilter: 'Default network filtering rule for all NICs', /* VM Template schema - capacity */ MaxMemory: 'Max memory', MemoryModification: 'Memory modification', @@ -485,9 +496,16 @@ module.exports = { ReportReadyToOneGateConcept: 'Sends READY=YES to OneGate, useful for OneFlow', StartScript: 'Start script', StartScriptConcept: ` - Text of the script executed when the machine starts up. It can contain - shebang in case it is not shell script`, + Text of the script executed when the machine starts up. It can contain + shebang in case it is not shell script`, EncodeScriptInBase64: 'Encode script in Base64', + ContextFiles: 'Files Datastores', + ContextFilesConcept: 'List of File images to include in context device', + InitScripts: 'Init scripts', + InitScriptsConcept: ` + The contextualization package executes an init.sh file if it exists. + If more than one script file is added, this list contains the scripts + to run and their order`, /* VM Template schema - Input/Output */ InputOrOutput: 'Input / Output', Inputs: 'Inputs', diff --git a/src/fireedge/src/client/models/Datastore.js b/src/fireedge/src/client/models/Datastore.js index 63eb30e31c..e996edcf52 100644 --- a/src/fireedge/src/client/models/Datastore.js +++ b/src/fireedge/src/client/models/Datastore.js @@ -34,6 +34,21 @@ export const getType = ({ TYPE } = {}) => DATASTORE_TYPES[TYPE] */ export const getState = ({ STATE = 0 } = {}) => DATASTORE_STATES[STATE] +/** + * Return the TM_MAD_SYSTEM attribute. + * + * @param {object} datastore - Datastore + * @returns {string[]} - The list of deploy modes available + */ +export const getDeployMode = (datastore = {}) => { + const { TEMPLATE = {} } = datastore + const isImage = getType(datastore)?.name === DATASTORE_TYPES[0]?.name + + return isImage + ? TEMPLATE?.TM_MAD_SYSTEM?.split(',')?.filter(Boolean) ?? [] + : [] +} + /** * Returns information about datastore capacity. * diff --git a/src/fireedge/src/client/utils/schema.js b/src/fireedge/src/client/utils/schema.js index 59a94aaf39..9d115cd52a 100644 --- a/src/fireedge/src/client/utils/schema.js +++ b/src/fireedge/src/client/utils/schema.js @@ -328,7 +328,7 @@ export const mapUserInputs = (userInputs = {}) => * * @param {any[]} array - List of option values * @param {object} [options] - Options to conversion - * @param {boolean} [options.addEmpty] - If `true`, add an empty option + * @param {boolean|string} [options.addEmpty] - If `true`, add an empty option * @param {function(any):any} [options.getText] - Function to get the text option * @param {function(any):any} [options.getValue] - Function to get the value option * @returns {SelectOption} Options @@ -338,7 +338,11 @@ export const arrayToOptions = (array = [], options = {}) => { const values = array.map(item => ({ text: getText(item), value: getValue(item) })) - addEmpty && values.unshift({ text: '-', value: '' }) + if (addEmpty) { + typeof addEmpty === 'string' + ? values.unshift({ text: addEmpty, value: '' }) + : values.unshift({ text: '-', value: '' }) + } return values }