diff --git a/src/fireedge/src/client/components/Consoles/HeaderVmInfo.js b/src/fireedge/src/client/components/Consoles/HeaderVmInfo.js index 68914030ba..7ffe309c61 100644 --- a/src/fireedge/src/client/components/Consoles/HeaderVmInfo.js +++ b/src/fireedge/src/client/components/Consoles/HeaderVmInfo.js @@ -40,7 +40,7 @@ const HeaderVmInfo = ({ id, type }) => { const { push: redirectTo } = useHistory() const { enqueueError } = useGeneralApi() - const { data: vm, isSuccess, isLoading, isError } = useGetVmQuery(id) + const { data: vm, isSuccess, isLoading, isError } = useGetVmQuery({ id }) const [getService, { data: serviceFlow }] = useLazyGetServiceQuery() const ips = getIps(vm) diff --git a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js index 73eb632150..206d27683c 100644 --- a/src/fireedge/src/client/components/Tables/VmTemplates/actions.js +++ b/src/fireedge/src/client/components/Tables/VmTemplates/actions.js @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * * limitations under the License. * * ------------------------------------------------------------------------- */ -/* eslint-disable jsdoc/require-jsdoc */ import { useMemo } from 'react' import { useHistory } from 'react-router-dom' +import { Typography } from '@mui/material' import { AddSquare, Import, @@ -32,40 +32,64 @@ import { useUnlockTemplateMutation, useCloneTemplateMutation, useRemoveTemplateMutation, + useChangeTemplateOwnershipMutation, + useChangeTemplatePermissionsMutation, } from 'client/features/OneApi/vmTemplate' -import { Tr, Translate } from 'client/components/HOC' +import { ChangeUserForm, ChangeGroupForm } from 'client/components/Forms/Vm' import { CloneForm } from 'client/components/Forms/VmTemplate' -import { createActions } from 'client/components/Tables/Enhanced/Utils' -import { PATH } from 'client/apps/sunstone/routesOne' +import { + createActions, + GlobalAction, +} from 'client/components/Tables/Enhanced/Utils' +import { Tr, Translate } from 'client/components/HOC' +import { PATH } from 'client/apps/sunstone/routesOne' import { T, VM_TEMPLATE_ACTIONS, RESOURCE_NAMES } from 'client/constants' -const MessageToConfirmAction = (rows) => { - const names = rows?.map?.(({ original }) => original?.NAME) +const ListVmTemplateNames = ({ rows = [] }) => + rows?.map?.(({ id, original }) => { + const { ID, NAME } = original - return ( - <> -

- - {`: ${names.join(', ')}`} -

-

- -

- - ) -} + return ( + + {`#${ID} ${NAME}`} + + ) + }) + +const SubHeader = (rows) => + +const MessageToConfirmAction = (rows, description) => ( + <> + + {description && } + + +) MessageToConfirmAction.displayName = 'MessageToConfirmAction' +/** + * Generates the actions to operate resources on VM Template table. + * + * @returns {GlobalAction} - Actions + */ const Actions = () => { const history = useHistory() const { view, getResourceView } = useViews() + const [lock] = useLockTemplateMutation() const [unlock] = useUnlockTemplateMutation() const [clone] = useCloneTemplateMutation() const [remove] = useRemoveTemplateMutation() + const [changeOwnership] = useChangeTemplateOwnershipMutation() + const [changePermissions] = useChangeTemplatePermissionsMutation() return useMemo( () => @@ -183,30 +207,72 @@ const Actions = () => { { accessor: VM_TEMPLATE_ACTIONS.CHANGE_OWNER, name: T.ChangeOwner, - disabled: true, - isConfirmDialog: true, - onSubmit: () => undefined, + dialogProps: { + title: T.ChangeOwner, + subheader: SubHeader, + dataCy: `modal-${VM_TEMPLATE_ACTIONS.CHANGE_OWNER}`, + }, + form: ChangeUserForm, + onSubmit: (rows) => (newOwnership) => { + rows?.map?.(({ original }) => + changeOwnership({ id: original?.ID, ...newOwnership }) + ) + }, + // onSubmit: (rows) => async (newOwnership) => { + // const ids = rows?.map?.(({ original }) => original?.ID) + // await Promise.all( + // ids.map((id) => changeOwnership({ id, ...newOwnership })) + // ) + // }, }, { accessor: VM_TEMPLATE_ACTIONS.CHANGE_GROUP, name: T.ChangeGroup, - disabled: true, - isConfirmDialog: true, - onSubmit: () => undefined, + dialogProps: { + title: T.ChangeGroup, + subheader: SubHeader, + dataCy: `modal-${VM_TEMPLATE_ACTIONS.CHANGE_GROUP}`, + }, + form: ChangeGroupForm, + onSubmit: (rows) => async (newOwnership) => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all( + ids.map((id) => changeOwnership({ id, ...newOwnership })) + ) + }, }, { accessor: VM_TEMPLATE_ACTIONS.SHARE, - disabled: true, name: T.Share, isConfirmDialog: true, - onSubmit: () => undefined, + dialogProps: { + title: T.Share, + children: (rows) => + MessageToConfirmAction(rows, T.ShareVmTemplateDescription), + }, + onSubmit: (rows) => () => { + rows?.map?.(({ original }) => + changePermissions({ id: original?.ID, groupUse: '1' }) + ) + }, }, { accessor: VM_TEMPLATE_ACTIONS.UNSHARE, - disabled: true, name: T.Unshare, isConfirmDialog: true, - onSubmit: () => undefined, + dialogProps: { + title: T.Unshare, + children: (rows) => + MessageToConfirmAction( + rows, + T.UnshareVmTemplateDescription + ), + }, + onSubmit: (rows) => () => { + rows?.map?.(({ original }) => + changePermissions({ id: original?.ID, groupUse: '0' }) + ) + }, }, ], }, @@ -239,7 +305,7 @@ const Actions = () => { }, onSubmit: (rows) => async () => { const ids = rows?.map?.(({ original }) => original?.ID) - await Promise.all(ids.map((id) => unlock(id))) + await Promise.all(ids.map((id) => unlock({ id }))) }, }, ], diff --git a/src/fireedge/src/client/components/Tables/Vms/actions.js b/src/fireedge/src/client/components/Tables/Vms/actions.js index 1a4cacec58..53f13474b1 100644 --- a/src/fireedge/src/client/components/Tables/Vms/actions.js +++ b/src/fireedge/src/client/components/Tables/Vms/actions.js @@ -582,7 +582,7 @@ const Actions = () => { }, onSubmit: (rows) => async () => { const ids = rows?.map?.(({ original }) => original?.ID) - await Promise.all(ids.map((id) => unlock(id))) + await Promise.all(ids.map((id) => unlock({ id }))) }, }, ], diff --git a/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js b/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js index 4f80552322..1913db4f19 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Capacity/index.js @@ -33,7 +33,7 @@ import { getActionsAvailable, jsonToXml } from 'client/models/Helper' */ const VmCapacityTab = ({ tabProps: { actions } = {}, id }) => { const [resizeCapacity] = useResizeMutation() - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const actionsAvailable = useMemo(() => { const hypervisor = getHypervisor(vm) diff --git a/src/fireedge/src/client/components/Tabs/Vm/Configuration.js b/src/fireedge/src/client/components/Tabs/Vm/Configuration.js index 6f2667afa5..b5feedf5d3 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Configuration.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Configuration.js @@ -30,7 +30,7 @@ import { T } from 'client/constants' * @returns {ReactElement} Configuration tab */ const VmConfigurationTab = ({ id }) => { - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const { TEMPLATE, USER_TEMPLATE } = vm return ( diff --git a/src/fireedge/src/client/components/Tabs/Vm/History/index.js b/src/fireedge/src/client/components/Tabs/Vm/History/index.js index 7091988cb2..f9a16bf648 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/History/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/History/index.js @@ -36,7 +36,7 @@ import { getActionsAvailable } from 'client/models/Helper' * @returns {ReactElement} History tab */ const VmHistoryTab = ({ tabProps: { actions } = {}, id }) => { - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const [records, actionsAvailable] = useMemo(() => { const hypervisor = getHypervisor(vm) 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 f2d0fe7dc8..446b6c0421 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Info/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Info/index.js @@ -67,7 +67,7 @@ const VmInfoTab = ({ tabProps = {}, id }) => { attributes_panel: attributesPanel, } = tabProps - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const [changeVmOwnership] = useChangeVmOwnershipMutation() const [changeVmPermissions] = useChangeVmPermissionsMutation() const [updateUserTemplate] = useUpdateUserTemplateMutation() diff --git a/src/fireedge/src/client/components/Tabs/Vm/Network/index.js b/src/fireedge/src/client/components/Tabs/Vm/Network/index.js index 5f5e432c09..3acaa7bf70 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Network/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Network/index.js @@ -47,7 +47,7 @@ const { ATTACH_NIC, DETACH_NIC, ATTACH_SEC_GROUP, DETACH_SEC_GROUP } = * @returns {ReactElement} Networks tab */ const VmNetworkTab = ({ tabProps: { actions } = {}, id }) => { - const { data: vm } = useGetVmQuery(id) + const { data: vm } = useGetVmQuery({ id }) const [nics, hypervisor, actionsAvailable] = useMemo(() => { const groupedNics = getNics(vm, { diff --git a/src/fireedge/src/client/components/Tabs/Vm/SchedActions.js b/src/fireedge/src/client/components/Tabs/Vm/SchedActions.js index 99e1ddbab2..893446ddc0 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/SchedActions.js +++ b/src/fireedge/src/client/components/Tabs/Vm/SchedActions.js @@ -59,7 +59,7 @@ const VmSchedulingTab = ({ tabProps: { actions } = {}, id }) => { const [addScheduledAction] = useAddScheduledActionMutation() const [updateScheduledAction] = useUpdateScheduledActionMutation() const [deleteScheduledAction] = useDeleteScheduledActionMutation() - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const [scheduling, actionsAvailable] = useMemo(() => { const hypervisor = getHypervisor(vm) diff --git a/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js b/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js index 4e97f0d9f2..539f71ca5c 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Snapshot/index.js @@ -45,7 +45,7 @@ const { SNAPSHOT_CREATE, SNAPSHOT_REVERT, SNAPSHOT_DELETE } = VM_ACTIONS * @returns {ReactElement} Snapshots tab */ const VmSnapshotTab = ({ tabProps: { actions } = {}, id }) => { - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const [snapshots, actionsAvailable] = useMemo(() => { const hypervisor = getHypervisor(vm) diff --git a/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js b/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js index 949de22cb7..a2fe7aace0 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/Storage/index.js @@ -60,7 +60,7 @@ const { * @returns {ReactElement} Storage tab */ const VmStorageTab = ({ tabProps: { actions } = {}, id }) => { - const { data: vm = {} } = useGetVmQuery(id) + const { data: vm = {} } = useGetVmQuery({ id }) const [disks, hypervisor, actionsAvailable] = useMemo(() => { const hyperV = getHypervisor(vm) diff --git a/src/fireedge/src/client/components/Tabs/Vm/index.js b/src/fireedge/src/client/components/Tabs/Vm/index.js index b27aca9823..f7f3458611 100644 --- a/src/fireedge/src/client/components/Tabs/Vm/index.js +++ b/src/fireedge/src/client/components/Tabs/Vm/index.js @@ -46,9 +46,10 @@ const getTabComponent = (tabName) => const VmTabs = memo(({ id }) => { const { view, getResourceView } = useViews() - const { isLoading, isError, error } = useGetVmQuery(id, { - refetchOnMountOrArgChange: 10, - }) + const { isLoading, isError, error } = useGetVmQuery( + { id }, + { refetchOnMountOrArgChange: 10 } + ) const tabsAvailable = useMemo(() => { const resource = RESOURCE_NAMES.VM diff --git a/src/fireedge/src/client/components/Tabs/index.js b/src/fireedge/src/client/components/Tabs/index.js index 64bfccda4a..f30c4bc751 100644 --- a/src/fireedge/src/client/components/Tabs/index.js +++ b/src/fireedge/src/client/components/Tabs/index.js @@ -53,7 +53,7 @@ const Content = ({ key={`tab-${id ?? name}`} data-cy={`tab-content-${id ?? name}`} hidden={hidden} - border={addBorder} + border={addBorder ? 'true' : undefined} > diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index 5f91e94ca1..09b8047cd0 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -741,6 +741,12 @@ module.exports = { CloneWithImagesConcept: ` You can also clone any Image referenced inside this Template. They will be cloned to a new Image, and made persistent`, + ShareVmTemplateDescription: ` + The VM Template(s), along with any image referenced by it, will + be shared with the group's users. Permission changed: GROUP USE`, + UnshareVmTemplateDescription: ` + The VM Template(s), along with any image referenced by it, will + be unshared with the group's users. Permission changed: GROUP USE`, /* Virtual Network schema - network */ IP: 'IP', diff --git a/src/fireedge/src/client/features/OneApi/common.js b/src/fireedge/src/client/features/OneApi/common.js new file mode 100644 index 0000000000..4ff6fd9213 --- /dev/null +++ b/src/fireedge/src/client/features/OneApi/common.js @@ -0,0 +1,257 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2022, 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 { Draft, ThunkAction } from '@reduxjs/toolkit' + +import userApi from 'client/features/OneApi/user' +import groupApi from 'client/features/OneApi/group' +import { LockLevel, Permission, User, Group } from 'client/constants' +import { xmlToJson } from 'client/models/Helper' + +/** + * Update the pool of resources with the new data. + * + * @param {string} params - The parameters from query + * @param {string} [params.id] - The id of the resource + * @param {string} [params.resourceFromQuery] - The resource from query (user, group, ...) + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updateResourceOnPool = + ({ id: resourceId, resourceFromQuery }) => + (draft) => { + if (resourceId !== undefined && Array.isArray(draft)) return + + const index = draft.findIndex(({ ID }) => +ID === +resourceId) + index !== -1 && (draft[index] = resourceFromQuery) + } + +/** + * Remove the resource from the pool. + * + * @param {string} params - The parameters from query + * @param {string} [params.id] - The id of the resource + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const removeResourceOnPool = + ({ id: resourceId }) => + (draft) => { + if (resourceId !== undefined && Array.isArray(draft)) return + + draft.filter(({ ID }) => +ID !== +resourceId) + } + +/** + * Update the name of a resource in the store. + * + * @param {string} params - The parameters from query + * @param {string} [params.id] - The id of the resource + * @param {string} [params.name] - The name of the resource + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updateNameOnResource = + ({ id: resourceId, name: newName }) => + (draft) => { + const updatePool = resourceId !== undefined && Array.isArray(draft) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +resourceId) + : draft + + if ((updatePool && !resource) || newName !== undefined) return + + resource.NAME = newName + } + +/** + * Update the lock level of a resource in the store. + * + * @param {string} params - The parameters from query + * @param {string} [params.id] - The id of the resource + * @param {LockLevel} [params.level] - The new lock level + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updateLockLevelOnResource = + ({ id: resourceId, level = '4' }) => + (draft) => { + const updatePool = resourceId !== undefined && Array.isArray(draft) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +resourceId) + : draft + + if (updatePool && !resource) return + + resource.LOCK = { LOCKED: level } + } + +/** + * Update to unlock a resource in the store. + * + * @param {string} params - The parameters from query + * @param {string} [params.id] - The id of the resource + * @param {string} [params.level] - The new lock level + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const removeLockLevelOnResource = + ({ id: resourceId }) => + (draft) => { + const updatePool = resourceId !== undefined && Array.isArray(draft) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +resourceId) + : draft + + if (updatePool && !resource) return + + resource.LOCK = undefined + } + +/** + * Update the permissions of a resource in the store. + * + * @param {object} params - Request parameters + * @param {string} params.id - The id of the resource + * @param {Permission|'-1'} params.ownerUse - User use + * @param {Permission|'-1'} params.ownerManage - User manage + * @param {Permission|'-1'} params.ownerAdmin - User administrator + * @param {Permission|'-1'} params.groupUse - Group use + * @param {Permission|'-1'} params.groupManage - Group manage + * @param {Permission|'-1'} params.groupAdmin - Group administrator + * @param {Permission|'-1'} params.otherUse - Other use + * @param {Permission|'-1'} params.otherManage - Other manage + * @param {Permission|'-1'} params.otherAdmin - Other administrator + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updatePermissionOnResource = + ({ id: resourceId, ...permissions }) => + (draft) => { + const updatePool = resourceId !== undefined && Array.isArray(draft) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +resourceId) + : draft + + if (updatePool && !resource) return + + Object.entries(permissions) + .filter(([_, value]) => value !== '-1') + .forEach(([name, value]) => { + const ensuredName = { + ownerUse: 'OWNER_U', + ownerManage: 'OWNER_M', + ownerAdmin: 'OWNER_A', + groupUse: 'GROUP_U', + groupManage: 'GROUP_M', + groupAdmin: 'GROUP_A', + otherUse: 'OTHER_U', + otherManage: 'OTHER_M', + otherAdmin: 'OTHER_A', + }[name] + + resource.PERMISSIONS[ensuredName] = value + }) + } + +/** + * Select the users and groups from the current state. + * - If `options.userId` is provided, only the user with the given id will be selected. + * - If `options.groupId` is provided, only the group with the given id will be selected. + * + * @param {object} state - The current state + * @param {string} options - The options to filter the users and groups + * @param {string} options.userId - The user id + * @param {string} options.groupId - The group id + * @returns { + * { users: User[], groups: Group[] } | { user: User, group: Group } + * } - The users and groups or the user and group by id + */ +export const selectOwnershipFromState = (state, { userId, groupId } = {}) => { + const { data: users } = userApi.endpoints.getUsers.select()(state) + const { data: groups } = groupApi.endpoints.getGroups.select()(state) + + if (!userId && !groupId) return { users, groups } + + const user = users.find(({ ID }) => +ID === +userId) + const group = groups.find(({ ID }) => +ID === +groupId) + + return { user, group } +} + +/** + * Update the ownership of a resource in the store. + * + * @param {object} state - The current state + * @param {string} [params] - The parameters from query + * @param {string} [params.id] - The id of the resource + * @param {string} [params.user] - The user id to update + * @param {string} [params.group] - The group id to update + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updateOwnershipOnResource = ( + state, + { id: resourceId, user: userId, group: groupId } = {} +) => { + const { user, group } = selectOwnershipFromState(state, { userId, groupId }) + + return (draft) => { + const updatePool = resourceId !== undefined && Array.isArray(draft) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +resourceId) + : draft + + if (updatePool && !resource) return + + user?.ID > -1 && (resource.UID = user.ID) + user?.NAME !== undefined && (resource.UNAME = user.NAME) + + group?.ID > -1 && (resource.GID = group.ID) + group?.NAME !== undefined && (resource.GNAME = group.NAME) + } +} + +/** + * Update the template or user template of a resource in the store. + * + * @param {object} params - Request params + * @param {number|string} params.id - The id of the resource + * @param {string} params.template - The new user template contents on XML format + * @param {0|1} params.replace + * - Update type: + * ``0``: Replace the whole template. + * ``1``: Merge new template with the existing one. + * @param {string} [userTemplateAttribute] - The attribute name of the user template. By default is `USER_TEMPLATE`. + * @returns {function(Draft):ThunkAction} - Dispatches the action + */ +export const updateUserTemplateOnResource = + ( + { id: resourceId, template: xml, replace = 0 }, + userTemplateAttribute = 'USER_TEMPLATE' + ) => + (draft) => { + const updatePool = resourceId !== undefined && Array.isArray(draft) + const newTemplateJson = xmlToJson(xml) + + const resource = updatePool + ? draft.find(({ ID }) => +ID === +resourceId) + : draft + + if (updatePool && !resource) return + + resource[userTemplateAttribute] = + +replace === 0 + ? newTemplateJson + : { ...resource[userTemplateAttribute], ...newTemplateJson } + } diff --git a/src/fireedge/src/client/features/OneApi/socket.js b/src/fireedge/src/client/features/OneApi/socket.js index 5740d7d9ff..ac3cdc5b9e 100644 --- a/src/fireedge/src/client/features/OneApi/socket.js +++ b/src/fireedge/src/client/features/OneApi/socket.js @@ -80,7 +80,7 @@ const getResourceFromEventState = (data) => { const UpdateFromSocket = ({ updateQueryData, resource }) => async ( - id, + { id }, { cacheEntryRemoved, cacheDataLoaded, updateCachedData, getState, dispatch } ) => { const { zone } = getState().general diff --git a/src/fireedge/src/client/features/OneApi/vm.js b/src/fireedge/src/client/features/OneApi/vm.js index ab226b0fec..b14881b36a 100644 --- a/src/fireedge/src/client/features/OneApi/vm.js +++ b/src/fireedge/src/client/features/OneApi/vm.js @@ -18,15 +18,25 @@ import { Actions as ExtraActions, Commands as ExtraCommands, } from 'server/routes/api/vm/routes' + import { oneApi, ONE_RESOURCES, ONE_RESOURCES_POOL, } from 'client/features/OneApi' +import { + updateResourceOnPool, + removeResourceOnPool, + updateNameOnResource, + updateLockLevelOnResource, + removeLockLevelOnResource, + updatePermissionOnResource, + updateOwnershipOnResource, + updateUserTemplateOnResource, +} from 'client/features/OneApi/common' import { actions as guacamoleActions } from 'client/features/Guacamole/slice' import { UpdateFromSocket } from 'client/features/OneApi/socket' import http from 'client/utils/rest' -import { xmlToJson } from 'client/models/Helper' import { LockLevel, FilterFlag, @@ -44,7 +54,7 @@ const vmApi = oneApi.injectEndpoints({ * Retrieves information for all or part of * the VMs in the pool. * - * @param {object} params - Request params + * @param {object} params - Request parameters * @param {boolean} params.extended - Retrieves information for all or part * @param {FilterFlag} [params.filter] - Filter flag * @param {number} [params.start] - Range start ID @@ -85,32 +95,37 @@ const vmApi = oneApi.injectEndpoints({ /** * Retrieves information for the virtual machine. * - * @param {string} id - VM id + * @param {object} params - Request parameters + * @param {string} params.id - VM id * @returns {VmType} Get VM identified by id * @throws Fails when response isn't code 200 */ - query: (id) => { + query: (params) => { const name = Actions.VM_INFO const command = { name, ...Commands[name] } - return { params: { id }, command } + return { params, command } }, transformResponse: (data) => data?.VM ?? {}, providesTags: (_, __, id) => [{ type: VM, id }], async onQueryStarted(id, { dispatch, queryFulfilled }) { try { - const { data: queryVm } = await queryFulfilled + const { data: resourceFromQuery } = await queryFulfilled dispatch( - vmApi.util.updateQueryData('getVms', undefined, (draft) => { - const index = draft.findIndex(({ ID }) => +ID === +id) - index !== -1 && (draft[index] = queryVm) - }) + vmApi.util.updateQueryData( + 'getVms', + undefined, + updateResourceOnPool({ id, resourceFromQuery }) + ) ) } catch { + // if the query fails, we want to remove the resource from the pool dispatch( - vmApi.util.updateQueryData('getVms', undefined, (draft) => - draft.filter(({ ID }) => +ID !== +id) + vmApi.util.updateQueryData( + 'getVms', + undefined, + removeResourceOnPool({ id }) ) ) } @@ -590,7 +605,7 @@ const vmApi = oneApi.injectEndpoints({ * If set any permission to -1, it's not changed. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {Permission|'-1'} params.ownerUse - User use * @param {Permission|'-1'} params.ownerManage - User manage * @param {Permission|'-1'} params.ownerAdmin - User administrator @@ -610,33 +625,18 @@ const vmApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: VM, id }], - async onQueryStarted( - { id, ...permissions }, - { dispatch, queryFulfilled } - ) { - const patchResult = dispatch( - vmApi.util.updateQueryData('getVm', id, (draft) => { - Object.entries(permissions) - .filter(([_, value]) => value !== '-1') - .forEach(([name, value]) => { - const ensuredName = { - ownerUse: 'OWNER_U', - ownerManage: 'OWNER_M', - ownerAdmin: 'OWNER_A', - groupUse: 'GROUP_U', - groupManage: 'GROUP_M', - groupAdmin: 'GROUP_A', - otherUse: 'OTHER_U', - otherManage: 'OTHER_M', - otherAdmin: 'OTHER_A', - }[name] + async onQueryStarted(params, { dispatch, queryFulfilled }) { + try { + const patchVm = dispatch( + vmApi.util.updateQueryData( + 'getVm', + { id: params.id }, + updatePermissionOnResource(params) + ) + ) - draft.PERMISSIONS[ensuredName] = value - }) - }) - ) - - queryFulfilled.catch(patchResult.undo) + queryFulfilled.catch(patchVm.undo) + } catch {} }, }), changeVmOwnership: builder.mutation({ @@ -644,7 +644,7 @@ const vmApi = oneApi.injectEndpoints({ * Changes the ownership bits of a virtual machine. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {number} params.user - The user id * @param {number} params.group - The group id * @returns {number} Virtual machine id @@ -657,13 +657,26 @@ const vmApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: VM, id }], + async onQueryStarted(params, { getState, dispatch, queryFulfilled }) { + try { + const patchVm = dispatch( + vmApi.util.updateQueryData( + 'getVm', + { id: params.id }, + updateOwnershipOnResource(getState(), params) + ) + ) + + queryFulfilled.catch(patchVm.undo()) + } catch {} + }, }), renameVm: builder.mutation({ /** * Renames a virtual machine. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {string} params.name - The new name * @returns {number} Virtual machine id * @throws Fails when response isn't code 200 @@ -675,16 +688,28 @@ const vmApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: VM, id }], - async onQueryStarted({ id, name }, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - await queryFulfilled - - dispatch( - vmApi.util.updateQueryData('getVms', undefined, (draft) => { - const vm = draft.find(({ ID }) => +ID === +id) - vm && (vm.NAME = name) - }) + const patchVm = dispatch( + vmApi.util.updateQueryData( + 'getVm', + { id: params.id }, + updateNameOnResource(params) + ) ) + + const patchVms = dispatch( + vmApi.util.updateQueryData( + 'getVms', + undefined, + updateNameOnResource(params) + ) + ) + + queryFulfilled.catch(() => { + patchVm.undo() + patchVms.undo() + }) } catch {} }, }), @@ -693,7 +718,7 @@ const vmApi = oneApi.injectEndpoints({ * Creates a new virtual machine snapshot. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {string} params.name - The new snapshot name * @returns {number} Virtual machine id * @throws Fails when response isn't code 200 @@ -711,8 +736,8 @@ const vmApi = oneApi.injectEndpoints({ * Reverts a virtual machine to a snapshot. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id - * @param {string|number} params.snapshot - The snapshot id + * @param {string} params.id - Virtual machine id + * @param {string} params.snapshot - The snapshot id * @returns {number} Virtual machine id * @throws Fails when response isn't code 200 */ @@ -729,8 +754,8 @@ const vmApi = oneApi.injectEndpoints({ * Deletes a virtual machine snapshot. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id - * @param {string|number} params.snapshot - The snapshot id + * @param {string} params.id - Virtual machine id + * @param {string} params.snapshot - The snapshot id * @returns {number} Virtual machine id * @throws Fails when response isn't code 200 */ @@ -747,7 +772,7 @@ const vmApi = oneApi.injectEndpoints({ * Changes the capacity of the virtual machine. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} 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 @@ -766,7 +791,7 @@ const vmApi = oneApi.injectEndpoints({ * Replaces the user template contents. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {string} params.template - The new user template contents on syntax XML * @param {0|1} params.replace * - Update type: @@ -782,33 +807,22 @@ const vmApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: VM, id }], - async onQueryStarted( - { id, template: xml, replace = 0 }, - { dispatch, queryFulfilled } - ) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - // update user template by id const patchVm = dispatch( - vmApi.util.updateQueryData('getVm', id, (draft) => { - draft.USER_TEMPLATE = - +replace === 0 - ? xmlToJson(xml) - : { ...draft.USER_TEMPLATE, ...xmlToJson(xml) } - }) + vmApi.util.updateQueryData( + 'getVm', + { id: params.id }, + updateUserTemplateOnResource(params) + ) ) - // update user template on pool by id (if exists) const patchVms = dispatch( - vmApi.util.updateQueryData('getVms', undefined, (draft) => { - const vm = draft.find(({ ID }) => +ID === +id) - - if (!vm) return - - vm.USER_TEMPLATE = - +replace === 0 - ? xmlToJson(xml) - : { ...vm.USER_TEMPLATE, ...xmlToJson(xml) } - }) + vmApi.util.updateQueryData( + 'getVms', + undefined, + updateUserTemplateOnResource(params) + ) ) queryFulfilled.catch(() => { @@ -823,7 +837,7 @@ const vmApi = oneApi.injectEndpoints({ * Updates (appends) a set of supported configuration attributes in the VM template. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {string} params.template - The new configuration contents on syntax XML * @returns {number} Virtual machine id * @throws Fails when response isn't code 200 @@ -845,7 +859,7 @@ const vmApi = oneApi.injectEndpoints({ * if the operation was successful or not. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {0|1|2|3|4} params.operation - Recover operation: * failure (0), success (1), retry (2), delete (3), delete-recreate (4) * @returns {number} Virtual machine id @@ -864,7 +878,7 @@ const vmApi = oneApi.injectEndpoints({ * Locks a Virtual Machine. Lock certain actions depending on blocking level. * * @param {object} params - Request parameters - * @param {string|number} params.id - Virtual machine id + * @param {string} params.id - Virtual machine id * @param {LockLevel} params.level - Lock level * @param {boolean} params.test - Checks if the object is already locked to return an error * @returns {number} Virtual machine id @@ -877,16 +891,28 @@ const vmApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: VM, id }], - async onQueryStarted({ id, level = '4' }, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - await queryFulfilled - - dispatch( - vmApi.util.updateQueryData('getVms', undefined, (draft) => { - const vm = draft.find(({ ID }) => +ID === +id) - vm && (vm.LOCK = { LOCKED: level }) - }) + const patchVm = dispatch( + vmApi.util.updateQueryData( + 'getVm', + { id: params.id }, + updateLockLevelOnResource(params) + ) ) + + const patchVms = dispatch( + vmApi.util.updateQueryData( + 'getVms', + undefined, + updateLockLevelOnResource(params) + ) + ) + + queryFulfilled.catch(() => { + patchVm.undo() + patchVms.undo() + }) } catch {} }, }), @@ -894,27 +920,40 @@ const vmApi = oneApi.injectEndpoints({ /** * Unlocks a Virtual Machine. * - * @param {string|number} id - Virtual machine id + * @param {object} params - Request parameters + * @param {string} params.id - Virtual machine id * @returns {number} Virtual machine id * @throws Fails when response isn't code 200 */ - query: (id) => { + query: (params) => { const name = Actions.VM_UNLOCK const command = { name, ...Commands[name] } - return { params: { id }, command } + return { params, command } }, invalidatesTags: (_, __, id) => [{ type: VM, id }], - async onQueryStarted(id, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - await queryFulfilled - - dispatch( - vmApi.util.updateQueryData('getVms', undefined, (draft) => { - const vm = draft.find(({ ID }) => +ID === +id) - vm && (vm.LOCK = undefined) - }) + const patchVm = dispatch( + vmApi.util.updateQueryData( + 'getVm', + { id: params.id }, + removeLockLevelOnResource(params) + ) ) + + const patchVms = dispatch( + vmApi.util.updateQueryData( + 'getVms', + undefined, + removeLockLevelOnResource(params) + ) + ) + + queryFulfilled.catch(() => { + patchVm.undo() + patchVms.undo() + }) } catch {} }, }), diff --git a/src/fireedge/src/client/features/OneApi/vmTemplate.js b/src/fireedge/src/client/features/OneApi/vmTemplate.js index f6bfa32e89..db7bd975d1 100644 --- a/src/fireedge/src/client/features/OneApi/vmTemplate.js +++ b/src/fireedge/src/client/features/OneApi/vmTemplate.js @@ -14,13 +14,23 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import { Actions, Commands } from 'server/utils/constants/commands/template' + import { oneApi, ONE_RESOURCES, ONE_RESOURCES_POOL, } from 'client/features/OneApi' +import { + updateResourceOnPool, + removeResourceOnPool, + updateNameOnResource, + updateLockLevelOnResource, + removeLockLevelOnResource, + updatePermissionOnResource, + updateOwnershipOnResource, + updateUserTemplateOnResource, +} from 'client/features/OneApi/common' import { LockLevel, FilterFlag, Permission, VmTemplate } from 'client/constants' -import { xmlToJson } from 'client/models/Helper' const { TEMPLATE } = ONE_RESOURCES const { TEMPLATE_POOL, VM_POOL } = ONE_RESOURCES_POOL @@ -49,7 +59,10 @@ const vmTemplateApi = oneApi.injectEndpoints({ providesTags: (vmTemplates) => vmTemplates ? [ - ...vmTemplates.map(({ ID }) => ({ type: TEMPLATE_POOL, ID })), + ...vmTemplates.map(({ ID }) => ({ + type: TEMPLATE_POOL, + id: `${ID}`, + })), TEMPLATE_POOL, ] : [TEMPLATE_POOL], @@ -75,19 +88,25 @@ const vmTemplateApi = oneApi.injectEndpoints({ providesTags: (_, __, { id }) => [{ type: TEMPLATE, id }], async onQueryStarted({ id }, { dispatch, queryFulfilled }) { try { - const { data: queryTemplate } = await queryFulfilled + const { data: resourceFromQuery } = await queryFulfilled dispatch( vmTemplateApi.util.updateQueryData( 'getTemplates', undefined, - (draft) => { - const index = draft.findIndex(({ ID }) => +ID === +id) - index !== -1 && (draft[index] = queryTemplate) - } + updateResourceOnPool({ id, resourceFromQuery }) ) ) - } catch {} + } catch { + // if the query fails, we want to remove the resource from the pool + dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplates', + undefined, + removeResourceOnPool({ id }) + ) + ) + } }, }), allocateTemplate: builder.mutation({ @@ -188,36 +207,21 @@ const vmTemplateApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: TEMPLATE, id }], - async onQueryStarted( - { id, template: xml, replace = 0 }, - { dispatch, queryFulfilled } - ) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - // update template by id const patchVmTemplate = dispatch( - vmTemplateApi.util.updateQueryData('getTemplate', id, (draft) => { - draft.TEMPLATE = - +replace === 0 - ? xmlToJson(xml) - : { ...draft.TEMPLATE, ...xmlToJson(xml) } - }) + vmTemplateApi.util.updateQueryData( + 'getTemplate', + { id: params.id }, + updateUserTemplateOnResource(params, 'TEMPLATE') + ) ) - // update template on pool by id (if exists) const patchVmTemplates = dispatch( vmTemplateApi.util.updateQueryData( 'getTemplates', undefined, - (draft) => { - const template = draft.find(({ ID }) => +ID === +id) - - if (!template) return - - template.TEMPLATE = - +replace === 0 - ? xmlToJson(xml) - : { ...template.TEMPLATE, ...xmlToJson(xml) } - } + updateUserTemplateOnResource(params, 'TEMPLATE') ) ) @@ -234,7 +238,7 @@ const vmTemplateApi = oneApi.injectEndpoints({ * If set any permission to -1, it's not changed. * * @param {object} params - Request parameters - * @param {string|number} params.id - VM Template id + * @param {string} params.id - VM Template id * @param {Permission|'-1'} params.ownerUse - User use * @param {Permission|'-1'} params.ownerManage - User manage * @param {Permission|'-1'} params.ownerAdmin - User administrator @@ -255,33 +259,29 @@ const vmTemplateApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: TEMPLATE, id }], - async onQueryStarted( - { id, ...permissions }, - { dispatch, queryFulfilled } - ) { - const patchResult = dispatch( - vmTemplateApi.util.updateQueryData('getTemplate', { id }, (draft) => { - Object.entries(permissions) - .filter(([_, value]) => value !== '-1') - .forEach(([name, value]) => { - const ensuredName = { - ownerUse: 'OWNER_U', - ownerManage: 'OWNER_M', - ownerAdmin: 'OWNER_A', - groupUse: 'GROUP_U', - groupManage: 'GROUP_M', - groupAdmin: 'GROUP_A', - otherUse: 'OTHER_U', - otherManage: 'OTHER_M', - otherAdmin: 'OTHER_A', - }[name] + async onQueryStarted(params, { dispatch, queryFulfilled }) { + try { + const patchVmTemplate = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplate', + { id: params.id }, + updatePermissionOnResource(params) + ) + ) - draft.PERMISSIONS[ensuredName] = value - }) + const patchVmTemplates = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplates', + undefined, + updatePermissionOnResource(params) + ) + ) + + queryFulfilled.catch(() => { + patchVmTemplate.undo() + patchVmTemplates.undo() }) - ) - - queryFulfilled.catch(patchResult.undo) + } catch {} }, }), changeTemplateOwnership: builder.mutation({ @@ -303,18 +303,29 @@ const vmTemplateApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: TEMPLATE, id }], - async onQueryStarted( - { id, user, group }, - { dispatch, queryFulfilled, getState } - ) { - const patchResult = dispatch( - vmTemplateApi.util.updateQueryData('getTemplate', id, (draft) => { - user > 0 && (draft.UID = user) - group > 0 && (draft.GID = group) - }) - ) + async onQueryStarted(params, { getState, dispatch, queryFulfilled }) { + try { + const patchVmTemplate = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplate', + { id: params.id }, + updateOwnershipOnResource(getState(), params) + ) + ) - queryFulfilled.catch(patchResult.undo) + const patchVmTemplates = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplates', + undefined, + updateOwnershipOnResource(getState(), params) + ) + ) + + queryFulfilled.catch(() => { + patchVmTemplate.undo() + patchVmTemplates.undo() + }) + } catch {} }, }), renameTemplate: builder.mutation({ @@ -334,20 +345,28 @@ const vmTemplateApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: TEMPLATE, id }], - async onQueryStarted({ id, name }, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - await queryFulfilled + const patchVmTemplate = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplate', + { id: params.id }, + updateNameOnResource(params) + ) + ) - dispatch( + const patchVmTemplates = dispatch( vmTemplateApi.util.updateQueryData( 'getTemplates', undefined, - (draft) => { - const template = draft.find(({ ID }) => +ID === +id) - template && (template.NAME = name) - } + updateNameOnResource(params) ) ) + + queryFulfilled.catch(() => { + patchVmTemplate.undo() + patchVmTemplates.undo() + }) } catch {} }, }), @@ -356,7 +375,7 @@ const vmTemplateApi = oneApi.injectEndpoints({ * Locks a VM Template. * * @param {object} params - Request parameters - * @param {string|number} params.id - VM Template id + * @param {string} params.id - VM Template id * @param {LockLevel} params.lock - Lock level * @param {boolean} params.test - Checks if the object is already locked to return an error * @returns {number} VM Template id @@ -369,20 +388,28 @@ const vmTemplateApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: TEMPLATE, id }], - async onQueryStarted({ id, level = '4' }, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - await queryFulfilled + const patchVmTemplate = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplate', + { id: params.id }, + updateLockLevelOnResource(params) + ) + ) - dispatch( + const patchVmTemplates = dispatch( vmTemplateApi.util.updateQueryData( 'getTemplates', undefined, - (draft) => { - const template = draft.find(({ ID }) => +ID === +id) - template && (template.LOCK = { LOCKED: level }) - } + updateLockLevelOnResource(params) ) ) + + queryFulfilled.catch(() => { + patchVmTemplate.undo() + patchVmTemplates.undo() + }) } catch {} }, }), @@ -390,31 +417,40 @@ const vmTemplateApi = oneApi.injectEndpoints({ /** * Unlocks a VM Template. * - * @param {string|number} id - VM Template id + * @param {object} params - Request parameters + * @param {string} params.id - VM Template id * @returns {number} VM Template id * @throws Fails when response isn't code 200 */ - query: (id) => { + query: (params) => { const name = Actions.TEMPLATE_UNLOCK const command = { name, ...Commands[name] } - return { params: { id }, command } + return { params, command } }, invalidatesTags: (_, __, id) => [{ type: TEMPLATE, id }], - async onQueryStarted(id, { dispatch, queryFulfilled }) { + async onQueryStarted(params, { dispatch, queryFulfilled }) { try { - await queryFulfilled + const patchVmTemplate = dispatch( + vmTemplateApi.util.updateQueryData( + 'getTemplate', + { id: params.id }, + removeLockLevelOnResource(params) + ) + ) - dispatch( + const patchVmTemplates = dispatch( vmTemplateApi.util.updateQueryData( 'getTemplates', undefined, - (draft) => { - const template = draft.find(({ ID }) => +ID === +id) - template && (template.LOCK = undefined) - } + removeLockLevelOnResource(params) ) ) + + queryFulfilled.catch(() => { + patchVmTemplate.undo() + patchVmTemplates.undo() + }) } catch {} }, }),