1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00

F OpenNebula/one#5422: Add resize vm dialog (#1385)

This commit is contained in:
Sergio Betanzos 2021-07-27 17:18:46 +02:00 committed by GitHub
parent c5d3bc82fa
commit 8d2a0a29b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 213 additions and 30 deletions

View File

@ -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',

View File

@ -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
}

View File

@ -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 (
<>
<InformationPanel
actions={actionsAvailable}
handleOpenResizeDialog={show}
vm={vm}
/>
{display && (
<DialogForm
title={`${T.ResizeCapacity}`}
resolver={() => SCHEMA}
values={SCHEMA.cast(TEMPLATE, { stripUnknown: true })}
onCancel={hide}
onSubmit={handleResize}
>
<FormWithSchema cy='form-dg-vm-resize' fields={FIELDS} />
</DialogForm>
)}
</>
)
}
VmCapacityTab.propTypes = {
tabProps: PropTypes.object
}
VmCapacityTab.displayName = 'VmCapacityTab'
export default VmCapacityTab

View File

@ -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 (
<Paper variant='outlined' className={classes.root}>
<div className={classes.actions}>
{actionsAvailable?.includes?.(VM_ACTIONS.RESIZE_CAPACITY) && (
{actions?.includes?.(VM_ACTIONS.RESIZE_CAPACITY) && (
<Button
data-cy='resize'
size='small'
color='secondary'
onClick={show}
onClick={handleOpenResizeDialog}
variant='contained'
>
{Tr(T.Resize)}
@ -131,24 +122,16 @@ const VmCapacityTab = ({ tabProps: { actions = [] } = {} }) => {
</Typography>
</div>
))}
{display && (
<DialogConfirmation
title={T.ResizeCapacity}
handleAccept={hide}
handleCancel={hide}
>
<p>TODO: should define in view yaml ??</p>
</DialogConfirmation>
)}
</Paper>
)
}
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

View File

@ -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 => {

View File

@ -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)

View File

@ -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) =>

View File

@ -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.
*

View File

@ -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))