diff --git a/src/fireedge/src/client/components/Dialogs/DialogForm.js b/src/fireedge/src/client/components/Dialogs/DialogForm.js index c178f22750..fea3317cc6 100644 --- a/src/fireedge/src/client/components/Dialogs/DialogForm.js +++ b/src/fireedge/src/client/components/Dialogs/DialogForm.js @@ -92,7 +92,7 @@ const DialogForm = memo( } ) -DialogForm.propTypes = { +export const DialogFormPropTypes = { open: PropTypes.bool.isRequired, title: PropTypes.string.isRequired, values: PropTypes.oneOfType([ @@ -109,6 +109,8 @@ DialogForm.propTypes = { ]) } +DialogForm.propTypes = DialogFormPropTypes + DialogForm.defaultProps = { open: true, title: 'Title dialog form', diff --git a/src/fireedge/src/client/components/Dialogs/index.js b/src/fireedge/src/client/components/Dialogs/index.js index a70ba7818f..164dabdd06 100644 --- a/src/fireedge/src/client/components/Dialogs/index.js +++ b/src/fireedge/src/client/components/Dialogs/index.js @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -import DialogForm from 'client/components/Dialogs/DialogForm' +import DialogForm, { DialogFormPropTypes } from 'client/components/Dialogs/DialogForm' import DialogRequest from 'client/components/Dialogs/DialogRequest' import DialogConfirmation from 'client/components/Dialogs/DialogConfirmation' export { DialogForm, + DialogFormPropTypes, DialogRequest, DialogConfirmation } diff --git a/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js b/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js new file mode 100644 index 0000000000..5796e8b2f1 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js @@ -0,0 +1,80 @@ +/* ------------------------------------------------------------------------- * + * 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 * as React from 'react' +import PropTypes from 'prop-types' + +import { useVmApi } from 'client/features/One' +import { useDialog } from 'client/hooks' +import { TabContext } from 'client/components/Tabs/TabProvider' +import { DialogForm } from 'client/components/Dialogs' +import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { SCHEMA, FIELDS } from 'client/formSchema/Vm/resize' + +import InformationPanel from 'client/components/Tabs/Vm/Capacity/information' + +import * as VirtualMachine from 'client/models/VirtualMachine' +import * as Helper from 'client/models/Helper' +import { T } from 'client/constants' + +const VmCapacityTab = ({ tabProps: { actions = [] } = {} }) => { + const { display, show, hide } = useDialog() + const { resize } = useVmApi() + + const { handleRefetch, data: vm = {} } = React.useContext(TabContext) + const { ID, TEMPLATE } = vm + + const hypervisor = VirtualMachine.getHypervisor(vm) + const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor) + + const handleResize = async formData => { + const { enforce, ...restOfData } = formData + const template = Helper.jsonToXml({ ROOT: restOfData }) + + const response = await resize(ID, { enforce, template }) + String(response) === String(ID) && await handleRefetch?.() + hide() + } + + return ( + <> + + {display && ( + SCHEMA} + values={SCHEMA.cast(TEMPLATE, { stripUnknown: true })} + onCancel={hide} + onSubmit={handleResize} + > + + + )} + + ) +} + +VmCapacityTab.propTypes = { + tabProps: PropTypes.object +} + +VmCapacityTab.displayName = 'VmCapacityTab' + +export default VmCapacityTab diff --git a/src/fireedge/src/client/components/Tabs/Vm/Capacity.js b/src/fireedge/src/client/components/Tabs/Vm/Capacity/information.js similarity index 78% rename from src/fireedge/src/client/components/Tabs/Vm/Capacity.js rename to src/fireedge/src/client/components/Tabs/Vm/Capacity/information.js index 84acca608b..4d2a547871 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Capacity.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Capacity/information.js @@ -18,14 +18,10 @@ import * as React from 'react' import PropTypes from 'prop-types' import { makeStyles, Paper, Typography, Button } from '@material-ui/core' -import { useDialog } from 'client/hooks' -import { TabContext } from 'client/components/Tabs/TabProvider' -import { DialogConfirmation } from 'client/components/Dialogs' import { Tr } from 'client/components/HOC' -import { prettyBytes } from 'client/utils' import * as VirtualMachine from 'client/models/VirtualMachine' -import * as Helper from 'client/models/Helper' +import { prettyBytes } from 'client/utils' import { T, VM_ACTIONS } from 'client/constants' const useStyles = makeStyles(theme => ({ @@ -63,16 +59,11 @@ const useStyles = makeStyles(theme => ({ } })) -const VmCapacityTab = ({ tabProps: { actions = [] } = {} }) => { +const InformationPanel = ({ actions, vm = {}, handleOpenResizeDialog }) => { const classes = useStyles() - const { display, show, hide } = useDialog() - - const { data: vm = {} } = React.useContext(TabContext) const { TEMPLATE } = vm const isVCenter = VirtualMachine.isVCenter(vm) - const hypervisor = VirtualMachine.getHypervisor(vm) - const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor) const capacity = [ { @@ -109,12 +100,12 @@ const VmCapacityTab = ({ tabProps: { actions = [] } = {} }) => { return (
- {actionsAvailable?.includes?.(VM_ACTIONS.RESIZE_CAPACITY) && ( + {actions?.includes?.(VM_ACTIONS.RESIZE_CAPACITY) && (
))} - - {display && ( - -

TODO: should define in view yaml ??

-
- )}
) } -VmCapacityTab.propTypes = { - tabProps: PropTypes.object +InformationPanel.propTypes = { + handleOpenResizeDialog: PropTypes.function, + actions: PropTypes.array, + vm: PropTypes.object } -VmCapacityTab.displayName = 'VmCapacityTab' +InformationPanel.displayName = 'InformationPanel' -export default VmCapacityTab +export default InformationPanel diff --git a/src/fireedge/src/client/components/Tabs/Vm/Info/index.js b/src/fireedge/src/client/components/Tabs/Vm/Info/index.js index 2fe08228d5..d7da97ab68 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Info/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Info/index.js @@ -45,7 +45,7 @@ const VmInfoTab = ({ tabProps = {} }) => { } = tabProps const { changeOwnership, changePermissions, rename, updateUserTemplate } = useVmApi() - const { handleRefetch, data: vm } = React.useContext(TabContext) + const { handleRefetch, data: vm = {} } = React.useContext(TabContext) const { ID, UNAME, UID, GNAME, GID, PERMISSIONS, USER_TEMPLATE, MONITORING } = vm const handleChangeOwnership = async newOwnership => { diff --git a/src/fireedge/src/client/features/One/vm/actions.js b/src/fireedge/src/client/features/One/vm/actions.js index 808d648a5f..8399100344 100644 --- a/src/fireedge/src/client/features/One/vm/actions.js +++ b/src/fireedge/src/client/features/One/vm/actions.js @@ -42,6 +42,7 @@ 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 detachNic = createAction('vm/detach/nic', vmService.detachNic) diff --git a/src/fireedge/src/client/features/One/vm/hooks.js b/src/fireedge/src/client/features/One/vm/hooks.js index c8d8f3cb28..e3ab269b4c 100644 --- a/src/fireedge/src/client/features/One/vm/hooks.js +++ b/src/fireedge/src/client/features/One/vm/hooks.js @@ -39,6 +39,7 @@ export const useVmApi = () => { updateUserTemplate: (id, template, replace) => unwrapDispatch(actions.updateUserTemplate({ id, template, replace })), rename: (id, name) => unwrapDispatch(actions.rename({ id, name })), + resize: (id, data) => unwrapDispatch(actions.resize({ id, ...data })), changePermissions: (id, permissions) => unwrapDispatch(actions.changePermissions({ id, permissions })), changeOwnership: (id, ownership) => diff --git a/src/fireedge/src/client/features/One/vm/services.js b/src/fireedge/src/client/features/One/vm/services.js index bba0d43a9e..5559d19a4c 100644 --- a/src/fireedge/src/client/features/One/vm/services.js +++ b/src/fireedge/src/client/features/One/vm/services.js @@ -120,6 +120,29 @@ export const vmService = ({ return res?.data }, + /** + * Changes the capacity of the virtual machine. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Virtual machine id + * @param {string} params.template - Template containing the new capacity + * @param {boolean} params.enforce + * - `true` to enforce the Host capacity isn't over committed. + * @returns {number} Virtual machine id + * @throws Fails when response isn't code 200 + */ + resize: async ({ id, template, enforce }) => { + const name = Actions.VM_RESIZE + const command = { name, ...Commands[name] } + const config = requestConfig({ id, template, enforce }, command) + + const res = await RestClient.request(config) + + if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data + + return res?.data + }, + /** * Replaces the user template contents. * diff --git a/src/fireedge/src/client/formSchema/Vm/resize.js b/src/fireedge/src/client/formSchema/Vm/resize.js new file mode 100644 index 0000000000..4bf8bab909 --- /dev/null +++ b/src/fireedge/src/client/formSchema/Vm/resize.js @@ -0,0 +1,92 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2021, OpenNebula Project, OpenNebula Systems * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain * + * a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * ------------------------------------------------------------------------- */ +/* eslint-disable jsdoc/require-jsdoc */ +import * as yup from 'yup' +import { INPUT_TYPES } from 'client/constants' +import { getValidationFromFields } from 'client/utils' + +const ENFORCE = { + name: 'enforce', + label: 'Enforce capacity checks', + type: INPUT_TYPES.CHECKBOX, + tooltip: ` + If it is set to true, the host capacity will be checked. + This will only affect oneadmin requests, regular users + resize requests will always be enforced`, + validation: yup + .boolean() + .transform(value => { + if (typeof value === 'boolean') return value + + return String(value).toUpperCase() === 'YES' + }) + .default(false), + grid: { md: 12 } +} + +const MEMORY = { + name: 'MEMORY', + label: 'Memory', + type: INPUT_TYPES.TEXT, + htmlType: 'number', + tooltip: 'Amount of RAM required for the VM', + validation: yup + .number() + .typeError('Memory value must be a number') + .required('Memory field is required') + .positive() + .default(undefined) +} + +const PHYSICAL_CPU = { + name: 'CPU', + label: 'Physical CPU', + type: INPUT_TYPES.TEXT, + htmlType: 'number', + tooltip: ` + Percentage of CPU divided by 100 required for the + Virtual Machine. Half a processor is written 0.5.`, + validation: yup + .number() + .typeError('Physical CPU value must be a number') + .required('Physical CPU field is required') + .positive() + .default(undefined) + .resolve() +} + +const VIRTUAL_CPU = { + name: 'VCPU', + label: 'Virtual CPU', + type: INPUT_TYPES.TEXT, + htmlType: 'number', + tooltip: ` + Number of virtual cpus. This value is optional, the default + hypervisor behavior is used, usually one virtual CPU.`, + validation: yup + .number() + .typeError('Virtual CPU value must be a number') + .default(undefined) +} + +export const FIELDS = [ + ENFORCE, + MEMORY, + PHYSICAL_CPU, + VIRTUAL_CPU +] + +export const SCHEMA = yup.object(getValidationFromFields(FIELDS))