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

F #5593: Implement OneProvision GUI to add hosts & IPs (#1739)

(cherry picked from commit 9733184d3301f7795d84941b5078bbaf27191602)
This commit is contained in:
Sergio Betanzos 2022-02-01 19:29:32 +01:00 committed by Tino Vazquez
parent 9e73cd5cbb
commit 1f21b0040b
No known key found for this signature in database
GPG Key ID: 14201E424D02047E
10 changed files with 225 additions and 77 deletions

View File

@ -69,7 +69,7 @@ const DatastoreCard = memo(
<Typography title={NAME} noWrap component="span">
{NAME}
</Typography>
<StatusChip text={type.name} />
<StatusChip text={type} />
</span>
}
subheader={`#${ID}`}

View File

@ -13,24 +13,30 @@
* 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 { object, boolean } from 'yup'
import { T, INPUT_TYPES } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
const CLEANUP = {
name: 'cleanup',
label: 'Cleanup',
label: T.Cleanup,
type: INPUT_TYPES.SWITCH,
tooltip: `
Force to terminate VMs running on provisioned Hosts
and delete all images in the datastores.`,
validation: yup
.boolean()
tooltip: T.CleanupConcept,
validation: boolean()
.notRequired()
.default(() => false),
}
export const FIELDS = [CLEANUP]
const FORCE = {
name: 'force',
label: T.Force,
type: INPUT_TYPES.SWITCH,
tooltip: T.ForceConcept,
validation: boolean()
.notRequired()
.default(() => false),
}
export const SCHEMA = yup.object(getValidationFromFields(FIELDS))
export const FIELDS = [CLEANUP, FORCE]
export const SCHEMA = object(getValidationFromFields(FIELDS))

View File

