From e31c5deec03028d660e21648b6cde736266b9ae1 Mon Sep 17 00:00:00 2001 From: Jorge Miguel Lobo Escalona Date: Mon, 19 Sep 2022 18:40:29 +0200 Subject: [PATCH] F #5903: Security Groups tab Sunstone (#2289) --- .../src/client/apps/sunstone/routesOne.js | 37 ++- .../src/client/components/Cards/NicCard.js | 91 +------ .../Forms/SecurityGroups/CloneForm/index.js | 24 ++ .../Forms/SecurityGroups/CloneForm/schema.js | 52 ++++ .../CreateForm/Steps/General/index.js | 49 ++++ .../CreateForm/Steps/General/schema.js | 56 ++++ .../CreateForm/Steps/Rules/index.js | 43 +++ .../CreateForm/Steps/Rules/rulesSection.js | 213 +++++++++++++++ .../CreateForm/Steps/Rules/schema.js | 247 +++++++++++++++++ .../SecurityGroups/CreateForm/Steps/index.js | 53 ++++ .../Forms/SecurityGroups/CreateForm/index.js | 16 ++ .../components/Forms/SecurityGroups/index.js | 34 +++ .../Tables/SecurityGroups/actions.js | 251 ++++++++++++++++++ .../components/Tabs/Common/RulesSecGroups.js | 224 ++++++++++++++++ .../client/components/Tabs/Common/index.js | 3 +- .../client/components/Tabs/Image/Vms/index.js | 2 +- .../Tabs/SecurityGroup/Info/index.js | 165 ++++++++++++ .../Tabs/SecurityGroup/Info/information.js | 69 +++++ .../Tabs/SecurityGroup/Vms/index.js | 59 ++++ .../components/Tabs/SecurityGroup/index.js | 64 +++++ .../src/client/constants/securityGroup.js | 16 ++ .../src/client/constants/translates.js | 28 +- .../src/client/containers/Files/index.js | 6 +- .../src/client/containers/Images/index.js | 8 +- .../containers/SecurityGroups/Create.js | 79 ++++++ .../client/containers/SecurityGroups/index.js | 157 +++++++++++ .../containers/VirtualNetworks/Detail.js | 36 +++ .../client/features/OneApi/securityGroup.js | 173 +++++++++++- 28 files changed, 2153 insertions(+), 102 deletions(-) create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/index.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/schema.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/index.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/schema.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/index.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/rulesSection.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/schema.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/index.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/index.js create mode 100644 src/fireedge/src/client/components/Forms/SecurityGroups/index.js create mode 100644 src/fireedge/src/client/components/Tables/SecurityGroups/actions.js create mode 100644 src/fireedge/src/client/components/Tabs/Common/RulesSecGroups.js create mode 100644 src/fireedge/src/client/components/Tabs/SecurityGroup/Info/index.js create mode 100644 src/fireedge/src/client/components/Tabs/SecurityGroup/Info/information.js create mode 100644 src/fireedge/src/client/components/Tabs/SecurityGroup/Vms/index.js create mode 100644 src/fireedge/src/client/components/Tabs/SecurityGroup/index.js create mode 100644 src/fireedge/src/client/containers/SecurityGroups/Create.js create mode 100644 src/fireedge/src/client/containers/SecurityGroups/index.js create mode 100644 src/fireedge/src/client/containers/VirtualNetworks/Detail.js diff --git a/src/fireedge/src/client/apps/sunstone/routesOne.js b/src/fireedge/src/client/apps/sunstone/routesOne.js index 28c6c5b1d9..a8332157f8 100644 --- a/src/fireedge/src/client/apps/sunstone/routesOne.js +++ b/src/fireedge/src/client/apps/sunstone/routesOne.js @@ -37,6 +37,7 @@ import { Home as SystemIcon, User as UserIcon, Group as GroupIcon, + HistoricShield as SecurityGroupIcon, } from 'iconoir-react' import loadable from '@loadable/component' @@ -104,6 +105,18 @@ const Files = loadable(() => import('client/containers/Files'), { const CreateFiles = loadable(() => import('client/containers/Files/Create'), { ssr: false, }) +const SecurityGroups = loadable( + () => import('client/containers/SecurityGroups'), + { + ssr: false, + } +) +const CreateSecurityGroups = loadable( + () => import('client/containers/SecurityGroups/Create'), + { + ssr: false, + } +) const CreateImages = loadable(() => import('client/containers/Images/Create'), { ssr: false, }) @@ -129,6 +142,10 @@ const VirtualNetworks = loadable( () => import('client/containers/VirtualNetworks'), { ssr: false } ) +const VirtualNetworksDetail = loadable( + () => import('client/containers/VirtualNetworks/Detail'), + { ssr: false } +) const CreateVirtualNetwork = loadable( () => import('client/containers/VirtualNetworks/Create'), { ssr: false } @@ -138,7 +155,6 @@ const VNetworkTemplates = loadable( { ssr: false } ) // const NetworkTopologies = loadable(() => import('client/containers/NetworkTopologies'), { ssr: false }) -// const SecurityGroups = loadable(() => import('client/containers/SecurityGroups'), { ssr: false }) const Clusters = loadable(() => import('client/containers/Clusters'), { ssr: false, @@ -236,6 +252,7 @@ export const PATH = { SEC_GROUPS: { LIST: `/${RESOURCE_NAMES.SEC_GROUP}`, DETAIL: `/${RESOURCE_NAMES.SEC_GROUP}/:id`, + CREATE: `/${RESOURCE_NAMES.SEC_GROUP}/create`, }, }, INFRASTRUCTURE: { @@ -434,6 +451,12 @@ const ENDPOINTS = [ title: T.Networks, icon: NetworksIcon, routes: [ + { + title: T.VirtualNetworks, + description: (params) => `#${params?.id}`, + path: PATH.NETWORK.VNETS.DETAIL, + Component: VirtualNetworksDetail, + }, { title: T.VirtualNetworks, path: PATH.NETWORK.VNETS.LIST, @@ -458,6 +481,18 @@ const ENDPOINTS = [ path: PATH.NETWORK.VNETS.CREATE, Component: CreateVirtualNetwork, }, + { + title: T.SecurityGroups, + path: PATH.NETWORK.SEC_GROUPS.LIST, + sidebar: true, + icon: SecurityGroupIcon, + Component: SecurityGroups, + }, + { + title: T.CreateSecurityGroup, + path: PATH.NETWORK.SEC_GROUPS.CREATE, + Component: CreateSecurityGroups, + }, ], }, { diff --git a/src/fireedge/src/client/components/Cards/NicCard.js b/src/fireedge/src/client/components/Cards/NicCard.js index 763a79d117..a960753550 100644 --- a/src/fireedge/src/client/components/Cards/NicCard.js +++ b/src/fireedge/src/client/components/Cards/NicCard.js @@ -35,7 +35,8 @@ import MultipleTags from 'client/components/MultipleTags' import { Translate } from 'client/components/HOC' import { stringToBoolean } from 'client/models/Helper' import { groupBy } from 'client/utils' -import { T, Nic, NicAlias, PrettySecurityGroupRule } from 'client/constants' +import { T, Nic, NicAlias } from 'client/constants' +import { SecurityGroupRules } from 'client/components/Tabs/Common/RulesSecGroups' const NicCard = memo( /** @@ -210,92 +211,4 @@ NicCard.propTypes = { NicCard.displayName = 'NicCard' -const SecurityGroupRules = memo(({ parentKey, id, actions, rules }) => { - const classes = rowStyles() - - const COLUMNS = useMemo( - () => [T.Protocol, T.Type, T.Range, T.Network, T.IcmpType], - [] - ) - - const name = rules?.[0]?.NAME ?? 'default' - - return ( - <> - - - {`#${id} ${name}`} - - {!!actions &&
{actions}
} -
- - {COLUMNS.map((col) => ( - - - - ))} - {rules.map((rule) => ( - - ))} - - - ) -}) - -SecurityGroupRules.propTypes = { - parentKey: PropTypes.string, - id: PropTypes.string, - rules: PropTypes.array, - actions: PropTypes.node, -} - -SecurityGroupRules.displayName = 'SecurityGroupRule' - -const SecurityGroupRule = memo(({ rule, 'data-cy': parentCy }) => { - /** @type {PrettySecurityGroupRule} */ - const { PROTOCOL, RULE_TYPE, ICMP_TYPE, RANGE, NETWORK_ID } = rule - - return ( - <> - {[ - { text: PROTOCOL, dataCy: 'protocol' }, - { text: RULE_TYPE, dataCy: 'ruletype' }, - { text: RANGE, dataCy: 'range' }, - { text: NETWORK_ID, dataCy: 'networkid' }, - { text: ICMP_TYPE, dataCy: 'icmp-type' }, - ].map(({ text, dataCy }) => ( - - {text} - - ))} - - ) -}) - -SecurityGroupRule.propTypes = { - rule: PropTypes.object, - 'data-cy': PropTypes.string, -} - -SecurityGroupRule.displayName = 'SecurityGroupRule' - export default NicCard diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/index.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/index.js new file mode 100644 index 0000000000..f4d1a115d4 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/index.js @@ -0,0 +1,24 @@ +/* ------------------------------------------------------------------------- * + * 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 { createForm } from 'client/utils' +import { + SCHEMA, + FIELDS, +} from 'client/components/Forms/SecurityGroups/CloneForm/schema' + +const cloneSecGroupForm = createForm(SCHEMA, FIELDS) + +export default cloneSecGroupForm diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/schema.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/schema.js new file mode 100644 index 0000000000..793220c0c6 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CloneForm/schema.js @@ -0,0 +1,52 @@ +/* ------------------------------------------------------------------------- * + * 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 { string, object } from 'yup' + +import { Field, getValidationFromFields } from 'client/utils' +import { T, INPUT_TYPES } from 'client/constants' + +const PREFIX = { + name: 'prefix', + label: T.Prefix, + tooltip: T.PrefixSecGroupsMultipleConcept, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .required() + .default(() => T.CopyOf), + grid: { md: 12 }, +} + +const SEC_GROUP = { + name: 'name', + label: T.Name, + type: INPUT_TYPES.TEXT, + validation: string() + .trim() + .required() + .default(() => ''), + grid: { md: 12 }, +} + +/** + * @param {object} [stepProps] - Step props + * @param {boolean} [stepProps.isMultiple] + * - If true, the prefix will be added to the name of the new template + * @returns {Field[]} Fields + */ +export const FIELDS = ({ isMultiple } = {}) => [isMultiple ? PREFIX : SEC_GROUP] + +export const SCHEMA = object(getValidationFromFields(FIELDS())) diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/index.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/index.js new file mode 100644 index 0000000000..c313a823ea --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/index.js @@ -0,0 +1,49 @@ +/* ------------------------------------------------------------------------- * + * 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 FormWithSchema from 'client/components/Forms/FormWithSchema' + +import { + SCHEMA, + FIELDS, +} from 'client/components/Forms/SecurityGroups/CreateForm/Steps/General/schema' +import { T } from 'client/constants' + +export const STEP_ID = 'general' + +const Content = () => ( + +) + +/** + * General configuration about Security Groups. + * + * @param {object} securityGroupData - security group data + * @returns {object} Security Groups configuration step + */ +const General = (securityGroupData = {}) => { + const isUpdate = securityGroupData?.NAME + + return { + id: STEP_ID, + label: T.Configuration, + resolver: (formdata) => SCHEMA(isUpdate), + optionsValidate: { abortEarly: false }, + content: () => Content(securityGroupData), + } +} + +export default General diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/schema.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/schema.js new file mode 100644 index 0000000000..cbe15b0718 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/General/schema.js @@ -0,0 +1,56 @@ +/* ------------------------------------------------------------------------- * + * 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 { string, object, ObjectSchema } from 'yup' +import { Field, getValidationFromFields } from 'client/utils' +import { T, INPUT_TYPES } from 'client/constants' + +export const IMAGE_LOCATION_TYPES = { + PATH: 'path', + UPLOAD: 'upload', +} + +/** @type {Field} name field */ +export const NAME = (isUpdate) => ({ + name: 'NAME', + label: T.Name, + type: INPUT_TYPES.TEXT, + validation: string().trim().required(), + grid: { xs: 12, md: 6 }, + ...(isUpdate && { fieldProps: { disabled: true } }), +}) + +/** @type {Field} Description field */ +export const DESCRIPTION = { + name: 'DESCRIPTION', + label: T.Description, + type: INPUT_TYPES.TEXT, + multiline: true, + validation: string().trim(), + grid: { xs: 12, md: 6 }, +} + +/** + * @param {boolean} isUpdate - is update. + * @returns {Field[]} Fields + */ +export const FIELDS = (isUpdate) => [NAME(isUpdate), DESCRIPTION] + +/** + * @param {boolean} isUpdate - is update. + * @returns {ObjectSchema} Schema + */ +export const SCHEMA = (isUpdate) => + object(getValidationFromFields(FIELDS(isUpdate))) diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/index.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/index.js new file mode 100644 index 0000000000..e8672a2d8e --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/index.js @@ -0,0 +1,43 @@ +/* ------------------------------------------------------------------------- * + * 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 { array, object } from 'yup' + +import RulesSection from './rulesSection' + +import { SCHEMA } from 'client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/schema' +import { T } from 'client/constants' + +export const STEP_ID = 'rules' + +const Content = () => + +/** + * Rules configuration about Security Groups. + * + * @returns {object} Rules configuration step + */ +const Rules = () => ({ + id: STEP_ID, + label: T.Rules, + resolver: object({ + RULES: array(SCHEMA), + }), + optionsValidate: { abortEarly: false }, + content: Content, +}) + +export default Rules diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/rulesSection.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/rulesSection.js new file mode 100644 index 0000000000..196cebfef0 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/rulesSection.js @@ -0,0 +1,213 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement, useCallback, memo, useMemo } from 'react' +import PropTypes from 'prop-types' +import { Link as RouterLink } from 'react-router-dom' +import makeStyles from '@mui/styles/makeStyles' +import { Stack, FormControl, Link, Button, IconButton } from '@mui/material' +import Table from '@mui/material/Table' +import TableBody from '@mui/material/TableBody' +import TableCell from '@mui/material/TableCell' +import TableContainer from '@mui/material/TableContainer' +import TableHead from '@mui/material/TableHead' +import TableRow from '@mui/material/TableRow' +import { DeleteCircledOutline, AddCircledOutline } from 'iconoir-react' +import { useFieldArray, useForm, FormProvider } from 'react-hook-form' +import { yupResolver } from '@hookform/resolvers/yup' + +import { FormWithSchema, Legend } from 'client/components/Forms' +import { Translate } from 'client/components/HOC' + +import { + FIELDS, + SCHEMA, +} from 'client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/schema' +import { + T, + ICMP_STRING, + ICMP_V6_STRING, + RESOURCE_NAMES, +} from 'client/constants' + +export const SECTION_ID = 'RULES' + +const useStyles = makeStyles({ + container: { + marginTop: '3rem', + }, +}) + +const RulesSection = memo( + /** + * @param {object} props - Props + * @param {string} [props.stepId] - ID of the step the section belongs to + * @returns {ReactElement} - Inputs section + */ + ({ stepId }) => { + const classes = useStyles() + + const fields = useMemo(() => FIELDS, []) + + const { + fields: rules, + append, + remove, + } = useFieldArray({ + name: useMemo( + () => [stepId, SECTION_ID].filter(Boolean).join('.'), + [stepId] + ), + }) + + const getCyPath = useCallback( + (cy) => [stepId, cy].filter(Boolean).join('-'), + [stepId] + ) + + const methods = useForm({ + defaultValues: { + [SECTION_ID]: SCHEMA.default(), + }, + resolver: yupResolver(SCHEMA), + }) + + const onSubmit = (newRule) => { + newRule?.RULES && delete newRule.RULES + append(newRule) + methods.reset() + } + + if (fields.length === 0) { + return null + } + + return ( + + + + + + + + + + + + + + {T.Protocol} + + + {T.Type} + + + {T.Range} + + + {T.Network} + + + {T.IcmpType} + + + {T.IcmpTypeV6} + + + + + + {rules?.map( + ( + { + id, + PROTOCOL, + RULE_TYPE, + RANGE = T.All, + IP, + SIZE, + NETWORK_ID, + ICMP_TYPE = T.Any, + // eslint-disable-next-line camelcase + ICMPv6_TYPE = T.Any, + }, + index + ) => { + let network = T.Any + if (IP && SIZE) { + network = `${T.Start}: ${IP}, ${T.Size}: ${SIZE}` + } else if (!isNaN(NETWORK_ID)) { + network = ( + + {NETWORK_ID} + + ) + } + + return ( + + {PROTOCOL} + {RULE_TYPE} + {RANGE} + {network} + {ICMP_STRING[ICMP_TYPE] || ''} + {ICMP_V6_STRING[ICMPv6_TYPE] || ''} + + remove(index)}> + + + + + ) + } + )} + +
+
+
+ ) + } +) + +RulesSection.propTypes = { + stepId: PropTypes.string, +} + +RulesSection.displayName = 'RulesSection' + +export default RulesSection diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/schema.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/schema.js new file mode 100644 index 0000000000..59113e8188 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/Rules/schema.js @@ -0,0 +1,247 @@ +/* ------------------------------------------------------------------------- * + * 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 { string, object, ObjectSchema, mixed } from 'yup' +import { + Field, + arrayToOptions, + getValidationFromFields, + upperCaseFirst, +} from 'client/utils' +import { VNetworksTable } from 'client/components/Tables' +import { + T, + RULE_TYPE_STRING, + INPUT_TYPES, + PROTOCOL_STRING, + ICMP_STRING, + ICMP_V6_STRING, +} from 'client/constants' + +/** @type {Field} Rule type field */ +export const RULE_TYPE = { + name: 'RULE_TYPE', + label: T.Traffic, + type: INPUT_TYPES.SELECT, + values: arrayToOptions(Object.values(RULE_TYPE_STRING), { + addEmpty: false, + getText: (ruleType) => { + switch (ruleType) { + case RULE_TYPE_STRING.OUTBOUND: + return T.Outbound + case RULE_TYPE_STRING.INBOUND: + return T.Inbound + default: + return upperCaseFirst(ruleType.toLowerCase()) + } + }, + getValue: (ruleType) => ruleType, + }), + validation: string() + .trim() + .default(() => RULE_TYPE_STRING.OUTBOUND) + .afterSubmit((value) => value.toLowerCase()), + grid: { md: 12 }, +} + +/** @type {Field} Protocol field */ +export const PROTOCOL = { + name: 'PROTOCOL', + label: T.Protocol, + type: INPUT_TYPES.SELECT, + values: arrayToOptions(Object.values(PROTOCOL_STRING), { + addEmpty: false, + getText: (protocol) => { + switch (protocol) { + case PROTOCOL_STRING.TCP: + return T.TCP + case PROTOCOL_STRING.UDP: + return T.UDP + case PROTOCOL_STRING.ICMP: + return T.ICMP + case PROTOCOL_STRING.ICMPV6: + return T.ICMPV6 + case PROTOCOL_STRING.IPSEC: + return T.IPSEC + case PROTOCOL_STRING.ALL: + return T.All + default: + return upperCaseFirst(protocol.toLowerCase()) + } + }, + getValue: (protocol) => protocol, + }), + validation: string() + .trim() + .default(() => PROTOCOL_STRING.TCP), + grid: { md: 12 }, +} + +/** @type {Field} ICMP_TYPE field */ +export const ICMP_TYPE = { + name: 'ICMP_TYPE', + dependOf: PROTOCOL.name, + label: T.ICMP, + type: INPUT_TYPES.SELECT, + htmlType: (protocol) => + protocol !== PROTOCOL_STRING.ICMP && INPUT_TYPES.HIDDEN, + values: arrayToOptions(Object.entries(ICMP_STRING), { + addEmpty: false, + getText: ([_, value]) => value, + getValue: ([key]) => key, + }), + validation: mixed().when(PROTOCOL.name, { + is: (protocol) => protocol === PROTOCOL_STRING.ICMP, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired().nullable(), + }), + grid: { md: 12 }, +} + +/** @type {Field} ICMPv6 field */ +export const ICMPV6_TYPE = { + name: 'ICMPv6_TYPE', + dependOf: PROTOCOL.name, + label: T.ICMPV6, + type: INPUT_TYPES.SELECT, + htmlType: (protocol) => + protocol !== PROTOCOL_STRING.ICMPV6 && INPUT_TYPES.HIDDEN, + values: arrayToOptions(Object.entries(ICMP_V6_STRING), { + addEmpty: false, + getText: ([_, value]) => value, + getValue: ([key]) => key, + }), + validation: mixed().when(PROTOCOL.name, { + is: (protocol) => protocol === PROTOCOL_STRING.ICMPV6, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired().nullable(), + }), + grid: { md: 12 }, +} + +/** @type {Field} RANGE, TYPE field */ +export const RANGE_TYPE = { + name: 'RANGE_TYPE', + label: T.PortRange, + type: INPUT_TYPES.SELECT, + values: arrayToOptions([T.All, T.PortRange], { + addEmpty: false, + }), + validation: string() + .trim() + .afterSubmit((value) => undefined), + grid: { xs: 12 }, +} + +/** @type {Field} Range field */ +export const RANGE = { + name: 'RANGE', + dependOf: RANGE_TYPE.name, + label: T.PortRange, + type: INPUT_TYPES.TEXT, + htmlType: (range) => range !== T.PortRange && INPUT_TYPES.HIDDEN, + validation: mixed().when(RANGE_TYPE.name, { + is: (protocol) => protocol === T.PortRange, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired().nullable(), + }), + grid: { md: 12 }, +} + +/** @type {Field} Target field */ +export const TARGET = { + name: 'TARGET', + label: T.TargetNetwork, + type: INPUT_TYPES.SELECT, + values: arrayToOptions( + [T.AnyNetwork, T.ManualNetwork, T.OpennebulaVirtualNetwork], + { + addEmpty: false, + } + ), + validation: string() + .trim() + .afterSubmit((value) => undefined), + grid: { xs: 12 }, +} + +/** @type {Field} IP field */ +export const IP = { + name: 'IP', + dependOf: TARGET.name, + label: T.FirstIPIPv6Address, + type: INPUT_TYPES.TEXT, + htmlType: (range) => range !== T.ManualNetwork && INPUT_TYPES.HIDDEN, + validation: mixed().when(TARGET.name, { + is: (protocol) => protocol === T.ManualNetwork, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired().nullable(), + }), + grid: { md: 12 }, +} + +/** @type {Field} SIZE field */ +export const SIZE = { + name: 'SIZE', + dependOf: TARGET.name, + label: T.Size, + type: INPUT_TYPES.TEXT, + htmlType: (range) => range !== T.ManualNetwork && INPUT_TYPES.HIDDEN, + validation: mixed().when(TARGET.name, { + is: (protocol) => protocol === T.ManualNetwork, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired().nullable(), + }), + grid: { md: 12 }, +} + +/** @type {Field} Networks field */ +const NETWORK_ID = { + name: 'NETWORK_ID', + label: T.SelectNewNetwork, + type: INPUT_TYPES.TABLE, + dependOf: TARGET.name, + htmlType: (range) => + range !== T.OpennebulaVirtualNetwork && INPUT_TYPES.HIDDEN, + Table: () => VNetworksTable, + validation: mixed().when(RANGE.name, { + is: (protocol) => protocol === T.OpennebulaVirtualNetwork, + then: (schema) => schema.required(), + otherwise: (schema) => schema.notRequired().nullable(), + }), + grid: { md: 12 }, +} + +/** + * @returns {Field[]} Fields + */ +export const FIELDS = [ + RULE_TYPE, + PROTOCOL, + ICMP_TYPE, + ICMPV6_TYPE, + RANGE_TYPE, + RANGE, + TARGET, + IP, + SIZE, + NETWORK_ID, +] + +/** + * @param {object} [stepProps] - Step props + * @returns {ObjectSchema} Schema + */ +export const SCHEMA = object(getValidationFromFields(FIELDS)) diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/index.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/index.js new file mode 100644 index 0000000000..b0a1b49fa8 --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/Steps/index.js @@ -0,0 +1,53 @@ +/* ------------------------------------------------------------------------- * + * 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 Rules, { + STEP_ID as RULES_ID, +} from 'client/components/Forms/SecurityGroups/CreateForm/Steps/Rules' + +import General, { + STEP_ID as GENERAL_ID, +} from 'client/components/Forms/SecurityGroups/CreateForm/Steps/General' + +import { createSteps } from 'client/utils' + +const Steps = createSteps([General, Rules], { + transformInitialValue: ({ TEMPLATE, ...secGroup } = {}, schema) => + schema.cast( + { + [GENERAL_ID]: { ...TEMPLATE, ...secGroup }, + [RULES_ID]: { + RULES: Array.isArray(TEMPLATE.RULE) ? TEMPLATE.RULE : [TEMPLATE.RULE], + }, + }, + { stripUnknown: true, context: secGroup } + ), + transformBeforeSubmit: (formData) => { + const { [GENERAL_ID]: general = {}, [RULES_ID]: rules = {} } = + formData ?? {} + + const rtn = { + template: { + ...general, + }, + } + + if (rules?.RULES) rtn.template.RULE = rules.RULES + + return rtn + }, +}) + +export default Steps diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/index.js b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/index.js new file mode 100644 index 0000000000..606f9a8c2b --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/CreateForm/index.js @@ -0,0 +1,16 @@ +/* ------------------------------------------------------------------------- * + * 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. * + * ------------------------------------------------------------------------- */ +export { default } from 'client/components/Forms/SecurityGroups/CreateForm/Steps' diff --git a/src/fireedge/src/client/components/Forms/SecurityGroups/index.js b/src/fireedge/src/client/components/Forms/SecurityGroups/index.js new file mode 100644 index 0000000000..f71b3dea3c --- /dev/null +++ b/src/fireedge/src/client/components/Forms/SecurityGroups/index.js @@ -0,0 +1,34 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement } from 'react' +import { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC' +import { CreateFormCallback } from 'client/utils/schema' + +/** + * @param {ConfigurationProps} configProps - Configuration + * @returns {ReactElement|CreateFormCallback} Asynchronous loaded form + */ +const CloneForm = (configProps) => + AsyncLoadForm({ formPath: 'SecurityGroups/CloneForm' }, configProps) + +/** + * @param {ConfigurationProps} configProps - Configuration + * @returns {ReactElement|CreateFormCallback} Asynchronous loaded form + */ +const CreateForm = (configProps) => + AsyncLoadForm({ formPath: 'SecurityGroups/CreateForm' }, configProps) + +export { CloneForm, CreateForm } diff --git a/src/fireedge/src/client/components/Tables/SecurityGroups/actions.js b/src/fireedge/src/client/components/Tables/SecurityGroups/actions.js new file mode 100644 index 0000000000..26b356971b --- /dev/null +++ b/src/fireedge/src/client/components/Tables/SecurityGroups/actions.js @@ -0,0 +1,251 @@ +/* ------------------------------------------------------------------------- * + * 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 { useMemo } from 'react' +import { useHistory } from 'react-router-dom' +import { Typography } from '@mui/material' +import { AddCircledOutline, Group, Trash } from 'iconoir-react' + +import { useViews } from 'client/features/Auth' +import { PATH } from 'client/apps/sunstone/routesOne' +import { + useCloneSegGroupMutation, + useChangeSecGroupOwnershipMutation, + useRemoveSecGroupMutation, + useCommitSegGroupMutation, +} from 'client/features/OneApi/securityGroup' + +import { ChangeUserForm, ChangeGroupForm } from 'client/components/Forms/Vm' +import { CloneForm } from 'client/components/Forms/SecurityGroups' +import { + createActions, + GlobalAction, +} from 'client/components/Tables/Enhanced/Utils' + +import { Tr, Translate } from 'client/components/HOC' +import { T, SEC_GROUP_ACTIONS, RESOURCE_NAMES } from 'client/constants' + +const ListSecGroupNames = ({ rows = [] }) => + rows?.map?.(({ id, original }) => { + const { ID, NAME } = original + + return ( + + {`#${ID} ${NAME}`} + + ) + }) + +const SubHeader = (rows) => + +const MessageToConfirmAction = (rows, message) => ( + <> + + {message && ( +

+ +

+ )} + + +) + +/** + * Generates the actions to operate resources on Security Groups table. + * + * @returns {GlobalAction} - Actions + */ +const Actions = () => { + const history = useHistory() + const { view, getResourceView } = useViews() + const [clone] = useCloneSegGroupMutation() + const [changeOwnership] = useChangeSecGroupOwnershipMutation() + const [deleteSecGroup] = useRemoveSecGroupMutation() + const [commitSecGroup] = useCommitSegGroupMutation() + + const resourcesView = getResourceView(RESOURCE_NAMES.SEC_GROUP)?.actions + + const segGroupActions = useMemo( + () => + createActions({ + filters: resourcesView, + actions: [ + { + accessor: SEC_GROUP_ACTIONS.CREATE_DIALOG, + dataCy: `securityGroup_${SEC_GROUP_ACTIONS.CREATE_DIALOG}`, + tooltip: T.Create, + icon: AddCircledOutline, + action: () => { + history.push(PATH.NETWORK.SEC_GROUPS.CREATE) + }, + }, + { + accessor: SEC_GROUP_ACTIONS.UPDATE_DIALOG, + label: T.Update, + tooltip: T.Update, + selected: { max: 1 }, + color: 'secondary', + action: (rows) => { + const secGroups = rows?.[0]?.original ?? {} + const path = PATH.NETWORK.SEC_GROUPS.CREATE + + history.push(path, secGroups) + }, + }, + { + accessor: SEC_GROUP_ACTIONS.CLONE, + label: T.Clone, + tooltip: T.Clone, + selected: true, + color: 'secondary', + options: [ + { + dialogProps: { + title: (rows) => { + const isMultiple = rows?.length > 1 + const { ID, NAME } = rows?.[0]?.original ?? {} + + return [ + Tr(isMultiple ? T.CloneSecGroups : T.CloneSecGroup), + !isMultiple && `#${ID} ${NAME}`, + ] + .filter(Boolean) + .join(' - ') + }, + dataCy: 'modal-clone', + }, + form: (rows) => { + const names = rows?.map(({ original }) => original?.NAME) + const stepProps = { isMultiple: names.length > 1 } + const initialValues = { name: `Copy of ${names?.[0]}` } + + return CloneForm({ stepProps, initialValues }) + }, + onSubmit: + (rows) => + async ({ prefix, name } = {}) => { + const secGroups = rows?.map?.( + ({ original: { ID, NAME } = {} }) => + // overwrite all names with prefix+NAME + ({ + id: ID, + name: prefix ? `${prefix} ${NAME}` : name, + }) + ) + + await Promise.all(secGroups.map(clone)) + }, + }, + ], + }, + { + accessor: SEC_GROUP_ACTIONS.COMMIT, + label: T.Commit, + tooltip: T.Commit, + selected: true, + color: 'secondary', + options: [ + { + isConfirmDialog: true, + dialogProps: { + title: T.Confirm, + dataCy: `modal-${SEC_GROUP_ACTIONS.COMMIT}`, + children: (rows) => + MessageToConfirmAction(rows, T.CommitMessageSecGroups), + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => commitSecGroup({ id }))) + }, + }, + ], + }, + { + tooltip: T.Ownership, + icon: Group, + selected: true, + color: 'secondary', + dataCy: 'securityGroup-ownership', + options: [ + { + accessor: SEC_GROUP_ACTIONS.CHANGE_OWNER, + name: T.ChangeOwner, + dialogProps: { + title: T.ChangeOwner, + subheader: SubHeader, + dataCy: `modal-${SEC_GROUP_ACTIONS.CHANGE_OWNER}`, + }, + form: ChangeUserForm, + onSubmit: (rows) => async (newOwnership) => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all( + ids.map((id) => changeOwnership({ id, ...newOwnership })) + ) + }, + }, + { + accessor: SEC_GROUP_ACTIONS.CHANGE_GROUP, + name: T.ChangeGroup, + dialogProps: { + title: T.ChangeGroup, + subheader: SubHeader, + dataCy: `modal-${SEC_GROUP_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: SEC_GROUP_ACTIONS.DELETE, + tooltip: T.Delete, + icon: Trash, + color: 'error', + selected: { min: 1 }, + dataCy: `secGroups_${SEC_GROUP_ACTIONS.DELETE}`, + options: [ + { + isConfirmDialog: true, + dialogProps: { + title: T.Delete, + dataCy: `modal-${SEC_GROUP_ACTIONS.DELETE}`, + children: MessageToConfirmAction, + }, + onSubmit: (rows) => async () => { + const ids = rows?.map?.(({ original }) => original?.ID) + await Promise.all(ids.map((id) => deleteSecGroup({ id }))) + }, + }, + ], + }, + ], + }), + [view] + ) + + return segGroupActions +} + +export default Actions diff --git a/src/fireedge/src/client/components/Tabs/Common/RulesSecGroups.js b/src/fireedge/src/client/components/Tabs/Common/RulesSecGroups.js new file mode 100644 index 0000000000..9f5d67c0d8 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/Common/RulesSecGroups.js @@ -0,0 +1,224 @@ +/* ------------------------------------------------------------------------- * + * 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 { memo, useMemo, ReactElement } from 'react' +import PropTypes from 'prop-types' +import { Link as RouterLink } from 'react-router-dom' +import makeStyles from '@mui/styles/makeStyles' +import { v4 as uuidv4 } from 'uuid' +import { Tr, Translate } from 'client/components/HOC' +import { T, PrettySecurityGroupRule, RESOURCE_NAMES } from 'client/constants' + +import { + styled, + List, + ListItem, + Typography, + Paper, + Stack, + Box, + Link, +} from '@mui/material' +import { rowStyles } from 'client/components/Tables/styles' + +const Title = styled(ListItem)(({ theme }) => ({ + fontWeight: theme.typography.fontWeightBold, + borderBottom: `1px solid ${theme.palette.divider}`, +})) + +const Item = styled(ListItem)(({ theme }) => ({ + gap: '1em', + '& > *': { + flex: '1 1 50%', + overflow: 'hidden', + minHeight: '100%', + }, + '&:hover': { + backgroundColor: theme.palette.action.hover, + }, +})) + +const useStyles = makeStyles({ + container: { + gridColumn: '1 / -1', + }, + item: { + '& > *:first-child': { + flex: '1 1 20%', + }, + }, +}) + +const RulesSecGroupsTable = memo(({ title, rules = [] }) => { + const classes = useStyles() + + return ( + + + {title && ( + + {typeof title === 'string' ? ( + <Typography noWrap>{Tr(title)}</Typography> + ) : ( + title + )} + + )} + + + + + + ) +}) + +RulesSecGroupsTable.propTypes = { + title: PropTypes.any, + rules: PropTypes.arrayOf( + PropTypes.shape({ + PROTOCOL: PropTypes.string, + RULE_TYPE: PropTypes.string, + RANGE: PropTypes.string, + NETWORK: PropTypes.string, + ICMP_TYPE: PropTypes.string, + }) + ), +} + +RulesSecGroupsTable.displayName = 'RulesSecGroupsTable' + +export const SecurityGroupRules = memo( + ({ parentKey = '', id, actions, rules }) => { + const classes = rowStyles() + + const COLUMNS = useMemo( + () => [T.Protocol, T.Type, T.Range, T.Network, T.IcmpType], + [] + ) + + const name = rules?.[0]?.NAME ?? 'default' + + return ( + <> + {id !== undefined && ( + + + {`#${id} ${name}`} + + + {!!actions &&
{actions}
} +
+ )} + + {COLUMNS.map((col) => ( + + + + ))} + {rules.map((rule) => ( + + ))} + + + ) + } +) + +SecurityGroupRules.propTypes = { + parentKey: PropTypes.string, + id: PropTypes.string, + rules: PropTypes.array, + actions: PropTypes.node, +} + +SecurityGroupRules.displayName = 'SecurityGroupRule' + +export const SecurityGroupRule = memo(({ rule = {}, 'data-cy': parentCy }) => { + /** @type {PrettySecurityGroupRule} */ + const { + PROTOCOL = '', + RULE_TYPE = '', + ICMP_TYPE = '', + RANGE = T.All, + NETWORK_ID = T.Any, + } = rule + + /** + * @param {object} rule - rule. + * @param {string} rule.text - rule text + * @param {string} rule.dataCy - rule data-cy + * @param {boolean} rule.link - rule link + * @returns {ReactElement} rule line + */ + const renderLine = ({ text, dataCy, link }) => ( + + {link && !isNaN(text) ? ( + + {text} + + ) : ( + text + )} + + ) + + return ( + <> + {[ + { text: String(PROTOCOL).toUpperCase(), dataCy: 'protocol' }, + { text: String(RULE_TYPE).toUpperCase(), dataCy: 'ruletype' }, + { text: String(RANGE).toUpperCase(), dataCy: 'range' }, + { + text: String(NETWORK_ID).toUpperCase(), + dataCy: 'networkid', + link: true, + }, + { text: String(ICMP_TYPE).toUpperCase(), dataCy: 'icmp-type' }, + ].map(renderLine)} + + ) +}) + +SecurityGroupRule.propTypes = { + rule: PropTypes.object, + 'data-cy': PropTypes.string, +} + +SecurityGroupRule.displayName = 'SecurityGroupRule' + +export default RulesSecGroupsTable diff --git a/src/fireedge/src/client/components/Tabs/Common/index.js b/src/fireedge/src/client/components/Tabs/Common/index.js index 724ef596bd..ba683c94f2 100644 --- a/src/fireedge/src/client/components/Tabs/Common/index.js +++ b/src/fireedge/src/client/components/Tabs/Common/index.js @@ -17,7 +17,8 @@ import AttributePanel from 'client/components/Tabs/Common/AttributePanel' import List from 'client/components/Tabs/Common/List' import Ownership from 'client/components/Tabs/Common/Ownership' import Permissions from 'client/components/Tabs/Common/Permissions' +import RulesSecGroupsTable from 'client/components/Tabs/Common/RulesSecGroups' export * from 'client/components/Tabs/Common/Attribute' -export { AttributePanel, List, Ownership, Permissions } +export { AttributePanel, List, Ownership, Permissions, RulesSecGroupsTable } diff --git a/src/fireedge/src/client/components/Tabs/Image/Vms/index.js b/src/fireedge/src/client/components/Tabs/Image/Vms/index.js index 9d3c5ae441..fded895e66 100644 --- a/src/fireedge/src/client/components/Tabs/Image/Vms/index.js +++ b/src/fireedge/src/client/components/Tabs/Image/Vms/index.js @@ -44,7 +44,7 @@ const VmsTab = ({ id }) => { displaySelectedRows host={image} onRowClick={(row) => handleRowClick(row.ID)} - noDataMessage={} + noDataMessage={} /> ) } diff --git a/src/fireedge/src/client/components/Tabs/SecurityGroup/Info/index.js b/src/fireedge/src/client/components/Tabs/SecurityGroup/Info/index.js new file mode 100644 index 0000000000..a2aee9775f --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/SecurityGroup/Info/index.js @@ -0,0 +1,165 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement, useCallback } from 'react' +import PropTypes from 'prop-types' +import { Stack } from '@mui/material' + +import { + useGetSecGroupQuery, + useChangeSecGroupOwnershipMutation, + useChangeSecGroupPermissionsMutation, + useUpdateSecGroupMutation, +} from 'client/features/OneApi/securityGroup' +import { + Permissions, + Ownership, + AttributePanel, + RulesSecGroupsTable, +} from 'client/components/Tabs/Common' +import Information from 'client/components/Tabs/SecurityGroup/Info/information' + +import { Tr } from 'client/components/HOC' +import { T } from 'client/constants' +import { + getActionsAvailable, + jsonToXml, + filterAttributes, +} from 'client/models/Helper' +import { cloneObject, set } from 'client/utils' + +const HIDDEN_ATTRIBUTES = /^(RULE)$/ + +/** + * Renders mainly information tab. + * + * @param {object} props - Props + * @param {object} props.tabProps - Tab information + * @param {string} props.id - Image id + * @returns {ReactElement} Information tab + */ +const SecurityGroupInfoTab = ({ tabProps = {}, id }) => { + const { + information_panel: informationPanel, + permissions_panel: permissionsPanel, + ownership_panel: ownershipPanel, + attributes_panel: attributesPanel, + rules_panel: rulesPanel, + } = tabProps + + const [changeOwnership] = useChangeSecGroupOwnershipMutation() + const [changePermissions] = useChangeSecGroupPermissionsMutation() + const [update] = useUpdateSecGroupMutation() + const { data: securityGroup } = useGetSecGroupQuery({ id }) + + const { UNAME, UID, GNAME, GID, PERMISSIONS, TEMPLATE } = securityGroup + + const { attributes } = filterAttributes(TEMPLATE, { + hidden: HIDDEN_ATTRIBUTES, + }) + + const handleChangeOwnership = async (newOwnership) => { + await changeOwnership({ id, ...newOwnership }) + } + + const handleChangePermission = async (newPermission) => { + await changePermissions({ id, ...newPermission }) + } + + const handleAttributeInXml = async (path, newValue) => { + const newTemplate = cloneObject(TEMPLATE) + set(newTemplate, path, newValue) + + const xml = jsonToXml(newTemplate) + await update({ id, template: xml, replace: 0 }) + } + + const getActions = useCallback( + (actions) => getActionsAvailable(actions), + [getActionsAvailable] + ) + + const ATTRIBUTE_FUNCTION = { + handleAdd: handleAttributeInXml, + handleEdit: handleAttributeInXml, + handleDelete: handleAttributeInXml, + } + + return ( + + {informationPanel?.enabled && ( + + )} + {permissionsPanel?.enabled && ( + + )} + {ownershipPanel?.enabled && ( + + )} + {rulesPanel?.enabled && ( + + )} + {attributesPanel?.enabled && ( + + )} + + ) +} + +SecurityGroupInfoTab.propTypes = { + tabProps: PropTypes.object, + id: PropTypes.string, +} + +SecurityGroupInfoTab.displayName = 'ImageInfoTab' + +export default SecurityGroupInfoTab diff --git a/src/fireedge/src/client/components/Tabs/SecurityGroup/Info/information.js b/src/fireedge/src/client/components/Tabs/SecurityGroup/Info/information.js new file mode 100644 index 0000000000..0771aa0268 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/SecurityGroup/Info/information.js @@ -0,0 +1,69 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement } from 'react' +import PropTypes from 'prop-types' + +import { useRenameSecGroupMutation } from 'client/features/OneApi/securityGroup' +import { List } from 'client/components/Tabs/Common' +import { T, Image, SEC_GROUP_ACTIONS } from 'client/constants' + +/** + * Renders mainly information tab. + * + * @param {object} props - Props + * @param {Image} props.securityGroup - Security Group resource + * @param {string[]} props.actions - Available actions to information tab + * @returns {ReactElement} Information tab + */ +const InformationPanel = ({ securityGroup = {}, actions }) => { + const [rename] = useRenameSecGroupMutation() + + const { ID, NAME } = securityGroup + + const handleRename = async (_, newName) => { + await rename({ id: ID, name: newName }) + } + + const info = [ + { name: T.ID, value: ID, dataCy: 'id' }, + { + name: T.Name, + value: NAME, + dataCy: 'name', + canEdit: actions?.includes?.(SEC_GROUP_ACTIONS.RENAME), + handleEdit: handleRename, + }, + ] + + return ( + <> + + + ) +} + +InformationPanel.propTypes = { + securityGroup: PropTypes.object, + actions: PropTypes.arrayOf(PropTypes.string), +} + +InformationPanel.displayName = 'InformationPanel' + +export default InformationPanel diff --git a/src/fireedge/src/client/components/Tabs/SecurityGroup/Vms/index.js b/src/fireedge/src/client/components/Tabs/SecurityGroup/Vms/index.js new file mode 100644 index 0000000000..1dd5e481aa --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/SecurityGroup/Vms/index.js @@ -0,0 +1,59 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement } from 'react' +import PropTypes from 'prop-types' +import { T } from 'client/constants' +import EmptyTab from 'client/components/Tabs/EmptyTab' +import { useHistory, generatePath } from 'react-router-dom' +import { PATH } from 'client/apps/sunstone/routesOne' +import { useGetSecGroupQuery } from 'client/features/OneApi/securityGroup' +import { VmsTable } from 'client/components/Tables' + +/** + * Renders mainly Vms tab. + * + * @param {object} props - Props + * @param {string} props.id - Image id + * @returns {ReactElement} vms tab + */ +const VmsTab = ({ id }) => { + const { data: secGroup = {} } = useGetSecGroupQuery({ id }) + const path = PATH.INSTANCE.VMS.DETAIL + const history = useHistory() + + const handleRowClick = (rowId) => { + history.push(generatePath(path, { id: String(rowId) })) + } + + return ( + handleRowClick(row.ID)} + noDataMessage={} + /> + ) +} + +VmsTab.propTypes = { + tabProps: PropTypes.object, + id: PropTypes.string, +} + +VmsTab.displayName = 'VmsTab' + +export default VmsTab diff --git a/src/fireedge/src/client/components/Tabs/SecurityGroup/index.js b/src/fireedge/src/client/components/Tabs/SecurityGroup/index.js new file mode 100644 index 0000000000..bd46d1989b --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/SecurityGroup/index.js @@ -0,0 +1,64 @@ +/* ------------------------------------------------------------------------- * + * 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 { memo, useMemo } from 'react' +import PropTypes from 'prop-types' +import { Alert, LinearProgress } from '@mui/material' + +import { useViews } from 'client/features/Auth' +import { useGetSecGroupQuery } from 'client/features/OneApi/securityGroup' +import { getAvailableInfoTabs } from 'client/models/Helper' +import { RESOURCE_NAMES } from 'client/constants' + +import Tabs from 'client/components/Tabs' +import Info from 'client/components/Tabs/SecurityGroup/Info' +import Vms from 'client/components/Tabs/SecurityGroup/Vms' + +const getTabComponent = (tabName) => + ({ + info: Info, + vms: Vms, + }[tabName]) + +const SecurityGroupTabs = memo(({ id }) => { + const { view, getResourceView } = useViews() + const { isLoading, isError, error } = useGetSecGroupQuery({ id }) + + const tabsAvailable = useMemo(() => { + const resource = RESOURCE_NAMES.SEC_GROUP + const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {} + + return getAvailableInfoTabs(infoTabs, getTabComponent, id) + }, [view]) + + if (isError) { + return ( + + {error.data} + + ) + } + + return isLoading ? ( + + ) : ( + + ) +}) + +SecurityGroupTabs.propTypes = { id: PropTypes.string.isRequired } +SecurityGroupTabs.displayName = 'SecurityGroupTabs' + +export default SecurityGroupTabs diff --git a/src/fireedge/src/client/constants/securityGroup.js b/src/fireedge/src/client/constants/securityGroup.js index 772f83dda7..351e2c34ba 100644 --- a/src/fireedge/src/client/constants/securityGroup.js +++ b/src/fireedge/src/client/constants/securityGroup.js @@ -14,6 +14,7 @@ * limitations under the License. * * ------------------------------------------------------------------------- */ import { T } from 'client/constants' +import * as ACTIONS from 'client/constants/actions' /** * @typedef SecurityGroupRule @@ -116,3 +117,18 @@ export const RULE_TYPE_STRING = { OUTBOUND: T.Outbound, INBOUND: T.Inbound, } + +/** @enum {string} Image actions */ +export const SEC_GROUP_ACTIONS = { + CREATE_DIALOG: 'create_dialog', + UPDATE_DIALOG: 'update_dialog', + DELETE: 'delete', + COMMIT: 'commit', + CLONE: 'clone', + + // INFORMATION + RENAME: ACTIONS.RENAME, + CHANGE_OWNER: ACTIONS.CHANGE_OWNER, + CHANGE_GROUP: ACTIONS.CHANGE_GROUP, + CHANGE_TYPE: 'chtype', +} diff --git a/src/fireedge/src/client/constants/translates.js b/src/fireedge/src/client/constants/translates.js index ad33208652..42936ed3f7 100644 --- a/src/fireedge/src/client/constants/translates.js +++ b/src/fireedge/src/client/constants/translates.js @@ -56,9 +56,13 @@ module.exports = { ChangeOwner: 'Change owner', Clear: 'Clear', ClickToCopy: 'Click to copy', + Confirm: 'Confirm', + Commit: 'Commit', Clone: 'Clone', CloneSeveralTemplates: 'Clone several Templates', CloneTemplate: 'Clone Template', + CloneSecGroup: 'Clone Security Group', + CloneSecGroups: 'Clone Security Groups', Close: 'Close', Collapse: 'Collapse', Configuration: 'Configuration', @@ -72,6 +76,7 @@ module.exports = { CreateVirtualNetwork: 'Create Virtual Network', CreateVmTemplate: 'Create VM Template', CreateImage: 'Create Image', + CreateSecurityGroup: 'Create Security Group', CreateFile: 'Create File', CreateDockerfile: 'Create Dockerfile', CurrentGroup: 'Current group: %s', @@ -870,6 +875,8 @@ module.exports = { CopyOf: 'Copy of ', PrefixMultipleConcept: 'Several templates are selected, please choose prefix to name the new copies', + PrefixSecGroupsMultipleConcept: + 'Several security groups are selected, please choose a prefix to name the new copies Prefix', NewTemplateNameConcept: 'New Image name', CloneWithImages: 'Clone with images', CloneWithImagesConcept: ` @@ -1017,6 +1024,7 @@ module.exports = { Any: 'Any', Protocol: 'Protocol', IcmpType: 'ICMP Type', + IcmpTypeV6: 'ICMPv6 Type', /* Host schema */ IM_MAD: 'IM MAD', @@ -1130,9 +1138,25 @@ module.exports = { DefaultValue: 'Default value', Mandatory: 'Mandatory', PressKeysToAddAValue: 'Press any of the following keys to add a value: %s', - + /** Security Groups */ + Start: 'Start', + Rules: 'Rules', + PortRange: 'Port Range', + FirstIPIPv6Address: 'First IP/IPv6 address', + TargetNetwork: 'Target Network', + AnyNetwork: 'Any Network', + ManualNetwork: 'Manual Network', + OpennebulaVirtualNetwork: 'OpenNebula Virtual Network', + SelectNewNetwork: 'Please select a network from the list', + NotVmsCurrentySecGroups: + 'There are currently no VMs associated with this Security Group', + CommitMessageSecGroups: ` + Please note: each time the rules are edited, the commit operation is done automatically. + This action will force the propagation of security group changes to VMs. + The operation takes time to iterate over all VMs in the security group, + the progress can be checked in the "VMs" panel.`, /** Image */ - NotVmsCurrenty: 'There are currently no VMs associated with this image', + NotVmsCurrentyImage: 'There are currently no VMs associated with this image', NotSnapshotCurrenty: 'There are currently no snapshots associated with this image', diff --git a/src/fireedge/src/client/containers/Files/index.js b/src/fireedge/src/client/containers/Files/index.js index 43bea7fac2..c916c5613f 100644 --- a/src/fireedge/src/client/containers/Files/index.js +++ b/src/fireedge/src/client/containers/Files/index.js @@ -88,6 +88,9 @@ const InfoTabs = memo(({ file, gotoPage, unselect }) => { return ( + + {`#${id} | ${name}`} + } @@ -111,9 +114,6 @@ const InfoTabs = memo(({ file, gotoPage, unselect }) => { onClick={() => unselect()} /> )} - - {`#${id} | ${name}`} - diff --git a/src/fireedge/src/client/containers/Images/index.js b/src/fireedge/src/client/containers/Images/index.js index f66bdb6819..4213ad6cdc 100644 --- a/src/fireedge/src/client/containers/Images/index.js +++ b/src/fireedge/src/client/containers/Images/index.js @@ -87,7 +87,10 @@ const InfoTabs = memo(({ image, gotoPage, unselect }) => { return ( - + + + {`#${id} | ${name}`} + } @@ -111,9 +114,6 @@ const InfoTabs = memo(({ image, gotoPage, unselect }) => { onClick={() => unselect()} /> )} - - {`#${id} | ${name}`} - diff --git a/src/fireedge/src/client/containers/SecurityGroups/Create.js b/src/fireedge/src/client/containers/SecurityGroups/Create.js new file mode 100644 index 0000000000..c6007f8b06 --- /dev/null +++ b/src/fireedge/src/client/containers/SecurityGroups/Create.js @@ -0,0 +1,79 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement } from 'react' +import { useHistory, useLocation } from 'react-router' + +import { jsonToXml } from 'client/models/Helper' +import { useGeneralApi } from 'client/features/General' +import { + useAllocateSecGroupMutation, + useUpdateSecGroupMutation, + useGetSecGroupQuery, +} from 'client/features/OneApi/securityGroup' + +import { + DefaultFormStepper, + SkeletonStepsForm, +} from 'client/components/FormStepper' +import { CreateForm } from 'client/components/Forms/SecurityGroups' +import { PATH } from 'client/apps/sunstone/routesOne' + +/** + * Displays the creation or modification form to a VM Template. + * + * @returns {ReactElement} VM Template form + */ +function CreateSecGroup() { + const history = useHistory() + const { state: { ID: secID, NAME } = {} } = useLocation() + + const [allocate] = useAllocateSecGroupMutation() + const [update] = useUpdateSecGroupMutation() + const { enqueueSuccess } = useGeneralApi() + + const { data } = useGetSecGroupQuery({ id: secID }) + + const onSubmit = async ({ template }) => { + try { + if (!secID) { + const newTemplateId = await allocate({ + template: jsonToXml(template), + }).unwrap() + history.push(PATH.NETWORK.SEC_GROUPS.LIST) + enqueueSuccess(`Security Group created - #${newTemplateId}`) + } else { + await update({ id: secID, template: jsonToXml(template) }).unwrap() + history.push(PATH.NETWORK.SEC_GROUPS.LIST) + enqueueSuccess(`Security Group updated - #${secID} ${NAME}`) + } + } catch {} + } + + return secID && !data ? ( + + ) : ( + } + initialValues={data} + stepProps={data} + > + {(config) => } + + ) +} + +export default CreateSecGroup diff --git a/src/fireedge/src/client/containers/SecurityGroups/index.js b/src/fireedge/src/client/containers/SecurityGroups/index.js new file mode 100644 index 0000000000..f009514b60 --- /dev/null +++ b/src/fireedge/src/client/containers/SecurityGroups/index.js @@ -0,0 +1,157 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement, useState, memo } from 'react' +import PropTypes from 'prop-types' +import GotoIcon from 'iconoir-react/dist/Pin' +import RefreshDouble from 'iconoir-react/dist/RefreshDouble' +import Cancel from 'iconoir-react/dist/Cancel' +import { Typography, Box, Stack, Chip } from '@mui/material' +import { Row } from 'react-table' + +import { useLazyGetSecGroupQuery } from 'client/features/OneApi/securityGroup' +import { SecurityGroupsTable } from 'client/components/Tables' +import SecurityGroupsActions from 'client/components/Tables/SecurityGroups/actions' +import SecurityGroupsTabs from 'client/components/Tabs/SecurityGroup' +import SplitPane from 'client/components/SplitPane' +import MultipleTags from 'client/components/MultipleTags' +import { SubmitButton } from 'client/components/FormControl' +import { Tr } from 'client/components/HOC' +import { T, Image } from 'client/constants' + +/** + * Displays a list of Security Groups with a split pane between the list and selected row(s). + * + * @returns {ReactElement} Security Groups list and selected row(s) + */ +function SecurityGroups() { + const [selectedRows, onSelectedRowsChange] = useState(() => []) + const actions = SecurityGroupsActions() + + const hasSelectedRows = selectedRows?.length > 0 + const moreThanOneSelected = selectedRows?.length > 1 + + return ( + + {({ getGridProps, GutterComponent }) => ( + + + + {hasSelectedRows && ( + <> + + {moreThanOneSelected ? ( + + ) : ( + selectedRows[0]?.toggleRowSelected(false)} + /> + )} + + )} + + )} + + ) +} + +/** + * Displays details of Security Group. + * + * @param {Image} securityGroup - Security Group to display + * @param {Function} [gotoPage] - Function to navigate to a page of an Security Group + * @param {Function} [unselect] - Function to unselect a Security Group + * @returns {ReactElement} Security Group details + */ +const InfoTabs = memo(({ securityGroup, gotoPage, unselect }) => { + const [getSecurityGroup, { data: lazyData, isFetching }] = + useLazyGetSecGroupQuery() + const id = lazyData?.ID ?? securityGroup.ID + const name = lazyData?.NAME ?? securityGroup.NAME + + return ( + + + + {`#${id} | ${name}`} + + } + tooltip={Tr(T.Refresh)} + isSubmitting={isFetching} + onClick={() => getSecurityGroup({ id })} + /> + {typeof gotoPage === 'function' && ( + } + tooltip={Tr(T.LocateOnTable)} + onClick={() => gotoPage()} + /> + )} + {typeof unselect === 'function' && ( + } + tooltip={Tr(T.Close)} + onClick={() => unselect()} + /> + )} + + + + ) +}) + +InfoTabs.propTypes = { + securityGroup: PropTypes.object.isRequired, + gotoPage: PropTypes.func, + unselect: PropTypes.func, +} + +InfoTabs.displayName = 'InfoTabs' + +/** + * Displays a list of tags that represent the selected rows. + * + * @param {Row[]} tags - Row(s) to display as tags + * @returns {ReactElement} List of tags + */ +const GroupedTags = memo(({ tags = [] }) => ( + + ( + toggleRowSelected(false)} + /> + ))} + /> + +)) + +GroupedTags.propTypes = { tags: PropTypes.array } +GroupedTags.displayName = 'GroupedTags' + +export default SecurityGroups diff --git a/src/fireedge/src/client/containers/VirtualNetworks/Detail.js b/src/fireedge/src/client/containers/VirtualNetworks/Detail.js new file mode 100644 index 0000000000..12cef9f229 --- /dev/null +++ b/src/fireedge/src/client/containers/VirtualNetworks/Detail.js @@ -0,0 +1,36 @@ +/* ------------------------------------------------------------------------- * + * 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 { ReactElement } from 'react' +import { useParams, Redirect } from 'react-router-dom' + +import VNetworkTabs from 'client/components/Tabs/VNetwork' + +/** + * Displays the detail information about a VM Template. + * + * @returns {ReactElement} VM Template detail component. + */ +function VNetworkDetail() { + const { id } = useParams() + + if (Number.isNaN(+id)) { + return + } + + return +} + +export default VNetworkDetail diff --git a/src/fireedge/src/client/features/OneApi/securityGroup.js b/src/fireedge/src/client/features/OneApi/securityGroup.js index 55e62e5b32..0e3aec50f6 100644 --- a/src/fireedge/src/client/features/OneApi/securityGroup.js +++ b/src/fireedge/src/client/features/OneApi/securityGroup.js @@ -19,7 +19,7 @@ import { ONE_RESOURCES, ONE_RESOURCES_POOL, } from 'client/features/OneApi' -import { FilterFlag } from 'client/constants' +import { FilterFlag, Permission } from 'client/constants' const { SECURITYGROUP } = ONE_RESOURCES const { SECURITYGROUP_POOL } = ONE_RESOURCES_POOL @@ -75,6 +75,169 @@ const securityGroupApi = oneApi.injectEndpoints({ transformResponse: (data) => data?.SECURITY_GROUP ?? {}, providesTags: (_, __, { id }) => [{ type: SECURITYGROUP, id }], }), + renameSecGroup: builder.mutation({ + /** + * Renames a Security Group. + * + * @param {object} params - Request parameters + * @param {string} params.id - Security group id + * @param {string} params.name - The new name + * @returns {number} Security group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_RENAME + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: (_, __, { id }) => [ + { type: SECURITYGROUP, id }, + SECURITYGROUP_POOL, + ], + }), + changeSecGroupPermissions: builder.mutation({ + /** + * Changes the permission bits of a Image. + * If set any permission to -1, it's not changed. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Image id + * @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 {number} Image id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_CHMOD + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: (_, __, { id }) => [{ type: SECURITYGROUP, id }], + }), + changeSecGroupOwnership: builder.mutation({ + /** + * Changes the ownership of Security Group. + * If set `user` or `group` to -1, it's not changed. + * + * @param {object} params - Request parameters + * @param {string|number} params.id - Security Group id + * @param {string|number|'-1'} [params.userId] - User id + * @param {Permission|'-1'} [params.groupId] - Group id + * @returns {number} Security Group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_CHOWN + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: (_, __, { id }) => [ + { type: SECURITYGROUP, id }, + SECURITYGROUP_POOL, + ], + }), + allocateSecGroup: builder.mutation({ + /** + * Allocates a new Security Group in OpenNebula. + * + * @param {object} params - Request params + * @param {string} params.template - A string containing the template of the Security Group on syntax XML + * @returns {number} Security Group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_ALLOCATE + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: [SECURITYGROUP_POOL], + }), + cloneSegGroup: builder.mutation({ + /** + * Clones an existing Security Group. + * + * @param {object} params - Request params + * @param {string} params.id - The id of the Security Group to be cloned + * @param {string} params.name - Name for the new Security Group + * @returns {number} The new Security Group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_CLONE + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: [SECURITYGROUP_POOL], + }), + removeSecGroup: builder.mutation({ + /** + * Deletes the given Security Group from the pool. + * + * @param {object} params - Request params + * @param {string} params.id - The object id + * @returns {number} Security Group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_DELETE + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: [SECURITYGROUP_POOL], + }), + updateSecGroup: builder.mutation({ + /** + * Replaces the Security Group template contents. + * + * @param {number|string} params - Request params + * @param {string} params.id - Security Group id + * @param {string} params.template - The new template contents on syntax XML + * @param {0|1} params.replace - + * - Update type: + * ``0``: Replace the whole template. + * ``1``: Merge new template with the existing one. + * @returns {number} Security Group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_UPDATE + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: (_, __, { id }) => [{ type: SECURITYGROUP, id }], + }), + commitSegGroup: builder.mutation({ + /** + * Commit an existing Security Group. + * + * @param {object} params - Request params + * @param {string} params.id - The id of the Security Group to be cloned + * @param {string} params.vms - Vms for the new Security Group + * @returns {number} The new Security Group id + * @throws Fails when response isn't code 200 + */ + query: (params) => { + const name = Actions.SECGROUP_COMMIT + const command = { name, ...Commands[name] } + + return { params, command } + }, + invalidatesTags: [SECURITYGROUP_POOL], + }), }), }) @@ -84,6 +247,14 @@ export const { useLazyGetSecGroupQuery, useGetSecGroupsQuery, useLazyGetSecGroupsQuery, + useRenameSecGroupMutation, + useAllocateSecGroupMutation, + useCloneSegGroupMutation, + useRemoveSecGroupMutation, + useUpdateSecGroupMutation, + useCommitSegGroupMutation, + useChangeSecGroupPermissionsMutation, + useChangeSecGroupOwnershipMutation, } = securityGroupApi export default securityGroupApi