From 1d83bd2de6f242cabdbc4b00d3b882efdff3fb6b Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Tue, 21 Sep 2021 10:19:30 +0200 Subject: [PATCH] F #5422: Add bulk actions to vm templates (#1469) --- .../etc/sunstone/admin/vm-template-tab.yaml | 11 + .../src/client/apps/provision/routes.js | 12 +- .../components/Dialogs/DialogConfirmation.js | 21 +- .../src/client/components/Dialogs/index.js | 8 +- .../components/FormControl/SubmitButton.js | 11 +- .../FormControl/TimeController-TODO.js | 126 ++++++++++ .../components/FormStepper/MobileStepper.js | 4 +- .../client/components/FormStepper/Skeleton.js | 55 +++++ .../client/components/FormStepper/Stepper.js | 4 +- .../client/components/FormStepper/index.js | 28 +-- .../components/Forms/ButtonToTriggerForm.js | 27 +- .../client/components/Forms/FormWithSchema.js | 2 + .../Steps/BasicConfiguration/index.js | 2 +- .../Steps/BasicConfiguration/schema.js | 0 .../CreateForm}/Steps/Connection/index.js | 6 +- .../CreateForm}/Steps/Connection/schema.js | 0 .../CreateForm}/Steps/Template/index.js | 6 +- .../CreateForm}/Steps/Template/schema.js | 0 .../Forms/Provider/CreateForm}/Steps/index.js | 42 ++-- .../Forms/Provider/CreateForm/index.js | 58 +++++ .../client/components/Forms/Provider/index.js | 20 ++ .../Steps/BasicConfiguration/index.js | 2 +- .../Steps/BasicConfiguration/schema.js | 0 .../CreateForm}/Steps/Inputs/index.js | 6 +- .../CreateForm}/Steps/Inputs/schema.js | 0 .../CreateForm}/Steps/Provider/index.js | 6 +- .../CreateForm}/Steps/Provider/schema.js | 0 .../CreateForm}/Steps/Template/index.js | 8 +- .../CreateForm}/Steps/Template/schema.js | 0 .../Forms/Provision/CreateForm/Steps/index.js | 52 ++++ .../Forms/Provision/CreateForm/index.js | 50 ++++ .../components/Forms/Provision/index.js | 20 ++ .../BasicConfiguration/capacitySchema.js | 14 +- .../Steps/BasicConfiguration/diskSchema.js | 20 +- .../Steps/BasicConfiguration/index.js | 4 +- .../BasicConfiguration/informationSchema.js | 20 +- .../Steps/ExtraConfiguration/booting.js | 35 +-- .../Steps/ExtraConfiguration/networking.js | 31 ++- .../Steps/ExtraConfiguration/schema.js | 9 +- .../Steps/VmTemplatesTable/index.js | 5 +- .../Steps/VmTemplatesTable/schema.js | 16 +- .../VmTemplate/InstantiateForm/Steps/index.js | 52 ++-- .../Forms/VmTemplate/InstantiateForm/index.js | 42 +++- .../src/client/components/Forms/index.js | 3 +- .../src/client/components/Status/Badge.js | 2 +- .../Enhanced/Utils/GlobalActions/Action.js | 137 +++++++++++ .../Enhanced/Utils/GlobalActions/index.js | 84 +++++++ .../components/Tables/Enhanced/Utils/index.js | 2 + .../components/Tables/Enhanced/Utils/utils.js | 36 ++- .../components/Tables/Enhanced/index.js | 3 + .../components/Tables/Enhanced/styles.js | 2 +- .../components/Tables/Enhanced/toolbar.js | 32 ++- .../components/Tables/VmTemplates/actions.js | 230 ++++++++++++++++++ .../components/Tables/VmTemplates/detail.js | 86 ------- .../components/Tables/VmTemplates/index.js | 11 +- .../Tabs/Vm/SchedActions/Actions.js | 12 +- .../components/Tabs/Vm/Snapshot/Actions.js | 12 +- .../components/Tabs/Vm/Storage/Actions.js | 18 +- src/fireedge/src/client/constants/index.js | 2 + .../src/client/constants/marketplaceApp.js | 19 ++ .../src/client/constants/translates.js | 1 + .../index.js => constants/vmTemplate.js} | 41 ++-- .../containers/Providers/{Form => }/Create.js | 54 +++- .../Providers/Form/ProviderForm/index.js | 116 --------- .../Provisions/{Form => }/Create.js | 25 +- .../Provisions/Form/ProvisionForm/index.js | 85 ------- .../containers/VmTemplates/Instantiate.js | 18 +- .../client/containers/VmTemplates/index.js | 7 +- .../features/One/application/actions.js | 10 +- .../client/features/One/application/hooks.js | 4 +- .../One/applicationTemplate/actions.js | 13 +- .../features/One/applicationTemplate/hooks.js | 4 +- .../client/features/One/cluster/actions.js | 10 +- .../src/client/features/One/cluster/hooks.js | 4 +- .../client/features/One/datastore/actions.js | 10 +- .../client/features/One/datastore/hooks.js | 4 +- .../src/client/features/One/group/actions.js | 10 +- .../src/client/features/One/group/hooks.js | 4 +- .../src/client/features/One/host/actions.js | 10 +- .../src/client/features/One/host/hooks.js | 3 +- .../src/client/features/One/image/actions.js | 10 +- .../src/client/features/One/image/hooks.js | 4 +- .../features/One/marketplace/actions.js | 10 +- .../client/features/One/marketplace/hooks.js | 4 +- .../features/One/marketplaceApp/actions.js | 7 +- .../features/One/marketplaceApp/hooks.js | 3 +- .../src/client/features/One/user/actions.js | 14 +- .../src/client/features/One/user/hooks.js | 4 +- .../src/client/features/One/vm/actions.js | 54 ++-- .../src/client/features/One/vm/hooks.js | 3 +- .../client/features/One/vmGroup/actions.js | 7 +- .../src/client/features/One/vmGroup/hooks.js | 4 +- .../client/features/One/vmTemplate/actions.js | 21 +- .../client/features/One/vmTemplate/hooks.js | 18 +- .../features/One/vmTemplate/services.js | 213 ++++++++++++++++ .../client/features/One/vnetwork/actions.js | 10 +- .../src/client/features/One/vnetwork/hooks.js | 4 +- .../features/One/vnetworkTemplate/actions.js | 7 +- .../features/One/vnetworkTemplate/hooks.js | 4 +- .../client/features/One/vrouter/actions.js | 7 +- .../src/client/features/One/vrouter/hooks.js | 4 +- .../src/client/features/One/zone/actions.js | 7 +- .../src/client/features/One/zone/hooks.js | 4 +- src/fireedge/src/client/hooks/useFetch.js | 5 +- src/fireedge/src/client/hooks/useListForm.js | 4 +- .../src/client/models/ProviderTemplate.js | 3 +- .../src/client/models/ProvisionTemplate.js | 1 - .../src/client/models/VirtualMachine.js | 2 +- src/fireedge/src/client/utils/schema.js | 14 +- .../utils/constants/commands/template.js | 8 +- 110 files changed, 1759 insertions(+), 660 deletions(-) create mode 100644 src/fireedge/src/client/components/FormControl/TimeController-TODO.js create mode 100644 src/fireedge/src/client/components/FormStepper/Skeleton.js rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm => components/Forms/Provider/CreateForm}/Steps/BasicConfiguration/index.js (95%) rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm => components/Forms/Provider/CreateForm}/Steps/BasicConfiguration/schema.js (100%) rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm => components/Forms/Provider/CreateForm}/Steps/Connection/index.js (92%) rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm => components/Forms/Provider/CreateForm}/Steps/Connection/schema.js (100%) rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm => components/Forms/Provider/CreateForm}/Steps/Template/index.js (95%) rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm => components/Forms/Provider/CreateForm}/Steps/Template/schema.js (100%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provider/CreateForm}/Steps/index.js (63%) create mode 100644 src/fireedge/src/client/components/Forms/Provider/CreateForm/index.js create mode 100644 src/fireedge/src/client/components/Forms/Provider/index.js rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/BasicConfiguration/index.js (95%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/BasicConfiguration/schema.js (100%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/Inputs/index.js (92%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/Inputs/schema.js (100%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/Provider/index.js (92%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/Provider/schema.js (100%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/Template/index.js (94%) rename src/fireedge/src/client/{containers/Provisions/Form/ProvisionForm => components/Forms/Provision/CreateForm}/Steps/Template/schema.js (100%) create mode 100644 src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/index.js create mode 100644 src/fireedge/src/client/components/Forms/Provision/CreateForm/index.js create mode 100644 src/fireedge/src/client/components/Forms/Provision/index.js create mode 100644 src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/Action.js create mode 100644 src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/index.js create mode 100644 src/fireedge/src/client/components/Tables/VmTemplates/actions.js delete mode 100644 src/fireedge/src/client/components/Tables/VmTemplates/detail.js create mode 100644 src/fireedge/src/client/constants/marketplaceApp.js rename src/fireedge/src/client/{containers/Providers/Form/ProviderForm/Steps/index.js => constants/vmTemplate.js} (61%) rename src/fireedge/src/client/containers/Providers/{Form => }/Create.js (60%) delete mode 100644 src/fireedge/src/client/containers/Providers/Form/ProviderForm/index.js rename src/fireedge/src/client/containers/Provisions/{Form => }/Create.js (82%) delete mode 100644 src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/index.js diff --git a/src/fireedge/etc/sunstone/admin/vm-template-tab.yaml b/src/fireedge/etc/sunstone/admin/vm-template-tab.yaml index 153d2cc16f..fd6b644e3c 100644 --- a/src/fireedge/etc/sunstone/admin/vm-template-tab.yaml +++ b/src/fireedge/etc/sunstone/admin/vm-template-tab.yaml @@ -24,8 +24,19 @@ resource_name: "VM-TEMPLATE" # Actions - Which buttons are visible to operate over the resources actions: + refresh: true create_dialog: true + import_dialog: true + update_dialog: true instantiate_dialog: true + clone: true + delete: true + chown: true + chgrp: true + lock: true + unlock: true + share: true + unshare: true # Filters - List of criteria to filter the resources diff --git a/src/fireedge/src/client/apps/provision/routes.js b/src/fireedge/src/client/apps/provision/routes.js index b9b2d2111b..33fed76439 100644 --- a/src/fireedge/src/client/apps/provision/routes.js +++ b/src/fireedge/src/client/apps/provision/routes.js @@ -25,10 +25,10 @@ import loadable from '@loadable/component' const Dashboard = loadable(() => import('client/containers/Dashboard/Provision'), { ssr: false }) const Providers = loadable(() => import('client/containers/Providers'), { ssr: false }) -const ProvidersFormCreate = loadable(() => import('client/containers/Providers/Form/Create'), { ssr: false }) +const CreateProvider = loadable(() => import('client/containers/Providers/Create'), { ssr: false }) const Provisions = loadable(() => import('client/containers/Provisions'), { ssr: false }) -const ProvisionsFormCreate = loadable(() => import('client/containers/Provisions/Form/Create'), { ssr: false }) +const CreateProvision = loadable(() => import('client/containers/Provisions/Create'), { ssr: false }) const Settings = loadable(() => import('client/containers/Settings'), { ssr: false }) @@ -65,12 +65,12 @@ export const ENDPOINTS = [ { label: 'Create Provider', path: PATH.PROVIDERS.CREATE, - Component: ProvidersFormCreate + Component: CreateProvider }, { label: 'Edit Provider template', path: PATH.PROVIDERS.EDIT, - Component: ProvidersFormCreate + Component: CreateProvider }, { label: 'Provisions', @@ -82,12 +82,12 @@ export const ENDPOINTS = [ { label: 'Create Provision', path: PATH.PROVISIONS.CREATE, - Component: ProvisionsFormCreate + Component: CreateProvision }, { label: 'Edit Provision template', path: PATH.PROVISIONS.EDIT, - Component: ProvisionsFormCreate + Component: CreateProvision }, { label: 'Settings', diff --git a/src/fireedge/src/client/components/Dialogs/DialogConfirmation.js b/src/fireedge/src/client/components/Dialogs/DialogConfirmation.js index 014f9e940a..b6f95b5d51 100644 --- a/src/fireedge/src/client/components/Dialogs/DialogConfirmation.js +++ b/src/fireedge/src/client/components/Dialogs/DialogConfirmation.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import { memo } from 'react' +import { memo, JSXElementConstructor } from 'react' import PropTypes from 'prop-types' import clsx from 'clsx' @@ -33,6 +33,21 @@ import { Action } from 'client/components/Cards/SelectCard' import { Tr } from 'client/components/HOC' import { T } from 'client/constants' +/** + * @typedef {object} DialogProps + * @property {boolean} [open] - If `true`, the component is shown + * @property {string|JSXElementConstructor} title - Title + * @property {string|JSXElementConstructor} [subheader] - Subtitle + * @property {object} [contentProps] - Content properties + * @property {function():Promise} handleAccept - Accept action + * @property {Function} handleCancel - Cancel action + * @property {object} [acceptButtonProps] - Accept button properties + * @property {object} [cancelButtonProps] - Cancel button properties + * @property {boolean} [fixedWidth] - Fix minimum with to dialog + * @property {boolean} [fixedHeight] - Fix minimum height to dialog + * @property {JSXElementConstructor} [children] - Fix minimum height + */ + const useStyles = makeStyles({ title: { display: 'flex', @@ -51,6 +66,10 @@ const useStyles = makeStyles({ } }) +/** + * @param {DialogProps} props - Dialog properties + * @returns {JSXElementConstructor} - Dialog with confirmation basic buttons + */ const DialogConfirmation = memo( ({ open = true, diff --git a/src/fireedge/src/client/components/Dialogs/index.js b/src/fireedge/src/client/components/Dialogs/index.js index 5da6efaf79..207404215d 100644 --- a/src/fireedge/src/client/components/Dialogs/index.js +++ b/src/fireedge/src/client/components/Dialogs/index.js @@ -15,11 +15,11 @@ * ------------------------------------------------------------------------- */ import DialogForm from 'client/components/Dialogs/DialogForm' import DialogRequest from 'client/components/Dialogs/DialogRequest' -import DialogConfirmation, { DialogPropTypes } from 'client/components/Dialogs/DialogConfirmation' +import DialogConfirmation from 'client/components/Dialogs/DialogConfirmation' +export * from 'client/components/Dialogs/DialogConfirmation' export { - DialogForm, - DialogRequest, DialogConfirmation, - DialogPropTypes + DialogForm, + DialogRequest } diff --git a/src/fireedge/src/client/components/FormControl/SubmitButton.js b/src/fireedge/src/client/components/FormControl/SubmitButton.js index 2bb8dafdf5..758b724d58 100644 --- a/src/fireedge/src/client/components/FormControl/SubmitButton.js +++ b/src/fireedge/src/client/components/FormControl/SubmitButton.js @@ -67,7 +67,11 @@ const TooltipComponent = ({ tooltip, tooltipProps, children }) => ( placement='bottom' title={{tooltip}} {...tooltipProps} - >{wrapperChildren} + > + + {wrapperChildren} + + )} > {children} @@ -77,6 +81,7 @@ const TooltipComponent = ({ tooltip, tooltipProps, children }) => ( const SubmitButton = memo( ({ isSubmitting, disabled, label, icon, className, ...props }) => { const classes = useStyles() + const progressSize = icon?.props?.size ?? 24 return ( @@ -91,7 +96,9 @@ const SubmitButton = memo( aria-label={label ?? T.Submit} {...props} > - {isSubmitting && } + {isSubmitting && ( + + )} {!isSubmitting && (icon ?? label ?? Tr(T.Submit))} diff --git a/src/fireedge/src/client/components/FormControl/TimeController-TODO.js b/src/fireedge/src/client/components/FormControl/TimeController-TODO.js new file mode 100644 index 0000000000..cfc542ae88 --- /dev/null +++ b/src/fireedge/src/client/components/FormControl/TimeController-TODO.js @@ -0,0 +1,126 @@ +/* ------------------------------------------------------------------------- * + * 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, useState, useEffect } from 'react' +import PropTypes from 'prop-types' + +import Flatpickr from 'react-flatpickr' +import { TextField } from '@material-ui/core' +import { Controller } from 'react-hook-form' + +import { Tr } from 'client/components/HOC' +import { ErrorHelper } from 'client/components/FormControl' + +const WrapperToLoadLib = ({ children, id, lib }) => { + const [loading, setLoading] = useState(true) + + useEffect(() => { + const loadLib = async lib => { + try { + await import(lib) + } finally { + setLoading(false) + } + } + + loadLib() + + return () => { + // remove all styles when component will be unmounted + document + .querySelectorAll(`[id^=${id}]`) + .forEach(child => child.parentNode.removeChild(child)) + } + }, []) + + return loading ? null : children +} + +const TimeController = memo( + ({ control, cy, name, label, error, fieldProps }) => ( + + { + const translated = typeof label === 'string' ? Tr(label) : label + + return ( + { onChange(undefined) }} + data-enable-time + options={{ allowInput: true }} + render={({ defaultValue, ...props }, ref) => ( + } + FormHelperTextProps={{ 'data-cy': `${cy}-error` }} + {...fieldProps} + /> + )} + /> + ) + }} + name={name} + control={control} + /> + + ), + (prevProps, nextProps) => prevProps.error === nextProps.error +) + +TimeController.propTypes = { + control: PropTypes.object, + cy: PropTypes.string, + multiline: PropTypes.bool, + name: PropTypes.string.isRequired, + label: PropTypes.string, + error: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.objectOf(PropTypes.any) + ]), + fieldProps: PropTypes.object, + formContext: PropTypes.shape({ + setValue: PropTypes.func, + setError: PropTypes.func, + clearErrors: PropTypes.func, + watch: PropTypes.func, + register: PropTypes.func + }) +} + +TimeController.defaultProps = { + control: {}, + cy: 'cy', + name: '', + label: '', + error: false, + fieldProps: undefined +} + +TimeController.displayName = 'TimeController' + +export default TimeController diff --git a/src/fireedge/src/client/components/FormStepper/MobileStepper.js b/src/fireedge/src/client/components/FormStepper/MobileStepper.js index ed1c63f521..8dc2922575 100644 --- a/src/fireedge/src/client/components/FormStepper/MobileStepper.js +++ b/src/fireedge/src/client/components/FormStepper/MobileStepper.js @@ -61,7 +61,9 @@ const CustomMobileStepper = ({ return ( - {label} + + {typeof label === 'string' ? Tr(label) : label} + {Boolean(errors[id]) && ( {errors[id]?.message} diff --git a/src/fireedge/src/client/components/FormStepper/Skeleton.js b/src/fireedge/src/client/components/FormStepper/Skeleton.js new file mode 100644 index 0000000000..b9a6ce4ad0 --- /dev/null +++ b/src/fireedge/src/client/components/FormStepper/Skeleton.js @@ -0,0 +1,55 @@ +/* ------------------------------------------------------------------------- * + * 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 { Skeleton } from '@material-ui/lab' +import { useMediaQuery, styled } from '@material-ui/core' + +const ControlWrapper = styled('div')(({ theme }) => ({ + marginBlock: '1em', + display: 'flex', + justifyContent: 'end', + gap: '1em', + [theme.breakpoints.down('sm')]: { + justifyContent: 'space-between', + alignItems: 'center' + } +})) + +/** + * Returns skeleton loader to stepper form. + * + * @returns {JSXElementConstructor} Skeleton loader component + */ +const SkeletonStepsForm = memo(() => { + const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm')) + + return ( +
+ + + + {isMobile && } + + + +
+ ) +}) + +SkeletonStepsForm.displayName = 'SkeletonStepsForm' + +export default SkeletonStepsForm diff --git a/src/fireedge/src/client/components/FormStepper/Stepper.js b/src/fireedge/src/client/components/FormStepper/Stepper.js index d98d8468cf..94470d78bb 100644 --- a/src/fireedge/src/client/components/FormStepper/Stepper.js +++ b/src/fireedge/src/client/components/FormStepper/Stepper.js @@ -95,7 +95,9 @@ const CustomStepper = ({ } }} {...(Boolean(errors[id]?.message) && { error: true })} - >{Tr(label)} + > + {typeof label === 'string' ? Tr(label) : label} + ))} diff --git a/src/fireedge/src/client/components/FormStepper/index.js b/src/fireedge/src/client/components/FormStepper/index.js index 63d7c68b1c..6db739da17 100644 --- a/src/fireedge/src/client/components/FormStepper/index.js +++ b/src/fireedge/src/client/components/FormStepper/index.js @@ -16,13 +16,15 @@ import { useState, useMemo, useCallback, useEffect, JSXElementConstructor } from 'react' import PropTypes from 'prop-types' +import { BaseSchema } from 'yup' import { useFormContext } from 'react-hook-form' import { useMediaQuery } from '@material-ui/core' import { useGeneral } from 'client/features/General' import CustomMobileStepper from 'client/components/FormStepper/MobileStepper' import CustomStepper from 'client/components/FormStepper/Stepper' -import { groupBy, Step, ResolverCallback } from 'client/utils' +import SkeletonStepsForm from 'client/components/FormStepper/Skeleton' +import { groupBy, Step } from 'client/utils' const FIRST_STEP = 0 @@ -32,11 +34,11 @@ const FIRST_STEP = 0 * * @param {object} props - Props * @param {Step[]} props.steps - Steps - * @param {ResolverCallback} props.schema - Function to get form schema + * @param {function():BaseSchema} props.schema - Function to get form schema * @param {Function} props.onSubmit - Submit function * @returns {JSXElementConstructor} Stepper form component */ -const FormStepper = ({ steps, schema, onSubmit }) => { +const FormStepper = ({ steps = [], schema, onSubmit }) => { const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs')) const { watch, reset, errors, setError } = useFormContext() const { isLoading } = useGeneral() @@ -53,13 +55,13 @@ const FormStepper = ({ steps, schema, onSubmit }) => { }, [formData]) const validateSchema = async stepIdx => { - const { id, resolver, optionsValidate, ...step } = steps[stepIdx] + const { id, resolver, optionsValidate: options, ...step } = steps[stepIdx] const stepData = watch(id) const allData = { ...formData, [id]: stepData } const stepSchema = typeof resolver === 'function' ? resolver(allData) : resolver - await stepSchema.validate(stepData, optionsValidate) + await stepSchema.validate(stepData, options) return { id, data: stepData, ...step } } @@ -105,9 +107,9 @@ const FormStepper = ({ steps, schema, onSubmit }) => { const { id, data } = await validateSchema(activeStep) if (activeStep === lastStep) { - const submitData = schema().cast({ ...formData, [id]: data }) - - onSubmit(submitData) + const submitData = { ...formData, [id]: data } + const schemaData = schema().cast(submitData, { context: submitData }) + onSubmit(schemaData) } else { setFormData(prev => ({ ...prev, [id]: data })) setActiveStep(prevActiveStep => prevActiveStep + 1) @@ -184,15 +186,13 @@ FormStepper.propTypes = { context: PropTypes.object }) }) - ), - schema: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, + ).isRequired, + schema: PropTypes.func.isRequired, onSubmit: PropTypes.func } -FormStepper.defaultProps = { - steps: [], - schema: {}, - onSubmit: console.log +export { + SkeletonStepsForm } export default FormStepper diff --git a/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js b/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js index 112897506a..fb381f629e 100644 --- a/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js +++ b/src/fireedge/src/client/components/Forms/ButtonToTriggerForm.js @@ -23,7 +23,8 @@ import { Paper, Popper, MenuItem, - MenuList + MenuList, + ListItemIcon } from '@material-ui/core' import { NavArrowDown } from 'iconoir-react' @@ -36,7 +37,6 @@ import { Translate } from 'client/components/HOC' const ButtonToTriggerForm = ({ buttonProps = {}, - isConfirmDialog = false, dialogProps = {}, options = [] }) => { @@ -47,7 +47,7 @@ const ButtonToTriggerForm = ({ const open = Boolean(anchorEl) const { display, show, hide, values: Form } = useDialog() - const { onSubmit: handleSubmit, form } = Form ?? {} + const { onSubmit: handleSubmit, form, isConfirmDialog = true } = Form ?? {} const formConfig = useMemo(() => form?.() ?? {}, [form]) const { steps, defaultValues, resolver, fields, transformBeforeSubmit } = formConfig @@ -89,18 +89,24 @@ const ButtonToTriggerForm = ({ id={buttonId} open={open} transition + style={{ zIndex: 2 }} > {({ TransitionProps }) => ( - + - - {options.map(({ cy, name, ...option }) => ( + + {options.map(({ cy, icon: Icon, name, ...option }) => ( openDialogForm(option)} > + {Icon && ( + + + + )} ))} @@ -142,20 +148,23 @@ const ButtonToTriggerForm = ({ ) } -ButtonToTriggerForm.propTypes = { +export const ButtonToTriggerFormPropTypes = { buttonProps: PropTypes.shape(SubmitButtonPropTypes), dialogProps: PropTypes.shape(DialogPropTypes), - isConfirmDialog: PropTypes.bool, options: PropTypes.arrayOf( PropTypes.shape({ cy: PropTypes.string, + isConfirmDialog: PropTypes.bool, name: PropTypes.string, + icon: PropTypes.any, form: PropTypes.func, - handleSubmit: PropTypes.func + onSubmit: PropTypes.func }) ) } +ButtonToTriggerForm.propTypes = ButtonToTriggerFormPropTypes + ButtonToTriggerForm.displayName = 'ButtonToTriggerForm' export default ButtonToTriggerForm diff --git a/src/fireedge/src/client/components/Forms/FormWithSchema.js b/src/fireedge/src/client/components/Forms/FormWithSchema.js index 56ff7f5caa..62e0c181b3 100644 --- a/src/fireedge/src/client/components/Forms/FormWithSchema.js +++ b/src/fireedge/src/client/components/Forms/FormWithSchema.js @@ -52,6 +52,8 @@ const FormWithSchema = ({ id, cy, fields, className, legend }) => { const getFields = useMemo(() => typeof fields === 'function' ? fields() : fields, []) + if (getFields.length === 0) return null + return (
{legend && {legend}} diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration/index.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/index.js similarity index 95% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration/index.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/index.js index 73e04c15c0..f8578bfe35 100644 --- a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/index.js @@ -21,7 +21,7 @@ import { T } from 'client/constants' import { FORM_FIELDS, STEP_FORM_SCHEMA -} from 'client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration/schema' +} from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/schema' export const STEP_ID = 'configuration' diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration/schema.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration/schema.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration/schema.js diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Connection/index.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Connection/index.js similarity index 92% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Connection/index.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Connection/index.js index 5c4119cf76..6750789853 100644 --- a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Connection/index.js +++ b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Connection/index.js @@ -27,11 +27,11 @@ import { T } from 'client/constants' import { FORM_FIELDS, STEP_FORM_SCHEMA -} from 'client/containers/Providers/Form/ProviderForm/Steps/Connection/schema' +} from 'client/components/Forms/Provider/CreateForm/Steps/Connection/schema' import { STEP_ID as TEMPLATE_ID -} from 'client/containers/Providers/Form/ProviderForm/Steps/Template' +} from 'client/components/Forms/Provider/CreateForm/Steps/Template' export const STEP_ID = 'connection' @@ -76,5 +76,5 @@ const Connection = ({ isUpdate }) => ({ }, []) }) -export * from 'client/containers/Providers/Form/ProviderForm/Steps/Connection/schema' +export * from 'client/components/Forms/Provider/CreateForm/Steps/Connection/schema' export default Connection diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Connection/schema.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Connection/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Connection/schema.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Connection/schema.js diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Template/index.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Template/index.js similarity index 95% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Template/index.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Template/index.js index c57a6ee20d..119bfb18e6 100644 --- a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Template/index.js +++ b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Template/index.js @@ -28,10 +28,10 @@ import { sanitize } from 'client/utils' import { isValidProviderTemplate, getProvisionTypeFromTemplate } from 'client/models/ProviderTemplate' import { T } from 'client/constants' -import { STEP_FORM_SCHEMA } from 'client/containers/Providers/Form/ProviderForm/Steps/Template/schema' +import { STEP_FORM_SCHEMA } from 'client/components/Forms/Provider/CreateForm/Steps/Template/schema' -import { STEP_ID as CONFIGURATION_ID } from 'client/containers/Providers/Form/ProviderForm/Steps/BasicConfiguration' -import { STEP_ID as CONNECTION_ID } from 'client/containers/Providers/Form/ProviderForm/Steps/Connection' +import { STEP_ID as CONFIGURATION_ID } from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration' +import { STEP_ID as CONNECTION_ID } from 'client/components/Forms/Provider/CreateForm/Steps/Connection' export const STEP_ID = 'template' diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Template/schema.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Template/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/Template/schema.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/Template/schema.js diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/index.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/index.js similarity index 63% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/index.js rename to src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/index.js index 52dec37c82..0f93f8786d 100644 --- a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/index.js +++ b/src/fireedge/src/client/components/Forms/Provider/CreateForm/Steps/index.js @@ -14,32 +14,26 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import Template from 'client/components/Forms/Provider/CreateForm/Steps/Template' +import BasicConfiguration from 'client/components/Forms/Provider/CreateForm/Steps/BasicConfiguration' +import Connection from 'client/components/Forms/Provider/CreateForm/Steps/Connection' +import { createSteps, deepmerge } from 'client/utils' -import Template from './Template' -import Provider from './Provider' -import BasicConfiguration from './BasicConfiguration' -import Inputs from './Inputs' +const Steps = createSteps(stepProps => { + const { isUpdate } = stepProps -const Steps = () => { - const template = Template() - const provider = Provider() - const configuration = BasicConfiguration() - const inputs = Inputs() + return [ + !isUpdate && Template, + BasicConfiguration, + Connection + ].filter(Boolean) +}, { + transformBeforeSubmit: formData => { + const { template, configuration, connection } = formData + const templateSelected = template?.[0] - const steps = [template, provider, configuration, inputs] - - const resolvers = () => yup - .object({ - [template.id]: template.resolver(), - [provider.id]: provider.resolver(), - [configuration.id]: configuration.resolver(), - [inputs.id]: inputs.resolver() - }) - - const defaultValues = resolvers().default() - - return { steps, defaultValues, resolvers } -} + return deepmerge(templateSelected, { ...configuration, connection }) + } +}) export default Steps diff --git a/src/fireedge/src/client/components/Forms/Provider/CreateForm/index.js b/src/fireedge/src/client/components/Forms/Provider/CreateForm/index.js new file mode 100644 index 0000000000..281b7a416e --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Provider/CreateForm/index.js @@ -0,0 +1,58 @@ +/* ------------------------------------------------------------------------- * + * 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 { useForm, FormProvider } from 'react-hook-form' +import { yupResolver } from '@hookform/resolvers/yup' + +import FormStepper from 'client/components/FormStepper' +import Steps from 'client/components/Forms/Provider/CreateForm/Steps' + +const CreateForm = ({ stepProps, initialValues, onSubmit }) => { + const { + steps, + defaultValues, + resolver, + transformBeforeSubmit + } = Steps(stepProps, initialValues) + + const methods = useForm({ + mode: 'onSubmit', + defaultValues, + resolver: yupResolver(resolver()) + }) + + return ( + + onSubmit(transformBeforeSubmit?.(data) ?? data)} + /> + + ) +} + +CreateForm.propTypes = { + stepProps: PropTypes.shape({ + isUpdate: PropTypes.bool + }), + initialValues: PropTypes.object, + onSubmit: PropTypes.func +} + +export default CreateForm diff --git a/src/fireedge/src/client/components/Forms/Provider/index.js b/src/fireedge/src/client/components/Forms/Provider/index.js new file mode 100644 index 0000000000..ef756eafa9 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Provider/index.js @@ -0,0 +1,20 @@ +/* ------------------------------------------------------------------------- * + * 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 CreateForm from 'client/components/Forms/Provider/CreateForm' + +export { + CreateForm +} diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration/index.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration/index.js similarity index 95% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration/index.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration/index.js index 1fa85ccca6..b067148d52 100644 --- a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration/index.js +++ b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration/index.js @@ -21,7 +21,7 @@ import { T } from 'client/constants' import { FORM_FIELDS, STEP_FORM_SCHEMA -} from 'client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration/schema' +} from 'client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration/schema' export const STEP_ID = 'configuration' diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration/schema.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration/schema.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration/schema.js diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Inputs/index.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Inputs/index.js similarity index 92% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Inputs/index.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Inputs/index.js index 8d7c24b381..e29f7179fe 100644 --- a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Inputs/index.js +++ b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Inputs/index.js @@ -26,11 +26,11 @@ import FormWithSchema from 'client/components/Forms/FormWithSchema' import { EmptyCard } from 'client/components/Cards' import { T } from 'client/constants' -import { STEP_ID as PROVIDER_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Provider' -import { STEP_ID as TEMPLATE_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Template' +import { STEP_ID as PROVIDER_ID } from 'client/components/Forms/Provision/CreateForm/Steps/Provider' +import { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/Provision/CreateForm/Steps/Template' import { FORM_FIELDS, STEP_FORM_SCHEMA -} from 'client/containers/Provisions/Form/ProvisionForm/Steps/Inputs/schema' +} from 'client/components/Forms/Provision/CreateForm/Steps/Inputs/schema' export const STEP_ID = 'inputs' diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Inputs/schema.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Inputs/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Inputs/schema.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Inputs/schema.js diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Provider/index.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Provider/index.js similarity index 92% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Provider/index.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Provider/index.js index e39439e3f7..4dda06f941 100644 --- a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Provider/index.js +++ b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Provider/index.js @@ -25,9 +25,9 @@ import { EmptyCard, ProvisionCard } from 'client/components/Cards' import { getProvisionTypeFromTemplate } from 'client/models/ProvisionTemplate' import { T } from 'client/constants' -import { STEP_ID as INPUTS_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Inputs' -import { STEP_ID as TEMPLATE_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Template' -import { STEP_FORM_SCHEMA } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Provider/schema' +import { STEP_ID as INPUTS_ID } from 'client/components/Forms/Provision/CreateForm/Steps/Inputs' +import { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/Provision/CreateForm/Steps/Template' +import { STEP_FORM_SCHEMA } from 'client/components/Forms/Provision/CreateForm/Steps/Provider/schema' export const STEP_ID = 'provider' diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Provider/schema.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Provider/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Provider/schema.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Provider/schema.js diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Template/index.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Template/index.js similarity index 94% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Template/index.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Template/index.js index 6434d8d3db..e0dc31738c 100644 --- a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Template/index.js +++ b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Template/index.js @@ -28,10 +28,10 @@ import { sanitize } from 'client/utils' import { isValidProvisionTemplate, getProvisionTypeFromTemplate } from 'client/models/ProvisionTemplate' import { T } from 'client/constants' -import { STEP_ID as PROVIDER_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Provider' -import { STEP_ID as CONFIGURATION_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/BasicConfiguration' -import { STEP_ID as INPUTS_ID } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Inputs' -import { STEP_FORM_SCHEMA } from 'client/containers/Provisions/Form/ProvisionForm/Steps/Template/schema' +import { STEP_ID as PROVIDER_ID } from 'client/components/Forms/Provision/CreateForm/Steps/Provider' +import { STEP_ID as CONFIGURATION_ID } from 'client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration' +import { STEP_ID as INPUTS_ID } from 'client/components/Forms/Provision/CreateForm/Steps/Inputs' +import { STEP_FORM_SCHEMA } from 'client/components/Forms/Provision/CreateForm/Steps/Template/schema' export const STEP_ID = 'template' diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Template/schema.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Template/schema.js similarity index 100% rename from src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/Steps/Template/schema.js rename to src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/Template/schema.js diff --git a/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/index.js new file mode 100644 index 0000000000..b91ba923da --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Provision/CreateForm/Steps/index.js @@ -0,0 +1,52 @@ +/* ------------------------------------------------------------------------- * + * 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 Template from 'client/components/Forms/Provision/CreateForm/Steps/Template' +import Provider from 'client/components/Forms/Provision/CreateForm/Steps/Provider' +import BasicConfiguration from 'client/components/Forms/Provision/CreateForm/Steps/BasicConfiguration' +import Inputs from 'client/components/Forms/Provision/CreateForm/Steps/Inputs' +import { set, createSteps, cloneObject, mapUserInputs } from 'client/utils' + +const Steps = createSteps( + [Template, Provider, BasicConfiguration, Inputs], + { + transformBeforeSubmit: formData => { + const { template, provider, configuration, inputs: dirtyInputs } = formData + const { name, description } = configuration + const providerName = provider?.[0]?.NAME + + // clone object from redux store + const provisionTemplateSelected = cloneObject(template?.[0] ?? {}) + + // update provider name if changed during form + if (provisionTemplateSelected.defaults?.provision?.provider_name) { + set(provisionTemplateSelected, 'defaults.provision.provider_name', providerName) + } else if (provisionTemplateSelected.hosts?.length > 0) { + provisionTemplateSelected.hosts.forEach(host => { + set(host, 'provision.provider_name', providerName) + }) + } + + const parseInputs = mapUserInputs(dirtyInputs) + const inputs = provisionTemplateSelected?.inputs + ?.map(input => ({ ...input, value: `${parseInputs[input?.name]}` })) + + return { ...provisionTemplateSelected, name, description, inputs } + } + } +) + +export default Steps diff --git a/src/fireedge/src/client/components/Forms/Provision/CreateForm/index.js b/src/fireedge/src/client/components/Forms/Provision/CreateForm/index.js new file mode 100644 index 0000000000..4ac1fd2845 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Provision/CreateForm/index.js @@ -0,0 +1,50 @@ +/* ------------------------------------------------------------------------- * + * 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 { useForm, FormProvider } from 'react-hook-form' +import { yupResolver } from '@hookform/resolvers/yup' + +import FormStepper from 'client/components/FormStepper' +import Steps from 'client/components/Forms/Provision/CreateForm/Steps' + +const CreateForm = ({ onSubmit }) => { + const { steps, defaultValues, resolver, transformBeforeSubmit } = Steps() + + const methods = useForm({ + mode: 'onSubmit', + defaultValues, + resolver: yupResolver(resolver()) + }) + + return ( + + onSubmit(transformBeforeSubmit?.(data) ?? data)} + /> + + ) +} + +CreateForm.propTypes = { + initialValues: PropTypes.object, + onSubmit: PropTypes.func +} + +export default CreateForm diff --git a/src/fireedge/src/client/components/Forms/Provision/index.js b/src/fireedge/src/client/components/Forms/Provision/index.js new file mode 100644 index 0000000000..4af9c90bbc --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Provision/index.js @@ -0,0 +1,20 @@ +/* ------------------------------------------------------------------------- * + * 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 CreateForm from 'client/components/Forms/Provision/CreateForm' + +export { + CreateForm +} 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 93a266776f..836dbbe911 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 @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import { number, object } from 'yup' import { getValidationFromFields } from 'client/utils' import { T, INPUT_TYPES } from 'client/constants' @@ -25,8 +25,8 @@ const MEMORY = { tooltip: 'Amount of RAM required for the VM.', type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: yup - .number() + 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') @@ -42,8 +42,7 @@ const PHYSICAL_CPU = { the Virtual Machine. Half a processor is written 0.5.`, type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: yup - .number() + validation: number() .positive('CPU should be positive number') .typeError('CPU must be a number') .required('CPU field is required') @@ -59,8 +58,7 @@ const VIRTUAL_CPU = { hypervisor behavior is used, usually one virtual CPU`, type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: yup - .number() + validation: number() .positive('Virtual CPU should be positive number') .notRequired() .default(() => undefined), @@ -73,4 +71,4 @@ export const FIELDS = [ VIRTUAL_CPU ] -export const SCHEMA = yup.object(getValidationFromFields(FIELDS)) +export const SCHEMA = object(getValidationFromFields(FIELDS)) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/diskSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/diskSchema.js index b347c42264..3e0ba4cfd9 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/diskSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/diskSchema.js @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import { object, array, number } from 'yup' import { StatusCircle, StatusChip } from 'client/components/Status' import { Translate } from 'client/components/HOC' @@ -61,8 +61,7 @@ const SIZE_FIELD = ({ the datastore after the VM is terminated (ie goes into DONE state)` : `Non-persistent disk. The changes will be lost once the VM is terminated (ie goes into DONE state)`, - validation: yup - .number() + validation: number() .positive() .min(0, 'Disk size field is required') .typeError('Disk must be a number') @@ -79,13 +78,8 @@ export const FIELDS = vmTemplate => { return disks?.map(SIZE_FIELD).map(addParentToField) } -export const SCHEMA = yup - .object({ - [PARENT]: yup.array(yup.object({ - [SIZE_FIELD().name]: SIZE_FIELD().validation - })) - }) - .transform(({ [PARENT]: disks, ...rest }) => ({ - ...rest, - [PARENT]: [disks].flat().filter(Boolean) - })) +export const SCHEMA = object({ + [PARENT]: array(object({ + [SIZE_FIELD().name]: SIZE_FIELD().validation + })).ensure() +}) 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 6f9648bab1..6b10bd9a54 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 @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { useCallback, useMemo } from 'react' +import { useMemo } from 'react' import { useFormContext } from 'react-hook-form' import FormWithSchema from 'client/components/Forms/FormWithSchema' @@ -80,7 +80,7 @@ const BasicConfiguration = () => ({ label: T.Configuration, resolver: SCHEMA, optionsValidate: { abortEarly: false }, - content: useCallback(Content, []) + content: Content }) export default BasicConfiguration diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js index 3a4a3b1164..7bb047ea77 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration/informationSchema.js @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import * as yup from 'yup' +import { string, number, boolean, object } from 'yup' import { getValidationFromFields } from 'client/utils' import { INPUT_TYPES } from 'client/constants' @@ -27,10 +27,7 @@ const NAME = { When creating several VMs, the wildcard %idx will be replaced with a number starting from 0`, type: INPUT_TYPES.TEXT, - validation: yup - .string() - .trim() - .default(() => undefined) + validation: string().trim().default(() => undefined) } const INSTANCES = { @@ -38,8 +35,7 @@ const INSTANCES = { label: 'Number of instances', type: INPUT_TYPES.TEXT, htmlType: 'number', - validation: yup - .number() + validation: number() .min(1, 'Instances minimum is 1') .integer('Instances should be an integer number') .required('Instances field is required') @@ -54,9 +50,7 @@ const HOLD = { Sets the new VM to hold state, instead of pending. The scheduler will not deploy VMs in this state. It can be released later, or deployed manually.`, - validation: yup - .boolean() - .default(() => false), + validation: boolean().default(() => false), grid: { md: 12 } } @@ -67,9 +61,7 @@ const PERSISTENT = { tooltip: ` Creates a private persistent copy of the template plus any image defined in DISK, and instantiates that copy.`, - validation: yup - .boolean() - .default(() => false), + validation: boolean().default(() => false), grid: { md: 12 } } @@ -80,4 +72,4 @@ export const FIELDS = [ PERSISTENT ] -export const SCHEMA = yup.object(getValidationFromFields(FIELDS)) +export const SCHEMA = object(getValidationFromFields(FIELDS)) 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 index 3d3ce5a423..90278a1397 100644 --- 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 @@ -14,7 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { useMemo } from 'react' +import { useMemo, SetStateAction } from 'react' import PropTypes from 'prop-types' import { @@ -27,7 +27,7 @@ import { Divider, makeStyles } from '@material-ui/core' import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd' import { useFormContext } from 'react-hook-form' -import { Tr } from 'client/components/HOC' +import { Translate } from 'client/components/HOC' import { Action } from 'client/components/Cards/SelectCard' import { STEP_ID as TEMPLATE_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable' import { TAB_ID as NIC_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/networking' @@ -53,10 +53,22 @@ const useStyles = makeStyles(theme => ({ } })) +/** + * @param {string[]} newBootOrder - New boot order + * @param {SetStateAction} setFormData - New boot order + */ +export const reorder = (newBootOrder, setFormData) => { + setFormData(prev => { + const newData = set({ ...prev }, 'extra.OS.BOOT', newBootOrder.join(',')) + + return { ...prev, extra: { ...prev.extra, OS: newData } } + }) +} + const Booting = ({ data, setFormData }) => { const classes = useStyles() const { watch } = useFormContext() - const bootOrder = data?.OS?.BOOT?.split(',') + const bootOrder = data?.OS?.BOOT?.split(',').filter(Boolean) const disks = useMemo(() => { const templateSeleted = watch(`${TEMPLATE_ID}[0]`) @@ -67,7 +79,7 @@ const Booting = ({ data, setFormData }) => { const isVolatile = !IMAGE && !IMAGE_ID const name = isVolatile - ? `DISK ${DISK_ID}: ${Tr(T.VolatileDisk)}` + ? <>`DISK ${DISK_ID}: ` : `DISK ${DISK_ID}: ${IMAGE}` return { @@ -91,7 +103,7 @@ const Booting = ({ data, setFormData }) => { {`NIC ${idx}: ${nic.NETWORK}`} ) - })) + })) ?? [] const enabledItems = [...disks, ...nics] .filter(item => bootOrder.includes(item.ID)) @@ -100,15 +112,6 @@ const Booting = ({ data, setFormData }) => { const restOfItems = [...disks, ...nics] .filter(item => !bootOrder.includes(item.ID)) - /** @param {string[]} newBootOrder - New boot order */ - const reorder = newBootOrder => { - setFormData(prev => { - const newData = set({ ...prev }, 'extra.OS.BOOT', newBootOrder.join(',')) - - return { ...prev, extra: { ...prev.extra, OS: newData } } - }) - } - /** @param {DropResult} result - Drop result */ const onDragEnd = result => { const { destination, source, draggableId } = result @@ -122,7 +125,7 @@ const Booting = ({ data, setFormData }) => { newBootOrder.splice(source.index, 1) // remove current position newBootOrder.splice(destination.index, 0, draggableId) // set in new position - reorder(newBootOrder) + reorder(newBootOrder, setFormData) } } @@ -134,7 +137,7 @@ const Booting = ({ data, setFormData }) => { ? newBootOrder.splice(itemIndex, 1) : newBootOrder.push(itemId) - reorder(newBootOrder) + reorder(newBootOrder, setFormData) } return ( 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 index fa115efd61..b5506429b4 100644 --- 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 @@ -24,8 +24,9 @@ import SelectCard, { Action } from 'client/components/Cards/SelectCard' import { AttachNicForm } from 'client/components/Forms/Vm' import { Tr, Translate } from 'client/components/HOC' -import { STEP_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' +import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' import { NIC_SCHEMA } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/schema' +import { reorder } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration/booting' import { T } from 'client/constants' const useStyles = makeStyles({ @@ -45,7 +46,7 @@ const Networking = ({ data, setFormData }) => { ?.map((nic, idx) => ({ ...nic, NAME: `NIC${idx}` })) const { handleRemove, handleSave } = useListForm({ - parent: STEP_ID, + parent: EXTRA_ID, key: TAB_ID, list: nics, setList: setFormData, @@ -53,6 +54,27 @@ const Networking = ({ data, setFormData }) => { addItemId: (item, id) => ({ ...item, NAME: id }) }) + const reorderBootOrder = nicId => { + const getIndexFromNicId = id => String(id).toLowerCase().replace('nic', '') + const idxToRemove = getIndexFromNicId(nicId) + + const nicIds = nics + .filter(nic => nic.NAME !== nicId) + .map(nic => String(nic.NAME).toLowerCase()) + + const newBootOrder = [...data?.OS?.BOOT?.split(',').filter(Boolean)] + .filter(bootId => !bootId.startsWith('nic') || nicIds.includes(bootId)) + .map(bootId => { + if (!bootId.startsWith('nic')) return bootId + + const nicId = getIndexFromNicId(bootId) + + return nicId < idxToRemove ? bootId : `nic${nicId - 1}` + }) + + reorder(newBootOrder, setFormData) + } + return ( <> { {!hasAlias && handleRemove(NAME)} + handleClick={() => { + handleRemove(NAME) + reorderBootOrder(NAME) + }} icon={} /> } 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 b4bcf86604..4840939958 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 @@ -81,8 +81,8 @@ export const SCHED_ACTION_SCHEMA = lazy(({ TIME } = {}) => { }) export const SCHEMA = object({ - NIC: array(NIC_SCHEMA), - SCHED_ACTION: array(SCHED_ACTION_SCHEMA), + NIC: array(NIC_SCHEMA).ensure(), + SCHED_ACTION: array(SCHED_ACTION_SCHEMA).ensure(), OS: object({ BOOT: string().trim().notRequired() }), @@ -93,8 +93,3 @@ export const SCHEMA = object({ DS_RANK_FIELD ]) }) - .transform(({ SCHED_ACTION, NIC, ...rest }) => ({ - ...rest, - SCHED_ACTION: [SCHED_ACTION ?? []].flat(), - NIC: [NIC ?? []].flat() - })) 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 fab939c4dd..5f7501ffca 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 @@ -14,7 +14,6 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { useCallback } from 'react' import PropTypes from 'prop-types' import { useListForm } from 'client/hooks' @@ -76,9 +75,9 @@ Content.propTypes = { const VmTemplateStep = () => ({ id: STEP_ID, - label: T.VMTemplate, + label: T.SelectVmTemplate, resolver: SCHEMA, - content: useCallback(Content, []) + content: Content }) export default VmTemplateStep diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/schema.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/schema.js index bc2c16ed41..58c8927430 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/schema.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/VmTemplatesTable/schema.js @@ -13,11 +13,19 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import * as yup from 'yup' +import { array, object, string } from 'yup' -export const SCHEMA = yup - .array(yup.object()) +const TEMPLATE_SCHEMA = object({ + ID: string(), + NAME: string(), + TEMPLATE: object({ + DISK: array().ensure(), + NIC: array().ensure() + }) +}) + +export const SCHEMA = array(TEMPLATE_SCHEMA) .min(1, 'Select VM Template') .max(1, 'Max. one template selected') .required('Template field is required') - .default([]) + .default(undefined) diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/index.js index 481932b7b6..e5ca99d541 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/Steps/index.js @@ -17,36 +17,36 @@ import VmTemplatesTable, { STEP_ID as TEMPLATE_ID } from 'client/components/Form import BasicConfiguration, { STEP_ID as BASIC_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/BasicConfiguration' import ExtraConfiguration, { STEP_ID as EXTRA_ID } from 'client/components/Forms/VmTemplate/InstantiateForm/Steps/ExtraConfiguration' import { jsonToXml } from 'client/models/Helper' -import { createSteps } from 'client/utils' +import { createSteps, deepmerge } from 'client/utils' -const Steps = createSteps(() => { - // const { [STEP_ID]: initialTemplate } = initialValues ?? {} +const Steps = createSteps( + [VmTemplatesTable, BasicConfiguration, ExtraConfiguration], + { + transformInitialValue: (vmTemplate, schema) => ({ + ...schema.cast({ + [TEMPLATE_ID]: [vmTemplate], + [BASIC_ID]: vmTemplate?.TEMPLATE, + [EXTRA_ID]: vmTemplate?.TEMPLATE + }, { stripUnknown: true }) + }), + transformBeforeSubmit: formData => { + const { + [TEMPLATE_ID]: [templateSelected] = [], + [BASIC_ID]: { name, instances, hold, persistent, ...restOfConfig } = {}, + [EXTRA_ID]: extraTemplate = {} + } = formData ?? {} - return [ - VmTemplatesTable, - BasicConfiguration, - ExtraConfiguration - ] -}, { - transformBeforeSubmit: formData => { - const { - [TEMPLATE_ID]: [templateSelected] = [], - [BASIC_ID]: { name, instances, hold, persistent, ...restOfConfig } = {}, - [EXTRA_ID]: extraTemplate = {} - } = formData ?? {} + // merge with template disks to get TYPE attribute + const DISK = deepmerge(templateSelected.TEMPLATE?.DISK, restOfConfig?.DISK) + const templateXML = jsonToXml({ ...extraTemplate, ...restOfConfig, DISK }) + const data = { instances, hold, persistent, template: templateXML } - const templates = [...new Array(instances)] - .map((_, idx) => { - const replacedName = name?.replace(/%idx/gi, idx) + const templates = [...new Array(instances)] + .map((_, idx) => ({ name: name?.replace(/%idx/gi, idx), ...data })) - const template = jsonToXml({ TEMPLATE: { ...extraTemplate, ...restOfConfig } }) - const data = { name: replacedName, instances, hold, persistent, template } - - return data - }) - - return [templateSelected, templates] + return [templateSelected, templates] + } } -}) +) export default Steps diff --git a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/index.js b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/index.js index 1f2e5f59d6..c2a619a5ed 100644 --- a/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/index.js +++ b/src/fireedge/src/client/components/Forms/VmTemplate/InstantiateForm/index.js @@ -14,21 +14,25 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ +import { useEffect, useMemo } from 'react' import PropTypes from 'prop-types' import { useForm, FormProvider } from 'react-hook-form' import { yupResolver } from '@hookform/resolvers/yup' -import FormStepper from 'client/components/FormStepper' +import { useUserApi, useVmGroupApi, useVmTemplateApi } from 'client/features/One' +import { useFetch } from 'client/hooks' +import FormStepper, { SkeletonStepsForm } from 'client/components/FormStepper' import Steps from 'client/components/Forms/VmTemplate/InstantiateForm/Steps' -const InstantiateForm = ({ initialValues, onSubmit }) => { - const { steps, defaultValues, resolver, transformBeforeSubmit } = Steps(initialValues) +const InstantiateForm = ({ template, onSubmit }) => { + const stepProps = useMemo(() => Steps(template, template), []) + const { steps, defaultValues, resolver, transformBeforeSubmit } = stepProps const methods = useForm({ mode: 'onSubmit', defaultValues, - resolver: yupResolver(resolver()) + resolver: yupResolver(resolver?.()) }) return ( @@ -42,9 +46,33 @@ const InstantiateForm = ({ initialValues, onSubmit }) => { ) } -InstantiateForm.propTypes = { - initialValues: PropTypes.object, +const PreFetchingForm = ({ templateId, ...props }) => { + const { getUsers } = useUserApi() + const { getVmGroups } = useVmGroupApi() + const { getVmTemplate } = useVmTemplateApi() + const { fetchRequest, data } = useFetch( + () => getVmTemplate(templateId, { extended: true }) + ) + + useEffect(() => { + templateId && fetchRequest() + getUsers() + getVmGroups() + }, []) + + return (templateId && !data) + ? + : +} + +PreFetchingForm.propTypes = { + templateId: PropTypes.string, onSubmit: PropTypes.func } -export default InstantiateForm +InstantiateForm.propTypes = { + template: PropTypes.object, + onSubmit: PropTypes.func +} + +export default PreFetchingForm diff --git a/src/fireedge/src/client/components/Forms/index.js b/src/fireedge/src/client/components/Forms/index.js index 02ff74a94c..b16cd2fccb 100644 --- a/src/fireedge/src/client/components/Forms/index.js +++ b/src/fireedge/src/client/components/Forms/index.js @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm' +import ButtonToTriggerForm, { ButtonToTriggerFormPropTypes } from 'client/components/Forms/ButtonToTriggerForm' import FormWithSchema from 'client/components/Forms/FormWithSchema' export { ButtonToTriggerForm, + ButtonToTriggerFormPropTypes, FormWithSchema } diff --git a/src/fireedge/src/client/components/Status/Badge.js b/src/fireedge/src/client/components/Status/Badge.js index 502ffe0f39..bc35a3987e 100644 --- a/src/fireedge/src/client/components/Status/Badge.js +++ b/src/fireedge/src/client/components/Status/Badge.js @@ -55,7 +55,7 @@ const StatusBadge = memo(({ stateColor, children, customTransform, ...props }) = diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/Action.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/Action.js new file mode 100644 index 0000000000..5e1816bd2e --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/Action.js @@ -0,0 +1,137 @@ +/* ------------------------------------------------------------------------- * + * 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 { Row } from 'react-table' + +import { Action } from 'client/components/Cards/SelectCard' +import { ButtonToTriggerForm } from 'client/components/Forms' +import { Tr } from 'client/components/HOC' +// eslint-disable-next-line no-unused-vars +import { DialogPropTypes, DialogProps } from 'client/components/Dialogs' +// eslint-disable-next-line no-unused-vars +import { CreateStepsCallback, CreateFormCallback } from 'client/utils' + +/** + * @typedef {object} Option + * @property {string} cy - Cypress selector + * @property {string} name - Label of option + * @property {JSXElementConstructor} [icon] - Icon + * @property {boolean} isConfirmDialog + * - If `true`, the form will be a dialog with confirmation buttons + * @property {function(object, Row[])} onSubmit - Function to handle after finish the form + * @property {function():CreateStepsCallback|CreateFormCallback} form - Form + */ + +/** + * @typedef {object} GlobalAction + * @property {string} accessor - Accessor action (id) + * @property {string} [tooltip] - Tooltip + * @property {string} [label] - Label + * @property {string} [color] - Color + * @property {string} [icon] - Icon + * @property {DialogProps} [dialogProps] - Dialog properties + * @property {Option[]} [options] - Group of actions + * @property {function(Row[])} [action] - Singular action without form + * @property {boolean|{min: number, max: number}} [selected] - Condition for selected rows + * @property {boolean} [disabled] - If `true`, action will be disabled + */ + +/** + * Render global action. + * + * @param {object} props - Props + * @param {GlobalAction[]} props.item - Item action + * @param {Row[]} props.selectedRows - Selected rows + * @returns {JSXElementConstructor} Component JSX + */ +const ActionItem = memo(({ item, selectedRows }) => { + const { + accessor, + tooltip, + label, + color = 'secondary', + icon: Icon, + dialogProps: { title, children, ...dialogProps } = {}, + options, + action, + disabled + } = item + + const buttonProps = { + color, + 'data-cy': accessor && `action.${accessor}`, + disabled, + icon: Icon && , + label: label && Tr(label), + title: tooltip && Tr(tooltip) + } + + return action ? ( + action?.(selectedRows)} /> + ) : ( + ({ + form: form ? () => form(selectedRows) : undefined, + onSubmit: data => onSubmit(data, selectedRows), + ...option + }))} + /> + ) +}, (prev, next) => prev.selectedRows?.length === next.selectedRows?.length) + +export const ActionPropTypes = PropTypes.shape({ + accessor: PropTypes.string, + color: PropTypes.string, + label: PropTypes.string, + tooltip: PropTypes.string, + icon: PropTypes.any, + disabled: PropTypes.bool, + selected: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.shape({ + min: PropTypes.number, + max: PropTypes.number + }) + ]), + action: PropTypes.func, + isConfirmDialog: PropTypes.bool, + dialogProps: PropTypes.shape(DialogPropTypes), + options: PropTypes.arrayOf( + PropTypes.shape({ + cy: PropTypes.string, + name: PropTypes.string, + icon: PropTypes.any, + form: PropTypes.func, + onSubmit: PropTypes.func + }) + ) +}) + +ActionItem.propTypes = { + item: ActionPropTypes, + selectedRows: PropTypes.array +} + +ActionItem.displayName = 'ActionItem' + +export default ActionItem diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/index.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/index.js new file mode 100644 index 0000000000..2bf55fcd8f --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/GlobalActions/index.js @@ -0,0 +1,84 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +import { JSXElementConstructor, useMemo } from 'react' +import PropTypes from 'prop-types' +import { Row } from 'react-table' +import { makeStyles } from '@material-ui/core' + +import Action, { ActionPropTypes, GlobalAction } from 'client/components/Tables/Enhanced/Utils/GlobalActions/Action' + +const useStyles = makeStyles({ + root: { + display: 'flex', + gap: '1em', + alignItems: 'center', + flexWrap: 'wrap' + } +}) + +/** + * Render bulk actions. + * + * @param {object} props - Props + * @param {GlobalAction[]} props.globalActions - Possible bulk actions + * @param {Row[]} props.selectedRows - Selected rows + * @returns {JSXElementConstructor} Component JSX with all actions + */ +const GlobalActions = ({ globalActions, selectedRows }) => { + const classes = useStyles() + + const numberOfRowSelected = Object.keys(selectedRows)?.length + + const [actionsSelected, actionsNoSelected] = useMemo( + () => globalActions.reduce((memoResult, item) => { + const { selected = false } = item + + selected ? memoResult[0].push(item) : memoResult[1].push(item) + + return memoResult + }, [[], []]), + [globalActions] + ) + + return ( +
+ {actionsNoSelected?.map(item => ( + + ))} + {numberOfRowSelected > 0 && ( + actionsSelected?.map(item => { + const { min = 1, max = Number.MAX_SAFE_INTEGER } = item?.selected ?? {} + const key = item.accessor ?? item.label + + if (min < numberOfRowSelected && numberOfRowSelected > max) { + return null + } + + return ( + + ) + }) + )} +
+ ) +} + +GlobalActions.propTypes = { + globalActions: PropTypes.arrayOf(ActionPropTypes), + selectedRows: PropTypes.array +} + +export default GlobalActions diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js index 2a03bd71e5..5a7fddde3a 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/index.js @@ -14,6 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter' +import GlobalActions from 'client/components/Tables/Enhanced/Utils/GlobalActions' import GlobalFilter from 'client/components/Tables/Enhanced/Utils/GlobalFilter' import GlobalSelectedRows from 'client/components/Tables/Enhanced/Utils/GlobalSelectedRows' import GlobalSort from 'client/components/Tables/Enhanced/Utils/GlobalSort' @@ -23,6 +24,7 @@ export * from 'client/components/Tables/Enhanced/Utils/utils' export { CategoryFilter, + GlobalActions, GlobalFilter, GlobalSelectedRows, GlobalSort, diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js index d9a39c6b38..34d30e7521 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js @@ -20,8 +20,7 @@ import { CategoryFilter } from 'client/components/Tables/Enhanced/Utils' * Add filters defined in view yaml to columns. * * @param {object} config - - * @param {object[]} config.filters - * - List of criteria to filter the columns. + * @param {object[]} config.filters - List of criteria to filter the columns. * @param {Column[]} config.columns - Columns * @returns {object} Column with filters */ @@ -60,3 +59,36 @@ export const createCategoryFilter = title => ({ }), filter: 'includesValue' }) + +/** + * Add filters defined in view yaml to bulk actions. + * + * @param {object} params - Config parameters + * @param {object[]} params.filters - Which buttons are visible to operate over the resources + * @param {object[]} params.actions - Actions + * @returns {object} Action with filters + */ +export const createActions = ({ filters = {}, actions = [] }) => { + if (Object.keys(filters).length === 0) return actions + + return actions + .filter(({ accessor }) => + !accessor || filters[String(accessor.toLowerCase())] === true + ) + .map(action => { + const { accessor, options } = action + + if (accessor) return action + + const groupActions = options?.filter(({ cy }) => { + const [, actionName] = cy?.split('.') + + return filters[String(actionName.toLowerCase())] === true + }) + + return groupActions?.length > 0 + ? { ...action, options: groupActions } + : undefined + }) + .filter(Boolean) +} diff --git a/src/fireedge/src/client/components/Tables/Enhanced/index.js b/src/fireedge/src/client/components/Tables/Enhanced/index.js index ac0cc97b42..f864cc28fe 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/index.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/index.js @@ -42,6 +42,7 @@ import { T } from 'client/constants' const EnhancedTable = ({ canFetchMore, columns, + globalActions, data, fetchMore, getRowId, @@ -131,6 +132,7 @@ const EnhancedTable = ({ {/* TOOLBAR */} {!isFetching && ( @@ -201,6 +203,7 @@ const EnhancedTable = ({ export const EnhancedTableProps = { canFetchMore: PropTypes.bool, + globalActions: PropTypes.array, columns: PropTypes.array, data: PropTypes.array, fetchMore: PropTypes.func, diff --git a/src/fireedge/src/client/components/Tables/Enhanced/styles.js b/src/fireedge/src/client/components/Tables/Enhanced/styles.js index 1a8c0ed32c..7439bbecfd 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/styles.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/styles.js @@ -16,7 +16,7 @@ import { makeStyles } from '@material-ui/core' export default makeStyles( - ({ palette, typography, breakpoints, shadows }) => ({ + ({ palette, typography, breakpoints }) => ({ root: { height: '100%', display: 'flex', diff --git a/src/fireedge/src/client/components/Tables/Enhanced/toolbar.js b/src/fireedge/src/client/components/Tables/Enhanced/toolbar.js index 00f43cc5e2..0b51136bda 100644 --- a/src/fireedge/src/client/components/Tables/Enhanced/toolbar.js +++ b/src/fireedge/src/client/components/Tables/Enhanced/toolbar.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 { JSXElementConstructor } from 'react' import PropTypes from 'prop-types' - import { makeStyles, useMediaQuery } from '@material-ui/core' +import { UseTableInstanceProps, UseRowSelectState, UseFiltersInstanceProps } from 'react-table' -import { GlobalSelectedRows, GlobalSort } from 'client/components/Tables/Enhanced/Utils' +import { GlobalActions, GlobalSelectedRows, GlobalSort } from 'client/components/Tables/Enhanced/Utils' const useToolbarStyles = makeStyles({ root: { @@ -29,9 +29,25 @@ const useToolbarStyles = makeStyles({ } }) -const Toolbar = ({ onlyGlobalSelectedRows = false, useTableProps = {} }) => { +/** + * @param {object} props - Props + * @param {object} props.globalActions - Global actions + * @param {object} props.onlyGlobalSelectedRows - Show only the selected rows + * @param {UseTableInstanceProps} props.useTableProps - Table props + * @returns {JSXElementConstructor} Returns table toolbar + */ +const Toolbar = ({ globalActions, onlyGlobalSelectedRows, useTableProps }) => { const classes = useToolbarStyles() - const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm')) + const isMobile = useMediaQuery(theme => theme.breakpoints.down('xs')) + const isSmallDevice = useMediaQuery(theme => theme.breakpoints.down('sm')) + + /** @type {UseRowSelectState} */ + const { selectedRowIds } = useTableProps?.state ?? {} + + /** @type {UseFiltersInstanceProps} */ + const { preFilteredRows } = useTableProps ?? {} + + const selectedRows = preFilteredRows.filter(row => !!selectedRowIds[row.id]) if (onlyGlobalSelectedRows) { return @@ -39,12 +55,16 @@ const Toolbar = ({ onlyGlobalSelectedRows = false, useTableProps = {} }) => { return isMobile ? null : (
- + {globalActions?.length > 0 && ( + + )} + {!isSmallDevice && }
) } Toolbar.propTypes = { + globalActions: PropTypes.array, onlyGlobalSelectedRows: PropTypes.bool, useTableProps: PropTypes.object } diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js new file mode 100644 index 0000000000..d5b9b73cde --- /dev/null +++ b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js @@ -0,0 +1,230 @@ +/* ------------------------------------------------------------------------- * + * 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 { useMemo } from 'react' +import { useHistory } from 'react-router-dom' +import { + RefreshDouble, + AddSquare, + Import, + Trash, + Lock, + NoLock, + UserSquareAlt, + Group, + ShareAndroid, + Undo, + Cart +} from 'iconoir-react' + +import { useAuth } from 'client/features/Auth' +import { useVmTemplateApi } from 'client/features/One' + +import { createActions } from 'client/components/Tables/Enhanced/Utils' +import { PATH } from 'client/apps/sunstone/routesOne' +import { VM_TEMPLATE_ACTIONS, MARKETPLACE_APP_ACTIONS } from 'client/constants' + +const Actions = () => { + const history = useHistory() + const { view, getResourceView } = useAuth() + const { getVmTemplate, getVmTemplates, lock, unlock, remove } = useVmTemplateApi() + + const vmTemplateActions = useMemo(() => createActions({ + filters: getResourceView('VM-TEMPLATE')?.actions, + actions: [ + { + accessor: VM_TEMPLATE_ACTIONS.REFRESH, + tooltip: 'Refresh', + icon: RefreshDouble, + action: async () => { + await getVmTemplates() + } + }, + { + accessor: VM_TEMPLATE_ACTIONS.CREATE_DIALOG, + tooltip: 'Create', + icon: AddSquare, + disabled: true, + action: rows => { + // TODO: go to CREATE form + // const { ID } = rows?.[0]?.original ?? {} + // const path = generatePath(PATH.TEMPLATE.VMS.CREATE, { id: ID }) + + // history.push(path) + } + }, + { + accessor: VM_TEMPLATE_ACTIONS.IMPORT_DIALOG, + tooltip: 'Import', + icon: Import, + selected: { max: 1 }, + disabled: true, + action: rows => { + // TODO: go to IMPORT form + } + }, + { + accessor: VM_TEMPLATE_ACTIONS.UPDATE_DIALOG, + label: 'Update', + tooltip: 'Update', + selected: { max: 1 }, + disabled: true, + action: rows => { + // const { ID } = rows?.[0]?.original ?? {} + // const path = generatePath(PATH.TEMPLATE.VMS.CREATE, { id: ID }) + + // history.push(path) + } + }, + { + accessor: VM_TEMPLATE_ACTIONS.INSTANTIATE_DIALOG, + label: 'Instantiate', + tooltip: 'Instantiate', + selected: { max: 1 }, + action: rows => { + const template = rows?.[0]?.original ?? {} + const path = PATH.TEMPLATE.VMS.INSTANTIATE + + history.push(path, template) + } + }, + { + accessor: VM_TEMPLATE_ACTIONS.CLONE, + label: 'Clone', + tooltip: 'Clone', + selected: true, + disabled: true, + options: [{ onSubmit: () => undefined }] + }, + { + tooltip: 'Change ownership', + label: 'Ownership', + selected: true, + disabled: true, + options: [{ + cy: `action.${VM_TEMPLATE_ACTIONS.CHANGE_OWNER}`, + icon: UserSquareAlt, + name: 'Change owner', + isConfirmDialog: true, + onSubmit: () => undefined + }, { + cy: `action.${VM_TEMPLATE_ACTIONS.CHANGE_GROUP}`, + icon: Group, + name: 'Change group', + isConfirmDialog: true, + onSubmit: () => undefined + }, { + cy: `action.${VM_TEMPLATE_ACTIONS.SHARE}`, + icon: ShareAndroid, + name: 'Share', + isConfirmDialog: true, + onSubmit: () => undefined + }, { + cy: `action.${VM_TEMPLATE_ACTIONS.UNSHARE}`, + icon: Undo, + name: 'Unshare', + isConfirmDialog: true, + onSubmit: () => undefined + }] + }, + { + accessor: VM_TEMPLATE_ACTIONS.LOCK, + tooltip: 'Lock', + label: 'Lock', + icon: Lock, + selected: true, + dialogProps: { + title: 'Lock', + children: rows => { + const templates = rows?.map?.(({ original }) => original?.NAME) + return 'Lock: ' + templates.join(', ') + } + }, + options: [{ + isConfirmDialog: true, + onSubmit: async (_, rows) => { + const templateIds = rows?.map?.(({ original }) => original?.ID) + await Promise.all([...new Array(templateIds)].map(id => lock(id))) + await Promise.all(templateIds.map(id => getVmTemplate(id))) + } + }] + }, + { + accessor: VM_TEMPLATE_ACTIONS.UNLOCK, + tooltip: 'Unlock', + label: 'Unlock', + icon: NoLock, + selected: true, + dialogProps: { + title: 'Unlock', + children: rows => { + const templates = rows?.map?.(({ original }) => original?.NAME) + return 'Unlock: ' + templates.join(', ') + } + }, + options: [{ + isConfirmDialog: true, + onSubmit: async (_, rows) => { + const templateIds = [...new Array(rows?.map?.(({ original }) => original?.ID))] + await Promise.all(templateIds.map(id => unlock(id))) + await Promise.all(templateIds.map(id => getVmTemplate(id))) + } + }] + }, + { + accessor: VM_TEMPLATE_ACTIONS.DELETE, + tooltip: 'Delete', + icon: Trash, + selected: true, + dialogProps: { + title: 'Delete', + children: rows => { + const templates = rows?.map?.(({ original }) => original?.NAME) + return 'Delete: ' + templates.join(', ') + } + }, + options: [{ + isConfirmDialog: true, + onSubmit: async (_, rows) => { + const templateIds = [...new Array(rows?.map?.(({ original }) => original?.ID))] + await Promise.all(templateIds.map(id => remove(id))) + await getVmTemplates() + } + }] + } + ] + }), [view]) + + const marketplaceAppActions = useMemo(() => createActions({ + filters: getResourceView('MARKETPLACE-APP')?.actions, + actions: [ + { + accessor: MARKETPLACE_APP_ACTIONS.CREATE_DIALOG, + tooltip: 'Create Marketplace App', + icon: Cart, + selected: { max: 1 }, + disabled: true, + action: rows => { + // TODO: go to Marketplace App CREATE form + } + } + ] + }), [view]) + + return [...vmTemplateActions, ...marketplaceAppActions] +} + +export default Actions diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/detail.js b/src/fireedge/src/client/components/Tables/VmTemplates/detail.js deleted file mode 100644 index 8a28881074..0000000000 --- a/src/fireedge/src/client/components/Tables/VmTemplates/detail.js +++ /dev/null @@ -1,86 +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 { useEffect } from 'react' -import PropTypes from 'prop-types' -import { LinearProgress } from '@material-ui/core' - -import Tabs from 'client/components/Tabs' - -import { useFetch } from 'client/hooks' -import { useVmTemplateApi } from 'client/features/One' - -import * as Helper from 'client/models/Helper' - -const VmTemplateDetail = ({ id }) => { - const { getVmTemplate } = useVmTemplateApi() - const { data, fetchRequest, loading, error } = useFetch(getVmTemplate) - - useEffect(() => { - fetchRequest(id) - }, [id]) - - if ((!data && !error) || loading) { - return - } - - if (error) { - return
{error}
- } - - const { ID, NAME, UNAME, GNAME, REGTIME, LOCK, TEMPLATE } = data - - const tabs = [ - { - name: 'info', - renderContent: ( -
- - {`#${ID} - ${NAME}`} - -
-

Owner: {UNAME}

-

Group: {GNAME}

-

Locked: {Helper.levelLockToString(LOCK?.LOCKED)}

-

Register time: {Helper.timeToString(REGTIME)}

-
-
- ) - }, - { - name: 'template', - renderContent: ( -
-
-            
-              {JSON.stringify(TEMPLATE, null, 2)}
-            
-          
-
- ) - } - ] - - return ( - - ) -} - -VmTemplateDetail.propTypes = { - id: PropTypes.string.isRequired -} - -export default VmTemplateDetail diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/index.js b/src/fireedge/src/client/components/Tables/VmTemplates/index.js index be036ad7ee..0a24a19398 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/index.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/index.js @@ -16,15 +16,22 @@ /* eslint-disable jsdoc/require-jsdoc */ import { useMemo, useEffect } from 'react' +import { useAuth } from 'client/features/Auth' import { useFetch } from 'client/hooks' import { useVmTemplate, useVmTemplateApi } from 'client/features/One' import { SkeletonTable, EnhancedTable } from 'client/components/Tables' +import { createColumns } from 'client/components/Tables/Enhanced/Utils' import VmTemplateColumns from 'client/components/Tables/VmTemplates/columns' import VmTemplateRow from 'client/components/Tables/VmTemplates/row' const VmTemplatesTable = props => { - const columns = useMemo(() => VmTemplateColumns, []) + const { view, getResourceView, filterPool } = useAuth() + + const columns = useMemo(() => createColumns({ + filters: getResourceView('VM-TEMPLATE')?.filters, + columns: VmTemplateColumns + }), [view]) const vmTemplates = useVmTemplate() const { getVmTemplates } = useVmTemplateApi() @@ -32,7 +39,7 @@ const VmTemplatesTable = props => { const { status, fetchRequest, loading, reloading, STATUS } = useFetch(getVmTemplates) const { INIT, PENDING } = STATUS - useEffect(() => { fetchRequest() }, []) + useEffect(() => { fetchRequest() }, [filterPool]) if (vmTemplates?.length === 0 && [INIT, PENDING].includes(status)) { return diff --git a/src/fireedge/src/client/components/Tabs/Vm/SchedActions/Actions.js b/src/fireedge/src/client/components/Tabs/Vm/SchedActions/Actions.js index 47d0029062..faa77543ac 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/SchedActions/Actions.js +++ b/src/fireedge/src/client/components/Tabs/Vm/SchedActions/Actions.js @@ -117,7 +117,6 @@ const DeleteSchedAction = memo(({ schedule, name }) => { return ( , @@ -127,7 +126,10 @@ const DeleteSchedAction = memo(({ schedule, name }) => { title: `${Tr(T.Delete)} ${Tr(T.ScheduledAction)}: ${name}`, children:

{Tr(T.DoYouWantProceed)}

}} - options={[{ onSubmit: handleDelete }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleDelete + }]} /> ) }) @@ -159,7 +161,6 @@ const CharterAction = memo(() => { return ( , @@ -194,7 +195,10 @@ const CharterAction = memo(() => { ) }} - options={[{ onSubmit: handleCreateCharter }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleCreateCharter + }]} /> ) }) diff --git a/src/fireedge/src/client/components/Tabs/Vm/Snapshot/Actions.js b/src/fireedge/src/client/components/Tabs/Vm/Snapshot/Actions.js index 84ef0eebc0..3e007a530e 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Snapshot/Actions.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Snapshot/Actions.js @@ -35,7 +35,6 @@ const RevertAction = memo(({ snapshot }) => { return ( @@ -44,7 +43,10 @@ const RevertAction = memo(({ snapshot }) => { title: `${Tr(T.Revert)}: #${SNAPSHOT_ID} - ${NAME}`, children:

{Tr(T.DoYouWantProceed)}

}} - options={[{ onSubmit: handleRevert }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleRevert + }]} /> ) }) @@ -58,7 +60,6 @@ const DeleteAction = memo(({ snapshot }) => { return ( @@ -67,7 +68,10 @@ const DeleteAction = memo(({ snapshot }) => { title: `${Tr(T.Delete)}: #${SNAPSHOT_ID} - ${NAME}`, children:

{Tr(T.DoYouWantProceed)}

}} - options={[{ onSubmit: handleDelete }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleDelete + }]} /> ) }) 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 2a69a5cd4a..b531a87bbb 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Storage/Actions.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Storage/Actions.js @@ -36,7 +36,6 @@ const DetachAction = memo(({ disk, name: imageName }) => { return ( , @@ -46,7 +45,10 @@ const DetachAction = memo(({ disk, name: imageName }) => { title: `${Tr(T.Detach)}: #${DISK_ID} - ${imageName}`, children:

{Tr(T.DoYouWantProceed)}

}} - options={[{ onSubmit: handleDetach }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleDetach + }]} /> ) }) @@ -185,7 +187,6 @@ const SnapshotRevertAction = memo(({ disk, snapshot }) => { return ( , @@ -195,7 +196,10 @@ const SnapshotRevertAction = memo(({ disk, snapshot }) => { title: `${Tr(T.Revert)}: #${ID} - ${NAME}`, children:

{Tr(T.DoYouWantProceed)}

}} - options={[{ onSubmit: handleRevert }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleRevert + }]} /> ) }) @@ -213,7 +217,6 @@ const SnapshotDeleteAction = memo(({ disk, snapshot }) => { return ( , @@ -223,7 +226,10 @@ const SnapshotDeleteAction = memo(({ disk, snapshot }) => { title: `${Tr(T.Delete)}: #${ID} - ${NAME}`, children:

{Tr(T.DoYouWantProceed)}

}} - options={[{ onSubmit: handleDelete }]} + options={[{ + isConfirmDialog: true, + onSubmit: handleDelete + }]} /> ) }) diff --git a/src/fireedge/src/client/constants/index.js b/src/fireedge/src/client/constants/index.js index c154056152..bd6c3c7a2e 100644 --- a/src/fireedge/src/client/constants/index.js +++ b/src/fireedge/src/client/constants/index.js @@ -89,9 +89,11 @@ export * from 'client/constants/flow' export * from 'client/constants/provision' export * from 'client/constants/cluster' export * from 'client/constants/vm' +export * from 'client/constants/vmTemplate' export * from 'client/constants/host' export * from 'client/constants/image' export * from 'client/constants/marketplace' +export * from 'client/constants/marketplaceApp' export * from 'client/constants/datastore' export * from 'client/constants/securityGroup' export * from 'client/constants/zone' diff --git a/src/fireedge/src/client/constants/marketplaceApp.js b/src/fireedge/src/client/constants/marketplaceApp.js new file mode 100644 index 0000000000..5515fc8516 --- /dev/null +++ b/src/fireedge/src/client/constants/marketplaceApp.js @@ -0,0 +1,19 @@ +/* ------------------------------------------------------------------------- * + * 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. * + * ------------------------------------------------------------------------- */ +export const MARKETPLACE_APP_ACTIONS = { + REFRESH: 'refresh', + CREATE_DIALOG: 'create_dialog' +} diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 94e4a77db5..49059e34f5 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -52,6 +52,7 @@ module.exports = { SaveAs: 'Save as', Search: 'Search', Select: 'Select', + SelectVmTemplate: 'Select a VM Template', SelectGroup: 'Select a group', SelectRequest: 'Select request', Show: 'Show', diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/index.js b/src/fireedge/src/client/constants/vmTemplate.js similarity index 61% rename from src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/index.js rename to src/fireedge/src/client/constants/vmTemplate.js index 5774cf0687..69be7a61d8 100644 --- a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/Steps/index.js +++ b/src/fireedge/src/client/constants/vmTemplate.js @@ -13,31 +13,22 @@ * 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 * as ACTIONS from 'client/constants/actions' -import Template from './Template' -import BasicConfiguration from './BasicConfiguration' -import Connection from './Connection' +export const VM_TEMPLATE_ACTIONS = { + REFRESH: 'refresh', + CREATE_DIALOG: 'create_dialog', + IMPORT_DIALOG: 'import_dialog', + UPDATE_DIALOG: 'update_dialog', + INSTANTIATE_DIALOG: 'instantiate_dialog', + CLONE: 'clone', + DELETE: 'delete', + LOCK: 'lock', + UNLOCK: 'unlock', + SHARE: 'share', + UNSHARE: 'unshare', -const Steps = ({ isUpdate }) => { - const template = Template() - const configuration = BasicConfiguration({ isUpdate }) - const connection = Connection({ isUpdate }) - - const steps = [configuration, connection] - !isUpdate && steps.unshift(template) - - const resolvers = () => yup - .object({ - [template.id]: template.resolver(), - [configuration.id]: configuration.resolver(), - [connection.id]: connection.resolver() - }) - - const defaultValues = resolvers().default() - - return { steps, defaultValues, resolvers } + RENAME: ACTIONS.RENAME, + CHANGE_OWNER: ACTIONS.CHANGE_OWNER, + CHANGE_GROUP: ACTIONS.CHANGE_GROUP } - -export default Steps diff --git a/src/fireedge/src/client/containers/Providers/Form/Create.js b/src/fireedge/src/client/containers/Providers/Create.js similarity index 60% rename from src/fireedge/src/client/containers/Providers/Form/Create.js rename to src/fireedge/src/client/containers/Providers/Create.js index 84734b6199..56210fbf0d 100644 --- a/src/fireedge/src/client/containers/Providers/Form/Create.js +++ b/src/fireedge/src/client/containers/Providers/Create.js @@ -15,25 +15,58 @@ * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ import { useEffect, useState } from 'react' -import { Redirect, useParams } from 'react-router' +import { Redirect, useParams, useHistory } from 'react-router' import { Container, LinearProgress } from '@material-ui/core' -import { useAuth } from 'client/features/Auth' import { useFetchAll } from 'client/hooks' +import { useAuth } from 'client/features/Auth' +import { useGeneralApi } from 'client/features/General' import { useProviderApi } from 'client/features/One' -import ProviderForm from 'client/containers/Providers/Form/ProviderForm' -import { getConnectionEditable } from 'client/models/ProviderTemplate' +import { CreateForm } from 'client/components/Forms/Provider' +import { isValidProviderTemplate, getConnectionEditable, getConnectionFixed } from 'client/models/ProviderTemplate' import { PATH } from 'client/apps/provision/routes' +import { isDevelopment, deepmerge } from 'client/utils' function ProviderCreateForm () { - const { id } = useParams() const [initialValues, setInitialValues] = useState(null) + const history = useHistory() + const { id } = useParams() const { providerConfig } = useAuth() - const { getProvider, getProviderConnection } = useProviderApi() + const { enqueueSuccess, enqueueError } = useGeneralApi() + const { getProvider, getProviderConnection, createProvider, updateProvider } = useProviderApi() const { data: preloadedData, fetchRequestAll, loading, error } = useFetchAll() + const onSubmit = async formData => { + try { + if (id !== undefined) { + const [provider = {}, connection = []] = preloadedData ?? [] + const providerId = provider?.ID + + const formatData = deepmerge({ connection }, formData) + + await updateProvider(id, formatData) + enqueueSuccess(`Provider updated - ID: ${providerId}`) + } else { + if (!isValidProviderTemplate(formData, providerConfig)) { + enqueueError('The template selected has a bad format. Ask your cloud administrator') + history.push(PATH.PROVIDERS.LIST) + } + + const connectionFixed = getConnectionFixed(formData, providerConfig) + const formatData = deepmerge(formData, { connection: connectionFixed }) + + const responseId = await createProvider(formatData) + enqueueSuccess(`Provider created - ID: ${responseId}`) + } + + history.push(PATH.PROVIDERS.LIST) + } catch (err) { + isDevelopment() && console.error(err) + } + } + useEffect(() => { const preloadFetchData = async () => { const data = await fetchRequestAll([ @@ -46,7 +79,8 @@ function ProviderCreateForm () { const { PLAIN: { provider: plainProvider } = {}, - PROVISION_BODY: { description, ...currentBodyTemplate } + // remove encrypted connection from body template + PROVISION_BODY: { description, connection: _, ...currentBodyTemplate } } = provider?.TEMPLATE const connectionEditable = getConnectionEditable( @@ -73,7 +107,11 @@ function ProviderCreateForm () { ) : ( - + ) } diff --git a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/index.js b/src/fireedge/src/client/containers/Providers/Form/ProviderForm/index.js deleted file mode 100644 index 499299f196..0000000000 --- a/src/fireedge/src/client/containers/Providers/Form/ProviderForm/index.js +++ /dev/null @@ -1,116 +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 { useHistory } from 'react-router' - -import { useForm, FormProvider } from 'react-hook-form' -import { yupResolver } from '@hookform/resolvers/yup' - -import FormStepper from 'client/components/FormStepper' -import Steps from 'client/containers/Providers/Form/ProviderForm/Steps' - -import { useProviderApi } from 'client/features/One' -import { useGeneralApi } from 'client/features/General' -import { isValidProviderTemplate, getConnectionFixed } from 'client/models/ProviderTemplate' -import { PATH } from 'client/apps/provision/routes' -import { useAuth } from 'client/features/Auth' - -const ProviderForm = ({ id, preloadedData, initialValues }) => { - const history = useHistory() - const isUpdate = id !== undefined - - const { providerConfig } = useAuth() - const { createProvider, updateProvider } = useProviderApi() - const { enqueueError, enqueueSuccess, changeLoading } = useGeneralApi() - - const { steps, defaultValues, resolvers } = Steps({ isUpdate }) - - const methods = useForm({ - mode: 'onSubmit', - defaultValues: initialValues ?? defaultValues, - resolver: yupResolver(resolvers()) - }) - - const redirectWithError = (message = 'Error') => { - enqueueError(message) - history.push(PATH.PROVIDERS.LIST) - } - - const callCreateProvider = async formData => { - const { template, configuration, connection } = formData - - const templateSelected = template?.[0] - - const isValid = isValidProviderTemplate(templateSelected, providerConfig) - - !isValid && redirectWithError(` - The template selected has a bad format. - Ask your cloud administrator` - ) - - const { name, description } = configuration - const connectionFixed = getConnectionFixed(templateSelected, providerConfig) - - const formatData = { - ...templateSelected, - connection: { ...connection, ...connectionFixed }, - description, - name - } - - createProvider(formatData) - .then(id => enqueueSuccess(`Provider created - ID: ${id}`)) - .then(() => history.push(PATH.PROVIDERS.LIST)) - } - - const callUpdateProvider = formData => { - const { configuration, connection: connectionEditable } = formData - const { description } = configuration - const [provider = {}, connection = []] = preloadedData - - const { PROVISION_BODY: currentBodyTemplate } = provider?.TEMPLATE - - const formatData = { - ...currentBodyTemplate, - description, - connection: { ...connection, ...connectionEditable } - } - - updateProvider(id, formatData) - .then(() => enqueueSuccess(`Provider updated - ID: ${id}`)) - .then(() => history.push(PATH.PROVIDERS.LIST)) - } - - const onSubmit = formData => { - changeLoading(true) - isUpdate ? callUpdateProvider(formData) : callCreateProvider(formData) - } - - return ( - - - - ) -} - -ProviderForm.propTypes = { - id: PropTypes.string, - preloadedData: PropTypes.object, - initialValues: PropTypes.object -} - -export default ProviderForm diff --git a/src/fireedge/src/client/containers/Provisions/Form/Create.js b/src/fireedge/src/client/containers/Provisions/Create.js similarity index 82% rename from src/fireedge/src/client/containers/Provisions/Form/Create.js rename to src/fireedge/src/client/containers/Provisions/Create.js index c43f3e788e..af1f2ba06b 100644 --- a/src/fireedge/src/client/containers/Provisions/Form/Create.js +++ b/src/fireedge/src/client/containers/Provisions/Create.js @@ -21,11 +21,13 @@ import { NavArrowLeft as ArrowBackIcon } from 'iconoir-react' import { makeStyles, Container, LinearProgress, IconButton, Typography } from '@material-ui/core' import { useFetch, useSocket } from 'client/hooks' -import { useProviderApi } from 'client/features/One' +import { useGeneralApi } from 'client/features/General' +import { useProviderApi, useProvisionApi } from 'client/features/One' import DebugLog from 'client/components/DebugLog' -import ProvisionForm from 'client/containers/Provisions/Form/ProvisionForm' +import { CreateForm } from 'client/components/Forms/Provision' import { PATH } from 'client/apps/provision/routes' import { Translate } from 'client/components/HOC' +import { isDevelopment } from 'client/utils' import { T } from 'client/constants' const useStyles = makeStyles({ @@ -46,12 +48,25 @@ function ProvisionCreateForm () { const [uuid, setUuid] = useState(undefined) const { getProvisionSocket: socket } = useSocket() + const { enqueueInfo } = useGeneralApi() + const { createProvision } = useProvisionApi() const { getProviders } = useProviderApi() const { data, fetchRequest, loading, error } = useFetch(getProviders) - const handleSetUuid = response => response && setUuid(response) + const onSubmit = async formData => { + try { + const response = await createProvision(formData) + enqueueInfo('Creating provision') - useEffect(() => { fetchRequest() }, []) + response && setUuid(response) + } catch (err) { + isDevelopment() && console.error(err) + } + } + + useEffect(() => { + fetchRequest() + }, []) if (uuid) { return }} /> @@ -65,7 +80,7 @@ function ProvisionCreateForm () { ) : ( - + ) } diff --git a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/index.js b/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/index.js deleted file mode 100644 index c84b5c26ea..0000000000 --- a/src/fireedge/src/client/containers/Provisions/Form/ProvisionForm/index.js +++ /dev/null @@ -1,85 +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 { useForm, FormProvider } from 'react-hook-form' -import { yupResolver } from '@hookform/resolvers/yup' - -import FormStepper from 'client/components/FormStepper' -import Steps from 'client/containers/Provisions/Form/ProvisionForm/Steps' - -import { useProvisionApi } from 'client/features/One' -import { useGeneralApi } from 'client/features/General' -import { set, cloneObject, mapUserInputs } from 'client/utils' - -const ProvisionForm = ({ handleAfterCreate }) => { - const { createProvision } = useProvisionApi() - const { enqueueInfo } = useGeneralApi() - - const { steps, defaultValues, resolvers } = Steps() - - const methods = useForm({ - mode: 'onSubmit', - defaultValues, - resolver: yupResolver(resolvers()) - }) - - const onSubmit = async formData => { - const { template, provider, configuration, inputs } = formData - const { name, description } = configuration - const providerName = provider?.[0]?.NAME - - // clone object from redux store - const provisionTemplateSelected = cloneObject(template?.[0] ?? {}) - - // update provider name if changed during form - if (provisionTemplateSelected.defaults?.provision?.provider_name) { - set(provisionTemplateSelected, 'defaults.provision.provider_name', providerName) - } else if (provisionTemplateSelected.hosts?.length > 0) { - provisionTemplateSelected.hosts.forEach(host => { - set(host, 'provision.provider_name', providerName) - }) - } - - const parseInputs = mapUserInputs(inputs) - - const formatData = { - ...provisionTemplateSelected, - name, - description, - inputs: provisionTemplateSelected?.inputs - ?.map(input => ({ ...input, value: `${parseInputs[input?.name]}` })) - } - - const response = await createProvision(formatData) - enqueueInfo('Creating provision') - - handleAfterCreate?.(response) - } - - return ( - - - - ) -} - -ProvisionForm.propTypes = { - handleAfterCreate: PropTypes.func -} - -export default ProvisionForm diff --git a/src/fireedge/src/client/containers/VmTemplates/Instantiate.js b/src/fireedge/src/client/containers/VmTemplates/Instantiate.js index 29820c6aad..91edc687a7 100644 --- a/src/fireedge/src/client/containers/VmTemplates/Instantiate.js +++ b/src/fireedge/src/client/containers/VmTemplates/Instantiate.js @@ -14,24 +14,21 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ /* eslint-disable jsdoc/require-jsdoc */ -import { useEffect } from 'react' -import { useHistory, useParams } from 'react-router' +import { useHistory, useLocation } from 'react-router' import { Container } from '@material-ui/core' import { useGeneralApi } from 'client/features/General' -import { useVmTemplateApi, useUserApi, useVmGroupApi } from 'client/features/One' +import { useVmTemplateApi } from 'client/features/One' import { InstantiateForm } from 'client/components/Forms/VmTemplate' import { PATH } from 'client/apps/sunstone/routesOne' import { isDevelopment } from 'client/utils' function InstantiateVmTemplate () { const history = useHistory() - const { templateId } = useParams() - const initialValues = { template: { ID: templateId } } + const { state } = useLocation() + const { ID: templateId } = state ?? {} const { enqueueInfo } = useGeneralApi() - const { getUsers } = useUserApi() - const { getVmGroups } = useVmGroupApi() const { instantiate } = useVmTemplateApi() const onSubmit = async ([templateSelected, templates]) => { @@ -47,14 +44,9 @@ function InstantiateVmTemplate () { } } - useEffect(() => { - getUsers() - getVmGroups() - }, []) - return ( - + ) } diff --git a/src/fireedge/src/client/containers/VmTemplates/index.js b/src/fireedge/src/client/containers/VmTemplates/index.js index 5428267d00..25a6a65548 100644 --- a/src/fireedge/src/client/containers/VmTemplates/index.js +++ b/src/fireedge/src/client/containers/VmTemplates/index.js @@ -19,11 +19,13 @@ import { useState } from 'react' import { Container, Box } from '@material-ui/core' import { VmTemplatesTable } from 'client/components/Tables' +import VmTemplateActions from 'client/components/Tables/VmTemplates/actions' import VmTemplateTabs from 'client/components/Tabs/VmTemplate' import SplitPane from 'client/components/SplitPane' function VmTemplates () { const [selectedRows, onSelectedRowsChange] = useState([]) + const actions = VmTemplateActions() const getRowIds = () => JSON.stringify(selectedRows?.map(row => row.id).join(', '), null, 2) @@ -38,7 +40,10 @@ function VmTemplates () { component={Container} > - + {selectedRows?.length > 0 && (
diff --git a/src/fireedge/src/client/features/One/application/actions.js b/src/fireedge/src/client/features/One/application/actions.js index 0e904a16b9..9974d4256b 100644 --- a/src/fireedge/src/client/features/One/application/actions.js +++ b/src/fireedge/src/client/features/One/application/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { applicationService } from 'client/features/One/application/services' import { RESOURCES } from 'client/features/One/slice' -export const getApplication = createAction('cluster', applicationService.getApplication) +/** @see {@link RESOURCES.document} */ +const SERVICE_APPLICATION = 'document[100]' + +export const getApplication = createAction( + `${SERVICE_APPLICATION}/detail`, + applicationService.getApplication +) export const getApplications = createAction( - 'application/pool', + `${SERVICE_APPLICATION}/pool`, applicationService.getApplications, response => ({ [RESOURCES.document[100]]: response }) ) diff --git a/src/fireedge/src/client/features/One/application/hooks.js b/src/fireedge/src/client/features/One/application/hooks.js index e061cbf22e..56382b4762 100644 --- a/src/fireedge/src/client/features/One/application/hooks.js +++ b/src/fireedge/src/client/features/One/application/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/application/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useApplication = () => ( - useSelector(state => state.one[RESOURCES.document[100]]) + useSelector(state => state[name]?.[RESOURCES.document[100]] ?? []) ) export const useApplicationApi = () => { diff --git a/src/fireedge/src/client/features/One/applicationTemplate/actions.js b/src/fireedge/src/client/features/One/applicationTemplate/actions.js index 3240dca9f7..64ba9c44ed 100644 --- a/src/fireedge/src/client/features/One/applicationTemplate/actions.js +++ b/src/fireedge/src/client/features/One/applicationTemplate/actions.js @@ -17,28 +17,31 @@ import { createAction } from 'client/features/One/utils' import { applicationTemplateService } from 'client/features/One/applicationTemplate/services' import { RESOURCES } from 'client/features/One/slice' +/** @see {@link RESOURCES.document} */ +const SERVICE_TEMPLATE = 'document[101]' + export const getApplicationTemplate = createAction( - 'application-template', + `${SERVICE_TEMPLATE}/detail`, applicationTemplateService.getApplicationTemplate ) export const getApplicationsTemplates = createAction( - 'application-template/pool', + `${SERVICE_TEMPLATE}/pool`, applicationTemplateService.getApplicationsTemplates, response => ({ [RESOURCES.document[101]]: response }) ) export const createApplicationTemplate = createAction( - 'application-template/create', + `${SERVICE_TEMPLATE}/create`, applicationTemplateService.createApplicationTemplate ) export const updateApplicationTemplate = createAction( - 'application-template/update', + `${SERVICE_TEMPLATE}/update`, applicationTemplateService.updateApplicationTemplate ) export const instantiateApplicationTemplate = createAction( - 'application-template/instantiate', + `${SERVICE_TEMPLATE}/instantiate`, applicationTemplateService.instantiateApplicationTemplate ) diff --git a/src/fireedge/src/client/features/One/applicationTemplate/hooks.js b/src/fireedge/src/client/features/One/applicationTemplate/hooks.js index ddb820a103..6757fe59b6 100644 --- a/src/fireedge/src/client/features/One/applicationTemplate/hooks.js +++ b/src/fireedge/src/client/features/One/applicationTemplate/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/applicationTemplate/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useApplicationTemplate = () => ( - useSelector(state => state.one[RESOURCES.document[101]]) + useSelector(state => state[name]?.[RESOURCES.document[101]] ?? []) ) export const useApplicationTemplateApi = () => { diff --git a/src/fireedge/src/client/features/One/cluster/actions.js b/src/fireedge/src/client/features/One/cluster/actions.js index 69b1b9bdeb..9eb250e328 100644 --- a/src/fireedge/src/client/features/One/cluster/actions.js +++ b/src/fireedge/src/client/features/One/cluster/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { clusterService } from 'client/features/One/cluster/services' import { RESOURCES } from 'client/features/One/slice' -export const getCluster = createAction('cluster', clusterService.getCluster) +/** @see {@link RESOURCES.cluster} */ +const CLUSTER = 'cluster' + +export const getCluster = createAction( + `${CLUSTER}/detail`, + clusterService.getCluster +) export const getClusters = createAction( - 'cluster/pool', + `${CLUSTER}/pool`, clusterService.getClusters, response => ({ [RESOURCES.cluster]: response }) ) diff --git a/src/fireedge/src/client/features/One/cluster/hooks.js b/src/fireedge/src/client/features/One/cluster/hooks.js index 03f357bef8..1ac29b8654 100644 --- a/src/fireedge/src/client/features/One/cluster/hooks.js +++ b/src/fireedge/src/client/features/One/cluster/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/cluster/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useCluster = () => ( - useSelector(state => state.one[RESOURCES.cluster]) + useSelector(state => state[name]?.[RESOURCES.cluster] ?? []) ) export const useClusterApi = () => { diff --git a/src/fireedge/src/client/features/One/datastore/actions.js b/src/fireedge/src/client/features/One/datastore/actions.js index 51c2f47d46..165befbfcc 100644 --- a/src/fireedge/src/client/features/One/datastore/actions.js +++ b/src/fireedge/src/client/features/One/datastore/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { datastoreService } from 'client/features/One/datastore/services' import { RESOURCES } from 'client/features/One/slice' -export const getDatastore = createAction('datastore', datastoreService.getDatastore) +/** @see {@link RESOURCES.datastore} */ +const DATASTORE = 'datastore' + +export const getDatastore = createAction( + `${DATASTORE}/detail`, + datastoreService.getDatastore +) export const getDatastores = createAction( - 'datastore/pool', + `${DATASTORE}/pool`, datastoreService.getDatastores, response => ({ [RESOURCES.datastore]: response }) ) diff --git a/src/fireedge/src/client/features/One/datastore/hooks.js b/src/fireedge/src/client/features/One/datastore/hooks.js index d4b26cf8d8..84dbc4d393 100644 --- a/src/fireedge/src/client/features/One/datastore/hooks.js +++ b/src/fireedge/src/client/features/One/datastore/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/datastore/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useDatastore = () => ( - useSelector(state => state.one[RESOURCES.datastore]) + useSelector(state => state[name]?.[RESOURCES.datastore] ?? []) ) export const useDatastoreApi = () => { diff --git a/src/fireedge/src/client/features/One/group/actions.js b/src/fireedge/src/client/features/One/group/actions.js index 5592515e17..3d07d83591 100644 --- a/src/fireedge/src/client/features/One/group/actions.js +++ b/src/fireedge/src/client/features/One/group/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { groupService } from 'client/features/One/group/services' import { RESOURCES } from 'client/features/One/slice' -export const getGroup = createAction('group', groupService.getGroup) +/** @see {@link RESOURCES.group} */ +const GROUP = 'group' + +export const getGroup = createAction( + `${GROUP}/detail`, + groupService.getGroup +) export const getGroups = createAction( - 'group/pool', + `${GROUP}/pool`, groupService.getGroups, response => ({ [RESOURCES.group]: response }) ) diff --git a/src/fireedge/src/client/features/One/group/hooks.js b/src/fireedge/src/client/features/One/group/hooks.js index afacb95d61..5d2caf1590 100644 --- a/src/fireedge/src/client/features/One/group/hooks.js +++ b/src/fireedge/src/client/features/One/group/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/group/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useGroup = () => ( - useSelector(state => state.one[RESOURCES.group]) + useSelector(state => state[name]?.[RESOURCES.group] ?? []) ) export const useGroupApi = () => { diff --git a/src/fireedge/src/client/features/One/host/actions.js b/src/fireedge/src/client/features/One/host/actions.js index e1287d71cc..ac34b9d73c 100644 --- a/src/fireedge/src/client/features/One/host/actions.js +++ b/src/fireedge/src/client/features/One/host/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { hostService } from 'client/features/One/host/services' import { RESOURCES } from 'client/features/One/slice' -export const getHost = createAction('host', hostService.getHost) +/** @see {@link RESOURCES.host} */ +const HOST = 'host' + +export const getHost = createAction( + `${HOST}/detail`, + hostService.getHost +) export const getHosts = createAction( - 'host/pool', + `${HOST}/pool`, hostService.getHosts, response => ({ [RESOURCES.host]: response }) ) diff --git a/src/fireedge/src/client/features/One/host/hooks.js b/src/fireedge/src/client/features/One/host/hooks.js index 351ffbcd87..435e6d926f 100644 --- a/src/fireedge/src/client/features/One/host/hooks.js +++ b/src/fireedge/src/client/features/One/host/hooks.js @@ -19,9 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/host/actions' +import { name, RESOURCES } from 'client/features/One/slice' export const useHost = () => ( - useSelector(state => state.one.hosts) + useSelector(state => state[name]?.[RESOURCES.host]) ) export const useHostApi = () => { diff --git a/src/fireedge/src/client/features/One/image/actions.js b/src/fireedge/src/client/features/One/image/actions.js index 599938f509..b17bad92cd 100644 --- a/src/fireedge/src/client/features/One/image/actions.js +++ b/src/fireedge/src/client/features/One/image/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { imageService } from 'client/features/One/image/services' import { RESOURCES } from 'client/features/One/slice' -export const getImage = createAction('image', imageService.getImage) +/** @see {@link RESOURCES.image} */ +const IMAGE = 'image' + +export const getImage = createAction( + `${IMAGE}/detail`, + imageService.getImage +) export const getImages = createAction( - 'image/pool', + `${IMAGE}/pool`, imageService.getImages, response => ({ [RESOURCES.image]: response }) ) diff --git a/src/fireedge/src/client/features/One/image/hooks.js b/src/fireedge/src/client/features/One/image/hooks.js index d6c3053337..3728b2606a 100644 --- a/src/fireedge/src/client/features/One/image/hooks.js +++ b/src/fireedge/src/client/features/One/image/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/image/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useImage = () => ( - useSelector(state => state.one[RESOURCES.image]) + useSelector(state => state[name]?.[RESOURCES.image] ?? []) ) export const useImageApi = () => { diff --git a/src/fireedge/src/client/features/One/marketplace/actions.js b/src/fireedge/src/client/features/One/marketplace/actions.js index 9f57f471ad..9cd708bec0 100644 --- a/src/fireedge/src/client/features/One/marketplace/actions.js +++ b/src/fireedge/src/client/features/One/marketplace/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { marketplaceService } from 'client/features/One/marketplace/services' import { RESOURCES } from 'client/features/One/slice' -export const getMarketplace = createAction('marketplace', marketplaceService.getMarketplace) +/** @see {@link RESOURCES.marketplace} */ +const MARKETPLACE = 'marketplace' + +export const getMarketplace = createAction( + `${MARKETPLACE}/detail`, + marketplaceService.getMarketplace +) export const getMarketplaces = createAction( - 'marketplace/pool', + `${MARKETPLACE}/pool`, marketplaceService.getMarketplaces, response => ({ [RESOURCES.marketplace]: response }) ) diff --git a/src/fireedge/src/client/features/One/marketplace/hooks.js b/src/fireedge/src/client/features/One/marketplace/hooks.js index 29d84e578f..e238619c9e 100644 --- a/src/fireedge/src/client/features/One/marketplace/hooks.js +++ b/src/fireedge/src/client/features/One/marketplace/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/marketplace/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useMarketplace = () => ( - useSelector(state => state.one[RESOURCES.marketplace]) + useSelector(state => state[name]?.[RESOURCES.marketplace] ?? []) ) export const useMarketplaceApi = () => { diff --git a/src/fireedge/src/client/features/One/marketplaceApp/actions.js b/src/fireedge/src/client/features/One/marketplaceApp/actions.js index eb57266466..4b918a8805 100644 --- a/src/fireedge/src/client/features/One/marketplaceApp/actions.js +++ b/src/fireedge/src/client/features/One/marketplaceApp/actions.js @@ -17,13 +17,16 @@ import { createAction } from 'client/features/One/utils' import { marketplaceAppService } from 'client/features/One/marketplaceApp/services' import { RESOURCES } from 'client/features/One/slice' +/** @see {@link RESOURCES.app} */ +const APP = 'app' + export const getMarketplaceApp = createAction( - 'app', + `${APP}/detail`, marketplaceAppService.getMarketplaceApp ) export const getMarketplaceApps = createAction( - 'app/pool', + `${APP}/pool`, marketplaceAppService.getMarketplaceApps, response => ({ [RESOURCES.app]: response }) ) diff --git a/src/fireedge/src/client/features/One/marketplaceApp/hooks.js b/src/fireedge/src/client/features/One/marketplaceApp/hooks.js index 9aecc29f01..4b3ec30730 100644 --- a/src/fireedge/src/client/features/One/marketplaceApp/hooks.js +++ b/src/fireedge/src/client/features/One/marketplaceApp/hooks.js @@ -19,9 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/marketplaceApp/actions' +import { name, RESOURCES } from 'client/features/One/slice' export const useMarketplaceApp = () => ( - useSelector(state => state.one.apps) + useSelector(state => state[name]?.[RESOURCES.app]) ) export const useMarketplaceAppApi = () => { diff --git a/src/fireedge/src/client/features/One/user/actions.js b/src/fireedge/src/client/features/One/user/actions.js index b6b2aedb49..87e47d3bec 100644 --- a/src/fireedge/src/client/features/One/user/actions.js +++ b/src/fireedge/src/client/features/One/user/actions.js @@ -17,13 +17,19 @@ import { createAction } from 'client/features/One/utils' import { userService } from 'client/features/One/user/services' import { RESOURCES } from 'client/features/One/slice' -export const changeGroup = createAction('user/change-group', userService.changeGroup) -export const getUser = createAction('user', userService.getUser) +/** @see {@link RESOURCES.user} */ +const USER = 'user' + +export const getUser = createAction( + `${USER}/detail`, + userService.getUser +) export const getUsers = createAction( - 'user/pool', + `${USER}/pool`, userService.getUsers, response => ({ [RESOURCES.user]: response }) ) -export const updateUser = createAction('user/update', userService.updateUser) +export const updateUser = createAction(`${USER}/update`, userService.updateUser) +export const changeGroup = createAction(`${USER}/change-group`, userService.changeGroup) diff --git a/src/fireedge/src/client/features/One/user/hooks.js b/src/fireedge/src/client/features/One/user/hooks.js index 21f112d039..2a7c040ac7 100644 --- a/src/fireedge/src/client/features/One/user/hooks.js +++ b/src/fireedge/src/client/features/One/user/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/user/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useUser = () => ( - useSelector(state => state.one[RESOURCES.user]) + useSelector(state => state[name]?.[RESOURCES.user] ?? []) ) export const useUserApi = () => { diff --git a/src/fireedge/src/client/features/One/vm/actions.js b/src/fireedge/src/client/features/One/vm/actions.js index 3e0ec28c5b..cc68d0eea5 100644 --- a/src/fireedge/src/client/features/One/vm/actions.js +++ b/src/fireedge/src/client/features/One/vm/actions.js @@ -16,21 +16,25 @@ import { createAction } from 'client/features/One/utils' import { vmService } from 'client/features/One/vm/services' import { filterBy } from 'client/utils' +import { RESOURCES } from 'client/features/One/slice' -export const getVm = createAction('vm/detail', vmService.getVm) +/** @see {@link RESOURCES.vm} */ +const VM = 'vm' + +export const getVm = createAction(`${VM}/detail`, vmService.getVm) export const getVms = createAction( - 'vm/pool', + `${VM}/pool`, vmService.getVms, (response, { vms: currentVms }) => { const vms = filterBy([...currentVms, ...response], 'ID') - return { vms } + return { [RESOURCES.vm]: vms } } ) export const terminateVm = createAction( - 'vm/delete', + `${VM}/delete`, payload => vmService.actionVm({ ...payload, action: { @@ -40,24 +44,24 @@ export const terminateVm = createAction( }) ) -export const updateUserTemplate = createAction('vm/update', vmService.updateUserTemplate) -export const rename = createAction('vm/rename', vmService.rename) -export const resize = createAction('vm/resize', vmService.resize) -export const changePermissions = createAction('vm/chmod', vmService.changePermissions) -export const changeOwnership = createAction('vm/chown', vmService.changeOwnership) -export const attachDisk = createAction('vm/attach/disk', vmService.attachDisk) -export const detachDisk = createAction('vm/detach/disk', vmService.detachDisk) -export const saveAsDisk = createAction('vm/saveas/disk', vmService.saveAsDisk) -export const resizeDisk = createAction('vm/resize/disk', vmService.resizeDisk) -export const createDiskSnapshot = createAction('vm/create/disk-snapshot', vmService.createDiskSnapshot) -export const renameDiskSnapshot = createAction('vm/rename/disk-snapshot', vmService.renameDiskSnapshot) -export const revertDiskSnapshot = createAction('vm/revert/disk-snapshot', vmService.revertDiskSnapshot) -export const deleteDiskSnapshot = createAction('vm/delete/disk-snapshot', vmService.deleteDiskSnapshot) -export const attachNic = createAction('vm/attach/nic', vmService.attachNic) -export const detachNic = createAction('vm/detach/nic', vmService.detachNic) -export const createSnapshot = createAction('vm/create/snapshot', vmService.createSnapshot) -export const revertSnapshot = createAction('vm/revert/snapshot', vmService.revertSnapshot) -export const deleteSnapshot = createAction('vm/delete/snapshot', vmService.deleteSnapshot) -export const addScheduledAction = createAction('vm/add/scheduled-action', vmService.addScheduledAction) -export const updateScheduledAction = createAction('vm/update/scheduled-action', vmService.updateScheduledAction) -export const deleteScheduledAction = createAction('vm/delete/scheduled-action', vmService.deleteScheduledAction) +export const updateUserTemplate = createAction(`${VM}/update`, vmService.updateUserTemplate) +export const rename = createAction(`${VM}/rename`, vmService.rename) +export const resize = createAction(`${VM}/resize`, vmService.resize) +export const changePermissions = createAction(`${VM}/chmod`, vmService.changePermissions) +export const changeOwnership = createAction(`${VM}/chown`, vmService.changeOwnership) +export const attachDisk = createAction(`${VM}/attach/disk`, vmService.attachDisk) +export const detachDisk = createAction(`${VM}/detach/disk`, vmService.detachDisk) +export const saveAsDisk = createAction(`${VM}/saveas/disk`, vmService.saveAsDisk) +export const resizeDisk = createAction(`${VM}/resize/disk`, vmService.resizeDisk) +export const createDiskSnapshot = createAction(`${VM}/create/disk-snapshot`, vmService.createDiskSnapshot) +export const renameDiskSnapshot = createAction(`${VM}/rename/disk-snapshot`, vmService.renameDiskSnapshot) +export const revertDiskSnapshot = createAction(`${VM}/revert/disk-snapshot`, vmService.revertDiskSnapshot) +export const deleteDiskSnapshot = createAction(`${VM}/delete/disk-snapshot`, vmService.deleteDiskSnapshot) +export const attachNic = createAction(`${VM}/attach/nic`, vmService.attachNic) +export const detachNic = createAction(`${VM}/detach/nic`, vmService.detachNic) +export const createSnapshot = createAction(`${VM}/create/snapshot`, vmService.createSnapshot) +export const revertSnapshot = createAction(`${VM}/revert/snapshot`, vmService.revertSnapshot) +export const deleteSnapshot = createAction(`${VM}/delete/snapshot`, vmService.deleteSnapshot) +export const addScheduledAction = createAction(`${VM}/add/scheduled-action`, vmService.addScheduledAction) +export const updateScheduledAction = createAction(`${VM}/update/scheduled-action`, vmService.updateScheduledAction) +export const deleteScheduledAction = createAction(`${VM}/delete/scheduled-action`, vmService.deleteScheduledAction) diff --git a/src/fireedge/src/client/features/One/vm/hooks.js b/src/fireedge/src/client/features/One/vm/hooks.js index a066554c56..bcd7d98e01 100644 --- a/src/fireedge/src/client/features/One/vm/hooks.js +++ b/src/fireedge/src/client/features/One/vm/hooks.js @@ -19,9 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/vm/actions' +import { name, RESOURCES } from 'client/features/One/slice' export const useVm = () => ( - useSelector(state => state.one.vms) + useSelector(state => state[name]?.[RESOURCES.vm] ?? []) ) export const useVmApi = () => { diff --git a/src/fireedge/src/client/features/One/vmGroup/actions.js b/src/fireedge/src/client/features/One/vmGroup/actions.js index 043e7d9814..048d8eeb44 100644 --- a/src/fireedge/src/client/features/One/vmGroup/actions.js +++ b/src/fireedge/src/client/features/One/vmGroup/actions.js @@ -17,13 +17,16 @@ import { createAction } from 'client/features/One/utils' import { vmGroupService } from 'client/features/One/vmGroup/services' import { RESOURCES } from 'client/features/One/slice' +/** @see {@link RESOURCES.vmgroup} */ +const VM_GROUP = 'vmgroup' + export const getVmGroup = createAction( - 'vmgroup/detail', + `${VM_GROUP}/detail`, vmGroupService.getVmGroup ) export const getVmGroups = createAction( - 'vmgroup/pool', + `${VM_GROUP}/pool`, vmGroupService.getVmGroups, response => ({ [RESOURCES.vmgroup]: response }) ) diff --git a/src/fireedge/src/client/features/One/vmGroup/hooks.js b/src/fireedge/src/client/features/One/vmGroup/hooks.js index 1f109d8ef6..26a558bb3b 100644 --- a/src/fireedge/src/client/features/One/vmGroup/hooks.js +++ b/src/fireedge/src/client/features/One/vmGroup/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/vmGroup/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useVmGroup = () => ( - useSelector(state => state.one[RESOURCES.vmgroup]) + useSelector(state => state[name]?.[RESOURCES.vmgroup]) ) export const useVmGroupApi = () => { diff --git a/src/fireedge/src/client/features/One/vmTemplate/actions.js b/src/fireedge/src/client/features/One/vmTemplate/actions.js index fbe08d5ea2..333d153005 100644 --- a/src/fireedge/src/client/features/One/vmTemplate/actions.js +++ b/src/fireedge/src/client/features/One/vmTemplate/actions.js @@ -17,12 +17,27 @@ import { createAction } from 'client/features/One/utils' import { vmTemplateService } from 'client/features/One/vmTemplate/services' import { RESOURCES } from 'client/features/One/slice' -export const getVmTemplate = createAction('vm-template', vmTemplateService.getVmTemplate) +/** @see {@link RESOURCES.template} */ +const TEMPLATE = 'template' + +export const getVmTemplate = createAction( + `${TEMPLATE}/detail`, + vmTemplateService.getVmTemplate +) export const getVmTemplates = createAction( - 'vm-template/pool', + `${TEMPLATE}/pool`, vmTemplateService.getVmTemplates, response => ({ [RESOURCES.template]: response }) ) -export const instantiate = createAction('vm-template/instantiate', vmTemplateService.instantiate) +export const instantiate = createAction(`${TEMPLATE}/instantiate`, vmTemplateService.instantiate) +export const allocate = createAction(`${TEMPLATE}/allocate`, vmTemplateService.allocate) +export const clone = createAction(`${TEMPLATE}/clone`, vmTemplateService.clone) +export const remove = createAction(`${TEMPLATE}/delete`, vmTemplateService.delete) +export const update = createAction(`${TEMPLATE}/update`, vmTemplateService.update) +export const changePermissions = createAction(`${TEMPLATE}/chmod`, vmTemplateService.changePermissions) +export const changeOwnership = createAction(`${TEMPLATE}/chown`, vmTemplateService.changeOwnership) +export const rename = createAction(`${TEMPLATE}/rename`, vmTemplateService.rename) +export const lock = createAction(`${TEMPLATE}/lock`, vmTemplateService.lock) +export const unlock = createAction(`${TEMPLATE}/unlock`, vmTemplateService.lock) diff --git a/src/fireedge/src/client/features/One/vmTemplate/hooks.js b/src/fireedge/src/client/features/One/vmTemplate/hooks.js index 4104605e47..e267b8e068 100644 --- a/src/fireedge/src/client/features/One/vmTemplate/hooks.js +++ b/src/fireedge/src/client/features/One/vmTemplate/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/vmTemplate/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useVmTemplate = () => ( - useSelector(state => state.one[RESOURCES.template]) + useSelector(state => state[name]?.[RESOURCES.template] ?? []) ) export const useVmTemplateApi = () => { @@ -36,6 +36,18 @@ export const useVmTemplateApi = () => { return { getVmTemplate: (id, data) => unwrapDispatch(actions.getVmTemplate({ id, ...data })), getVmTemplates: () => unwrapDispatch(actions.getVmTemplates()), - instantiate: (id, data) => unwrapDispatch(actions.instantiate({ id, ...data })) + instantiate: (id, data) => unwrapDispatch(actions.instantiate({ id, ...data })), + allocate: template => unwrapDispatch(actions.allocate(template)), + clone: (id, data) => unwrapDispatch(actions.clone({ id, ...data })), + remove: (id, image) => unwrapDispatch(actions.remove({ id, image })), + update: (id, template, replace) => + unwrapDispatch(actions.update({ id, template, replace })), + changePermissions: (id, data) => + unwrapDispatch(actions.changePermissions({ id, ...data })), + changeOwnership: (id, ownership) => + unwrapDispatch(actions.changeOwnership({ id, ownership })), + rename: (id, name) => unwrapDispatch(actions.rename({ id, name })), + lock: (id, data) => unwrapDispatch(actions.lock({ id, ...data })), + unlock: id => unwrapDispatch(actions.unlock({ id })) } } diff --git a/src/fireedge/src/client/features/One/vmTemplate/services.js b/src/fireedge/src/client/features/One/vmTemplate/services.js index eaa34a0318..867d1a5d66 100644 --- a/src/fireedge/src/client/features/One/vmTemplate/services.js +++ b/src/fireedge/src/client/features/One/vmTemplate/services.js @@ -63,6 +63,219 @@ export const vmTemplateService = ({ return [res?.data?.VMTEMPLATE_POOL?.VMTEMPLATE ?? []].flat() }, + /** + * Allocates a new template in OpenNebula. + * + * @param {object} params - Request params + * @param {string} params.template - A string containing the template contents + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + allocate: async params => { + const name = Actions.TEMPLATE_ALLOCATE + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res + + return res?.data + }, + + /** + * Clones an existing virtual machine template. + * + * @param {object} params - Request params + * @param {number|string} params.id - The ID of the template to be cloned + * @param {string} params.name - Name for the new template + * @param {boolean} params.image + * - `true` to clone the template plus any image defined in DISK. + * The new IMAGE_ID is set into each DISK + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + clone: async params => { + const name = Actions.TEMPLATE_CLONE + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res + + return res?.data + }, + + /** + * Deletes the given template from the pool. + * + * @param {object} params - Request params + * @param {number|string} params.id - Template id + * @param {boolean} params.image + * - `true` to delete the template plus any image defined in DISK + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + delete: async params => { + const name = Actions.TEMPLATE_DELETE + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res + + return res?.data + }, + + /** + * Replaces the template contents. + * + * @param {object} params - Request params + * @param {number|string} params.id - Template id + * @param {boolean} params.template - The new template contents + * @param {0|1} params.replace + * - Update type: + * ``0``: Replace the whole template. + * ``1``: Merge new template with the existing one. + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + update: async params => { + const name = Actions.TEMPLATE_UPDATE + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res + + return res?.data + }, + + /** + * Changes the permission bits of a template. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Template id + * @param {{ + * ownerUse: number, + * ownerManage: number, + * ownerAdmin: number, + * groupUse: number, + * groupManage: number, + * groupAdmin: number, + * otherUse: number, + * otherManage: number, + * otherAdmin: number + * }} params.permissions - Permissions data + * @param {boolean} params.image + * - `true` to chmod the template plus any image defined in DISK + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + changePermissions: async ({ id, image, permissions }) => { + const name = Actions.TEMPLATE_CHMOD + const command = { name, ...Commands[name] } + const config = requestConfig({ id, image, ...permissions }, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + + /** + * Changes the ownership of a template. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Template id + * @param {{user: number, group: number}} params.ownership - Ownership data + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + changeOwnership: async ({ id, ownership }) => { + const name = Actions.TEMPLATE_CHOWN + const command = { name, ...Commands[name] } + const config = requestConfig({ id, ...ownership }, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + + /** + * Renames a Template. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Template id + * @param {string} params.name - New name + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + rename: async params => { + const name = Actions.TEMPLATE_RENAME + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + + /** + * Locks a Template. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Template id + * @param {1|2|3|4} params.lock + * - Lock level: + * ``1``: Use + * ``2``: Manage + * ``3``: Admin + * ``4``: All + * @param {boolean} params.test - Check if the object is already locked to return an error + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + lock: async params => { + const name = Actions.TEMPLATE_LOCK + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + + /** + * Unlocks a Template. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Template id + * @returns {number} Template id + * @throws Fails when response isn't code 200 + */ + unlock: async params => { + const name = Actions.TEMPLATE_UNLOCK + const command = { name, ...Commands[name] } + const config = requestConfig(params, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + /** * Instantiates a new virtual machine from a template. * diff --git a/src/fireedge/src/client/features/One/vnetwork/actions.js b/src/fireedge/src/client/features/One/vnetwork/actions.js index 8162ea7549..54b6c63f2e 100644 --- a/src/fireedge/src/client/features/One/vnetwork/actions.js +++ b/src/fireedge/src/client/features/One/vnetwork/actions.js @@ -17,10 +17,16 @@ import { createAction } from 'client/features/One/utils' import { vNetworkService } from 'client/features/One/vnetwork/services' import { RESOURCES } from 'client/features/One/slice' -export const getVNetwork = createAction('vnet', vNetworkService.getVNetwork) +/** @see {@link RESOURCES.vn} */ +const VNET = 'vn' + +export const getVNetwork = createAction( + `${VNET}/detail`, + vNetworkService.getVNetwork +) export const getVNetworks = createAction( - 'vnet/pool', + `${VNET}/pool`, vNetworkService.getVNetworks, response => ({ [RESOURCES.vn]: response }) ) diff --git a/src/fireedge/src/client/features/One/vnetwork/hooks.js b/src/fireedge/src/client/features/One/vnetwork/hooks.js index 38bd5e3bb6..59ab64b80c 100644 --- a/src/fireedge/src/client/features/One/vnetwork/hooks.js +++ b/src/fireedge/src/client/features/One/vnetwork/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/vnetwork/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useVNetwork = () => ( - useSelector(state => state.one[RESOURCES.vn]) + useSelector(state => state[name]?.[RESOURCES.vn] ?? []) ) export const useVNetworkApi = () => { diff --git a/src/fireedge/src/client/features/One/vnetworkTemplate/actions.js b/src/fireedge/src/client/features/One/vnetworkTemplate/actions.js index 063b8b5316..cf56f102f3 100644 --- a/src/fireedge/src/client/features/One/vnetworkTemplate/actions.js +++ b/src/fireedge/src/client/features/One/vnetworkTemplate/actions.js @@ -17,13 +17,16 @@ import { createAction } from 'client/features/One/utils' import { vNetworkTemplateService } from 'client/features/One/vnetworkTemplate/services' import { RESOURCES } from 'client/features/One/slice' +/** @see {@link RESOURCES.vntemplate} */ +const VNET_TEMPLATE = 'vntemplate' + export const getVNetworkTemplate = createAction( - 'vnet-template', + `${VNET_TEMPLATE}/detail`, vNetworkTemplateService.getVNetworkTemplate ) export const getVNetworkTemplates = createAction( - 'vnet-template/pool', + `${VNET_TEMPLATE}/pool`, vNetworkTemplateService.getVNetworkTemplates, response => ({ [RESOURCES.vntemplate]: response }) ) diff --git a/src/fireedge/src/client/features/One/vnetworkTemplate/hooks.js b/src/fireedge/src/client/features/One/vnetworkTemplate/hooks.js index f8cfbe8878..01bc6c8efb 100644 --- a/src/fireedge/src/client/features/One/vnetworkTemplate/hooks.js +++ b/src/fireedge/src/client/features/One/vnetworkTemplate/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/vnetworkTemplate/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useVNetworkTemplate = () => ( - useSelector(state => state.one[RESOURCES.vntemplate]) + useSelector(state => state[name]?.[RESOURCES.vntemplate] ?? []) ) export const useVNetworkTemplateApi = () => { diff --git a/src/fireedge/src/client/features/One/vrouter/actions.js b/src/fireedge/src/client/features/One/vrouter/actions.js index 6f4a10bb8a..a5f58f5b34 100644 --- a/src/fireedge/src/client/features/One/vrouter/actions.js +++ b/src/fireedge/src/client/features/One/vrouter/actions.js @@ -17,13 +17,16 @@ import { createAction } from 'client/features/One/utils' import { vRouterService } from 'client/features/One/vrouter/services' import { RESOURCES } from 'client/features/One/slice' +/** @see {@link RESOURCES.vrouter} */ +const VROUTER = 'vrouter' + export const getVRouter = createAction( - 'vrouter/detail', + `${VROUTER}/detail`, vRouterService.getVRouter ) export const getVRouters = createAction( - 'vrouter/pool', + `${VROUTER}/pool`, vRouterService.getVRouters, response => ({ [RESOURCES.vrouter]: response }) ) diff --git a/src/fireedge/src/client/features/One/vrouter/hooks.js b/src/fireedge/src/client/features/One/vrouter/hooks.js index e0ef638a15..fbfbcad6ef 100644 --- a/src/fireedge/src/client/features/One/vrouter/hooks.js +++ b/src/fireedge/src/client/features/One/vrouter/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/vrouter/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useVRouter = () => ( - useSelector(state => state.one[RESOURCES.vrouter]) + useSelector(state => state[name]?.[RESOURCES.vrouter] ?? []) ) export const useVRouterApi = () => { diff --git a/src/fireedge/src/client/features/One/zone/actions.js b/src/fireedge/src/client/features/One/zone/actions.js index 2b3bc26d04..4ab70ec5fa 100644 --- a/src/fireedge/src/client/features/One/zone/actions.js +++ b/src/fireedge/src/client/features/One/zone/actions.js @@ -17,13 +17,16 @@ import { createAction } from 'client/features/One/utils' import { zoneService } from 'client/features/One/zone/services' import { RESOURCES } from 'client/features/One/slice' +/** @see {@link RESOURCES.zone} */ +const ZONE = 'zone' + export const getZone = createAction( - 'zone/detail', + `${ZONE}/detail`, zoneService.getZone ) export const getZones = createAction( - 'zone/pool', + `${ZONE}/pool`, zoneService.getZones, response => ({ [RESOURCES.zone]: response }) ) diff --git a/src/fireedge/src/client/features/One/zone/hooks.js b/src/fireedge/src/client/features/One/zone/hooks.js index ad098d8212..188bee4abc 100644 --- a/src/fireedge/src/client/features/One/zone/hooks.js +++ b/src/fireedge/src/client/features/One/zone/hooks.js @@ -19,10 +19,10 @@ import { useDispatch, useSelector } from 'react-redux' import { unwrapResult } from '@reduxjs/toolkit' import * as actions from 'client/features/One/zone/actions' -import { RESOURCES } from 'client/features/One/slice' +import { name, RESOURCES } from 'client/features/One/slice' export const useZone = () => ( - useSelector(state => state.one[RESOURCES.zone]) + useSelector(state => state[name]?.[RESOURCES.zone] ?? []) ) export const useZoneApi = () => { diff --git a/src/fireedge/src/client/hooks/useFetch.js b/src/fireedge/src/client/hooks/useFetch.js index 46b104d774..673a52029a 100644 --- a/src/fireedge/src/client/hooks/useFetch.js +++ b/src/fireedge/src/client/hooks/useFetch.js @@ -140,7 +140,7 @@ const useFetch = (request, socket) => { * @param {number} options.delay - Delay to trigger the request * @returns {Promise} - Returns a promise with response or error */ - (payload, options = {}) => { + async (payload, options = {}) => { const { reload = false, delay = 0 } = options if (!(Number.isInteger(delay) && delay >= 0)) { @@ -149,7 +149,8 @@ const useFetch = (request, socket) => { If you're using it as a function, it must also return a number >= 0.`) } - return fakeDelay(delay).then(() => doFetch(payload, reload)) + await fakeDelay(delay) + await doFetch(payload, reload) }, [request]) return { ...state, fetchRequest, STATUS } diff --git a/src/fireedge/src/client/hooks/useListForm.js b/src/fireedge/src/client/hooks/useListForm.js index 3d09396347..42973b503e 100644 --- a/src/fireedge/src/client/hooks/useListForm.js +++ b/src/fireedge/src/client/hooks/useListForm.js @@ -151,7 +151,7 @@ const useListForm = ({ handleSetList(newList) }, - [list] + [list, setList] ) const handleClear = useCallback( @@ -174,7 +174,7 @@ const useListForm = ({ [list, defaultValue] ) - const handleRemove = useCallback(handleUnselect, [key, list]) + const handleRemove = handleUnselect const handleSave = useCallback( (values, id = getItemId(values) ?? uuidv4()) => { diff --git a/src/fireedge/src/client/models/ProviderTemplate.js b/src/fireedge/src/client/models/ProviderTemplate.js index 53de9348e8..53a41fd3c8 100644 --- a/src/fireedge/src/client/models/ProviderTemplate.js +++ b/src/fireedge/src/client/models/ProviderTemplate.js @@ -19,8 +19,7 @@ * @property {string} [description] - Description * @property {string} provider - Provider type * @property {object} plain - Information in plain format - * @property {string} [plain.image] - Image to card - * @property {string} plain.provision_type - Provision type + * @property {string} plain.provider - Provider type * @property {string|string[]} plain.location_key - Location key/s * @property {object} connection - Connections * @property {Array} inputs - Inputs to provision form diff --git a/src/fireedge/src/client/models/ProvisionTemplate.js b/src/fireedge/src/client/models/ProvisionTemplate.js index 836e864bf3..61f935de56 100644 --- a/src/fireedge/src/client/models/ProvisionTemplate.js +++ b/src/fireedge/src/client/models/ProvisionTemplate.js @@ -18,7 +18,6 @@ * @property {string} name - Name * @property {string} image - Image to card * @property {string} provider - Provider type - * @property {string} provision_type - Provision type * @property {object} defaults - Common configuration attributes * @property {object} [defaults.provision] - Provision information * @property {string} [defaults.provision.provider_name] - Provider name diff --git a/src/fireedge/src/client/models/VirtualMachine.js b/src/fireedge/src/client/models/VirtualMachine.js index 372ec413c4..b7a71486d6 100644 --- a/src/fireedge/src/client/models/VirtualMachine.js +++ b/src/fireedge/src/client/models/VirtualMachine.js @@ -58,7 +58,7 @@ export const getHistoryRecords = vm => export const getLastHistory = vm => { const records = getHistoryRecords(vm) - return records.at(-1) + return records.at(-1) ?? {} } /** diff --git a/src/fireedge/src/client/utils/schema.js b/src/fireedge/src/client/utils/schema.js index e1b16e4d6f..905e63c4da 100644 --- a/src/fireedge/src/client/utils/schema.js +++ b/src/fireedge/src/client/utils/schema.js @@ -118,7 +118,7 @@ import { INPUT_TYPES } from 'client/constants' /** * @typedef {object} ExtraParams * @property {function(object):object} [transformBeforeSubmit] - Transform validated form data after submit - * @property {function(object):object} [transformInitialValue] - Transform initial value after load form + * @property {function(object, BaseSchema):object} [transformInitialValue] - Transform initial value after load form */ /** @@ -313,21 +313,23 @@ export const createSteps = (steps, extraParams = {}) => const stepCallbacks = typeof steps === 'function' ? steps(stepProps) : steps const performedSteps = stepCallbacks.map(step => step(stepProps)) - const schemas = object() + const schemas = {} for (const { id, resolver } of performedSteps) { const schema = typeof resolver === 'function' ? resolver() : resolver - schemas.concat(object({ [id]: schema })) + schemas[id] = schema } + const allResolver = object(schemas) + const defaultValues = initialValues - ? transformInitialValue(initialValues, schemas) - : schemas.default() + ? transformInitialValue(initialValues, allResolver) + : allResolver.default() return { steps: performedSteps, defaultValues, - resolver: () => schemas, + resolver: () => allResolver, ...extraParams } } diff --git a/src/fireedge/src/server/utils/constants/commands/template.js b/src/fireedge/src/server/utils/constants/commands/template.js index fed14b5766..8b2ade48d7 100644 --- a/src/fireedge/src/server/utils/constants/commands/template.js +++ b/src/fireedge/src/server/utils/constants/commands/template.js @@ -194,11 +194,11 @@ module.exports = { from: resource, default: 0 }, - userId: { + user: { from: postBody, default: -1 }, - groupId: { + group: { from: postBody, default: -1 } @@ -229,6 +229,10 @@ module.exports = { lock: { from: postBody, default: 4 + }, + test: { + from: postBody, + default: false } } },