@ -228,6 +228,12 @@ module.exports = {
ProviderTemplate: 'Provider template',
ProvisionTemplate: 'Provision template',
ConfigureInputs: 'Configure inputs',
AddIP: 'Add IP',
AddHost: 'Add Host',
Cleanup: 'Cleanup',
CleanupConcept: 'Delete all vms and images first, then delete the resources',
Force: 'Force',
ForceConcept: 'Force configure to execute',
/* sections */
Dashboard: 'Dashboard',

View File

@ -13,25 +13,42 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { memo, useEffect } from 'react'
import { memo, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { Trash as DeleteIcon, Settings as ConfigureIcon } from 'iconoir-react'
import {
Trash as DeleteIcon,
Settings as ConfigureIcon,
AddCircledOutline,
} from 'iconoir-react'
import { Stack, TextField } from '@mui/material'
import { useFetchAll } from 'client/hooks'
import { useFetch, useFetchAll } from 'client/hooks'
import { useHostApi, useProvisionApi } from 'client/features/One'
import { useGeneralApi } from 'client/features/General'
import { SubmitButton } from 'client/components/FormControl'
import { ListCards } from 'client/components/List'
import { HostCard } from 'client/components/Cards'
import { Translate } from 'client/components/HOC'
import { T } from 'client/constants'
const Hosts = memo(
({ hidden, data, reloading, refetchProvision, disableAllActions }) => {
const [amount, setAmount] = useState(() => 1)
const { hosts = [] } = data?.TEMPLATE?.BODY?.provision?.infrastructure
const { enqueueSuccess, enqueueInfo } = useGeneralApi()
const { configureHost, deleteHost } = useProvisionApi()
const { configureHost, deleteHost, addHost } = useProvisionApi()
const { getHost } = useHostApi()
const { fetchRequest, loading: loadingAddHost } = useFetch(
async (payload) => {
await addHost(data?.ID, payload)
await refetchProvision()
enqueueSuccess(`Host added ${amount}x`)
}
)
const { data: list, fetchRequestAll, loading } = useFetchAll()
const fetchHosts = () =>
fetchRequestAll(hosts?.map(({ id }) => getHost(id)))
@ -45,35 +62,59 @@ const Hosts = memo(
}, [reloading])
return (
<ListCards
list={list}
isLoading={!list && loading}
CardComponent={HostCard}
cardsProps={({ value: { ID } }) =>
!disableAllActions && {
actions: [
{
handleClick: () =>
configureHost(ID)
.then(() => enqueueInfo(`Configuring host - ID: ${ID}`))
.then(refetchProvision),
icon: <ConfigureIcon />,
cy: `provision-host-configure-${ID}`,
},
{
handleClick: () =>
deleteHost(ID)
.then(refetchProvision)
.then(() => enqueueSuccess(`Host deleted - ID: ${ID}`)),
icon: <DeleteIcon color="error" />,
cy: `provision-host-delete-${ID}`,
},
],
<>
<Stack direction="row" mb="0.5em">
<TextField
type="number"
inputProps={{ 'data-cy': 'amount' }}
onChange={(event) => {
const newAmount = event.target.value
;+newAmount > 0 && setAmount(newAmount)
}}
value={amount}
/>
<Stack alignSelf="center">
<SubmitButton
data-cy="add-host"
color="secondary"
sx={{ ml: 1, display: 'flex', alignItems: 'flex-start' }}
endicon={<AddCircledOutline />}
label={<Translate word={T.AddHost} />}
isSubmitting={loadingAddHost}
onClick={() => fetchRequest(amount)}
/>
</Stack>
</Stack>
<ListCards
list={list}
isLoading={!list && loading}
CardComponent={HostCard}
cardsProps={({ value: { ID } }) =>
!disableAllActions && {
actions: [
{
handleClick: () =>
configureHost(ID)
.then(() => enqueueInfo(`Configuring host - ID: ${ID}`))
.then(refetchProvision),
icon: <ConfigureIcon />,
cy: `provision-host-configure-${ID}`,
},
{
handleClick: () =>
deleteHost(ID)
.then(refetchProvision)
.then(() => enqueueSuccess(`Host deleted - ID: ${ID}`)),
icon: <DeleteIcon color="error" />,
cy: `provision-host-delete-${ID}`,
},
],
}
}
}
displayEmpty
breakpoints={{ xs: 12, md: 6 }}
/>
displayEmpty
breakpoints={{ xs: 12, md: 6 }}
/>
</>
)
},
(prev, next) =>

View File

@ -13,25 +13,38 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { memo, useEffect } from 'react'
import { memo, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import { Trash as DeleteIcon } from 'iconoir-react'
import { Trash as DeleteIcon, AddCircledOutline } from 'iconoir-react'
import { Stack, TextField } from '@mui/material'
import { useFetchAll } from 'client/hooks'
import { useFetch, useFetchAll } from 'client/hooks'
import { useVNetworkApi, useProvisionApi } from 'client/features/One'
import { useGeneralApi } from 'client/features/General'
import { SubmitButton } from 'client/components/FormControl'
import { ListCards } from 'client/components/List'
import { NetworkCard } from 'client/components/Cards'
import { Translate } from 'client/components/HOC'
import { T } from 'client/constants'
const Networks = memo(
({ hidden, data, reloading, refetchProvision, disableAllActions }) => {
const [amount, setAmount] = useState(() => 1)
const { networks = [] } = data?.TEMPLATE?.BODY?.provision?.infrastructure
const { enqueueSuccess } = useGeneralApi()
const { deleteVNetwork } = useProvisionApi()
const { deleteVNetwork, addIp } = useProvisionApi()
const { getVNetwork } = useVNetworkApi()
const { fetchRequest, loading: loadingAddIp } = useFetch(
async (payload) => {
await addIp(data?.ID, payload)
await refetchProvision()
enqueueSuccess(`IP added ${amount}x`)
}
)
const { data: list, fetchRequestAll, loading } = useFetchAll()
const fetchVNetworks = () =>
fetchRequestAll(networks?.map(({ id }) => getVNetwork(id)))
@ -45,27 +58,53 @@ const Networks = memo(
}, [reloading])
return (
<ListCards
list={list}
isLoading={!list && loading}
CardComponent={NetworkCard}
cardsProps={({ value: { ID } }) =>
!disableAllActions && {
actions: [
{
handleClick: () =>
deleteVNetwork(ID)
.then(refetchProvision)
.then(() => enqueueSuccess(`VNetwork deleted - ID: ${ID}`)),
icon: <DeleteIcon color="error" />,
cy: `provision-vnet-delete-${ID}`,
},
],
<>
<Stack direction="row" mb="0.5em">
<TextField
type="number"
inputProps={{ 'data-cy': 'amount' }}
onChange={(event) => {
const newAmount = event.target.value
;+newAmount > 0 && setAmount(newAmount)
}}
value={amount}
/>
<Stack alignSelf="center">
<SubmitButton
data-cy="add-ip"
color="secondary"
endicon={<AddCircledOutline />}
label={<Translate word={T.AddIP} />}
sx={{ ml: 1, display: 'flex', alignItems: 'flex-start' }}
isSubmitting={loadingAddIp}
onClick={() => fetchRequest(amount)}
/>
</Stack>
</Stack>
<ListCards
list={list}
isLoading={!list && loading}
CardComponent={NetworkCard}
cardsProps={({ value: { ID } }) =>
!disableAllActions && {
actions: [
{
handleClick: () =>
deleteVNetwork(ID)
.then(refetchProvision)
.then(() =>
enqueueSuccess(`VNetwork deleted - ID: ${ID}`)
),
icon: <DeleteIcon color="error" />,
cy: `provision-vnet-delete-${ID}`,
},
],
}
}
}
displayEmpty
breakpoints={{ xs: 12, md: 6 }}
/>
displayEmpty
breakpoints={{ xs: 12, md: 6 }}
/>
</>
)
},
(prev, next) =>

View File

@ -72,3 +72,8 @@ export const configureHost = createAction(
'provision/host/configure',
provisionService.configureHost
)
export const addHost = createAction(
'provision/host/add',
provisionService.addHost
)
export const addIp = createAction('provision/ip/add', provisionService.addIp)

View File

@ -55,5 +55,7 @@ export const useProvisionApi = () => {
deleteVNetwork: (id) => unwrapDispatch(actions.deleteVNetwork({ id })),
deleteHost: (id) => unwrapDispatch(actions.deleteHost({ id })),
configureHost: (id) => unwrapDispatch(actions.configureHost({ id })),
addHost: (id, amount) => unwrapDispatch(actions.addHost({ id, amount })),
addIp: (id, amount) => unwrapDispatch(actions.addIp({ id, amount })),
}
}

View File

@ -137,7 +137,8 @@ export const provisionService = {
*
* @param {object} params - Request parameters
* @param {object} params.id - Provider id
* @param {object} params.cleanup
* @param {boolean} params.force - Force configure to execute
* @param {boolean} 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
@ -261,4 +262,52 @@ export const provisionService = {
return res?.data ?? {}
},
/**
* Provisions and configures a new host or amount of hosts.
*
* @param {object} params - Request parameters
* @param {object} params.id - Provision id
* @param {object} params.amount - Amount of hosts to add to the provision
* @returns {object} Object of document updated
* @throws Fails when response isn't code 200
*/
addHost: async ({ id, amount }) => {
const res = await RestClient.request({
method: PUT,
url: `/api/${PROVISION}/host/${id}`,
data: { amount },
})
if (!res?.id || res?.id !== httpCodes.ok.id) {
if (res?.id === httpCodes.accepted.id) return res
throw res
}
return res?.data ?? {}
},
/**
* Adds more IPs to the provision..
*
* @param {object} params - Request parameters
* @param {object} params.id - Provision id
* @param {object} params.amount - Amount of ips to add to the provision
* @returns {object} Object of document updated
* @throws Fails when response isn't code 200
*/
addIp: async ({ id, amount }) => {
const res = await RestClient.request({
method: PUT,
url: `/api/${PROVISION}/ip/${id}`,
data: { amount },
})
if (!res?.id || res?.id !== httpCodes.ok.id) {
if (res?.id === httpCodes.accepted.id) return res
throw res
}
return res?.data ?? {}
},
}

View File

@ -1381,7 +1381,7 @@ const hostAdd = (
'host',
'add',
id,
'amount',
'--amount',
amount,
...authCommand,
...endpoint,
@ -1423,7 +1423,7 @@ const ipAdd = (
'ip',
'add',
id,
'amount',
'--amount',
amount,
...authCommand,
...endpoint,

View File

@ -287,7 +287,7 @@ module.exports = {
},
},
[PROVISION_DELETE_HOST_RESOURCE]: {
path: `${basepath}/host/:resource/:id`,
path: `${basepath}/:resource/:id`,
httpMethod: DELETE,
auth: true,
params: {
@ -300,7 +300,7 @@ module.exports = {
},
},
[PROVISION_DELETE_IMAGE_RESOURCE]: {
path: `${basepath}/image/:resource/:id`,
path: `${basepath}/:resource/:id`,
httpMethod: DELETE,
auth: true,
params: {
@ -313,7 +313,7 @@ module.exports = {
},
},
[PROVISION_DELETE_NETWORK_RESOURCE]: {
path: `${basepath}/network/:resource/:id`,
path: `${basepath}/:resource/:id`,
httpMethod: DELETE,
auth: true,
params: {
@ -326,7 +326,7 @@ module.exports = {
},
},
[PROVISION_DELETE_VNTEMPLATE_RESOURCE]: {
path: `${basepath}/vntemplate/:resource/:id`,
path: `${basepath}/:resource/:id`,
httpMethod: DELETE,
auth: true,
params: {
@ -339,7 +339,7 @@ module.exports = {
},
},
[PROVISION_DELETE_TEMPLATE_RESOURCE]: {
path: `${basepath}/template/:resource/:id`,
path: `${basepath}/:resource/:id`,
httpMethod: DELETE,
auth: true,
params: {
@ -352,7 +352,7 @@ module.exports = {
},
},
[PROVISION_DELETE_CLUSTER_RESOURCE]: {
path: `${basepath}/cluster/:resource/:id`,
path: `${basepath}/:resource/:id`,
httpMethod: DELETE,
auth: true,
params: {