1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-08 21:17:43 +03:00

F #5552: Add cleanup option to delete provision form (#1485)

This commit is contained in:
Sergio Betanzos 2021-09-27 18:46:19 +02:00 committed by GitHub
parent 12b783fd2a
commit 4b89382035
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 118 additions and 40 deletions

View File

@ -18,6 +18,7 @@ import PropTypes from 'prop-types'
import { Db as ProviderIcon, Cloud as ProvisionIcon } from 'iconoir-react'
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
import { StatusBadge } from 'client/components/Status'
import Image from 'client/components/Image'
@ -31,7 +32,7 @@ import {
} from 'client/constants'
const ProvisionCard = memo(
({ value, image: propImage, isSelected, handleClick, isProvider, actions }) => {
({ value, image: propImage, isSelected, handleClick, isProvider, actions, deleteAction }) => {
const { ID, NAME, TEMPLATE: { BODY = {} } } = value
const IMAGES_URL = isProvider ? PROVIDER_IMAGES_URL : PROVISION_IMAGES_URL
@ -48,8 +49,15 @@ const ProvisionCard = memo(
return (
<SelectCard
action={actions?.map(action =>
<Action key={action?.cy} {...action} />
action={(actions?.length > 0 || deleteAction) && (
<>
{actions?.map(action =>
<Action key={action?.cy} {...action} />
)}
{deleteAction && (
<ButtonToTriggerForm {...deleteAction} />
)}
</>
)}
dataCy={isProvider ? 'provider' : 'provision'}
handleClick={handleClick}
@ -89,6 +97,7 @@ ProvisionCard.propTypes = {
handleClick: PropTypes.func,
isProvider: PropTypes.bool,
image: PropTypes.string,
deleteAction: PropTypes.func,
actions: PropTypes.arrayOf(
PropTypes.shape({
handleClick: PropTypes.func.isRequired,
@ -104,6 +113,7 @@ ProvisionCard.defaultProps = {
isProvider: false,
isSelected: undefined,
image: undefined,
deleteAction: undefined,
value: {}
}

View File

@ -30,8 +30,8 @@ import { NavArrowDown } from 'iconoir-react'
import { useDialog } from 'client/hooks'
import { DialogConfirmation, DialogForm, DialogPropTypes } from 'client/components/Dialogs'
import { SubmitButton, SubmitButtonPropTypes } from 'client/components/FormControl'
import FormWithSchema from 'client/components/Forms/FormWithSchema'
import SubmitButton, { SubmitButtonPropTypes } from 'client/components/FormControl/SubmitButton'
import FormStepper from 'client/components/FormStepper'
import { Translate } from 'client/components/HOC'

View File

@ -57,18 +57,14 @@ const Provider = () => ({
const {
handleSelect,
handleUnselect
handleClear
} = useListForm({ key: STEP_ID, setList: setFormData })
const handleClick = (provider, isSelected) => {
const { ID } = provider
// reset inputs when selected provider changes
setFormData(prev => ({ ...prev, [INPUTS_ID]: undefined }))
isSelected
? handleUnselect(ID, item => item.ID !== ID)
: handleSelect(provider)
isSelected ? handleClear() : handleSelect(provider)
}
return (

View File

@ -0,0 +1,21 @@
/* ------------------------------------------------------------------------- *
* 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/utils'
import { SCHEMA, FIELDS } from 'client/components/Forms/Provision/DeleteForm/schema'
const DeleteForm = createForm(SCHEMA, FIELDS)
export default DeleteForm

View File

@ -0,0 +1,35 @@
/* ------------------------------------------------------------------------- *
* 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 CLEANUP = {
name: 'cleanup',
label: 'Cleanup',
type: INPUT_TYPES.SWITCH,
tooltip: `
Force to terminate VMs running on provisioned Hosts
and delete all images in the datastores.`,
validation: yup.boolean().notRequired().default(() => false)
}
export const FIELDS = [
CLEANUP
]
export const SCHEMA = yup.object(getValidationFromFields(FIELDS))

View File

@ -14,7 +14,9 @@
* limitations under the License. *
* ------------------------------------------------------------------------- */
import CreateForm from 'client/components/Forms/Provision/CreateForm'
import DeleteForm from 'client/components/Forms/Provision/DeleteForm'
export {
CreateForm
CreateForm,
DeleteForm
}

View File

@ -25,9 +25,11 @@ import { useFetch, useSearch } from 'client/hooks'
import { useProvision, useProvisionApi } from 'client/features/One'
import { useGeneralApi } from 'client/features/General'
import { DeleteForm } from 'client/components/Forms/Provision'
import { ListHeader, ListCards } from 'client/components/List'
import AlertError from 'client/components/Alerts/Error'
import { ProvisionCard } from 'client/components/Cards'
import { Translate } from 'client/components/HOC'
import { DialogRequest } from 'client/components/Dialogs'
import DialogInfo from 'client/containers/Provisions/DialogInfo'
@ -98,28 +100,34 @@ function Provisions () {
.then(() => fetchRequest(undefined, { reload: true })),
icon: <EditIcon />,
cy: 'provision-configure'
},
{
handleClick: () => setShowDialog({
id: ID,
content: props => createElement(DialogInfo, {
...props,
disableAllActions: true,
displayName: 'DialogDeleteProvision'
}),
title: `DELETE - #${ID} ${NAME}`,
handleAccept: () => {
handleCloseDialog()
return deleteProvision(ID)
.then(() => enqueueInfo(`Deleting provision - ID: ${ID}`))
.then(() => fetchRequest(undefined, { reload: true }))
}
}),
icon: <DeleteIcon color={theme.palette.error.dark} />,
cy: 'provision-delete'
}
]
],
deleteAction: {
buttonProps: {
'data-cy': 'provision-delete',
icon: <DeleteIcon color={theme.palette.error.dark} />
},
options: [{
dialogProps: {
title: (
<Translate
word={T.DeleteSomething}
values={`#${ID} ${NAME}`}
/>
)
},
form: DeleteForm,
onSubmit: async formData => {
try {
await deleteProvision(ID, formData)
enqueueInfo(`Deleting provision - ID: ${ID}`)
} finally {
handleCloseDialog()
fetchRequest(undefined, { reload: true })
}
}
}]
}
})}
/>
)}

View File

@ -19,14 +19,14 @@ import { useDispatch, useSelector } from 'react-redux'
import { unwrapResult } from '@reduxjs/toolkit'
import * as actions from 'client/features/One/provision/actions'
import { RESOURCES } from 'client/features/One/slice'
import { name, RESOURCES } from 'client/features/One/slice'
export const useProvisionTemplate = () => (
useSelector(state => state.one[RESOURCES.document.defaults])
useSelector(state => state[name]?.[RESOURCES.document.defaults])
)
export const useProvision = () => (
useSelector(state => state.one[RESOURCES.document[103]])
useSelector(state => state[name]?.[RESOURCES.document[103]])
)
export const useProvisionApi = () => {
@ -45,7 +45,7 @@ export const useProvisionApi = () => {
getProvisions: () => dispatch(actions.getProvisions()),
createProvision: data => unwrapDispatch(actions.createProvision({ data })),
configureProvision: id => unwrapDispatch(actions.configureProvision({ id })),
deleteProvision: id => unwrapDispatch(actions.deleteProvision({ id })),
deleteProvision: (id, data) => unwrapDispatch(actions.deleteProvision({ id, ...data })),
getProvisionLog: id => unwrapDispatch(actions.getProvisionLog({ id })),
deleteDatastore: id => unwrapDispatch(actions.deleteDatastore({ id })),

View File

@ -138,13 +138,17 @@ export const provisionService = ({
*
* @param {object} params - Request parameters
* @param {object} params.id - Provider id
* @param {object} params.cleanup
* - If `true`, force to terminate VMs running
* on provisioned Hosts and delete all images in the datastores
* @returns {object} Object of document deleted
* @throws Fails when response isn't code 200
*/
deleteProvision: async ({ id }) => {
deleteProvision: async ({ id, ...data }) => {
const res = await RestClient.request({
method: DELETE,
url: `/api/${PROVISION}/delete/${id}`
url: `/api/${PROVISION}/delete/${id}`,
data
})
if (!res?.id || res?.id !== httpCodes.ok.id) {

View File

@ -498,7 +498,8 @@ const deleteProvision = (res = {}, next = defaultEmptyFunction, params = {}, use
const command = 'delete'
const endpoint = getEndpoint()
const authCommand = ['--user', user, '--password', password]
const paramsCommand = [command, params.id, '--batch', '--debug', '--json', ...authCommand, ...endpoint]
const cleanup = params.cleanup ? ['--cleanup'] : []
const paramsCommand = [command, params.id, '--batch', '--debug', '--json', ...cleanup, ...authCommand, ...endpoint]
// get Log file
const dataLog = logData(params.id, true)

View File

@ -142,7 +142,8 @@ const routes = {
delete: {
action: deleteProvision,
params: {
id: { from: fromData.resource, name: 'id', front: true }
id: { from: fromData.resource, name: 'id', front: true },
cleanup: { from: fromData.postBody, name: 'cleanup', front: true }
},
websocket: true
},