diff --git a/src/fireedge/src/client/apps/sunstone/routesOne.js b/src/fireedge/src/client/apps/sunstone/routesOne.js index 463f08ac40..2a14e380ad 100644 --- a/src/fireedge/src/client/apps/sunstone/routesOne.js +++ b/src/fireedge/src/client/apps/sunstone/routesOne.js @@ -207,6 +207,12 @@ const ClusterDetail = loadable( () => import('client/containers/Clusters/Detail'), { ssr: false } ) +const CreateCluster = loadable( + () => import('client/containers/Clusters/Create'), + { + ssr: false, + } +) const Hosts = loadable(() => import('client/containers/Hosts'), { ssr: false }) const HostDetail = loadable(() => import('client/containers/Hosts/Detail'), { ssr: false, @@ -358,6 +364,7 @@ export const PATH = { CLUSTERS: { LIST: `/${RESOURCE_NAMES.CLUSTER}`, DETAIL: `/${RESOURCE_NAMES.CLUSTER}/:id`, + CREATE: `/${RESOURCE_NAMES.CLUSTER}/create`, }, HOSTS: { LIST: `/${RESOURCE_NAMES.HOST}`, @@ -687,6 +694,11 @@ const ENDPOINTS = [ icon: ClusterIcon, Component: Clusters, }, + { + title: T.CreateCluster, + path: PATH.INFRASTRUCTURE.CLUSTERS.CREATE, + Component: CreateCluster, + }, { title: T.Cluster, description: (params) => `#${params?.id}`, diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Datastores/index.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Datastores/index.js new file mode 100644 index 0000000000..757972c09e --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Datastores/index.js @@ -0,0 +1,104 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 PropTypes from 'prop-types' +import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { T } from 'client/constants' +import { SCHEMA, FIELDS } from './schema' +import { Grid, Card, CardContent, Typography } from '@mui/material' +import { Tr } from 'client/components/HOC' +import { generateDocLink } from 'client/utils' + +export const STEP_ID = 'datastores' + +const Content = (version) => ( + + + + + + + + + {Tr(T['cluster.form.create.datastores.help.title'])} + + + {Tr(T['cluster.form.create.datastores.help.paragraph.1'])} + + + {Tr(T['cluster.form.create.datastores.help.paragraph.2'])} + + + {Tr(T['cluster.form.create.datastores.help.paragraph.3'])} + + + {' '} + + {Tr(T['cluster.form.create.help.link'])} + + + + + + +) + +/** + * Datastores Cluster configuration. + * + * @param {object} props - Step properties + * @param {string} props.version - OpeNebula version + * @returns {object} Datastores configuration step + */ +const Datastores = ({ version }) => ({ + id: STEP_ID, + label: T.SelectDatastores, + resolver: SCHEMA, + optionsValidate: { abortEarly: false }, + content: () => Content(version), +}) + +Datastores.propTypes = { + data: PropTypes.object, + setFormData: PropTypes.func, +} + +export default Datastores diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Datastores/schema.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Datastores/schema.js new file mode 100644 index 0000000000..a1d6581c81 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Datastores/schema.js @@ -0,0 +1,39 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { INPUT_TYPES, T } from 'client/constants' +import { Field, getObjectSchemaFromFields } from 'client/utils' +import { string, array } from 'yup' +import { DatastoresTable } from 'client/components/Tables' + +/** @type {Field} Datastores field */ +const DATASTORES = { + name: 'ID', + label: T.SelectDatastores, + type: INPUT_TYPES.TABLE, + Table: () => DatastoresTable, + singleSelect: false, + validation: array(string().trim()).default(() => undefined), + grid: { md: 12 }, + fieldProps: { + preserveState: true, + }, +} + +const FIELDS = [DATASTORES] + +const SCHEMA = getObjectSchemaFromFields(FIELDS) + +export { SCHEMA, FIELDS } diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/General/index.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/General/index.js new file mode 100644 index 0000000000..65e5ced213 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/General/index.js @@ -0,0 +1,118 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 PropTypes from 'prop-types' +import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { T } from 'client/constants' +import { SCHEMA, FIELDS } from './schema' +import { Grid, Card, CardContent, Typography } from '@mui/material' +import { Tr } from 'client/components/HOC' +import { generateDocLink } from 'client/utils' + +export const STEP_ID = 'general' + +const Content = (version) => ( + + + + + + + + + {' '} + {Tr(T['cluster.form.create.general.help.title'])}{' '} + + + + {Tr(T['cluster.form.create.general.help.paragraph.1.1'])}{' '} + + +
    +
  • + + {Tr(T['cluster.form.create.general.help.paragraph.1.2'])}{' '} + +
  • +
  • + + {Tr(T['cluster.form.create.general.help.paragraph.1.3'])}{' '} + +
  • +
+ + + {Tr(T['cluster.form.create.general.help.paragraph.2'])}{' '} + + + + {' '} + + {Tr(T['cluster.form.create.help.link'])} + + +
+
+
+
+) + +/** + * General Cluster configuration. + * + * @param {object} props - Step properties + * @param {string} props.version - OpeNebula version + * @returns {object} General configuration step + */ +const General = ({ version }) => ({ + id: STEP_ID, + label: T.General, + resolver: SCHEMA, + optionsValidate: { abortEarly: false }, + content: () => Content(version), +}) + +General.propTypes = { + data: PropTypes.object, + setFormData: PropTypes.func, +} + +export default General diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/General/schema.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/General/schema.js new file mode 100644 index 0000000000..52095814de --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/General/schema.js @@ -0,0 +1,36 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { INPUT_TYPES, T } from 'client/constants' +import { Field, getObjectSchemaFromFields } from 'client/utils' +import { string } from 'yup' + +/** @type {Field} Name field */ +const NAME = { + name: 'NAME', + label: T['cluster.create.name'], + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .required() + .default(() => undefined), + grid: { md: 12 }, +} + +const FIELDS = [NAME] + +const SCHEMA = getObjectSchemaFromFields(FIELDS) + +export { SCHEMA, FIELDS } diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Hosts/index.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Hosts/index.js new file mode 100644 index 0000000000..b33d47d9ea --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Hosts/index.js @@ -0,0 +1,104 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 PropTypes from 'prop-types' +import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { T } from 'client/constants' +import { SCHEMA, FIELDS } from './schema' +import { Grid, Card, CardContent, Typography } from '@mui/material' +import { Tr } from 'client/components/HOC' +import { generateDocLink } from 'client/utils' + +export const STEP_ID = 'hosts' + +const Content = (version) => ( + + + + + + + + + {' '} + {Tr(T['cluster.form.create.hosts.help.title'])}{' '} + + + {' '} + {Tr(T['cluster.form.create.hosts.help.paragraph.1'])}{' '} + + + {' '} + {Tr(T['cluster.form.create.hosts.help.paragraph.2'])}{' '} + + + {' '} + + {Tr(T['cluster.form.create.help.link'])} + + + + + + +) + +/** + * Hosts Cluster configuration. + * + * @param {object} props - Step properties + * @param {string} props.version - OpeNebula version + * @returns {object} Hosts configuration step + */ +const Hosts = ({ version }) => ({ + id: STEP_ID, + label: T.SelectHosts, + resolver: SCHEMA, + optionsValidate: { abortEarly: false }, + content: () => Content(version), +}) + +Hosts.propTypes = { + data: PropTypes.object, + setFormData: PropTypes.func, +} + +export default Hosts diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Hosts/schema.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Hosts/schema.js new file mode 100644 index 0000000000..7a91448137 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Hosts/schema.js @@ -0,0 +1,39 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { INPUT_TYPES, T } from 'client/constants' +import { Field, getObjectSchemaFromFields } from 'client/utils' +import { string, array } from 'yup' +import { HostsTable } from 'client/components/Tables' + +/** @type {Field} HostsTable field */ +const HOSTS = { + name: 'ID', + label: T.SelectHosts, + type: INPUT_TYPES.TABLE, + Table: () => HostsTable, + singleSelect: false, + validation: array(string().trim()).default(() => undefined), + grid: { md: 12 }, + fieldProps: { + preserveState: true, + }, +} + +const FIELDS = [HOSTS] + +const SCHEMA = getObjectSchemaFromFields(FIELDS) + +export { SCHEMA, FIELDS } diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Vnets/index.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Vnets/index.js new file mode 100644 index 0000000000..833f4c808b --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Vnets/index.js @@ -0,0 +1,101 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 PropTypes from 'prop-types' +import FormWithSchema from 'client/components/Forms/FormWithSchema' +import { T } from 'client/constants' +import { SCHEMA, FIELDS } from './schema' +import { Grid, Card, CardContent, Typography } from '@mui/material' +import { Tr } from 'client/components/HOC' +import { generateDocLink } from 'client/utils' + +export const STEP_ID = 'vnets' + +const Content = (version) => ( + + + + + + + + + {Tr(T['cluster.form.create.vnets.help.title'])} + + + {Tr(T['cluster.form.create.vnets.help.paragraph.1'])} + + + {Tr(T['cluster.form.create.vnets.help.paragraph.2'])} + + + {' '} + + {Tr(T['cluster.form.create.help.link'])} + + + + + + +) + +/** + * Vnets Cluster configuration. + * + * @param {object} props - Step properties + * @param {string} props.version - OpeNebula version + * @returns {object} Vnets configuration step + */ +const Vnets = ({ version }) => ({ + id: STEP_ID, + label: T.SelectVirtualNetworks, + resolver: SCHEMA, + optionsValidate: { abortEarly: false }, + content: () => Content(version), +}) + +Vnets.propTypes = { + data: PropTypes.object, + setFormData: PropTypes.func, +} + +export default Vnets diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Vnets/schema.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Vnets/schema.js new file mode 100644 index 0000000000..4f5625e993 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/Vnets/schema.js @@ -0,0 +1,39 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { INPUT_TYPES, T } from 'client/constants' +import { Field, getObjectSchemaFromFields } from 'client/utils' +import { string, array } from 'yup' +import { VNetworksTable } from 'client/components/Tables' + +/** @type {Field} Vnets field */ +const VNETS = { + name: 'ID', + label: T.SelectVirtualNetworks, + type: INPUT_TYPES.TABLE, + Table: () => VNetworksTable, + singleSelect: false, + validation: array(string().trim()).default(() => undefined), + grid: { md: 12 }, + fieldProps: { + preserveState: true, + }, +} + +const FIELDS = [VNETS] + +const SCHEMA = getObjectSchemaFromFields(FIELDS) + +export { SCHEMA, FIELDS } diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/index.js new file mode 100644 index 0000000000..de0ae21fb0 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/Steps/index.js @@ -0,0 +1,135 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 General, { + STEP_ID as GENERAL_ID, +} from 'client/components/Forms/Cluster/CreateForm/Steps/General' + +import Hosts, { + STEP_ID as HOSTS_ID, +} from 'client/components/Forms/Cluster/CreateForm/Steps/Hosts' + +import Vnets, { + STEP_ID as VNETS_ID, +} from 'client/components/Forms/Cluster/CreateForm/Steps/Vnets' + +import Datastores, { + STEP_ID as DATASTORES_ID, +} from 'client/components/Forms/Cluster/CreateForm/Steps/Datastores' + +import { createSteps } from 'client/utils' +const _ = require('lodash') + +/** + * Create steps for Cluster Create Form: + * 1. General: Name of the cluster + * 2. Hosts: Select hosts + * 3. Vnets: Select virtual networks + * 4. Datastores: Select datastores + */ +const Steps = createSteps([General, Hosts, Vnets, Datastores], { + transformInitialValue: (cluster, schema) => { + const knownTemplate = schema.cast( + { + [GENERAL_ID]: { NAME: cluster.NAME }, + [HOSTS_ID]: !_.isEmpty(cluster?.HOSTS) + ? { + ID: Array.isArray(cluster?.HOSTS?.ID) + ? cluster?.HOSTS?.ID + : [cluster?.HOSTS?.ID], + } + : undefined, + [VNETS_ID]: !_.isEmpty(cluster?.VNETS) + ? { + ID: Array.isArray(cluster?.VNETS?.ID) + ? cluster?.VNETS?.ID + : [cluster?.VNETS?.ID], + } + : undefined, + [DATASTORES_ID]: !_.isEmpty(cluster?.DATASTORES) + ? { + ID: Array.isArray(cluster?.DATASTORES?.ID) + ? cluster?.DATASTORES?.ID + : [cluster?.DATASTORES?.ID], + } + : undefined, + }, + { + stripUnknown: true, + } + ) + + return knownTemplate + }, + transformBeforeSubmit: (formData, initialValues) => { + const update = !!initialValues + + if (update) { + // Get hosts to add and hosts to delete + const initialHosts = !_.isEmpty(initialValues?.HOSTS?.ID) + ? Array.isArray(initialValues?.HOSTS?.ID) + ? initialValues?.HOSTS?.ID + : [initialValues?.HOSTS?.ID] + : undefined + const addHosts = _.difference(formData?.hosts?.ID, initialHosts) + const removeHosts = _.difference(initialHosts, formData?.hosts?.ID) + + // Get vnets to add and vnets to delete + const initialVnets = !_.isEmpty(initialValues?.VNETS?.ID) + ? Array.isArray(initialValues?.VNETS?.ID) + ? initialValues?.VNETS?.ID + : [initialValues?.VNETS?.ID] + : undefined + const addVnets = _.difference(formData?.vnets?.ID, initialVnets) + const removeVnets = _.difference(initialVnets, formData?.vnets?.ID) + + // Get datastores to add and datastores to delete + const initialDatastores = !_.isEmpty(initialValues?.DATASTORES?.ID) + ? Array.isArray(initialValues?.DATASTORES?.ID) + ? initialValues?.DATASTORES?.ID + : [initialValues?.DATASTORES?.ID] + : undefined + const addDatastores = _.difference( + formData?.datastores?.ID, + initialDatastores + ) + const removeDatastores = _.difference( + initialDatastores, + formData?.datastores?.ID + ) + + // Check if the name has been changed + const changeName = + initialValues?.NAME === formData?.general?.NAME + ? undefined + : formData?.general?.NAME + + return { + ...formData, + addHosts, + removeHosts, + addVnets, + removeVnets, + addDatastores, + removeDatastores, + changeName, + } + } + + return formData + }, +}) + +export default Steps diff --git a/src/fireedge/src/client/components/Forms/Cluster/CreateForm/index.js b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/index.js new file mode 100644 index 0000000000..4fde39ca39 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/Cluster/CreateForm/index.js @@ -0,0 +1,16 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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. * + * ------------------------------------------------------------------------- */ +export { default } from 'client/components/Forms/Cluster/CreateForm/Steps' diff --git a/src/fireedge/src/client/components/Forms/Cluster/index.js b/src/fireedge/src/client/components/Forms/Cluster/index.js index 9324be0326..0ac79305c8 100644 --- a/src/fireedge/src/client/components/Forms/Cluster/index.js +++ b/src/fireedge/src/client/components/Forms/Cluster/index.js @@ -24,4 +24,11 @@ import { CreateFormCallback } from 'client/utils/schema' const ChangeClusterForm = (configProps) => AsyncLoadForm({ formPath: 'Cluster/ChangeClusterForm' }, configProps) -export { ChangeClusterForm } +/** + * @param {ConfigurationProps} configProps - Configuration + * @returns {ReactElement|CreateFormCallback} Asynchronous loaded form + */ +const CreateForm = (configProps) => + AsyncLoadForm({ formPath: 'Cluster/CreateForm' }, configProps) + +export { ChangeClusterForm, CreateForm } diff --git a/src/fireedge/src/client/components/Tables/Clusters/actions.js b/src/fireedge/src/client/components/Tables/Clusters/actions.js new file mode 100644 index 0000000000..b89b023c6f --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Clusters/actions.js @@ -0,0 +1,121 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { Typography } from '@mui/material' +import { AddCircledOutline, Trash } from 'iconoir-react' +import { useMemo } from 'react' +import { useHistory } from 'react-router-dom' + +import { useViews } from 'client/features/Auth' +import { useRemoveClusterMutation } from 'client/features/OneApi/cluster' + +import { + createActions, + GlobalAction, +} from 'client/components/Tables/Enhanced/Utils' + +import { PATH } from 'client/apps/sunstone/routesOne' +import { Translate } from 'client/components/HOC' +import { RESOURCE_NAMES, T, CLUSTER_ACTIONS } from 'client/constants' + +const ListClusterNames = ({ rows = [] }) => + rows?.map?.(({ id, original }) => { + const { ID, NAME } = original + + return ( + + {`#${ID} ${NAME}`} + + ) + }) + +const MessageToConfirmAction = (rows, description) => ( + <> + + {description && } + + +) + +MessageToConfirmAction.displayName = 'MessageToConfirmAction' + +/** + * Generates the actions to operate resources on Clusters table. + * + * @returns {GlobalAction} - Actions + */ +const Actions = () => { + const history = useHistory() + const { view, getResourceView } = useViews() + const [remove] = useRemoveClusterMutation() + + return useMemo( + () => + createActions({ + filters: getResourceView(RESOURCE_NAMES.CLUSTER)?.actions, + actions: [ + { + accessor: CLUSTER_ACTIONS.CREATE_DIALOG, + tooltip: T.Create, + icon: AddCircledOutline, + action: () => history.push(PATH.INFRASTRUCTURE.CLUSTERS.CREATE), + }, + { + accessor: CLUSTER_ACTIONS.UPDATE_DIALOG, + label: T.Update, + tooltip: T.Update, + selected: { max: 1 }, + color: 'secondary', + action: (rows) => { + const cluster = rows?.[0]?.original ?? {} + const path = PATH.INFRASTRUCTURE.CLUSTERS.CREATE + + history.push(path, cluster) + }, + }, + { + accessor: CLUSTER_ACTIONS.DELETE, + tooltip: T.Delete, + icon: Trash, + color: 'error', + selected: { min: 1 }, + dataCy: `cluster_${CLUSTER_ACTIONS.DELETE}`, + options: [ + { + isConfirmDialog: true, + dialogProps: { + title: T.Delete, + dataCy: `modal-${CLUSTER_ACTIONS.DELETE}`, + children: MessageToConfirmAction, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => remove({ id }))) + }, + }, + ], + }, + ], + }), + [view] + ) +} + +export default Actions diff --git a/src/fireedge/src/client/components/Tables/Clusters/row.js b/src/fireedge/src/client/components/Tables/Clusters/row.js index 752c3bad0e..c3a4684c2a 100644 --- a/src/fireedge/src/client/components/Tables/Clusters/row.js +++ b/src/fireedge/src/client/components/Tables/Clusters/row.js @@ -29,23 +29,29 @@ const Row = ({ original, value, ...props }) => {
- + {NAME}
- {`#${ID}`} - + {`#${ID}`} + - {` ${HOSTS}`} + {`${HOSTS}`} - - - {` ${DATASTORES}`} - - + - {` ${VNETS}`} + {`${VNETS}`} + + + + {`${DATASTORES}`} {PROVIDER_NAME && ( diff --git a/src/fireedge/src/client/components/Tables/Datastores/index.js b/src/fireedge/src/client/components/Tables/Datastores/index.js index 5c88d540c9..6781c56ed7 100644 --- a/src/fireedge/src/client/components/Tables/Datastores/index.js +++ b/src/fireedge/src/client/components/Tables/Datastores/index.js @@ -54,7 +54,7 @@ const DatastoresTable = (props) => { let values - if (typeof filter === 'function') { + if (typeof filter === 'function' && dependOf) { const { watch } = useFormContext() const getDataForDepend = useCallback( diff --git a/src/fireedge/src/client/components/Tables/Hosts/index.js b/src/fireedge/src/client/components/Tables/Hosts/index.js index dfba785de5..39b2815130 100644 --- a/src/fireedge/src/client/components/Tables/Hosts/index.js +++ b/src/fireedge/src/client/components/Tables/Hosts/index.js @@ -54,7 +54,7 @@ const HostsTable = (props) => { let values - if (typeof filter === 'function') { + if (typeof filter === 'function' && dependOf) { const { watch } = useFormContext() const getDataForDepend = useCallback( diff --git a/src/fireedge/src/client/components/Tables/VNetworks/index.js b/src/fireedge/src/client/components/Tables/VNetworks/index.js index 382a731571..b992fd61df 100644 --- a/src/fireedge/src/client/components/Tables/VNetworks/index.js +++ b/src/fireedge/src/client/components/Tables/VNetworks/index.js @@ -54,7 +54,7 @@ const VNetworksTable = (props) => { let values - if (typeof filter === 'function') { + if (typeof filter === 'function' && dependOf) { const { watch } = useFormContext() const getDataForDepend = useCallback( diff --git a/src/fireedge/src/client/components/Tabs/Cluster/Datastores/index.js b/src/fireedge/src/client/components/Tabs/Cluster/Datastores/index.js new file mode 100644 index 0000000000..8fa792c0f3 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Cluster/Datastores/index.js @@ -0,0 +1,77 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { ReactElement } from 'react' +import PropTypes from 'prop-types' +import { Stack } from '@mui/material' +import { DatastoresTable } from 'client/components/Tables' +import { useGetClusterQuery } from 'client/features/OneApi/cluster' +import { useHistory, generatePath } from 'react-router-dom' +import { PATH } from 'client/apps/sunstone/routesOne' +const _ = require('lodash') + +/** + * Renders datastores tab showing the datastores of the cluster. + * + * @param {object} props - Props + * @param {string} props.id - Cluster id + * @returns {ReactElement} Datastores tab + */ +const Datastores = ({ id }) => { + // Get info about the cluster + const { data: cluster } = useGetClusterQuery({ id }) + + // Define function to get details of a datastore + const history = useHistory() + const handleRowClick = (rowId) => { + history.push( + generatePath(PATH.STORAGE.DATASTORES.DETAIL, { id: String(rowId) }) + ) + } + + // Get datastores of the cluster + const datastores = _.isEmpty(cluster?.DATASTORES) + ? [] + : Array.isArray(cluster?.DATASTORES?.ID) + ? cluster?.DATASTORES?.ID + : [cluster?.DATASTORES?.ID] + + return ( +
+ + + dataToFilter.filter((ds) => _.includes(datastores, ds.ID)) + } + onRowClick={(row) => handleRowClick(row.ID)} + /> + +
+ ) +} + +Datastores.propTypes = { + id: PropTypes.string, +} + +Datastores.displayName = 'Datastores' + +export default Datastores diff --git a/src/fireedge/src/client/components/Tabs/Cluster/Hosts/index.js b/src/fireedge/src/client/components/Tabs/Cluster/Hosts/index.js new file mode 100644 index 0000000000..77ea6e2f58 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Cluster/Hosts/index.js @@ -0,0 +1,77 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { ReactElement } from 'react' +import PropTypes from 'prop-types' +import { Stack } from '@mui/material' +import { HostsTable } from 'client/components/Tables' +import { useGetClusterQuery } from 'client/features/OneApi/cluster' +import { useHistory, generatePath } from 'react-router-dom' +import { PATH } from 'client/apps/sunstone/routesOne' +const _ = require('lodash') + +/** + * Renders hosts tab showing the hosts of the cluster. + * + * @param {object} props - Props + * @param {string} props.id - Cluster id + * @returns {ReactElement} Hosts tab + */ +const Hosts = ({ id }) => { + // Get info about the cluster + const { data: cluster } = useGetClusterQuery({ id }) + + // Define function to get details of a host + const history = useHistory() + const handleRowClick = (rowId) => { + history.push( + generatePath(PATH.INFRASTRUCTURE.HOSTS.DETAIL, { id: String(rowId) }) + ) + } + + // Get hosts of the cluster + const hosts = _.isEmpty(cluster?.HOSTS) + ? [] + : Array.isArray(cluster?.HOSTS?.ID) + ? cluster?.HOSTS?.ID + : [cluster?.HOSTS?.ID] + + return ( +
+ + + dataToFilter.filter((host) => _.includes(hosts, host.ID)) + } + onRowClick={(row) => handleRowClick(row.ID)} + /> + +
+ ) +} + +Hosts.propTypes = { + id: PropTypes.string, +} + +Hosts.displayName = 'Hosts' + +export default Hosts diff --git a/src/fireedge/src/client/components/Tabs/Cluster/Info/information.js b/src/fireedge/src/client/components/Tabs/Cluster/Info/information.js index b8a9bd69b9..f05ccc002c 100644 --- a/src/fireedge/src/client/components/Tabs/Cluster/Info/information.js +++ b/src/fireedge/src/client/components/Tabs/Cluster/Info/information.js @@ -15,10 +15,13 @@ * ------------------------------------------------------------------------- */ import { ReactElement } from 'react' import PropTypes from 'prop-types' - -import { useRenameClusterMutation } from 'client/features/OneApi/cluster' +import { + useRenameClusterMutation, + useUpdateClusterMutation, +} from 'client/features/OneApi/cluster' import { List } from 'client/components/Tabs/Common' import { T, Cluster, CLUSTER_ACTIONS } from 'client/constants' +import { jsonToXml } from 'client/models/Helper' /** * Renders mainly information tab. @@ -30,6 +33,7 @@ import { T, Cluster, CLUSTER_ACTIONS } from 'client/constants' */ const InformationPanel = ({ cluster = {}, actions }) => { const [renameCluster] = useRenameClusterMutation() + const [updateCluster] = useUpdateClusterMutation() const { ID, NAME, TEMPLATE } = cluster const { RESERVED_MEM, RESERVED_CPU } = TEMPLATE @@ -37,6 +41,7 @@ const InformationPanel = ({ cluster = {}, actions }) => { await renameCluster({ id: ID, name: newName }) } + // Info section const info = [ { name: T.ID, value: ID, dataCy: 'id' }, { @@ -48,9 +53,64 @@ const InformationPanel = ({ cluster = {}, actions }) => { }, ] + /** + * Update reserved CPU on the template cluster. + * + * @param {string} name - Name of the attribute + * @param {number} value - Value of the attribute + */ + const handleOvercommitmentCPU = async (name, value) => { + const newTemplate = { + RESERVED_CPU: value + '%', + } + + await updateCluster({ + id: ID, + template: jsonToXml(newTemplate), + replace: 1, + }) + } + + /** + * Update reserved memory on the template cluster. + * + * @param {string} name - Name of the attribute + * @param {number} value - Value of the attribute + */ + const handleOvercommitmentMemory = async (name, value) => { + const newTemplate = { + RESERVED_MEM: value + '%', + } + + await updateCluster({ + id: ID, + template: jsonToXml(newTemplate), + replace: 1, + }) + } + + // Overcommitment section const overcommitment = [ - { name: T.ReservedMemory, value: RESERVED_MEM }, - { name: T.ReservedCpu, value: RESERVED_CPU }, + { + name: T.ReservedCpu, + handleEdit: handleOvercommitmentCPU, + canEdit: true, + value: {RESERVED_CPU}, + min: '-100', + max: '100', + currentValue: RESERVED_CPU?.replace(/%/g, ''), + dataCy: 'allocated-cpu', + }, + { + name: T.ReservedMemory, + handleEdit: handleOvercommitmentMemory, + canEdit: true, + value: {RESERVED_MEM}, + min: '-100', + max: '100', + currentValue: RESERVED_MEM?.replace(/%/g, ''), + dataCy: 'allocated-memory', + }, ] return ( diff --git a/src/fireedge/src/client/components/Tabs/Cluster/Vnets/index.js b/src/fireedge/src/client/components/Tabs/Cluster/Vnets/index.js new file mode 100644 index 0000000000..fa25c558cd --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Cluster/Vnets/index.js @@ -0,0 +1,75 @@ +/* ------------------------------------------------------------------------- * + * Copyright 2002-2023, 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 { ReactElement } from 'react' +import PropTypes from 'prop-types' +import { Stack } from '@mui/material' +import { VNetworksTable } from 'client/components/Tables' +import { useGetClusterQuery } from 'client/features/OneApi/cluster' +import { useHistory, generatePath } from 'react-router-dom' +import { PATH } from 'client/apps/sunstone/routesOne' +const _ = require('lodash') + +/** + * Renders vnets tab showing the vnets of the cluster. + * + * @param {object} props - Props + * @param {string} props.id - Cluster id + * @returns {ReactElement} Vnets tab + */ +const Vnets = ({ id }) => { + // Get info about the cluster + const { data: cluster } = useGetClusterQuery({ id }) + + // Define function to get details of a vnet + const history = useHistory() + const handleRowClick = (rowId) => { + history.push(generatePath(PATH.NETWORK.VNETS.DETAIL, { id: String(rowId) })) + } + + // Get vnets of the cluster + const vnets = _.isEmpty(cluster?.VNETS) + ? [] + : Array.isArray(cluster?.VNETS?.ID) + ? cluster?.VNETS?.ID + : [cluster?.VNETS?.ID] + + return ( +
+ + + dataToFilter.filter((vnet) => _.includes(vnets, vnet.ID)) + } + onRowClick={(row) => handleRowClick(row.ID)} + /> + +
+ ) +} + +Vnets.propTypes = { + id: PropTypes.string, +} + +Vnets.displayName = 'Vnets' + +export default Vnets diff --git a/src/fireedge/src/client/components/Tabs/Cluster/index.js b/src/fireedge/src/client/components/Tabs/Cluster/index.js index c8af8ae8c7..225800dc80 100644 --- a/src/fireedge/src/client/components/Tabs/Cluster/index.js +++ b/src/fireedge/src/client/components/Tabs/Cluster/index.js @@ -24,10 +24,16 @@ import { getAvailableInfoTabs } from 'client/models/Helper' import Tabs from 'client/components/Tabs' import Info from 'client/components/Tabs/Cluster/Info' +import Hosts from 'client/components/Tabs/Cluster/Hosts' +import Vnets from 'client/components/Tabs/Cluster/Vnets' +import Datastores from 'client/components/Tabs/Cluster/Datastores' const getTabComponent = (tabName) => ({ info: Info, + host: Hosts, + vnet: Vnets, + datastore: Datastores, }[tabName]) const ClusterTabs = memo(({ id }) => { diff --git a/src/fireedge/src/client/components/Tabs/Common/Attribute/Inputs.js b/src/fireedge/src/client/components/Tabs/Common/Attribute/Inputs.js index d6a5bb52d3..719dbc02c0 100644 --- a/src/fireedge/src/client/components/Tabs/Common/Attribute/Inputs.js +++ b/src/fireedge/src/client/components/Tabs/Common/Attribute/Inputs.js @@ -150,8 +150,8 @@ const SliderInput = forwardRef( value={newValue} marks={[ { - value: 0, - label: unitParser ? prettyBytes(0) : '0', + value: min ?? 0, + label: unitParser ? prettyBytes(0) : min ?? '0', }, { value: max, diff --git a/src/fireedge/src/client/components/Tabs/Host/Info/information.js b/src/fireedge/src/client/components/Tabs/Host/Info/information.js index 670b363440..2a626546f8 100644 --- a/src/fireedge/src/client/components/Tabs/Host/Info/information.js +++ b/src/fireedge/src/client/components/Tabs/Host/Info/information.js @@ -73,22 +73,30 @@ const InformationPanel = ({ host = {}, actions }) => { await renameHost({ id: ID, name: newName }) } - const handleOvercommitment = async (name, value) => { + const handleOvercommitmentCPU = async (name, value) => { let valueNumber = +value - let newTemplate - if (/cpu/i.test(name)) { - valueNumber === 0 && (valueNumber = usageCpu) - newTemplate = { - RESERVED_CPU: - value !== totalCpu ? totalCpu - valueNumber : reservedCpu ? 0 : '', - } + + valueNumber === 0 && (valueNumber = usageCpu) + const newTemplate = { + RESERVED_CPU: + value !== totalCpu ? totalCpu - valueNumber : reservedCpu ? 0 : '', } - if (/memory/i.test(name)) { - valueNumber === 0 && (valueNumber = usageMem) - newTemplate = { - RESERVED_MEM: - value !== totalMem ? totalMem - valueNumber : reservedMem ? 0 : '', - } + + newTemplate && + (await updateHost({ + id: ID, + template: jsonToXml(newTemplate), + replace: 1, + })) + } + + const handleOvercommitmentMemory = async (name, value) => { + let valueNumber = +value + + valueNumber === 0 && (valueNumber = usageMem) + const newTemplate = { + RESERVED_MEM: + value !== totalMem ? totalMem - valueNumber : reservedMem ? 0 : '', } newTemplate && @@ -129,7 +137,7 @@ const InformationPanel = ({ host = {}, actions }) => { const capacity = [ { name: T.AllocatedCpu, - handleEdit: handleOvercommitment, + handleEdit: handleOvercommitmentCPU, canEdit: true, value: ( { }, { name: T.AllocatedMemory, - handleEdit: handleOvercommitment, + handleEdit: handleOvercommitmentMemory, canEdit: true, value: ( { + try { + // Request to create a cluster but not to update + if (!clusterId) { + // Create cluster + const newClusterId = await createCluster({ + name: general?.NAME, + }).unwrap() + + // Add hosts + if (newClusterId && hosts?.ID) { + const hostIds = hosts?.ID?.map?.((host) => host) + await Promise.all( + hostIds.map((hostId) => addHost({ id: newClusterId, host: hostId })) + ) + } + + // Add vnets + if (newClusterId && vnets?.ID) { + const vnetIds = vnets?.ID?.map?.((vnet) => vnet) + await Promise.all( + vnetIds.map((vnetId) => addVnet({ id: newClusterId, vnet: vnetId })) + ) + } + + // Add datastores + if (newClusterId && datastores?.ID) { + const datastoreIds = datastores?.ID?.map?.((ds) => ds) + await Promise.all( + datastoreIds.map((dsId) => + addDatastore({ id: newClusterId, datastore: dsId }) + ) + ) + } + + // Only show cluster message + enqueueSuccess(`Cluster created - #${newClusterId}`) + + // Go to clusters list + history.push(PATH.INFRASTRUCTURE.CLUSTERS.LIST) + } else { + // Add hosts + if (addHosts?.length > 0) { + const hostIds = addHosts?.map?.((host) => host) + await Promise.all( + hostIds.map((hostId) => addHost({ id: clusterId, host: hostId })) + ) + } + + // Remove hosts + if (removeHosts?.length > 0) { + const hostIds = removeHosts?.map?.((host) => host) + await Promise.all( + hostIds.map((hostId) => removeHost({ id: clusterId, host: hostId })) + ) + } + + // Add vnets + if (addVnets?.length > 0) { + const vnetIds = addVnets?.map?.((vnet) => vnet) + await Promise.all( + vnetIds.map((vnetId) => addVnet({ id: clusterId, vnet: vnetId })) + ) + } + + // Remove vnets + if (removeVnets?.length > 0) { + const vnetIds = removeVnets?.map?.((vnet) => vnet) + await Promise.all( + vnetIds.map((vnetId) => removeVnet({ id: clusterId, vnet: vnetId })) + ) + } + + // Add datastores + if (addDatastores?.length > 0) { + const datastoreIds = addDatastores?.map?.((ds) => ds) + await Promise.all( + datastoreIds.map((dsId) => + addDatastore({ id: clusterId, datastore: dsId }) + ) + ) + } + + // Remove datastores + if (removeDatastores?.length > 0) { + const datastoreIds = removeDatastores?.map?.((ds) => ds) + await Promise.all( + datastoreIds.map((dsId) => + removeDatastore({ id: clusterId, datastore: dsId }) + ) + ) + } + + // Rename if the name has been changed + if (changeName) { + await rename({ id: clusterId, name: general?.NAME }).unwrap() + } + + // Only show cluster message + enqueueSuccess(`Cluster updated - #${clusterId}`) + + // Go to clusters list + history.push(PATH.INFRASTRUCTURE.CLUSTERS.LIST) + } + } catch (error) { + enqueueError('Error performing operation on cluster') + } + } + + return views && version && (!clusterId || (clusterId && cluster)) ? ( + } + > + {(config) => } + + ) : ( + + ) +} + +export default CreateCluster diff --git a/src/fireedge/src/client/containers/Clusters/index.js b/src/fireedge/src/client/containers/Clusters/index.js index 165d909ac2..7718fcb112 100644 --- a/src/fireedge/src/client/containers/Clusters/index.js +++ b/src/fireedge/src/client/containers/Clusters/index.js @@ -32,6 +32,7 @@ import MultipleTags from 'client/components/MultipleTags' import { SubmitButton } from 'client/components/FormControl' import { Tr } from 'client/components/HOC' import { T, Cluster } from 'client/constants' +import ClusterActions from 'client/components/Tables/Clusters/actions' /** * Displays a list of Clusters with a split pane between the list and selected row(s). @@ -44,6 +45,8 @@ function Clusters() { const hasSelectedRows = selectedRows?.length > 0 const moreThanOneSelected = selectedRows?.length > 1 + const actions = ClusterActions() + return ( {({ getGridProps, GutterComponent }) => ( @@ -51,6 +54,7 @@ function Clusters() { {hasSelectedRows && ( diff --git a/src/fireedge/src/client/features/OneApi/cluster.js b/src/fireedge/src/client/features/OneApi/cluster.js index 87085007aa..3d045d0d4a 100644 --- a/src/fireedge/src/client/features/OneApi/cluster.js +++ b/src/fireedge/src/client/features/OneApi/cluster.js @@ -25,6 +25,12 @@ import { ONE_RESOURCES_POOL, } from 'client/features/OneApi' import { Cluster } from 'client/constants' +import { + removeResourceOnPool, + updateNameOnResource, + updateResourceOnPool, + updateTemplateOnResource, +} from 'client/features/OneApi/common' const { CLUSTER, HOST, DATASTORE } = ONE_RESOURCES const { CLUSTER_POOL, HOST_POOL, DATASTORE_POOL } = ONE_RESOURCES_POOL @@ -78,19 +84,25 @@ const clusterApi = oneApi.injectEndpoints({ providesTags: (_, __, { id }) => [{ type: CLUSTER, id }], async onQueryStarted({ id }, { dispatch, queryFulfilled }) { try { - const { data: queryVm } = await queryFulfilled + const { data: resourceFromQuery } = await queryFulfilled + dispatch( + clusterApi.util.updateQueryData( + 'getGClusters', + undefined, + updateResourceOnPool({ id, resourceFromQuery }) + ) + ) + } catch { + // if the query fails, we want to remove the resource from the pool dispatch( clusterApi.util.updateQueryData( 'getClusters', undefined, - (draft) => { - const index = draft.findIndex(({ ID }) => +ID === +id) - index !== -1 && (draft[index] = queryVm) - } + removeResourceOnPool({ id }) ) ) - } catch {} + } }, }), getClusterAdmin: builder.query({ @@ -132,17 +144,41 @@ const clusterApi = oneApi.injectEndpoints({ /** * Deletes the given cluster from the pool. * - * @param {number|string} id - Cluster id + * @param {number|string} params - Cluster id * @returns {number} Cluster id * @throws Fails when response isn't code 200 */ - query: (id) => { + query: (params) => { const name = Actions.CLUSTER_DELETE const command = { name, ...Commands[name] } - return { params: { id }, command } + return { params, command } }, invalidatesTags: [CLUSTER_POOL], + async onQueryStarted(params, { dispatch, queryFulfilled }) { + try { + const patchCluster = dispatch( + clusterApi.util.updateQueryData( + 'getCluster', + { id: params.id }, + updateNameOnResource(params) + ) + ) + + const patchClusters = dispatch( + clusterApi.util.updateQueryData( + 'getClusters', + undefined, + updateNameOnResource(params) + ) + ) + + queryFulfilled.catch(() => { + patchCluster.undo() + patchClusters.undo() + }) + } catch {} + }, }), updateCluster: builder.mutation({ /** @@ -165,6 +201,30 @@ const clusterApi = oneApi.injectEndpoints({ return { params, command } }, invalidatesTags: (_, __, { id }) => [{ type: CLUSTER, id }], + async onQueryStarted(params, { dispatch, queryFulfilled }) { + try { + const patchCluster = dispatch( + clusterApi.util.updateQueryData( + 'getCluster', + { id: params.id }, + updateTemplateOnResource(params) + ) + ) + + const patchClusters = dispatch( + clusterApi.util.updateQueryData( + 'getClusters', + undefined, + updateTemplateOnResource(params) + ) + ) + + queryFulfilled.catch(() => { + patchCluster.undo() + patchClusters.undo() + }) + } catch {} + }, }), addHostToCluster: builder.mutation({ /** diff --git a/src/fireedge/src/server/utils/constants/commands/cluster.js b/src/fireedge/src/server/utils/constants/commands/cluster.js index d1af9ab357..696e385931 100644 --- a/src/fireedge/src/server/utils/constants/commands/cluster.js +++ b/src/fireedge/src/server/utils/constants/commands/cluster.js @@ -110,7 +110,7 @@ module.exports = { default: 0, }, host: { - from: query, + from: postBody, default: 0, }, },