From 1f21b0040b045db78efaa89ce311fac904ae8724 Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Tue, 1 Feb 2022 19:29:32 +0100 Subject: [PATCH] F #5593: Implement OneProvision GUI to add hosts & IPs (#1739) (cherry picked from commit 9733184d3301f7795d84941b5078bbaf27191602) --- .../client/components/Cards/DatastoreCard.js | 2 +- .../Forms/Provision/DeleteForm/schema.js | 28 +++-- .../src/client/constants/translates.js | 6 + .../containers/Provisions/DialogInfo/hosts.js | 105 ++++++++++++------ .../Provisions/DialogInfo/networks.js | 87 +++++++++++---- .../client/features/One/provision/actions.js | 5 + .../client/features/One/provision/hooks.js | 2 + .../client/features/One/provision/services.js | 51 ++++++++- .../api/oneprovision/provision/functions.js | 4 +- .../api/oneprovision/provision/routes.js | 12 +- 10 files changed, 225 insertions(+), 77 deletions(-) diff --git a/src/fireedge/src/client/components/Cards/DatastoreCard.js b/src/fireedge/src/client/components/Cards/DatastoreCard.js index d3994db972..c9d727c374 100644 --- a/src/fireedge/src/client/components/Cards/DatastoreCard.js +++ b/src/fireedge/src/client/components/Cards/DatastoreCard.js @@ -69,7 +69,7 @@ const DatastoreCard = memo( {NAME} - + } subheader={`#${ID}`} diff --git a/src/fireedge/src/client/components/Forms/Provision/DeleteForm/schema.js b/src/fireedge/src/client/components/Forms/Provision/DeleteForm/schema.js index 50f91c1d52..d09123d7ca 100644 --- a/src/fireedge/src/client/components/Forms/Provision/DeleteForm/schema.js +++ b/src/fireedge/src/client/components/Forms/Provision/DeleteForm/schema.js @@ -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)) diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 6165330db7..a9eb94af2c 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -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', diff --git a/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js b/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js index dbfcc7ae62..c08b25ac2c 100644 --- a/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js +++ b/src/fireedge/src/client/containers/Provisions/DialogInfo/hosts.js @@ -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 ( - - !disableAllActions && { - actions: [ - { - handleClick: () => - configureHost(ID) - .then(() => enqueueInfo(`Configuring host - ID: ${ID}`)) - .then(refetchProvision), - icon: , - cy: `provision-host-configure-${ID}`, - }, - { - handleClick: () => - deleteHost(ID) - .then(refetchProvision) - .then(() => enqueueSuccess(`Host deleted - ID: ${ID}`)), - icon: , - cy: `provision-host-delete-${ID}`, - }, - ], + <> + + { + const newAmount = event.target.value + ;+newAmount > 0 && setAmount(newAmount) + }} + value={amount} + /> + + } + label={} + isSubmitting={loadingAddHost} + onClick={() => fetchRequest(amount)} + /> + + + + !disableAllActions && { + actions: [ + { + handleClick: () => + configureHost(ID) + .then(() => enqueueInfo(`Configuring host - ID: ${ID}`)) + .then(refetchProvision), + icon: , + cy: `provision-host-configure-${ID}`, + }, + { + handleClick: () => + deleteHost(ID) + .then(refetchProvision) + .then(() => enqueueSuccess(`Host deleted - ID: ${ID}`)), + icon: , + cy: `provision-host-delete-${ID}`, + }, + ], + } } - } - displayEmpty - breakpoints={{ xs: 12, md: 6 }} - /> + displayEmpty + breakpoints={{ xs: 12, md: 6 }} + /> + ) }, (prev, next) => diff --git a/src/fireedge/src/client/containers/Provisions/DialogInfo/networks.js b/src/fireedge/src/client/containers/Provisions/DialogInfo/networks.js index 32d5d981b5..416e661e37 100644 --- a/src/fireedge/src/client/containers/Provisions/DialogInfo/networks.js +++ b/src/fireedge/src/client/containers/Provisions/DialogInfo/networks.js @@ -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 ( - - !disableAllActions && { - actions: [ - { - handleClick: () => - deleteVNetwork(ID) - .then(refetchProvision) - .then(() => enqueueSuccess(`VNetwork deleted - ID: ${ID}`)), - icon: , - cy: `provision-vnet-delete-${ID}`, - }, - ], + <> + + { + const newAmount = event.target.value + ;+newAmount > 0 && setAmount(newAmount) + }} + value={amount} + /> + + } + label={} + sx={{ ml: 1, display: 'flex', alignItems: 'flex-start' }} + isSubmitting={loadingAddIp} + onClick={() => fetchRequest(amount)} + /> + + + + !disableAllActions && { + actions: [ + { + handleClick: () => + deleteVNetwork(ID) + .then(refetchProvision) + .then(() => + enqueueSuccess(`VNetwork deleted - ID: ${ID}`) + ), + icon: , + cy: `provision-vnet-delete-${ID}`, + }, + ], + } } - } - displayEmpty - breakpoints={{ xs: 12, md: 6 }} - /> + displayEmpty + breakpoints={{ xs: 12, md: 6 }} + /> + ) }, (prev, next) => diff --git a/src/fireedge/src/client/features/One/provision/actions.js b/src/fireedge/src/client/features/One/provision/actions.js index f80773b6e5..28ab06fddb 100644 --- a/src/fireedge/src/client/features/One/provision/actions.js +++ b/src/fireedge/src/client/features/One/provision/actions.js @@ -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) diff --git a/src/fireedge/src/client/features/One/provision/hooks.js b/src/fireedge/src/client/features/One/provision/hooks.js index 50e8abcfc0..3a7bdf4984 100644 --- a/src/fireedge/src/client/features/One/provision/hooks.js +++ b/src/fireedge/src/client/features/One/provision/hooks.js @@ -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 })), } } diff --git a/src/fireedge/src/client/features/One/provision/services.js b/src/fireedge/src/client/features/One/provision/services.js index 5934e8d622..e893aabb5b 100644 --- a/src/fireedge/src/client/features/One/provision/services.js +++ b/src/fireedge/src/client/features/One/provision/services.js @@ -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 ?? {} + }, } diff --git a/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js b/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js index 9f54e939ba..9efc2dadfe 100644 --- a/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js +++ b/src/fireedge/src/server/routes/api/oneprovision/provision/functions.js @@ -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, diff --git a/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js b/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js index 36bf3c708e..a20854d87f 100644 --- a/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js +++ b/src/fireedge/src/server/routes/api/oneprovision/provision/routes.js @@ -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: {