mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-22 18:50:08 +03:00
parent
2abb92c1be
commit
15a4825445
@ -19,7 +19,6 @@ import {
|
||||
Server as ClusterIcon,
|
||||
Db as DatastoreIcon,
|
||||
Archive as FileIcon,
|
||||
Folder as VmGroupIcon,
|
||||
Group as GroupIcon,
|
||||
HardDrive as HostIcon,
|
||||
BoxIso as ImageIcon,
|
||||
@ -40,6 +39,7 @@ import {
|
||||
User as UserIcon,
|
||||
List as VDCIcon,
|
||||
Shuffle as VRoutersIcons,
|
||||
Folder as VmGroupIcon,
|
||||
ModernTv as VmsIcons,
|
||||
MinusPinAlt as ZoneIcon,
|
||||
KeyAlt as ACLIcon,
|
||||
@ -198,6 +198,17 @@ const VNetworkTemplates = loadable(
|
||||
() => import('client/containers/VNetworkTemplates'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const InstantiateVirtualNetwork = loadable(
|
||||
() => import('client/containers/VNetworkTemplates/Instantiate'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const CreateVirtualNetworkTemplate = loadable(
|
||||
() => import('client/containers/VNetworkTemplates/Create'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
// const NetworkTopologies = loadable(() => import('client/containers/NetworkTopologies'), { ssr: false })
|
||||
|
||||
const Clusters = loadable(() => import('client/containers/Clusters'), {
|
||||
@ -352,7 +363,10 @@ export const PATH = {
|
||||
},
|
||||
VN_TEMPLATES: {
|
||||
LIST: `/${RESOURCE_NAMES.VN_TEMPLATE}`,
|
||||
INSTANTIATE: `/${RESOURCE_NAMES.VN_TEMPLATE}/instantiate`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VN_TEMPLATE}/:id`,
|
||||
CREATE: `/${RESOURCE_NAMES.VN_TEMPLATE}/create`,
|
||||
UPDATE: `/${RESOURCE_NAMES.VN_TEMPLATE}/update`,
|
||||
},
|
||||
SEC_GROUPS: {
|
||||
LIST: `/${RESOURCE_NAMES.SEC_GROUP}`,
|
||||
@ -639,11 +653,19 @@ const ENDPOINTS = [
|
||||
icon: NetworkIcon,
|
||||
Component: VirtualNetworks,
|
||||
},
|
||||
// JORGE
|
||||
{
|
||||
title: T.InstantiateVnTemplate,
|
||||
description: (_, state) =>
|
||||
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
|
||||
path: PATH.NETWORK.VN_TEMPLATES.INSTANTIATE,
|
||||
Component: InstantiateVirtualNetwork,
|
||||
},
|
||||
{
|
||||
title: (_, state) =>
|
||||
state?.ID !== undefined
|
||||
? T.UpdateVirtualNetwork
|
||||
: T.CreateVirtualNetwork,
|
||||
? T.UpdateVirtualNetworkTemplate
|
||||
: T.CreateVirtualNetworkTemplate,
|
||||
description: (_, state) =>
|
||||
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
|
||||
path: PATH.NETWORK.VNETS.CREATE,
|
||||
@ -669,6 +691,16 @@ const ENDPOINTS = [
|
||||
icon: NetworkTemplateIcon,
|
||||
Component: VNetworkTemplates,
|
||||
},
|
||||
{
|
||||
title: (_, state) =>
|
||||
state?.ID !== undefined
|
||||
? T.UpdateVirtualNetworkTemplate
|
||||
: T.CreateVirtualNetworkTemplate,
|
||||
description: (_, state) =>
|
||||
state?.ID !== undefined && `#${state.ID} ${state.NAME}`,
|
||||
path: PATH.NETWORK.VN_TEMPLATES.CREATE,
|
||||
Component: CreateVirtualNetworkTemplate,
|
||||
},
|
||||
{
|
||||
title: T.SecurityGroups,
|
||||
path: PATH.NETWORK.SEC_GROUPS.LIST,
|
||||
|
@ -13,23 +13,23 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import AddIcon from 'iconoir-react/dist/Plus'
|
||||
import EditIcon from 'iconoir-react/dist/Edit'
|
||||
import AddIcon from 'iconoir-react/dist/Plus'
|
||||
import TrashIcon from 'iconoir-react/dist/Trash'
|
||||
import PropTypes from 'prop-types'
|
||||
import { memo } from 'react'
|
||||
|
||||
import {
|
||||
useAddRangeToVNetMutation,
|
||||
useUpdateVNetRangeMutation,
|
||||
useRemoveRangeFromVNetMutation,
|
||||
} from 'client/features/OneApi/network'
|
||||
import ButtonToTriggerForm from 'client/components/Forms/ButtonToTriggerForm'
|
||||
import { AddRangeForm } from 'client/components/Forms/VNetwork'
|
||||
import {
|
||||
useAddRangeToVNetMutation,
|
||||
useRemoveRangeFromVNetMutation,
|
||||
useUpdateVNetRangeMutation,
|
||||
} from 'client/features/OneApi/network'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { RESTRICTED_ATTRIBUTES_TYPE, T, VN_ACTIONS } from 'client/constants'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import { T, VN_ACTIONS, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
|
||||
import { hasRestrictedAttributes } from 'client/utils'
|
||||
|
||||
@ -104,7 +104,9 @@ const UpdateAddressRangeAction = memo(
|
||||
options={[
|
||||
{
|
||||
dialogProps: {
|
||||
title: `${Tr(T.AddressRange)}: #${AR_ID}`,
|
||||
title: AR_ID
|
||||
? `${Tr(T.AddressRange)}: #${AR_ID}`
|
||||
: `${Tr(T.AddressRange)}`,
|
||||
dataCy: 'modal-update-ar',
|
||||
},
|
||||
form: () =>
|
||||
@ -156,12 +158,9 @@ const DeleteAddressRangeAction = memo(
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: (
|
||||
<>
|
||||
<Translate word={T.DeleteAddressRange} />
|
||||
{`: #${AR_ID}`}
|
||||
</>
|
||||
),
|
||||
title: AR_ID
|
||||
? `${Tr(T.DeleteAddressRange)}: #${AR_ID}`
|
||||
: `${Tr(T.DeleteAddressRange)}`,
|
||||
children: <p>{Tr(T.DoYouWantProceed)}</p>,
|
||||
},
|
||||
onSubmit: handleRemove,
|
||||
@ -189,6 +188,6 @@ DeleteAddressRangeAction.displayName = 'DeleteAddressRangeAction'
|
||||
|
||||
export {
|
||||
AddAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
DeleteAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
}
|
||||
|
@ -13,27 +13,27 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, memo, useMemo } from 'react'
|
||||
import { Box, Link, Typography } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement, memo, useMemo } from 'react'
|
||||
import { Link as RouterLink, generatePath } from 'react-router-dom'
|
||||
import { Typography, Link, Box } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import { getARLeasesInfo } from 'client/models/VirtualNetwork'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
import {
|
||||
T,
|
||||
VirtualNetwork,
|
||||
AddressRange,
|
||||
VNET_THRESHOLD,
|
||||
RESOURCE_NAMES,
|
||||
T,
|
||||
VNET_THRESHOLD,
|
||||
VirtualNetwork,
|
||||
} from 'client/constants'
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { getARLeasesInfo } from 'client/models/VirtualNetwork'
|
||||
|
||||
const { VNET } = RESOURCE_NAMES
|
||||
|
||||
@ -108,7 +108,7 @@ const AddressRangeCard = memo(
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span" data-cy="id">
|
||||
{`#${AR_ID}`}
|
||||
{`#${AR_ID || '-'}`}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags tags={labels} limitTags={labels.length} />
|
||||
|
@ -0,0 +1,140 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack, Typography } from '@mui/material'
|
||||
import AddressesIcon from 'iconoir-react/dist/Menu'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFieldArray } from 'react-hook-form'
|
||||
|
||||
import {
|
||||
AddAddressRangeAction,
|
||||
DeleteAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
} from 'client/components/Buttons'
|
||||
import { AddressRangeCard } from 'client/components/Cards'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { mapNameByIndex } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = 'AR'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('AR')
|
||||
|
||||
const AddressesContent = ({ oneConfig, adminGroup }) => {
|
||||
const {
|
||||
fields: addresses,
|
||||
remove,
|
||||
update,
|
||||
append,
|
||||
} = useFieldArray({
|
||||
name: `${EXTRA_ID}.${TAB_ID}`,
|
||||
keyName: 'ID',
|
||||
})
|
||||
|
||||
const handleCreateAction = (action) => {
|
||||
append(mapNameFunction(action, addresses.length))
|
||||
}
|
||||
|
||||
const handleUpdate = (action, index) => {
|
||||
update(index, mapNameFunction(action, index))
|
||||
}
|
||||
|
||||
const handleRemove = (index) => {
|
||||
remove(index)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack flexDirection="row" gap="1em">
|
||||
<AddAddressRangeAction
|
||||
onSubmit={handleCreateAction}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
pb="1em"
|
||||
display="grid"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(300px, 0.5fr))"
|
||||
gap="1em"
|
||||
mt="1em"
|
||||
>
|
||||
{addresses?.map((ar, index) => {
|
||||
const key = ar.ID ?? ar.NAME
|
||||
const fakeValues = { ...ar, AR_ID: index }
|
||||
|
||||
return (
|
||||
<AddressRangeCard
|
||||
key={key}
|
||||
ar={fakeValues}
|
||||
actions={
|
||||
<>
|
||||
<UpdateAddressRangeAction
|
||||
vm={{}}
|
||||
ar={fakeValues}
|
||||
onSubmit={(updatedAr) => handleUpdate(updatedAr, index)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
<DeleteAddressRangeAction
|
||||
ar={fakeValues}
|
||||
onSubmit={() => handleRemove(index)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
AddressesContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) =>
|
||||
isUpdate ? (
|
||||
<Typography variant="subtitle2">
|
||||
<Translate word={T.DisabledAddressRangeInForm} />
|
||||
</Typography>
|
||||
) : (
|
||||
<AddressesContent oneConfig={oneConfig} adminGroup={adminGroup} />
|
||||
)
|
||||
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'addresses',
|
||||
name: T.Addresses,
|
||||
icon: AddressesIcon,
|
||||
Content,
|
||||
getError: (error) => !!error?.[TAB_ID],
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,82 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack } from '@mui/material'
|
||||
import ClusterIcon from 'iconoir-react/dist/Server'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
|
||||
import { ClustersTable } from 'client/components/Tables'
|
||||
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { isRestrictedAttributes } from 'client/utils'
|
||||
|
||||
export const TAB_ID = 'CLUSTER'
|
||||
|
||||
const ClustersContent = ({ oneConfig, adminGroup }) => {
|
||||
const TAB_PATH = `${EXTRA_ID}.${TAB_ID}`
|
||||
|
||||
const { setValue } = useFormContext()
|
||||
const clusters = useWatch({ name: TAB_PATH })
|
||||
|
||||
const selectedRowIds =
|
||||
clusters?.reduce((res, id) => ({ ...res, [id]: true }), {}) || {}
|
||||
|
||||
const handleSelectedRows = (rows) => {
|
||||
const newValue = rows?.map((row) => row.original.ID) || []
|
||||
setValue(TAB_PATH, newValue)
|
||||
}
|
||||
|
||||
const readOnly =
|
||||
!adminGroup &&
|
||||
isRestrictedAttributes(
|
||||
'CLUSTER',
|
||||
undefined,
|
||||
oneConfig?.VNET_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
return (
|
||||
<ClustersTable
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
pageSize={5}
|
||||
initialState={{ selectedRowIds }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
ClustersContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'clusters',
|
||||
name: T.Clusters,
|
||||
icon: ClusterIcon,
|
||||
Content: ClustersContent,
|
||||
getError: (error) => !!error?.[TAB_ID],
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,52 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 ConfigurationsIcon from 'iconoir-react/dist/Settings'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { FIELDS } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/configuration/schema'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const ConfigurationContent = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
<FormWithSchema
|
||||
id={EXTRA_ID}
|
||||
cy="configuration"
|
||||
fields={FIELDS(oneConfig, adminGroup)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
ConfigurationContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'configuration',
|
||||
name: T.Configuration,
|
||||
icon: ConfigurationsIcon,
|
||||
Content: ConfigurationContent,
|
||||
getError: (error) => FIELDS().some(({ name }) => error?.[name]),
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,267 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { boolean, lazy, object, string } from 'yup'
|
||||
|
||||
import {
|
||||
INPUT_TYPES,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
T,
|
||||
VN_DRIVERS,
|
||||
VN_DRIVERS_STR,
|
||||
} from 'client/constants'
|
||||
import {
|
||||
Field,
|
||||
OPTION_SORTERS,
|
||||
arrayToOptions,
|
||||
disableFields,
|
||||
getObjectSchemaFromFields,
|
||||
} from 'client/utils'
|
||||
|
||||
const {
|
||||
fw,
|
||||
dot1Q,
|
||||
vxlan,
|
||||
ovswitch,
|
||||
ovswitch_vxlan: openVSwitchVXLAN,
|
||||
} = VN_DRIVERS
|
||||
|
||||
const drivers = Object.keys(VN_DRIVERS_STR)
|
||||
|
||||
/** @type {Field} Driver field */
|
||||
const DRIVER_FIELD = {
|
||||
name: 'VN_MAD',
|
||||
type: INPUT_TYPES.TOGGLE,
|
||||
values: () =>
|
||||
arrayToOptions(drivers, {
|
||||
addEmpty: false,
|
||||
getText: (key) => VN_DRIVERS_STR[key],
|
||||
sorter: OPTION_SORTERS.unsort,
|
||||
}),
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => drivers[0]),
|
||||
grid: { md: 12 },
|
||||
notNull: true,
|
||||
}
|
||||
|
||||
/** @type {Field} Bridge linux field */
|
||||
const BRIDGE_FIELD = {
|
||||
name: 'BRIDGE',
|
||||
label: T.Bridge,
|
||||
tooltip: T.BridgeConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.default(() => undefined),
|
||||
}
|
||||
|
||||
/** @type {Field} Physical device field */
|
||||
const PHYDEV_FIELD = {
|
||||
name: 'PHYDEV',
|
||||
label: T.PhysicalDevice,
|
||||
tooltip: T.PhysicalDeviceConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined)
|
||||
.when(DRIVER_FIELD.name, {
|
||||
is: (driver) => [dot1Q, vxlan, openVSwitchVXLAN].includes(driver),
|
||||
then: (schema) => schema.required(),
|
||||
}),
|
||||
}
|
||||
|
||||
/** @type {Field} Filter MAC spoofing field */
|
||||
const FILTER_MAC_SPOOFING_FIELD = {
|
||||
name: 'FILTER_MAC_SPOOFING',
|
||||
label: T.MacSpoofingFilter,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
dependOf: DRIVER_FIELD.name,
|
||||
htmlType: (driver) => {
|
||||
const allowedDrivers = [fw, dot1Q, vxlan, ovswitch, openVSwitchVXLAN]
|
||||
|
||||
return !allowedDrivers.includes(driver) && INPUT_TYPES.HIDDEN
|
||||
},
|
||||
validation: boolean().yesOrNo(),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} Filter IP spoofing field */
|
||||
const FILTER_IP_SPOOFING_FIELD = {
|
||||
name: 'FILTER_IP_SPOOFING',
|
||||
label: T.IpSpoofingFilter,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
dependOf: DRIVER_FIELD.name,
|
||||
htmlType: (driver) => {
|
||||
const allowedDrivers = [fw, dot1Q, vxlan, ovswitch, openVSwitchVXLAN]
|
||||
|
||||
return !allowedDrivers.includes(driver) && INPUT_TYPES.HIDDEN
|
||||
},
|
||||
validation: boolean().yesOrNo(),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} MTU field */
|
||||
const MTU_FIELD = {
|
||||
name: 'MTU',
|
||||
label: T.MTU,
|
||||
tooltip: T.MTUConcept,
|
||||
dependOf: DRIVER_FIELD.name,
|
||||
htmlType: (driver) => {
|
||||
const allowedDrivers = [dot1Q, vxlan, ovswitch, openVSwitchVXLAN]
|
||||
|
||||
return !allowedDrivers.includes(driver) && INPUT_TYPES.HIDDEN
|
||||
},
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined),
|
||||
}
|
||||
|
||||
/** @type {Field} Automatic VLAN field */
|
||||
const AUTOMATIC_VLAN_FIELD = {
|
||||
name: 'AUTOMATIC_VLAN_ID',
|
||||
label: T.AutomaticVlanId,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
dependOf: DRIVER_FIELD.name,
|
||||
htmlType: (driver) => {
|
||||
const allowedDrivers = [dot1Q, vxlan, ovswitch, openVSwitchVXLAN]
|
||||
|
||||
return !allowedDrivers.includes(driver) && INPUT_TYPES.HIDDEN
|
||||
},
|
||||
validation: lazy((_, { context }) =>
|
||||
boolean()
|
||||
.yesOrNo()
|
||||
.default(() => context?.AUTOMATIC_VLAN_ID === '1')
|
||||
),
|
||||
grid: (self) => (self ? { md: 12 } : { sm: 6 }),
|
||||
}
|
||||
|
||||
/** @type {Field} VLAN ID field */
|
||||
const VLAN_ID_FIELD = {
|
||||
name: 'VLAN_ID',
|
||||
label: T.VlanId,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
dependOf: [DRIVER_FIELD.name, AUTOMATIC_VLAN_FIELD.name],
|
||||
htmlType: ([driver, automatic] = []) => {
|
||||
const allowedDrivers = [dot1Q, vxlan, ovswitch, openVSwitchVXLAN]
|
||||
if (automatic) {
|
||||
return INPUT_TYPES.HIDDEN
|
||||
} else if (!allowedDrivers.includes(driver)) {
|
||||
return INPUT_TYPES.HIDDEN
|
||||
}
|
||||
},
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined)
|
||||
.when(AUTOMATIC_VLAN_FIELD.name, {
|
||||
is: (automatic) => !automatic,
|
||||
then: (schema) => schema.required(),
|
||||
}),
|
||||
grid: { sm: 6 },
|
||||
}
|
||||
|
||||
/** @type {Field} Automatic Outer VLAN field */
|
||||
const AUTOMATIC_OUTER_VLAN_ID_FIELD = {
|
||||
name: 'AUTOMATIC_OUTER_VLAN_ID',
|
||||
label: T.AutomaticOuterVlanId,
|
||||
type: INPUT_TYPES.SWITCH,
|
||||
dependOf: DRIVER_FIELD.name,
|
||||
htmlType: (driver) => {
|
||||
const allowedDrivers = [openVSwitchVXLAN]
|
||||
|
||||
return !allowedDrivers.includes(driver) && INPUT_TYPES.HIDDEN
|
||||
},
|
||||
validation: lazy((_, { context }) =>
|
||||
boolean()
|
||||
.yesOrNo()
|
||||
.default(() => context?.AUTOMATIC_OUTER_VLAN_ID === '1')
|
||||
),
|
||||
grid: (self) => (self ? { md: 12 } : { sm: 6 }),
|
||||
}
|
||||
|
||||
/** @type {Field} Outer VLAN ID field */
|
||||
const OUTER_VLAN_ID_FIELD = {
|
||||
name: 'OUTER_VLAN_ID',
|
||||
label: T.OuterVlanId,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
dependOf: [DRIVER_FIELD.name, AUTOMATIC_OUTER_VLAN_ID_FIELD.name],
|
||||
htmlType: ([driver, oAutomatic] = []) => {
|
||||
const allowedDrivers = [openVSwitchVXLAN]
|
||||
if (oAutomatic) {
|
||||
return INPUT_TYPES.HIDDEN
|
||||
} else if (!allowedDrivers.includes(driver)) {
|
||||
return INPUT_TYPES.HIDDEN
|
||||
}
|
||||
},
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined)
|
||||
.when(AUTOMATIC_OUTER_VLAN_ID_FIELD.name, {
|
||||
is: (oAutomatic) => !oAutomatic,
|
||||
then: (schema) => schema.required(),
|
||||
}),
|
||||
grid: { sm: 6 },
|
||||
}
|
||||
|
||||
const IP_LINK_CONF_FIELD = {
|
||||
name: 'IP_LINK_CONF',
|
||||
validation: object().afterSubmit((conf, { parent }) => {
|
||||
if (vxlan !== parent[DRIVER_FIELD.name]) return
|
||||
|
||||
// => string result: IP_LINK_CONF="option1=value1,option2=,..."
|
||||
return Object.entries(conf || {})
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join(',')
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Array} Fields
|
||||
*/
|
||||
const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[
|
||||
DRIVER_FIELD,
|
||||
BRIDGE_FIELD,
|
||||
PHYDEV_FIELD,
|
||||
FILTER_MAC_SPOOFING_FIELD,
|
||||
FILTER_IP_SPOOFING_FIELD,
|
||||
AUTOMATIC_VLAN_FIELD,
|
||||
VLAN_ID_FIELD,
|
||||
AUTOMATIC_OUTER_VLAN_ID_FIELD,
|
||||
OUTER_VLAN_ID_FIELD,
|
||||
MTU_FIELD,
|
||||
],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {object} Schema
|
||||
*/
|
||||
const SCHEMA = (oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(FIELDS(oneConfig, adminGroup))
|
||||
|
||||
export { FIELDS, IP_LINK_CONF_FIELD, SCHEMA }
|
@ -0,0 +1,80 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useCallback, useMemo } from 'react'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
import { reach } from 'yup'
|
||||
|
||||
import { getUnknownVars } from 'client/components/Forms/VNTemplate/CreateForm/Steps'
|
||||
import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
|
||||
import { Legend } from 'client/components/Forms'
|
||||
import { AttributePanel } from 'client/components/Tabs/Common'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Renders the context attributes to Virtual Network form.
|
||||
*
|
||||
* @returns {ReactElement} - Context attributes section
|
||||
*/
|
||||
const ContextAttrsSection = () => {
|
||||
const { enqueueError } = useGeneralApi()
|
||||
const { setValue, getResolver } = useFormContext()
|
||||
const extraStepVars = useWatch({ name: EXTRA_ID }) || {}
|
||||
|
||||
const unknownVars = useMemo(
|
||||
() => getUnknownVars(extraStepVars, getResolver()),
|
||||
[extraStepVars]
|
||||
)
|
||||
|
||||
const handleChangeAttribute = useCallback(
|
||||
(path, newValue) => {
|
||||
try {
|
||||
const extraSchema = reach(getResolver(), EXTRA_ID)
|
||||
|
||||
// retrieve the schema for the given path
|
||||
reach(extraSchema, path)
|
||||
enqueueError(T.InvalidAttribute)
|
||||
} catch (e) {
|
||||
// When the path is not found, it means that
|
||||
// the attribute is correct and we can set it
|
||||
setValue(`${EXTRA_ID}.${path}`, newValue)
|
||||
}
|
||||
},
|
||||
[setValue]
|
||||
)
|
||||
|
||||
return (
|
||||
<AttributePanel
|
||||
collapse
|
||||
title={
|
||||
<Legend
|
||||
disableGutters
|
||||
data-cy={'custom-attributes'}
|
||||
title={T.CustomAttributes}
|
||||
/>
|
||||
}
|
||||
allActionsEnabled
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={unknownVars}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContextAttrsSection
|
@ -0,0 +1,53 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import CustomAttributes from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/context/customAttributes'
|
||||
import { FIELDS } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/context/schema'
|
||||
import ContextIcon from 'iconoir-react/dist/Folder'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const ContextContent = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
<FormWithSchema
|
||||
id={EXTRA_ID}
|
||||
cy="context"
|
||||
fields={FIELDS(oneConfig, adminGroup)}
|
||||
/>
|
||||
<CustomAttributes />
|
||||
</>
|
||||
)
|
||||
|
||||
ContextContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'context',
|
||||
name: T.Context,
|
||||
icon: ContextIcon,
|
||||
Content: ContextContent,
|
||||
getError: (error) => FIELDS().some(({ name }) => error?.[name]),
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,139 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { string } from 'yup'
|
||||
|
||||
import {
|
||||
Field,
|
||||
arrayToOptions,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
INPUT_TYPES,
|
||||
VNET_METHODS,
|
||||
VNET_METHODS6,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
} from 'client/constants'
|
||||
|
||||
/** @type {Field} Network address field */
|
||||
const NETWORK_ADDRESS_FIELD = {
|
||||
name: 'NETWORK_ADDRESS',
|
||||
label: T.NetworkAddress,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Network mask field */
|
||||
const NETWORK_MASK_FIELD = {
|
||||
name: 'NETWORK_MASK',
|
||||
label: T.NetworkMask,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Gateway field */
|
||||
const GATEWAY_FIELD = {
|
||||
name: 'GATEWAY',
|
||||
label: T.Gateway,
|
||||
tooltip: T.GatewayConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Gateway for IPv6 field */
|
||||
const GATEWAY6_FIELD = {
|
||||
name: 'GATEWAY6',
|
||||
label: T.Gateway6,
|
||||
tooltip: T.Gateway6Concept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} DNS field */
|
||||
const DNS_FIELD = {
|
||||
name: 'DNS',
|
||||
label: T.DNS,
|
||||
tooltip: T.DNSConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Guest MTU field */
|
||||
const GUEST_MTU_FIELD = {
|
||||
name: 'GUEST_MTU',
|
||||
label: T.GuestMTU,
|
||||
tooltip: T.GuestMTUConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Method field */
|
||||
const METHOD_FIELD = {
|
||||
name: 'METHOD',
|
||||
label: T.NetMethod,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.entries(VNET_METHODS), {
|
||||
addEmpty: 'none (Use default)',
|
||||
getText: ([, text]) => text,
|
||||
getValue: ([value]) => value,
|
||||
}),
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Method for IPv6 field */
|
||||
const IP6_METHOD_FIELD = {
|
||||
name: 'IP6_METHOD',
|
||||
label: T.NetMethod6,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.entries(VNET_METHODS6), {
|
||||
addEmpty: 'none (Use default)',
|
||||
getText: ([, text]) => text,
|
||||
getValue: ([value]) => value,
|
||||
}),
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Array} Fields
|
||||
*/
|
||||
export const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[
|
||||
NETWORK_ADDRESS_FIELD,
|
||||
NETWORK_MASK_FIELD,
|
||||
GATEWAY_FIELD,
|
||||
GATEWAY6_FIELD,
|
||||
DNS_FIELD,
|
||||
GUEST_MTU_FIELD,
|
||||
METHOD_FIELD,
|
||||
IP6_METHOD_FIELD,
|
||||
],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {object} Schema
|
||||
*/
|
||||
export const SCHEMA = (oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(FIELDS(oneConfig, adminGroup))
|
@ -0,0 +1,109 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import PropTypes from 'prop-types'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { FieldErrors, useFormContext } from 'react-hook-form'
|
||||
|
||||
import Addresses from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/addresses'
|
||||
import Clusters from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/clusters'
|
||||
import Configuration from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/configuration'
|
||||
import Context from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/context'
|
||||
import QoS from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/qos'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import Tabs from 'client/components/Tabs'
|
||||
|
||||
import { SCHEMA } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/schema'
|
||||
import { STEP_ID as GENERAL_ID } from 'client/components/Forms/VNTemplate/CreateForm/Steps/General'
|
||||
import { T, VirtualNetwork } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @typedef {object} TabType
|
||||
* @property {string} id - Id will be to use in view yaml to hide/display the tab
|
||||
* @property {string} name - Label of tab
|
||||
* @property {ReactElement} Content - Content tab
|
||||
* @property {object} [icon] - Icon of tab
|
||||
* @property {boolean} [immutable] - If `true`, the section will not be displayed whe is updating
|
||||
* @property {function(FieldErrors):boolean} [getError] - Returns `true` if the tab contains an error in form
|
||||
*/
|
||||
|
||||
export const STEP_ID = 'extra'
|
||||
|
||||
/** @type {TabType[]} */
|
||||
export const TABS = [Configuration, Clusters, Addresses, QoS, Context]
|
||||
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) => {
|
||||
const {
|
||||
watch,
|
||||
formState: { errors },
|
||||
} = useFormContext()
|
||||
|
||||
const driver = useMemo(() => watch(`${GENERAL_ID}.VN_MAD`), [])
|
||||
|
||||
const totalErrors = Object.keys(errors[STEP_ID] ?? {}).length
|
||||
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
TABS.map(({ Content: TabContent, name, getError, ...section }) => ({
|
||||
...section,
|
||||
name,
|
||||
label: <Translate word={name} />,
|
||||
renderContent: () => (
|
||||
<TabContent
|
||||
isUpdate={isUpdate}
|
||||
driver={driver}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
),
|
||||
error: getError?.(errors[STEP_ID]),
|
||||
})),
|
||||
[totalErrors, driver]
|
||||
)
|
||||
|
||||
return <Tabs tabs={tabs} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional configuration about Virtual network.
|
||||
*
|
||||
* @param {VirtualNetwork} data - Virtual network
|
||||
* @returns {object} Optional configuration step
|
||||
*/
|
||||
const ExtraConfiguration = ({ data, oneConfig, adminGroup }) => {
|
||||
const isUpdate = data?.NAME !== undefined
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA(isUpdate, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (formProps) =>
|
||||
Content({ ...formProps, isUpdate, oneConfig, adminGroup }),
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ExtraConfiguration
|
@ -0,0 +1,62 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Alert } from '@mui/material'
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import {
|
||||
FIELDS,
|
||||
SECTIONS,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/qos/schema'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import QoSIcon from 'iconoir-react/dist/DataTransferBoth'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const QoSContent = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
<Alert severity="info" variant="outlined">
|
||||
<Translate word={T.MessageQos} />
|
||||
</Alert>
|
||||
{SECTIONS(oneConfig, adminGroup).map(({ id, ...section }) => (
|
||||
<FormWithSchema
|
||||
key={id}
|
||||
id={EXTRA_ID}
|
||||
cy={`${EXTRA_ID}-${id}`}
|
||||
{...section}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
||||
QoSContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'qos',
|
||||
name: T.QoS,
|
||||
icon: QoSIcon,
|
||||
Content: QoSContent,
|
||||
getError: (error) => FIELDS().some(({ name }) => error?.[name]),
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,116 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { ObjectSchema, string } from 'yup'
|
||||
|
||||
import {
|
||||
Field,
|
||||
Section,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import { T, INPUT_TYPES, RESTRICTED_ATTRIBUTES_TYPE } from 'client/constants'
|
||||
|
||||
const commonFieldProps = {
|
||||
type: INPUT_TYPES.TEXT,
|
||||
htmlType: 'number',
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Inbound AVG Bandwidth field */
|
||||
const INBOUND_AVG_BW_FIELD = {
|
||||
...commonFieldProps,
|
||||
name: 'INBOUND_AVG_BW',
|
||||
label: T.AverageBandwidth,
|
||||
tooltip: T.InboundAverageBandwidthConcept,
|
||||
}
|
||||
|
||||
/** @type {Field} Inbound Peak Bandwidth field */
|
||||
const INBOUND_PEAK_BW_FIELD = {
|
||||
...commonFieldProps,
|
||||
name: 'INBOUND_PEAK_BW',
|
||||
label: T.PeakBandwidth,
|
||||
tooltip: T.InboundPeakBandwidthConcept,
|
||||
}
|
||||
|
||||
/** @type {Field} Inbound Peak Burst field */
|
||||
const INBOUND_PEAK_KB_FIELD = {
|
||||
...commonFieldProps,
|
||||
name: 'INBOUND_PEAK_KB',
|
||||
label: T.PeakBurst,
|
||||
tooltip: T.PeakBurstConcept,
|
||||
}
|
||||
|
||||
/** @type {Field} Outbound AVG Bandwidth field */
|
||||
const OUTBOUND_AVG_BW_FIELD = {
|
||||
...commonFieldProps,
|
||||
name: 'OUTBOUND_AVG_BW',
|
||||
label: T.AverageBandwidth,
|
||||
tooltip: T.OutboundAverageBandwidthConcept,
|
||||
}
|
||||
|
||||
/** @type {Field} Outbound Peak Bandwidth field */
|
||||
const OUTBOUND_PEAK_BW_FIELD = {
|
||||
...commonFieldProps,
|
||||
name: 'OUTBOUND_PEAK_BW',
|
||||
label: T.PeakBandwidth,
|
||||
tooltip: T.OutboundPeakBandwidthConcept,
|
||||
}
|
||||
|
||||
/** @type {Field} Outbound Peak Burst field */
|
||||
const OUTBOUND_PEAK_KB_FIELD = {
|
||||
...commonFieldProps,
|
||||
name: 'OUTBOUND_PEAK_KB',
|
||||
label: T.PeakBurst,
|
||||
tooltip: T.PeakBurstConcept,
|
||||
}
|
||||
|
||||
/** @type {Section[]} Sections */
|
||||
const SECTIONS = (oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'qos-inbound',
|
||||
legend: T.InboundTraffic,
|
||||
fields: disableFields(
|
||||
[INBOUND_AVG_BW_FIELD, INBOUND_PEAK_BW_FIELD, INBOUND_PEAK_KB_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'qos-outbound',
|
||||
legend: T.OutboundTraffic,
|
||||
fields: disableFields(
|
||||
[OUTBOUND_AVG_BW_FIELD, OUTBOUND_PEAK_BW_FIELD, OUTBOUND_PEAK_KB_FIELD],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
/** @type {Field[]} List of QoS fields */
|
||||
const FIELDS = (oneConfig, adminGroup) =>
|
||||
SECTIONS(oneConfig, adminGroup)
|
||||
.map(({ fields }) => fields)
|
||||
.flat()
|
||||
|
||||
/** @type {ObjectSchema} QoS schema */
|
||||
const SCHEMA = (oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(FIELDS(oneConfig, adminGroup))
|
||||
|
||||
export { SCHEMA, SECTIONS, FIELDS }
|
@ -0,0 +1,57 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { array, object, ObjectSchema } from 'yup'
|
||||
|
||||
import { SCHEMA as CONTEXT_SCHEMA } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/context/schema'
|
||||
import { SCHEMA as QOS_SCHEMA } from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration/qos/schema'
|
||||
|
||||
/**
|
||||
* Map name attribute if not exists.
|
||||
*
|
||||
* @param {string} prefixName - Prefix to add in name
|
||||
* @returns {object[]} Resource object
|
||||
*/
|
||||
const mapNameByIndex = (prefixName) => (resource, idx) => ({
|
||||
...resource,
|
||||
NAME:
|
||||
resource?.NAME?.startsWith(prefixName) || !resource?.NAME
|
||||
? `${prefixName}${idx}`
|
||||
: resource?.NAME,
|
||||
})
|
||||
|
||||
const AR_SCHEMA = object({
|
||||
AR: array()
|
||||
.ensure()
|
||||
.transform((actions) => actions.map(mapNameByIndex('AR'))),
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {boolean} isUpdate - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ObjectSchema} Extra configuration schema
|
||||
*/
|
||||
export const SCHEMA = (isUpdate, oneConfig, adminGroup) => {
|
||||
const schema = object({ SECURITY_GROUPS: array().ensure() })
|
||||
.concat(CONTEXT_SCHEMA(oneConfig, adminGroup))
|
||||
.concat(QOS_SCHEMA(oneConfig, adminGroup))
|
||||
|
||||
!isUpdate && schema.concat(AR_SCHEMA)
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
export { AR_SCHEMA, mapNameByIndex }
|
@ -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 PropTypes from 'prop-types'
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import {
|
||||
SCHEMA,
|
||||
SECTIONS,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/General/schema'
|
||||
|
||||
import { T, VirtualNetwork } from 'client/constants'
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
/**
|
||||
* @param {boolean} isUpdate - True if it is an update operation
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ReactElement} Form content component
|
||||
*/
|
||||
const Content = (isUpdate, oneConfig, adminGroup) => {
|
||||
const sections = useMemo(() => SECTIONS(isUpdate, oneConfig, adminGroup))
|
||||
|
||||
return (
|
||||
<>
|
||||
{sections.map(({ id, ...section }) => (
|
||||
<FormWithSchema
|
||||
key={id}
|
||||
id={STEP_ID}
|
||||
cy={`${STEP_ID}-${id}`}
|
||||
{...section}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* General configuration about Virtual network.
|
||||
*
|
||||
* @param {VirtualNetwork} data - Virtual network
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = ({ data, oneConfig, adminGroup }) => {
|
||||
const isUpdate = data?.NAME !== undefined
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: T.General,
|
||||
resolver: () => SCHEMA(isUpdate, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content(isUpdate, oneConfig, adminGroup),
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default General
|
@ -0,0 +1,58 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { string } from 'yup'
|
||||
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field } from 'client/utils'
|
||||
|
||||
/**
|
||||
* @param {boolean} isUpdate - If `true`, the form is being updated
|
||||
* @returns {Field} Name field
|
||||
*/
|
||||
export const NAME_FIELD = (isUpdate = false) => ({
|
||||
name: 'NAME',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.required()
|
||||
.default(() => undefined)
|
||||
// if the form is updating then display the name but not change it
|
||||
.afterSubmit((name) => (isUpdate ? undefined : name)),
|
||||
grid: { md: 12 },
|
||||
fieldProps: { disabled: isUpdate },
|
||||
})
|
||||
|
||||
/** @type {Field} Description field */
|
||||
export const DESCRIPTION_FIELD = {
|
||||
name: 'DESCRIPTION',
|
||||
label: T.Description,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
multiline: true,
|
||||
validation: string()
|
||||
.trim()
|
||||
.notRequired()
|
||||
.default(() => undefined)
|
||||
.afterSubmit((description) => description),
|
||||
grid: { md: 12 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} isUpdate - If `true`, the form is being updated
|
||||
* @returns {Field[]} List of information fields
|
||||
*/
|
||||
export const FIELDS = (isUpdate) =>
|
||||
[NAME_FIELD(isUpdate), DESCRIPTION_FIELD].filter(Boolean)
|
@ -0,0 +1,56 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { BaseSchema } from 'yup'
|
||||
|
||||
import { FIELDS as INFORMATION_FIELDS } from './informationSchema'
|
||||
|
||||
import { RESTRICTED_ATTRIBUTES_TYPE, T } from 'client/constants'
|
||||
import { Section, disableFields, getObjectSchemaFromFields } from 'client/utils'
|
||||
|
||||
/**
|
||||
* @param {boolean} [isUpdate] - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Section[]} Fields
|
||||
*/
|
||||
const SECTIONS = (isUpdate, oneConfig, adminGroup) => [
|
||||
{
|
||||
id: 'information',
|
||||
legend: T.Information,
|
||||
fields: disableFields(
|
||||
INFORMATION_FIELDS(isUpdate),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* @param {boolean} [isUpdate] - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {BaseSchema} Step schema
|
||||
*/
|
||||
const SCHEMA = (isUpdate, oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(
|
||||
SECTIONS(isUpdate, oneConfig, adminGroup)
|
||||
.map(({ schema, fields }) => schema ?? fields)
|
||||
.flat()
|
||||
)
|
||||
|
||||
export { SCHEMA, SECTIONS }
|
@ -0,0 +1,85 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { ObjectSchema, reach } from 'yup'
|
||||
|
||||
import ExtraConfiguration, {
|
||||
STEP_ID as EXTRA_ID,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/ExtraConfiguration'
|
||||
import General, {
|
||||
STEP_ID as GENERAL_ID,
|
||||
} from 'client/components/Forms/VNTemplate/CreateForm/Steps/General'
|
||||
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { createSteps } from 'client/utils'
|
||||
|
||||
const existsOnSchema = (schema, key) => {
|
||||
try {
|
||||
return reach(schema, key) && true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} fromAttributes - Attributes to check
|
||||
* @param {ObjectSchema} schema - Current form schema
|
||||
* @returns {object} List of unknown attributes
|
||||
*/
|
||||
export const getUnknownVars = (fromAttributes = {}, schema) => {
|
||||
const unknown = {}
|
||||
|
||||
for (const [key, value] of Object.entries(fromAttributes)) {
|
||||
if (
|
||||
!!value &&
|
||||
!existsOnSchema(schema, `${GENERAL_ID}.${key}`) &&
|
||||
!existsOnSchema(schema, `${EXTRA_ID}.${key}`)
|
||||
) {
|
||||
// When the path is not found, it means that
|
||||
// the attribute is correct and we can set it
|
||||
unknown[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return unknown
|
||||
}
|
||||
|
||||
const Steps = createSteps([General, ExtraConfiguration], {
|
||||
transformInitialValue: ({ TEMPLATE, ...vnet } = {}, schema) => {
|
||||
const { AR = {}, DESCRIPTION = '' } = TEMPLATE
|
||||
const initialValue = schema.cast(
|
||||
{
|
||||
[GENERAL_ID]: { ...vnet, DESCRIPTION },
|
||||
[EXTRA_ID]: { ...TEMPLATE, AR, ...vnet },
|
||||
},
|
||||
{ stripUnknown: true, context: vnet }
|
||||
)
|
||||
|
||||
initialValue[EXTRA_ID] = {
|
||||
...getUnknownVars(TEMPLATE, schema),
|
||||
...initialValue[EXTRA_ID],
|
||||
}
|
||||
|
||||
return initialValue
|
||||
},
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const { [GENERAL_ID]: general = {}, [EXTRA_ID]: extra = {} } =
|
||||
formData ?? {}
|
||||
|
||||
return jsonToXml({ ...extra, ...general })
|
||||
},
|
||||
})
|
||||
|
||||
export default Steps
|
@ -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/VNTemplate/CreateForm/Steps'
|
@ -0,0 +1,140 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Stack, Typography } from '@mui/material'
|
||||
import AddressesIcon from 'iconoir-react/dist/Menu'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFieldArray } from 'react-hook-form'
|
||||
|
||||
import {
|
||||
AddAddressRangeAction,
|
||||
DeleteAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
} from 'client/components/Buttons'
|
||||
import { AddressRangeCard } from 'client/components/Cards'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration'
|
||||
import { mapNameByIndex } from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/schema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
export const TAB_ID = 'AR'
|
||||
|
||||
const mapNameFunction = mapNameByIndex('AR')
|
||||
|
||||
const AddressesContent = ({ oneConfig, adminGroup }) => {
|
||||
const {
|
||||
fields: addresses,
|
||||
remove,
|
||||
update,
|
||||
append,
|
||||
} = useFieldArray({
|
||||
name: `${EXTRA_ID}.${TAB_ID}`,
|
||||
keyName: 'ID',
|
||||
})
|
||||
|
||||
const handleCreateAction = (action) => {
|
||||
append(mapNameFunction(action, addresses.length))
|
||||
}
|
||||
|
||||
const handleUpdate = (action, index) => {
|
||||
update(index, mapNameFunction(action, index))
|
||||
}
|
||||
|
||||
const handleRemove = (index) => {
|
||||
remove(index)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack flexDirection="row" gap="1em">
|
||||
<AddAddressRangeAction
|
||||
onSubmit={handleCreateAction}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
pb="1em"
|
||||
display="grid"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(300px, 0.5fr))"
|
||||
gap="1em"
|
||||
mt="1em"
|
||||
>
|
||||
{addresses?.map((ar, index) => {
|
||||
const key = ar.ID ?? ar.NAME
|
||||
const fakeValues = { ...ar, AR_ID: index }
|
||||
|
||||
return (
|
||||
<AddressRangeCard
|
||||
key={key}
|
||||
ar={fakeValues}
|
||||
actions={
|
||||
<>
|
||||
<UpdateAddressRangeAction
|
||||
vm={{}}
|
||||
ar={fakeValues}
|
||||
onSubmit={(updatedAr) => handleUpdate(updatedAr, index)}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
<DeleteAddressRangeAction
|
||||
ar={fakeValues}
|
||||
onSubmit={() => handleRemove(index)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
AddressesContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) =>
|
||||
isUpdate ? (
|
||||
<Typography variant="subtitle2">
|
||||
<Translate word={T.DisabledAddressRangeInForm} />
|
||||
</Typography>
|
||||
) : (
|
||||
<AddressesContent oneConfig={oneConfig} adminGroup={adminGroup} />
|
||||
)
|
||||
|
||||
Content.propTypes = {
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'addresses',
|
||||
name: T.Addresses,
|
||||
icon: AddressesIcon,
|
||||
Content,
|
||||
getError: (error) => !!error?.[TAB_ID],
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,80 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useCallback, useMemo } from 'react'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
import { reach } from 'yup'
|
||||
|
||||
import { getUnknownVars } from 'client/components/Forms/VNTemplate/InstantiateForm/Steps'
|
||||
import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
|
||||
import { Legend } from 'client/components/Forms'
|
||||
import { AttributePanel } from 'client/components/Tabs/Common'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Renders the context attributes to Virtual Network form.
|
||||
*
|
||||
* @returns {ReactElement} - Context attributes section
|
||||
*/
|
||||
const ContextAttrsSection = () => {
|
||||
const { enqueueError } = useGeneralApi()
|
||||
const { setValue, getResolver } = useFormContext()
|
||||
const extraStepVars = useWatch({ name: EXTRA_ID }) || {}
|
||||
|
||||
const unknownVars = useMemo(
|
||||
() => getUnknownVars(extraStepVars, getResolver()),
|
||||
[extraStepVars]
|
||||
)
|
||||
|
||||
const handleChangeAttribute = useCallback(
|
||||
(path, newValue) => {
|
||||
try {
|
||||
const extraSchema = reach(getResolver(), EXTRA_ID)
|
||||
|
||||
// retrieve the schema for the given path
|
||||
reach(extraSchema, path)
|
||||
enqueueError(T.InvalidAttribute)
|
||||
} catch (e) {
|
||||
// When the path is not found, it means that
|
||||
// the attribute is correct and we can set it
|
||||
setValue(`${EXTRA_ID}.${path}`, newValue)
|
||||
}
|
||||
},
|
||||
[setValue]
|
||||
)
|
||||
|
||||
return (
|
||||
<AttributePanel
|
||||
collapse
|
||||
title={
|
||||
<Legend
|
||||
disableGutters
|
||||
data-cy={'custom-attributes'}
|
||||
title={T.CustomAttributes}
|
||||
/>
|
||||
}
|
||||
allActionsEnabled
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={unknownVars}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContextAttrsSection
|
@ -0,0 +1,53 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration'
|
||||
import CustomAttributes from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/context/customAttributes'
|
||||
import { FIELDS } from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/context/schema'
|
||||
import ContextIcon from 'iconoir-react/dist/Folder'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const ContextContent = ({ oneConfig, adminGroup }) => (
|
||||
<>
|
||||
<FormWithSchema
|
||||
id={EXTRA_ID}
|
||||
cy="context"
|
||||
fields={FIELDS(oneConfig, adminGroup)}
|
||||
/>
|
||||
<CustomAttributes />
|
||||
</>
|
||||
)
|
||||
|
||||
ContextContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'context',
|
||||
name: T.Context,
|
||||
icon: ContextIcon,
|
||||
Content: ContextContent,
|
||||
getError: (error) => FIELDS().some(({ name }) => error?.[name]),
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,139 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { string } from 'yup'
|
||||
|
||||
import {
|
||||
Field,
|
||||
arrayToOptions,
|
||||
getObjectSchemaFromFields,
|
||||
disableFields,
|
||||
} from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
INPUT_TYPES,
|
||||
VNET_METHODS,
|
||||
VNET_METHODS6,
|
||||
RESTRICTED_ATTRIBUTES_TYPE,
|
||||
} from 'client/constants'
|
||||
|
||||
/** @type {Field} Network address field */
|
||||
const NETWORK_ADDRESS_FIELD = {
|
||||
name: 'NETWORK_ADDRESS',
|
||||
label: T.NetworkAddress,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Network mask field */
|
||||
const NETWORK_MASK_FIELD = {
|
||||
name: 'NETWORK_MASK',
|
||||
label: T.NetworkMask,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Gateway field */
|
||||
const GATEWAY_FIELD = {
|
||||
name: 'GATEWAY',
|
||||
label: T.Gateway,
|
||||
tooltip: T.GatewayConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Gateway for IPv6 field */
|
||||
const GATEWAY6_FIELD = {
|
||||
name: 'GATEWAY6',
|
||||
label: T.Gateway6,
|
||||
tooltip: T.Gateway6Concept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} DNS field */
|
||||
const DNS_FIELD = {
|
||||
name: 'DNS',
|
||||
label: T.DNS,
|
||||
tooltip: T.DNSConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Guest MTU field */
|
||||
const GUEST_MTU_FIELD = {
|
||||
name: 'GUEST_MTU',
|
||||
label: T.GuestMTU,
|
||||
tooltip: T.GuestMTUConcept,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Method field */
|
||||
const METHOD_FIELD = {
|
||||
name: 'METHOD',
|
||||
label: T.NetMethod,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.entries(VNET_METHODS), {
|
||||
addEmpty: 'none (Use default)',
|
||||
getText: ([, text]) => text,
|
||||
getValue: ([value]) => value,
|
||||
}),
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/** @type {Field} Method for IPv6 field */
|
||||
const IP6_METHOD_FIELD = {
|
||||
name: 'IP6_METHOD',
|
||||
label: T.NetMethod6,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(Object.entries(VNET_METHODS6), {
|
||||
addEmpty: 'none (Use default)',
|
||||
getText: ([, text]) => text,
|
||||
getValue: ([value]) => value,
|
||||
}),
|
||||
validation: string().trim().notRequired(),
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {Array} Fields
|
||||
*/
|
||||
export const FIELDS = (oneConfig, adminGroup) =>
|
||||
disableFields(
|
||||
[
|
||||
NETWORK_ADDRESS_FIELD,
|
||||
NETWORK_MASK_FIELD,
|
||||
GATEWAY_FIELD,
|
||||
GATEWAY6_FIELD,
|
||||
DNS_FIELD,
|
||||
GUEST_MTU_FIELD,
|
||||
METHOD_FIELD,
|
||||
IP6_METHOD_FIELD,
|
||||
],
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
RESTRICTED_ATTRIBUTES_TYPE.VNET
|
||||
)
|
||||
|
||||
/**
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {object} Schema
|
||||
*/
|
||||
export const SCHEMA = (oneConfig, adminGroup) =>
|
||||
getObjectSchemaFromFields(FIELDS(oneConfig, adminGroup))
|
@ -0,0 +1,102 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import PropTypes from 'prop-types'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { ReactElement, useMemo } from 'react'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { FieldErrors, useFormContext } from 'react-hook-form'
|
||||
|
||||
import Addresses from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/addresses'
|
||||
import Context from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/context'
|
||||
import Security from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/security'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import Tabs from 'client/components/Tabs'
|
||||
|
||||
import { SCHEMA } from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/schema'
|
||||
import { T, VirtualNetwork } from 'client/constants'
|
||||
|
||||
/**
|
||||
* @typedef {object} TabType
|
||||
* @property {string} id - Id will be to use in view yaml to hide/display the tab
|
||||
* @property {string} name - Label of tab
|
||||
* @property {ReactElement} Content - Content tab
|
||||
* @property {object} [icon] - Icon of tab
|
||||
* @property {boolean} [immutable] - If `true`, the section will not be displayed whe is updating
|
||||
* @property {function(FieldErrors):boolean} [getError] - Returns `true` if the tab contains an error in form
|
||||
*/
|
||||
|
||||
export const STEP_ID = 'extra'
|
||||
|
||||
/** @type {TabType[]} */
|
||||
export const TABS = [Addresses, Security, Context]
|
||||
|
||||
const Content = ({ isUpdate, oneConfig, adminGroup }) => {
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext()
|
||||
|
||||
const totalErrors = Object.keys(errors[STEP_ID] ?? {}).length
|
||||
|
||||
const tabs = useMemo(
|
||||
() =>
|
||||
TABS.map(({ Content: TabContent, name, getError, ...section }) => ({
|
||||
...section,
|
||||
name,
|
||||
label: <Translate word={name} />,
|
||||
renderContent: () => (
|
||||
<TabContent
|
||||
isUpdate={isUpdate}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
/>
|
||||
),
|
||||
error: getError?.(errors[STEP_ID]),
|
||||
})),
|
||||
[totalErrors]
|
||||
)
|
||||
|
||||
return <Tabs tabs={tabs} />
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional configuration about Virtual network.
|
||||
*
|
||||
* @param {VirtualNetwork} data - Virtual network
|
||||
* @returns {object} Optional configuration step
|
||||
*/
|
||||
const ExtraConfiguration = ({ data, oneConfig, adminGroup }) => {
|
||||
const isUpdate = data?.NAME !== undefined
|
||||
|
||||
return {
|
||||
id: STEP_ID,
|
||||
label: T.AdvancedOptions,
|
||||
resolver: SCHEMA(isUpdate, oneConfig, adminGroup),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (formProps) =>
|
||||
Content({ ...formProps, isUpdate, oneConfig, adminGroup }),
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
isUpdate: PropTypes.bool,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default ExtraConfiguration
|
@ -0,0 +1,56 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { array, object, ObjectSchema } from 'yup'
|
||||
|
||||
import { SCHEMA as CONTEXT_SCHEMA } from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration/context/schema'
|
||||
|
||||
/**
|
||||
* Map name attribute if not exists.
|
||||
*
|
||||
* @param {string} prefixName - Prefix to add in name
|
||||
* @returns {object[]} Resource object
|
||||
*/
|
||||
const mapNameByIndex = (prefixName) => (resource, idx) => ({
|
||||
...resource,
|
||||
NAME:
|
||||
resource?.NAME?.startsWith(prefixName) || !resource?.NAME
|
||||
? `${prefixName}${idx}`
|
||||
: resource?.NAME,
|
||||
})
|
||||
|
||||
const AR_SCHEMA = object({
|
||||
AR: array()
|
||||
.ensure()
|
||||
.transform((actions) => actions.map(mapNameByIndex('AR'))),
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {boolean} isUpdate - If `true`, the form is being updated
|
||||
* @param {object} oneConfig - Open Nebula configuration
|
||||
* @param {boolean} adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ObjectSchema} Extra configuration schema
|
||||
*/
|
||||
export const SCHEMA = (isUpdate, oneConfig, adminGroup) => {
|
||||
const schema = object({ SECURITY_GROUPS: array().ensure() }).concat(
|
||||
CONTEXT_SCHEMA(oneConfig, adminGroup)
|
||||
)
|
||||
|
||||
!isUpdate && schema.concat(AR_SCHEMA)
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
export { AR_SCHEMA, mapNameByIndex }
|
@ -0,0 +1,86 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Alert } from '@mui/material'
|
||||
import {
|
||||
STEP_ID as EXTRA_ID,
|
||||
TabType,
|
||||
} from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { SecurityGroupsTable } from 'client/components/Tables'
|
||||
import { T } from 'client/constants'
|
||||
import { isRestrictedAttributes } from 'client/utils'
|
||||
import SecurityIcon from 'iconoir-react/dist/HistoricShield'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
|
||||
export const TAB_ID = 'SECURITY_GROUPS'
|
||||
|
||||
const SecurityContent = ({ oneConfig, adminGroup }) => {
|
||||
const TAB_PATH = `${EXTRA_ID}.${TAB_ID}`
|
||||
|
||||
const { setValue } = useFormContext()
|
||||
const secGroups = useWatch({ name: TAB_PATH })
|
||||
|
||||
const selectedRowIds = secGroups?.reduce(
|
||||
(res, id) => ({ ...res, [id]: true }),
|
||||
{}
|
||||
)
|
||||
|
||||
const handleSelectedRows = (rows) => {
|
||||
const newValue = rows?.map((row) => row.original.ID) || []
|
||||
setValue(TAB_PATH, newValue)
|
||||
}
|
||||
|
||||
const readOnly =
|
||||
!adminGroup &&
|
||||
isRestrictedAttributes(
|
||||
'SECURITY_GROUPS',
|
||||
undefined,
|
||||
oneConfig?.VNET_RESTRICTED_ATTR
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Alert severity="warning" variant="outlined">
|
||||
<Translate word={T.MessageAddSecGroupDefault} />
|
||||
</Alert>
|
||||
<SecurityGroupsTable
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
pageSize={5}
|
||||
initialState={{ selectedRowIds }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
SecurityContent.propTypes = {
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/** @type {TabType} */
|
||||
const TAB = {
|
||||
id: 'security',
|
||||
name: T.Security,
|
||||
icon: SecurityIcon,
|
||||
Content: SecurityContent,
|
||||
getError: (error) => !!error?.[TAB_ID],
|
||||
}
|
||||
|
||||
export default TAB
|
@ -0,0 +1,105 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useEffect, useMemo } from 'react'
|
||||
|
||||
import FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import {
|
||||
SCHEMA,
|
||||
SECTIONS,
|
||||
} from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/General/schema'
|
||||
import useStyles from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/General/styles'
|
||||
import { RESOURCE_NAMES, T, VmTemplate } from 'client/constants'
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper'
|
||||
import { scaleVcpuByCpuFactor } from 'client/models/VirtualMachine'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
let generalFeatures
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
const Content = ({ vmTemplate, oneConfig, adminGroup }) => {
|
||||
const classes = useStyles()
|
||||
const { view, getResourceView } = useViews()
|
||||
const { getValues, setValue } = useFormContext()
|
||||
|
||||
const resource = RESOURCE_NAMES.VM_TEMPLATE
|
||||
const { features, dialogs } = getResourceView(resource)
|
||||
|
||||
const sections = useMemo(() => {
|
||||
const hypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
|
||||
const dialog = dialogs?.instantiate_dialog
|
||||
const sectionsAvailable = getSectionsAvailable(dialog, hypervisor)
|
||||
|
||||
generalFeatures = features
|
||||
|
||||
return SECTIONS(vmTemplate, features, oneConfig, adminGroup).filter(
|
||||
({ id, required }) => required || sectionsAvailable.includes(id)
|
||||
)
|
||||
}, [view])
|
||||
|
||||
useEffect(() => {
|
||||
if (vmTemplate?.TEMPLATE?.VCPU && features?.cpu_factor) {
|
||||
const oldValues = {
|
||||
...getValues(),
|
||||
}
|
||||
oldValues.configuration.CPU = `${scaleVcpuByCpuFactor(
|
||||
vmTemplate.TEMPLATE.VCPU,
|
||||
features.cpu_factor
|
||||
)}`
|
||||
|
||||
setValue(`${STEP_ID}`, oldValues)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{sections.map(({ id, legend, fields }) => (
|
||||
<FormWithSchema
|
||||
key={id}
|
||||
cy={id}
|
||||
rootProps={{ className: classes[id] }}
|
||||
fields={fields}
|
||||
legend={legend}
|
||||
id={STEP_ID}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
vmTemplate: PropTypes.object,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic configuration about VM Template.
|
||||
*
|
||||
* @param {VmTemplate} vmTemplate - VM Template
|
||||
* @returns {object} Basic configuration step
|
||||
*/
|
||||
const BasicConfiguration = ({ data: vmTemplate, oneConfig, adminGroup }) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: () => SCHEMA(vmTemplate, generalFeatures),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (props) => Content({ ...props, vmTemplate, oneConfig, adminGroup }),
|
||||
})
|
||||
|
||||
export default BasicConfiguration
|
@ -0,0 +1,30 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { string } from 'yup'
|
||||
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
|
||||
const NAME = {
|
||||
name: 'name',
|
||||
label: T.VNName,
|
||||
tooltip: T.VnTemplateNameHelper,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string()
|
||||
.trim()
|
||||
.default(() => undefined),
|
||||
}
|
||||
|
||||
export const FIELDS = [NAME]
|
@ -0,0 +1,73 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { BaseSchema } from 'yup'
|
||||
|
||||
import { FIELDS as INFORMATION_FIELDS } from './informationSchema'
|
||||
|
||||
// get schemas from VmTemplate/CreateForm
|
||||
|
||||
import { T, VmTemplate, VmTemplateFeatures } from 'client/constants'
|
||||
import {
|
||||
Field,
|
||||
Section,
|
||||
disableFields,
|
||||
filterFieldsByHypervisor,
|
||||
getObjectSchemaFromFields,
|
||||
} from 'client/utils'
|
||||
|
||||
/**
|
||||
* @param {VmTemplate} [vmTemplate] - VM Template
|
||||
* @param {VmTemplateFeatures} [features] - Features
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @returns {Section[]} Sections
|
||||
*/
|
||||
const SECTIONS = (vmTemplate, features, oneConfig, adminGroup) => {
|
||||
const hypervisor = vmTemplate?.TEMPLATE?.HYPERVISOR
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'information',
|
||||
legend: T.Information,
|
||||
fields: disableFields(
|
||||
filterFieldsByHypervisor(INFORMATION_FIELDS, hypervisor),
|
||||
'',
|
||||
oneConfig,
|
||||
adminGroup
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {VmTemplate} [vmTemplate] - VM Template
|
||||
* @param {boolean} [hideCpu] - If `true`, the CPU fields is hidden
|
||||
* @returns {Field[]} Basic configuration fields
|
||||
*/
|
||||
const FIELDS = (vmTemplate, hideCpu) =>
|
||||
SECTIONS(vmTemplate, hideCpu)
|
||||
.map(({ fields }) => fields)
|
||||
.flat()
|
||||
|
||||
/**
|
||||
* @param {VmTemplate} [vmTemplate] - VM Template
|
||||
* @param {boolean} [hideCpu] - If `true`, the CPU fields is hidden
|
||||
* @returns {BaseSchema} Step schema
|
||||
*/
|
||||
const SCHEMA = (vmTemplate, hideCpu) =>
|
||||
getObjectSchemaFromFields(FIELDS(vmTemplate, hideCpu))
|
||||
|
||||
export { FIELDS, SCHEMA, SECTIONS }
|
@ -0,0 +1,31 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 makeStyles from '@mui/styles/makeStyles'
|
||||
|
||||
export default makeStyles((theme) => ({
|
||||
root: {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr 1fr',
|
||||
gap: '2em',
|
||||
overflow: 'auto',
|
||||
[theme.breakpoints.down('lg')]: {
|
||||
gridTemplateColumns: '1fr',
|
||||
},
|
||||
},
|
||||
information: {
|
||||
gridColumn: '1 / -1',
|
||||
},
|
||||
}))
|
@ -0,0 +1,94 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 ExtraConfiguration, {
|
||||
STEP_ID as EXTRA_ID,
|
||||
} from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/ExtraConfiguration'
|
||||
import General, {
|
||||
STEP_ID as GENERAL_ID,
|
||||
} from 'client/components/Forms/VNTemplate/InstantiateForm/Steps/General'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
import { createSteps } from 'client/utils'
|
||||
import { ObjectSchema, reach } from 'yup'
|
||||
|
||||
const DYNAMIC_FIELDS = ['AR']
|
||||
|
||||
const existsOnSchema = (schema, key) => {
|
||||
try {
|
||||
return reach(schema, key) && true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} fromAttributes - Attributes to check
|
||||
* @param {ObjectSchema} schema - Current form schema
|
||||
* @returns {object} List of unknown attributes
|
||||
*/
|
||||
export const getUnknownVars = (fromAttributes = {}, schema) => {
|
||||
const unknown = {}
|
||||
|
||||
for (const [key, value] of Object.entries(fromAttributes)) {
|
||||
if (
|
||||
!!value &&
|
||||
!DYNAMIC_FIELDS.includes(key) &&
|
||||
!existsOnSchema(schema, `${GENERAL_ID}.${key}`) &&
|
||||
!existsOnSchema(schema, `${EXTRA_ID}.${key}`)
|
||||
) {
|
||||
// When the path is not found, it means that
|
||||
// the attribute is correct and we can set it
|
||||
unknown[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return unknown
|
||||
}
|
||||
|
||||
const Steps = createSteps(() => [General, ExtraConfiguration].filter(Boolean), {
|
||||
transformInitialValue: (vmTemplate, schema) => {
|
||||
const initialValue = schema.cast(
|
||||
{
|
||||
[GENERAL_ID]: vmTemplate?.TEMPLATE,
|
||||
[EXTRA_ID]: vmTemplate?.TEMPLATE,
|
||||
},
|
||||
{ stripUnknown: true }
|
||||
)
|
||||
|
||||
return initialValue
|
||||
},
|
||||
transformBeforeSubmit: (formData, vnTemplate) => {
|
||||
const { [GENERAL_ID]: { name } = {}, [EXTRA_ID]: extraTemplate = {} } =
|
||||
formData ?? {}
|
||||
|
||||
Array.isArray(extraTemplate?.SECURITY_GROUPS) &&
|
||||
extraTemplate?.SECURITY_GROUPS?.length &&
|
||||
(extraTemplate.SECURITY_GROUPS = extraTemplate.SECURITY_GROUPS.join(','))
|
||||
|
||||
const templateXML = jsonToXml({
|
||||
...extraTemplate,
|
||||
})
|
||||
|
||||
const templates = {
|
||||
id: vnTemplate.ID,
|
||||
name,
|
||||
template: templateXML,
|
||||
}
|
||||
|
||||
return templates
|
||||
},
|
||||
})
|
||||
|
||||
export default Steps
|
@ -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/VNTemplate/InstantiateForm/Steps'
|
34
src/fireedge/src/client/components/Forms/VNTemplate/index.js
Normal file
34
src/fireedge/src/client/components/Forms/VNTemplate/index.js
Normal file
@ -0,0 +1,34 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC'
|
||||
import { CreateStepsCallback } from 'client/utils/schema'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const CreateForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'VNTemplate/CreateForm' }, configProps)
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const InstantiateForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'VNTemplate/InstantiateForm' }, configProps)
|
||||
|
||||
export { CreateForm, InstantiateForm }
|
@ -14,11 +14,11 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, useCallback, useMemo } from 'react'
|
||||
import { reach } from 'yup'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
import { reach } from 'yup'
|
||||
|
||||
import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VNetwork/CreateForm/Steps/ExtraConfiguration'
|
||||
import { getUnknownVars } from 'client/components/Forms/VNetwork/CreateForm/Steps'
|
||||
import { STEP_ID as EXTRA_ID } from 'client/components/Forms/VNetwork/CreateForm/Steps/ExtraConfiguration'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
|
||||
import { Legend } from 'client/components/Forms'
|
||||
@ -46,8 +46,7 @@ const ContextAttrsSection = () => {
|
||||
const extraSchema = reach(getResolver(), EXTRA_ID)
|
||||
|
||||
// retrieve the schema for the given path
|
||||
const existsSchema = reach(extraSchema, path)
|
||||
console.log(existsSchema)
|
||||
reach(extraSchema, path)
|
||||
enqueueError(T.InvalidAttribute)
|
||||
} catch (e) {
|
||||
// When the path is not found, it means that
|
||||
|
@ -0,0 +1,274 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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,
|
||||
Group,
|
||||
Lock,
|
||||
PlayOutline,
|
||||
// Import,
|
||||
Trash,
|
||||
} from 'iconoir-react'
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useAddNetworkToClusterMutation } from 'client/features/OneApi/cluster'
|
||||
import {
|
||||
useChangeVNTemplateOwnershipMutation,
|
||||
useLockVNTemplateMutation,
|
||||
// useRecoverVNetMutation,
|
||||
useRemoveVNTemplateMutation,
|
||||
// useReserveAddressMutation,
|
||||
useUnlockVNTemplateMutation,
|
||||
} from 'client/features/OneApi/networkTemplate'
|
||||
|
||||
import { ChangeClusterForm } from 'client/components/Forms/Cluster'
|
||||
import { ChangeGroupForm, ChangeUserForm } from 'client/components/Forms/Vm'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import {
|
||||
GlobalAction,
|
||||
createActions,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { RESOURCE_NAMES, T, VN_TEMPLATE_ACTIONS } from 'client/constants'
|
||||
|
||||
const ListNames = ({ rows = [] }) =>
|
||||
rows?.map?.(({ id, original }) => {
|
||||
const { ID, NAME } = original
|
||||
|
||||
return (
|
||||
<Typography
|
||||
key={`vnet-${id}`}
|
||||
variant="inherit"
|
||||
component="span"
|
||||
display="block"
|
||||
>
|
||||
{`#${ID} ${NAME}`}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
|
||||
const SubHeader = (rows) => <ListNames rows={rows} />
|
||||
|
||||
const MessageToConfirmAction = (rows) => {
|
||||
const names = rows?.map?.(({ original }) => original?.NAME)
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
<Translate word={T.VirtualNetworks} />
|
||||
{`: ${names.join(', ')}`}
|
||||
</p>
|
||||
<p>
|
||||
<Translate word={T.DoYouWantProceed} />
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
|
||||
|
||||
/**
|
||||
* Generates the actions to operate resources on Virtual networks table.
|
||||
*
|
||||
* @returns {GlobalAction} - Actions
|
||||
*/
|
||||
const Actions = () => {
|
||||
const history = useHistory()
|
||||
const { view, getResourceView } = useViews()
|
||||
// const [reserve] = useReserveAddressMutation()
|
||||
// const [recover] = useRecoverVNetMutation()
|
||||
const [changeCluster] = useAddNetworkToClusterMutation()
|
||||
const [lock] = useLockVNTemplateMutation()
|
||||
const [unlock] = useUnlockVNTemplateMutation()
|
||||
const [changeOwnership] = useChangeVNTemplateOwnershipMutation()
|
||||
const [remove] = useRemoveVNTemplateMutation()
|
||||
|
||||
const actions = useMemo(
|
||||
() =>
|
||||
createActions({
|
||||
filters: getResourceView(RESOURCE_NAMES.VN_TEMPLATE)?.actions,
|
||||
actions: [
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.CREATE_DIALOG,
|
||||
dataCy: `vnettemplate-${VN_TEMPLATE_ACTIONS.CREATE_DIALOG}`,
|
||||
tooltip: T.Create,
|
||||
icon: AddCircledOutline,
|
||||
action: () => history.push(PATH.NETWORK.VN_TEMPLATES.CREATE),
|
||||
},
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.INSTANTIATE_DIALOG,
|
||||
dataCy: `vnettemplate-${VN_TEMPLATE_ACTIONS.INSTANTIATE_DIALOG}`,
|
||||
tooltip: T.Instantiate,
|
||||
icon: PlayOutline,
|
||||
selected: { max: 1 },
|
||||
action: (rows) => {
|
||||
const template = rows?.[0]?.original ?? {}
|
||||
const path = PATH.NETWORK.VN_TEMPLATES.INSTANTIATE
|
||||
|
||||
history.push(path, template)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.UPDATE_DIALOG,
|
||||
dataCy: `vnettemplate-${VN_TEMPLATE_ACTIONS.UPDATE_DIALOG}`,
|
||||
label: T.Update,
|
||||
tooltip: T.Update,
|
||||
selected: { max: 1 },
|
||||
color: 'secondary',
|
||||
action: (rows) => {
|
||||
const vnet = rows?.[0]?.original ?? {}
|
||||
const path = PATH.NETWORK.VN_TEMPLATES.CREATE
|
||||
|
||||
history.push(path, vnet)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.CHANGE_CLUSTER,
|
||||
color: 'secondary',
|
||||
dataCy: `vnettemplate-${VN_TEMPLATE_ACTIONS.CHANGE_CLUSTER}`,
|
||||
label: T.SelectCluster,
|
||||
tooltip: T.SelectCluster,
|
||||
selected: true,
|
||||
options: [
|
||||
{
|
||||
dialogProps: {
|
||||
title: T.SelectCluster,
|
||||
dataCy: 'modal-select-cluster',
|
||||
},
|
||||
form: () => ChangeClusterForm(),
|
||||
onSubmit: (rows) => async (formData) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
|
||||
await Promise.all(
|
||||
ids.map((id) =>
|
||||
changeCluster({ id: formData.cluster, vnet: id })
|
||||
)
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tooltip: T.Ownership,
|
||||
icon: Group,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
dataCy: 'vnettemplate-ownership',
|
||||
options: [
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.CHANGE_OWNER,
|
||||
name: T.ChangeOwner,
|
||||
dialogProps: {
|
||||
title: T.ChangeOwner,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${VN_TEMPLATE_ACTIONS.CHANGE_OWNER}`,
|
||||
},
|
||||
form: ChangeUserForm,
|
||||
onSubmit: (rows) => (newOwnership) => {
|
||||
rows?.map?.(({ original }) =>
|
||||
changeOwnership({ id: original?.ID, ...newOwnership })
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.CHANGE_GROUP,
|
||||
name: T.ChangeGroup,
|
||||
dialogProps: {
|
||||
title: T.ChangeGroup,
|
||||
subheader: SubHeader,
|
||||
dataCy: `modal-${VN_TEMPLATE_ACTIONS.CHANGE_GROUP}`,
|
||||
},
|
||||
form: ChangeGroupForm,
|
||||
onSubmit: (rows) => async (newOwnership) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(
|
||||
ids.map((id) => changeOwnership({ id, ...newOwnership }))
|
||||
)
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
tooltip: T.Lock,
|
||||
icon: Lock,
|
||||
selected: true,
|
||||
color: 'secondary',
|
||||
dataCy: 'vnettemplate-lock',
|
||||
options: [
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.LOCK,
|
||||
name: T.Lock,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Lock,
|
||||
dataCy: `modal-${VN_TEMPLATE_ACTIONS.LOCK}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => lock({ id })))
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.UNLOCK,
|
||||
name: T.Unlock,
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Unlock,
|
||||
dataCy: `modal-${VN_TEMPLATE_ACTIONS.UNLOCK}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => unlock({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
accessor: VN_TEMPLATE_ACTIONS.DELETE,
|
||||
dataCy: `vnettemplate-${VN_TEMPLATE_ACTIONS.DELETE}`,
|
||||
tooltip: T.Delete,
|
||||
icon: Trash,
|
||||
selected: true,
|
||||
color: 'error',
|
||||
options: [
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Delete,
|
||||
children: MessageToConfirmAction,
|
||||
dataCy: `modal-vnet-${VN_TEMPLATE_ACTIONS.DELETE}`,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => remove({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
export default Actions
|
@ -13,43 +13,43 @@
|
||||
* 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 {
|
||||
AddCircledOutline,
|
||||
// Import,
|
||||
Trash,
|
||||
PlayOutline,
|
||||
Lock,
|
||||
Group,
|
||||
} from 'iconoir-react'
|
||||
import { Typography } from '@mui/material'
|
||||
import { makeStyles } from '@mui/styles'
|
||||
import {
|
||||
AddCircledOutline,
|
||||
Group,
|
||||
Lock,
|
||||
PlayOutline,
|
||||
// Import,
|
||||
Trash,
|
||||
} from 'iconoir-react'
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useAddNetworkToClusterMutation } from 'client/features/OneApi/cluster'
|
||||
import {
|
||||
useReserveAddressMutation,
|
||||
useLockVNetMutation,
|
||||
useUnlockVNetMutation,
|
||||
useChangeVNetOwnershipMutation,
|
||||
useRemoveVNetMutation,
|
||||
useLockVNetMutation,
|
||||
useRecoverVNetMutation,
|
||||
useRemoveVNetMutation,
|
||||
useReserveAddressMutation,
|
||||
useUnlockVNetMutation,
|
||||
} from 'client/features/OneApi/network'
|
||||
import { isAvailableAction } from 'client/models/VirtualNetwork'
|
||||
|
||||
import { ChangeUserForm, ChangeGroupForm } from 'client/components/Forms/Vm'
|
||||
import { ReserveForm, RecoverForm } from 'client/components/Forms/VNetwork'
|
||||
import { ChangeClusterForm } from 'client/components/Forms/Cluster'
|
||||
import { RecoverForm, ReserveForm } from 'client/components/Forms/VNetwork'
|
||||
import { ChangeGroupForm, ChangeUserForm } from 'client/components/Forms/Vm'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import {
|
||||
createActions,
|
||||
GlobalAction,
|
||||
createActions,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
import VNetworkTemplatesTable from 'client/components/Tables/VNetworkTemplates'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { T, VN_ACTIONS, RESOURCE_NAMES } from 'client/constants'
|
||||
import { RESOURCE_NAMES, T, VN_ACTIONS } from 'client/constants'
|
||||
|
||||
const isDisabled = (action) => (rows) =>
|
||||
!isAvailableAction(
|
||||
@ -125,17 +125,6 @@ const Actions = () => {
|
||||
icon: AddCircledOutline,
|
||||
action: () => history.push(PATH.NETWORK.VNETS.CREATE),
|
||||
},
|
||||
/* {
|
||||
// TODO: Import Virtual Network from vCenter
|
||||
accessor: VN_ACTIONS.IMPORT_DIALOG,
|
||||
tooltip: T.Import,
|
||||
icon: Import,
|
||||
selected: { max: 1 },
|
||||
disabled: true,
|
||||
action: (rows) => {
|
||||
// TODO: go to IMPORT form
|
||||
},
|
||||
}, */
|
||||
{
|
||||
accessor: VN_ACTIONS.INSTANTIATE_DIALOG,
|
||||
dataCy: `vnet-${VN_ACTIONS.INSTANTIATE_DIALOG}`,
|
||||
|
@ -13,18 +13,18 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Box, Stack } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import { useGetVNetworkQuery } from 'client/features/OneApi/network'
|
||||
|
||||
import AddressRangeCard from 'client/components/Cards/AddressRangeCard'
|
||||
import {
|
||||
AddAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
DeleteAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
} from 'client/components/Buttons'
|
||||
import AddressRangeCard from 'client/components/Cards/AddressRangeCard'
|
||||
|
||||
import { AddressRange, VN_ACTIONS } from 'client/constants'
|
||||
|
||||
@ -50,7 +50,7 @@ const AddressTab = ({
|
||||
const { data: vnet } = useGetVNetworkQuery({ id })
|
||||
|
||||
/** @type {AddressRange[]} */
|
||||
const addressRanges = [vnet.AR_POOL.AR ?? []].flat()
|
||||
const addressRanges = [vnet?.AR_POOL?.AR ?? []].flat()
|
||||
|
||||
return (
|
||||
<Box padding={{ sm: '0.8em' }}>
|
||||
|
@ -22,8 +22,7 @@ import { Box } from '@mui/material'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetClustersQuery } from 'client/features/OneApi/cluster'
|
||||
import { useGetVNetworkQuery } from 'client/features/OneApi/network'
|
||||
// import {} from 'client/components/Tabs/VNetwork/Address/Actions'
|
||||
import { useGetVNTemplateQuery } from 'client/features/OneApi/networkTemplate'
|
||||
|
||||
import { ClustersTable } from 'client/components/Tables'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
@ -40,7 +39,7 @@ const { CLUSTER } = RESOURCE_NAMES
|
||||
*/
|
||||
const ClustersTab = ({ id }) => {
|
||||
const { push: redirectTo } = useHistory()
|
||||
const { data: vnet } = useGetVNetworkQuery({ id })
|
||||
const { data: vnet } = useGetVNTemplateQuery({ id })
|
||||
|
||||
const { view, hasAccessToResource } = useViews()
|
||||
const detailAccess = useMemo(() => hasAccessToResource(CLUSTER), [view])
|
||||
|
@ -0,0 +1,178 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Box, Stack } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
import {
|
||||
AddAddressRangeAction,
|
||||
DeleteAddressRangeAction,
|
||||
UpdateAddressRangeAction,
|
||||
} from 'client/components/Buttons'
|
||||
import AddressRangeCard from 'client/components/Cards/AddressRangeCard'
|
||||
import {
|
||||
useGetVNTemplateQuery,
|
||||
useUpdateVNTemplateMutation,
|
||||
} from 'client/features/OneApi/networkTemplate'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
import { AddressRange, VN_ACTIONS } from 'client/constants'
|
||||
|
||||
const { ADD_AR, UPDATE_AR, DELETE_AR } = VN_ACTIONS
|
||||
|
||||
const handleAdd = async ({ value, id, update, template }) => {
|
||||
const addressRanges = [template?.AR ?? []].flat()
|
||||
addressRanges.push(value)
|
||||
const templateJson = { ...template, AR: addressRanges }
|
||||
|
||||
const newTemplate = jsonToXml(templateJson)
|
||||
await update({ id, template: newTemplate }).unwrap()
|
||||
}
|
||||
|
||||
const handleUpdate = async ({ value, id, addressID, update, template }) => {
|
||||
let templateJson = { ...template, AR: value }
|
||||
|
||||
if (Array.isArray(template?.AR)) {
|
||||
const addressRanges = [template.AR ?? []].flat()
|
||||
addressRanges[addressID] = value
|
||||
templateJson = { ...template, AR: addressRanges }
|
||||
}
|
||||
|
||||
const newTemplate = jsonToXml(templateJson)
|
||||
await update({ id, template: newTemplate }).unwrap()
|
||||
}
|
||||
|
||||
const handleDelete = async ({ id, addressID, update, template }) => {
|
||||
const { AR, ...rest } = template
|
||||
let templateJson = { ...rest }
|
||||
|
||||
if (Array.isArray(template?.AR)) {
|
||||
const addressRanges = [template.AR ?? []].flat()
|
||||
addressRanges.splice(addressID, 1)
|
||||
templateJson = { ...template, AR: addressRanges }
|
||||
}
|
||||
|
||||
const newTemplate = jsonToXml(templateJson)
|
||||
await update({ id, template: newTemplate }).unwrap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the list of address ranges from a Virtual Network.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string[]} props.tabProps.actions - Actions tab
|
||||
* @param {string} props.id - Virtual Network id
|
||||
* @param {object} props.oneConfig - Open Nebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to oneadmin group
|
||||
* @returns {ReactElement} AR tab
|
||||
*/
|
||||
const AddressTab = ({
|
||||
tabProps: { actions } = {},
|
||||
id,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}) => {
|
||||
const { data: vnet } = useGetVNTemplateQuery(
|
||||
{ id },
|
||||
{ refetchOnMountOrArgChange: true }
|
||||
)
|
||||
const [update] = useUpdateVNTemplateMutation()
|
||||
|
||||
/** @type {AddressRange[]} */
|
||||
const addressRanges = [vnet?.TEMPLATE?.AR ?? []].flat()
|
||||
const template = vnet?.TEMPLATE
|
||||
|
||||
return (
|
||||
<Box padding={{ sm: '0.8em' }}>
|
||||
{actions[ADD_AR] === true && (
|
||||
<AddAddressRangeAction
|
||||
vnetId={id}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
onSubmit={(value) =>
|
||||
handleAdd({
|
||||
value,
|
||||
id,
|
||||
update,
|
||||
template,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Stack gap="1em" py="0.8em">
|
||||
{addressRanges.map((ar, addressID) => (
|
||||
<AddressRangeCard
|
||||
key={addressID}
|
||||
vnet={vnet}
|
||||
ar={ar}
|
||||
actions={
|
||||
<>
|
||||
{actions[UPDATE_AR] === true && (
|
||||
<UpdateAddressRangeAction
|
||||
vnetId={id}
|
||||
ar={ar}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
template={vnet}
|
||||
onSubmit={(value) =>
|
||||
handleUpdate({
|
||||
value,
|
||||
id,
|
||||
addressID,
|
||||
update,
|
||||
template,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{actions[DELETE_AR] === true && (
|
||||
<DeleteAddressRangeAction
|
||||
vnetId={id}
|
||||
ar={ar}
|
||||
oneConfig={oneConfig}
|
||||
adminGroup={adminGroup}
|
||||
template={vnet}
|
||||
onSubmit={() =>
|
||||
handleDelete({
|
||||
id,
|
||||
addressID,
|
||||
update,
|
||||
template,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
AddressTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
AddressTab.displayName = 'AddressTab'
|
||||
|
||||
export default AddressTab
|
@ -0,0 +1,87 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { ReactElement, useMemo } from 'react'
|
||||
|
||||
import { Box } from '@mui/material'
|
||||
import { useHistory } from 'react-router'
|
||||
import { generatePath } from 'react-router-dom'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetClustersQuery } from 'client/features/OneApi/cluster'
|
||||
import { useGetVNTemplateQuery } from 'client/features/OneApi/networkTemplate'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { ClustersTable } from 'client/components/Tables'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const { CLUSTER } = RESOURCE_NAMES
|
||||
|
||||
/**
|
||||
* Renders the list of clusters from a Virtual Network.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Virtual Network id
|
||||
* @returns {ReactElement} Clusters tab
|
||||
*/
|
||||
const ClustersTab = ({ id }) => {
|
||||
const { push: redirectTo } = useHistory()
|
||||
const { data: vnet } = useGetVNTemplateQuery(
|
||||
{ id },
|
||||
{ refetchOnMountOrArgChange: true }
|
||||
)
|
||||
|
||||
const { view, hasAccessToResource } = useViews()
|
||||
const detailAccess = useMemo(() => hasAccessToResource(CLUSTER), [view])
|
||||
|
||||
const clusters = [vnet?.TEMPLATE?.CLUSTER_IDS?.split(',') ?? []]
|
||||
.flat()
|
||||
.map((clId) => +clId)
|
||||
|
||||
const redirectToCluster = (row) => {
|
||||
const clusterPath = PATH.INFRASTRUCTURE.CLUSTERS.DETAIL
|
||||
redirectTo(generatePath(clusterPath, { id: row.ID }))
|
||||
}
|
||||
|
||||
const useQuery = () =>
|
||||
useGetClustersQuery(undefined, {
|
||||
selectFromResult: ({ data: result = [], ...rest }) => ({
|
||||
data: result?.filter((cluster) => clusters.includes(+cluster.ID)),
|
||||
...rest,
|
||||
}),
|
||||
})
|
||||
|
||||
return (
|
||||
<Box padding={{ sm: '0.8em', overflow: 'auto' }}>
|
||||
<ClustersTable
|
||||
disableGlobalSort
|
||||
disableRowSelect
|
||||
pageSize={5}
|
||||
onRowClick={detailAccess ? redirectToCluster : undefined}
|
||||
useQuery={useQuery}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
ClustersTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
ClustersTab.displayName = 'ClustersTab'
|
||||
|
||||
export default ClustersTab
|
@ -13,28 +13,30 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement, useCallback } from 'react'
|
||||
import { Box, Stack } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
import { ReactElement, useCallback } from 'react'
|
||||
|
||||
import {
|
||||
useGetVNTemplateQuery,
|
||||
AttributePanel,
|
||||
Ownership,
|
||||
Permissions,
|
||||
} from 'client/components/Tabs/Common'
|
||||
import QOS from 'client/components/Tabs/VNetwork/Info/qos'
|
||||
import Information from 'client/components/Tabs/VNetworkTemplate/Info/information'
|
||||
import {
|
||||
useChangeVNTemplateOwnershipMutation,
|
||||
useChangeVNTemplatePermissionsMutation,
|
||||
useGetVNTemplateQuery,
|
||||
useUpdateVNTemplateMutation,
|
||||
} from 'client/features/OneApi/networkTemplate'
|
||||
import {
|
||||
Permissions,
|
||||
Ownership,
|
||||
AttributePanel,
|
||||
} from 'client/components/Tabs/Common'
|
||||
import Information from 'client/components/Tabs/VNetworkTemplate/Info/information'
|
||||
|
||||
import makeStyles from '@mui/styles/makeStyles'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
import {
|
||||
getActionsAvailable,
|
||||
filterAttributes,
|
||||
getActionsAvailable,
|
||||
jsonToXml,
|
||||
} from 'client/models/Helper'
|
||||
import { cloneObject, set } from 'client/utils'
|
||||
@ -44,6 +46,15 @@ const VCENTER_ATTRIBUTES_REG = /^VCENTER_/
|
||||
const HIDDEN_ATTRIBUTES_REG =
|
||||
/^(AR|CLUSTERS|SECURITY_GROUPS|INBOUND_AVG_BW|INBOUND_PEAK_BW|INBOUND_PEAK_KB|OUTBOUND_AVG_BW|OUTBOUND_PEAK_BW|OUTBOUND_PEAK_KB)$/
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
gridColumn: '1 / -1',
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto auto',
|
||||
gap: '1rem',
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
@ -53,10 +64,13 @@ const HIDDEN_ATTRIBUTES_REG =
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const VNetTemplateInfoTab = ({ tabProps = {}, id }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const {
|
||||
information_panel: informationPanel,
|
||||
permissions_panel: permissionsPanel,
|
||||
ownership_panel: ownershipPanel,
|
||||
qos_panel: qosPanel,
|
||||
attributes_panel: attributesPanel,
|
||||
vcenter_panel: vcenterPanel,
|
||||
lxc_panel: lxcPanel,
|
||||
@ -146,6 +160,11 @@ const VNetTemplateInfoTab = ({ tabProps = {}, id }) => {
|
||||
groupName={GNAME}
|
||||
/>
|
||||
)}
|
||||
{qosPanel?.enabled && (
|
||||
<Box className={classes.container}>
|
||||
<QOS vnet={vnetTemplate} />
|
||||
</Box>
|
||||
)}
|
||||
{attributesPanel?.enabled && attributes && (
|
||||
<AttributePanel
|
||||
{...ATTRIBUTE_FUNCTION}
|
||||
|
@ -13,12 +13,12 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useRenameVNTemplateMutation } from 'client/features/OneApi/networkTemplate'
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
import { T, VNetworkTemplate, VN_TEMPLATE_ACTIONS } from 'client/constants'
|
||||
import { T, VN_TEMPLATE_ACTIONS, VNetworkTemplate } from 'client/constants'
|
||||
import { useRenameVNTemplateMutation } from 'client/features/OneApi/networkTemplate'
|
||||
import { booleanToString, stringToBoolean } from 'client/models/Helper'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
@ -30,7 +30,7 @@ import { T, VNetworkTemplate, VN_TEMPLATE_ACTIONS } from 'client/constants'
|
||||
*/
|
||||
const InformationPanel = ({ vnetTemplate = {}, actions }) => {
|
||||
const [rename] = useRenameVNTemplateMutation()
|
||||
const { ID, NAME } = vnetTemplate
|
||||
const { ID, NAME, VLAN_ID_AUTOMATIC, OUTER_VLAN_ID_AUTOMATIC } = vnetTemplate
|
||||
|
||||
const handleRename = async (_, newName) => {
|
||||
await rename({ id: ID, name: newName })
|
||||
@ -45,16 +45,30 @@ const InformationPanel = ({ vnetTemplate = {}, actions }) => {
|
||||
canEdit: actions?.includes?.(VN_TEMPLATE_ACTIONS.RENAME),
|
||||
handleEdit: handleRename,
|
||||
},
|
||||
{
|
||||
name: T.AutomaticVlanId,
|
||||
value: booleanToString(stringToBoolean(VLAN_ID_AUTOMATIC)),
|
||||
dataCy: 'vlan_id_automatic',
|
||||
handleEdit: handleRename,
|
||||
},
|
||||
{
|
||||
name: T.OuterVlanId,
|
||||
value: OUTER_VLAN_ID_AUTOMATIC || '-',
|
||||
dataCy: 'outer_vlan_id_automatic',
|
||||
},
|
||||
{
|
||||
name: T.AutomaticOuterVlanId,
|
||||
value: booleanToString(stringToBoolean(OUTER_VLAN_ID_AUTOMATIC)),
|
||||
dataCy: 'outer_vlan_automatic',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<List
|
||||
title={T.Information}
|
||||
list={info}
|
||||
containerProps={{ sx: { gridRow: 'span 3' } }}
|
||||
/>
|
||||
</>
|
||||
<List
|
||||
title={T.Information}
|
||||
list={info}
|
||||
containerProps={{ sx: { gridRow: 'span 3' } }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,94 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { ReactElement, useMemo } from 'react'
|
||||
|
||||
import { Box } from '@mui/material'
|
||||
import { useHistory } from 'react-router'
|
||||
import { generatePath } from 'react-router-dom'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetVNTemplateQuery } from 'client/features/OneApi/networkTemplate'
|
||||
import { useGetSecGroupsQuery } from 'client/features/OneApi/securityGroup'
|
||||
// import {} from 'client/components/Tabs/VNetwork/Address/Actions'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { SecurityGroupsTable } from 'client/components/Tables'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const { SEC_GROUP } = RESOURCE_NAMES
|
||||
|
||||
/**
|
||||
* Renders the list of security groups from a Virtual Network.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string[]} props.tabProps.actions - Actions tab
|
||||
* @param {string} props.id - Virtual Network id
|
||||
* @param {object} props.oneConfig - OpenNebula configuration
|
||||
* @param {boolean} props.adminGroup - If the user belongs to the oneadmin group
|
||||
* @returns {ReactElement} Security Groups tab
|
||||
*/
|
||||
const SecurityTab = ({
|
||||
tabProps: { actions } = {},
|
||||
id,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}) => {
|
||||
const { push: redirectTo } = useHistory()
|
||||
const { data: vnet } = useGetVNTemplateQuery({ id })
|
||||
|
||||
const { view, hasAccessToResource } = useViews()
|
||||
const detailAccess = useMemo(() => hasAccessToResource(SEC_GROUP), [view])
|
||||
|
||||
const splittedSecGroups = vnet?.TEMPLATE.SECURITY_GROUPS?.split(',') ?? []
|
||||
const secGroups = [splittedSecGroups].flat().map((sgId) => +sgId)
|
||||
|
||||
const redirectToSecGroup = (row) => {
|
||||
redirectTo(generatePath(PATH.NETWORK.SEC_GROUPS.DETAIL, { id: row.ID }))
|
||||
}
|
||||
|
||||
const useQuery = () =>
|
||||
useGetSecGroupsQuery(undefined, {
|
||||
selectFromResult: ({ data: result = [], ...rest }) => ({
|
||||
data: result?.filter((secgroup) => secGroups.includes(+secgroup.ID)),
|
||||
...rest,
|
||||
}),
|
||||
})
|
||||
|
||||
return (
|
||||
<Box padding={{ sm: '0.8em', overflow: 'auto' }}>
|
||||
<SecurityGroupsTable
|
||||
disableGlobalSort
|
||||
disableRowSelect
|
||||
pageSize={5}
|
||||
onRowClick={detailAccess ? redirectToSecGroup : undefined}
|
||||
useQuery={useQuery}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
SecurityTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
oneConfig: PropTypes.object,
|
||||
adminGroup: PropTypes.bool,
|
||||
}
|
||||
|
||||
SecurityTab.displayName = 'SecurityTab'
|
||||
|
||||
export default SecurityTab
|
@ -23,11 +23,17 @@ import { useGetVNTemplateQuery } from 'client/features/OneApi/networkTemplate'
|
||||
import { getAvailableInfoTabs } from 'client/models/Helper'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import Address from 'client/components/Tabs/VNetworkTemplate/Address'
|
||||
import Clusters from 'client/components/Tabs/VNetworkTemplate/Clusters'
|
||||
import Info from 'client/components/Tabs/VNetworkTemplate/Info'
|
||||
import Security from 'client/components/Tabs/VNetworkTemplate/Security'
|
||||
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
address: Address,
|
||||
security: Security,
|
||||
cluster: Clusters,
|
||||
}[tabName])
|
||||
|
||||
const VNetTemplateTabs = memo(({ id }) => {
|
||||
|
@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { Permissions, LockInfo } from 'client/constants/common'
|
||||
import * as ACTIONS from 'client/constants/actions'
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import { LockInfo, Permissions } from 'client/constants/common'
|
||||
|
||||
/**
|
||||
* @typedef VNetworkTemplate
|
||||
@ -35,6 +35,11 @@ import * as ACTIONS from 'client/constants/actions'
|
||||
/** @enum {string} Virtual network template actions */
|
||||
export const VN_TEMPLATE_ACTIONS = {
|
||||
CREATE_DIALOG: 'create_dialog',
|
||||
UPDATE_DIALOG: 'update_dialog',
|
||||
INSTANTIATE_DIALOG: 'instantiate_dialog',
|
||||
CHANGE_CLUSTER: 'change_cluster',
|
||||
LOCK: 'lock',
|
||||
UNLOCK: 'unlock',
|
||||
DELETE: 'delete',
|
||||
|
||||
// INFORMATION
|
||||
|
@ -84,6 +84,7 @@ module.exports = {
|
||||
CreateUser: 'Create User',
|
||||
UpdateUser: 'Update User',
|
||||
CreateVirtualNetwork: 'Create Virtual Network',
|
||||
CreateVirtualNetworkTemplate: 'Create Virtual Network Template',
|
||||
CreateVmTemplate: 'Create VM Template',
|
||||
CreateVDC: 'Create VDC',
|
||||
UpdateVDC: 'Update VDC',
|
||||
@ -126,6 +127,7 @@ module.exports = {
|
||||
Info: 'Info',
|
||||
Instantiate: 'Instantiate',
|
||||
InstantiateVmTemplate: 'Instantiate VM Template',
|
||||
InstantiateVnTemplate: 'Instantiate Network Template',
|
||||
LocateOnTable: 'Locate on table',
|
||||
Lock: 'Lock',
|
||||
Migrate: 'Migrate',
|
||||
@ -212,6 +214,7 @@ module.exports = {
|
||||
UpdateScheduleAction: 'Update schedule action: %s',
|
||||
UpdateServiceTemplate: 'Update Service Template',
|
||||
UpdateVirtualNetwork: 'Update Virtual Network',
|
||||
UpdateVirtualNetworkTemplate: 'Update Virtual Network Template',
|
||||
UpdateVmConfiguration: 'Update VM Configuration',
|
||||
UpdateVmTemplate: 'Update VM Template',
|
||||
|
||||
@ -730,6 +733,7 @@ module.exports = {
|
||||
Timezone: 'Timezone',
|
||||
/* VM schema - info */
|
||||
VmName: 'VM name',
|
||||
VNName: 'Virtual Network Name',
|
||||
UserTemplate: 'User Template',
|
||||
Template: 'Template',
|
||||
WhereIsRunning:
|
||||
@ -847,6 +851,9 @@ module.exports = {
|
||||
Virtualization: 'Virtualization',
|
||||
CustomInformation: 'Custom information',
|
||||
CustomVirtualization: 'Custom virtualization',
|
||||
VnTemplateNameHelper: `
|
||||
Defaults to 'template name-<vmid>' when empty.
|
||||
When creating several Virtual Network, the wildcard %%idx will be replaced with a number starting from 0`,
|
||||
VmTemplateNameHelper: `
|
||||
Defaults to 'template name-<vmid>' when empty.
|
||||
When creating several VMs, the wildcard %%idx will be
|
||||
@ -1321,6 +1328,8 @@ module.exports = {
|
||||
AddToExistingReservation: 'Add to an existing Reservation',
|
||||
FirstAddress: 'First address',
|
||||
IpOrMac: 'IP or MAC',
|
||||
MessageQos:
|
||||
'These values apply to each VM interface individually, they are not global values for the Virtual Network',
|
||||
|
||||
/* security group schema */
|
||||
Security: 'Security',
|
||||
@ -1470,6 +1479,8 @@ module.exports = {
|
||||
ManualNetwork: 'Manual Network',
|
||||
OpennebulaVirtualNetwork: 'OpenNebula Virtual Network',
|
||||
SelectNewNetwork: 'Please select a network from the list',
|
||||
MessageAddSecGroupDefault:
|
||||
'The default Security Group 0 is automatically added to new Virtual Networks',
|
||||
NotVmsCurrentySecGroups:
|
||||
'There are currently no VMs associated with this Security Group',
|
||||
CommitMessageSecGroups: `
|
||||
|
@ -0,0 +1,88 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useHistory, useLocation } from 'react-router'
|
||||
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import {
|
||||
useAllocateVNTemplateMutation,
|
||||
useGetVNTemplateQuery,
|
||||
useUpdateVNTemplateMutation,
|
||||
} from 'client/features/OneApi/networkTemplate'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import {
|
||||
DefaultFormStepper,
|
||||
SkeletonStepsForm,
|
||||
} from 'client/components/FormStepper'
|
||||
import { CreateForm } from 'client/components/Forms/VNTemplate'
|
||||
|
||||
import { useSystemData } from 'client/features/Auth'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a Virtual Network.
|
||||
*
|
||||
* @returns {ReactElement} Virtual Network form
|
||||
*/
|
||||
const CreateVirtualNetworkTemplate = () => {
|
||||
const history = useHistory()
|
||||
const { state: { ID: vnetId, NAME } = {} } = useLocation()
|
||||
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const [update] = useUpdateVNTemplateMutation()
|
||||
const [allocate] = useAllocateVNTemplateMutation()
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
|
||||
const { data } = useGetVNTemplateQuery(
|
||||
{ id: vnetId, extended: true },
|
||||
{ skip: vnetId === undefined }
|
||||
)
|
||||
|
||||
const onSubmit = async (xml) => {
|
||||
try {
|
||||
if (!vnetId) {
|
||||
const newVnetId = await allocate({ template: xml }).unwrap()
|
||||
enqueueSuccess(`Virtual Network Template created - #${newVnetId}`)
|
||||
} else {
|
||||
await update({ id: vnetId, template: xml }).unwrap()
|
||||
enqueueSuccess(`Virtual Network Template updated - #${vnetId} ${NAME}`)
|
||||
}
|
||||
|
||||
history.push(PATH.NETWORK.VN_TEMPLATES.LIST)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return !_.isEmpty(oneConfig) && ((vnetId && data) || !vnetId) ? (
|
||||
<CreateForm
|
||||
initialValues={data}
|
||||
stepProps={{
|
||||
data,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</CreateForm>
|
||||
) : (
|
||||
<SkeletonStepsForm />
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateVirtualNetworkTemplate
|
@ -0,0 +1,89 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { Redirect, useHistory, useLocation } from 'react-router'
|
||||
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import {
|
||||
useGetVNTemplateQuery,
|
||||
useInstantiateVNTemplateMutation,
|
||||
} from 'client/features/OneApi/networkTemplate'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import {
|
||||
DefaultFormStepper,
|
||||
SkeletonStepsForm,
|
||||
} from 'client/components/FormStepper'
|
||||
import { InstantiateForm } from 'client/components/Forms/VNTemplate'
|
||||
|
||||
import { useSystemData } from 'client/features/Auth'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Displays the instantiation form for a VM Template.
|
||||
*
|
||||
* @returns {ReactElement} Instantiation form
|
||||
*/
|
||||
const InstantiateVnTemplate = () => {
|
||||
const history = useHistory()
|
||||
const { state: { ID: templateId, NAME: templateName } = {} } = useLocation()
|
||||
|
||||
const { enqueueInfo } = useGeneralApi()
|
||||
const [instantiate] = useInstantiateVNTemplateMutation()
|
||||
|
||||
const { adminGroup, oneConfig } = useSystemData()
|
||||
|
||||
const { data: apiTemplateDataExtended, isError } = useGetVNTemplateQuery(
|
||||
{ id: templateId },
|
||||
{ skip: templateId === undefined }
|
||||
)
|
||||
|
||||
const dataTemplateExtended = _.cloneDeep(apiTemplateDataExtended)
|
||||
|
||||
const onSubmit = async (template) => {
|
||||
try {
|
||||
await instantiate(template).unwrap()
|
||||
history.push(PATH.NETWORK.VN_TEMPLATES.LIST)
|
||||
|
||||
const templateInfo = `#${templateId} ${templateName}`
|
||||
enqueueInfo(`VN Template instantiated - ${templateInfo}`)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (!templateId || isError) {
|
||||
return <Redirect to={PATH.NETWORK.VN_TEMPLATES.LIST} />
|
||||
}
|
||||
|
||||
return !dataTemplateExtended || _.isEmpty(oneConfig) ? (
|
||||
<SkeletonStepsForm />
|
||||
) : (
|
||||
<InstantiateForm
|
||||
initialValues={dataTemplateExtended}
|
||||
stepProps={{
|
||||
dataTemplateExtended,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</InstantiateForm>
|
||||
)
|
||||
}
|
||||
|
||||
export default InstantiateVnTemplate
|
@ -13,25 +13,26 @@
|
||||
* 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 { Box, Chip, Stack, Typography } from '@mui/material'
|
||||
import Cancel from 'iconoir-react/dist/Cancel'
|
||||
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 PropTypes from 'prop-types'
|
||||
import { ReactElement, memo, useState } from 'react'
|
||||
import { Row } from 'react-table'
|
||||
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import SplitPane from 'client/components/SplitPane'
|
||||
import { VNetworkTemplatesTable } from 'client/components/Tables'
|
||||
import VNetworkTemplateActions from 'client/components/Tables/VNetworkTemplates/actions'
|
||||
import VNetworkTemplateTabs from 'client/components/Tabs/VNetworkTemplate'
|
||||
import { T, VNetworkTemplate } from 'client/constants'
|
||||
import {
|
||||
useLazyGetVNTemplateQuery,
|
||||
useUpdateVNTemplateMutation,
|
||||
} from 'client/features/OneApi/networkTemplate'
|
||||
import { VNetworkTemplatesTable } from 'client/components/Tables'
|
||||
import VNetworkTemplateTabs from 'client/components/Tabs/VNetworkTemplate'
|
||||
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, VNetworkTemplate } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Displays a list of VNet Templates with a split pane between the list and selected row(s).
|
||||
@ -40,7 +41,7 @@ import { T, VNetworkTemplate } from 'client/constants'
|
||||
*/
|
||||
function VNetworkTemplates() {
|
||||
const [selectedRows, onSelectedRowsChange] = useState(() => [])
|
||||
|
||||
const actions = VNetworkTemplateActions()
|
||||
const hasSelectedRows = selectedRows?.length > 0
|
||||
const moreThanOneSelected = selectedRows?.length > 1
|
||||
|
||||
@ -50,6 +51,7 @@ function VNetworkTemplates() {
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<VNetworkTemplatesTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
useUpdateMutation={useUpdateVNTemplateMutation}
|
||||
/>
|
||||
|
||||
|
@ -13,13 +13,17 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/vntemplate'
|
||||
import { FilterFlag, Permission, VNetworkTemplate } from 'client/constants'
|
||||
import {
|
||||
oneApi,
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
oneApi,
|
||||
} from 'client/features/OneApi'
|
||||
import { FilterFlag, Permission, VNetworkTemplate } from 'client/constants'
|
||||
import {
|
||||
updateOwnershipOnResource,
|
||||
updateTemplateOnResource,
|
||||
} from 'client/features/OneApi/common'
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/vntemplate'
|
||||
|
||||
const { VNTEMPLATE } = ONE_RESOURCES
|
||||
const { VNET_POOL, VNTEMPLATE_POOL } = ONE_RESOURCES_POOL
|
||||
@ -168,7 +172,31 @@ const vNetworkTemplateApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
providesTags: (_, __, { id }) => [{ type: VNTEMPLATE, id }],
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VNTEMPLATE, id }],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchVNTemplate = dispatch(
|
||||
vNetworkTemplateApi.util.updateQueryData(
|
||||
'getVNTemplate',
|
||||
{ id: params.id },
|
||||
updateTemplateOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchVNTemplates = dispatch(
|
||||
vNetworkTemplateApi.util.updateQueryData(
|
||||
'getVNTemplates',
|
||||
undefined,
|
||||
updateTemplateOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchVNTemplate.undo()
|
||||
patchVNTemplates.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
changeVNTemplatePermissions: builder.mutation({
|
||||
/**
|
||||
@ -215,10 +243,20 @@ const vNetworkTemplateApi = oneApi.injectEndpoints({
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [
|
||||
{ type: VNTEMPLATE, id },
|
||||
VNTEMPLATE_POOL,
|
||||
],
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VNTEMPLATE, id }],
|
||||
async onQueryStarted(params, { getState, dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchVNet = dispatch(
|
||||
vNetworkTemplateApi.util.updateQueryData(
|
||||
'getVNTemplate',
|
||||
{ id: params.id },
|
||||
updateOwnershipOnResource(getState(), params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(patchVNet.undo)
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
renameVNTemplate: builder.mutation({
|
||||
/**
|
||||
@ -270,15 +308,15 @@ const vNetworkTemplateApi = oneApi.injectEndpoints({
|
||||
/**
|
||||
* Unlocks a VN Template.
|
||||
*
|
||||
* @param {string|number} id - VN Template id
|
||||
* @param {object} params - Request parameters
|
||||
* @returns {number} VN Template id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (id) => {
|
||||
query: (params) => {
|
||||
const name = Actions.VNTEMPLATE_UNLOCK
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params: { id }, command }
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, id) => [
|
||||
{ type: VNTEMPLATE, id },
|
||||
|
@ -174,11 +174,11 @@ module.exports = {
|
||||
from: resource,
|
||||
default: 0,
|
||||
},
|
||||
userId: {
|
||||
user: {
|
||||
from: postBody,
|
||||
default: -1,
|
||||
},
|
||||
groupId: {
|
||||
group: {
|
||||
from: postBody,
|
||||
default: -1,
|
||||
},
|
||||
@ -220,10 +220,14 @@ module.exports = {
|
||||
from: resource,
|
||||
default: 0,
|
||||
},
|
||||
lock: {
|
||||
level: {
|
||||
from: postBody,
|
||||
default: 4,
|
||||
},
|
||||
test: {
|
||||
from: postBody,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
[VNTEMPLATE_UNLOCK]: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user