mirror of
https://github.com/OpenNebula/one.git
synced 2025-02-02 09:47:00 +03:00
Co-authored-by: Frederick Borges <fborges@opennebula.io>
This commit is contained in:
parent
201d388601
commit
b06bd9a5ff
@ -2970,6 +2970,7 @@ FIREEDGE_SUNSTONE_ETC_VIEW_ADMIN="src/fireedge/etc/sunstone/admin/vm-tab.yaml \
|
||||
src/fireedge/etc/sunstone/admin/sec-group-tab.yaml\
|
||||
src/fireedge/etc/sunstone/admin/backup-tab.yaml \
|
||||
src/fireedge/etc/sunstone/admin/datastore-tab.yaml \
|
||||
src/fireedge/etc/sunstone/admin/vdc-tab.yaml \
|
||||
src/fireedge/etc/sunstone/admin/host-tab.yaml"
|
||||
|
||||
FIREEDGE_SUNSTONE_ETC_VIEW_USER="src/fireedge/etc/sunstone/user/vm-tab.yaml \
|
||||
|
56
src/fireedge/etc/sunstone/admin/vdc-tab.yaml
Normal file
56
src/fireedge/etc/sunstone/admin/vdc-tab.yaml
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
# This file describes the information and actions available in the VIRTUAL DATA CENTER tab
|
||||
|
||||
# Resource
|
||||
|
||||
resource_name: "VIRTUAL-DATA-CENTER"
|
||||
|
||||
# Actions - Which buttons are visible to operate over the resources
|
||||
|
||||
actions:
|
||||
create_dialog: true
|
||||
update_dialog: true
|
||||
delete: true
|
||||
edit_labels: true
|
||||
|
||||
# Filters - List of criteria to filter the resources
|
||||
|
||||
filters:
|
||||
label: true
|
||||
|
||||
# Info Tabs - Which info tabs are used to show extended information
|
||||
|
||||
info-tabs:
|
||||
info:
|
||||
enabled: true
|
||||
information_panel:
|
||||
enabled: true
|
||||
actions:
|
||||
rename: true
|
||||
attributes_panel:
|
||||
enabled: true
|
||||
actions:
|
||||
copy: true
|
||||
add: true
|
||||
edit: true
|
||||
delete: true
|
||||
|
||||
groups:
|
||||
enabled: true
|
||||
clusters:
|
||||
enabled: true
|
||||
datastores:
|
||||
enabled: true
|
||||
hosts:
|
||||
enabled: true
|
||||
vnets:
|
||||
enabled: true
|
||||
|
||||
|
||||
# Dialogs
|
||||
dialogs:
|
||||
create_dialog:
|
||||
clusters: true
|
||||
datastores: true
|
||||
hosts: true
|
||||
vnets: true
|
12
src/fireedge/package-lock.json
generated
12
src/fireedge/package-lock.json
generated
@ -3305,9 +3305,9 @@
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
|
||||
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
|
||||
"version": "17.0.58",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz",
|
||||
"integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
@ -15763,9 +15763,9 @@
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.0.26",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.26.tgz",
|
||||
"integrity": "sha512-hCR3PJQsAIXyxhTNSiDFY//LhnMZWpNNr5etoCqx/iUfGc5gXWtQR2Phl908jVR6uPXacojQWTg4qRpkxTuGug==",
|
||||
"version": "17.0.58",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.58.tgz",
|
||||
"integrity": "sha512-c1GzVY97P0fGxwGxhYq989j4XwlcHQoto6wQISOC2v6wm3h0PORRWJFHlkRjfGsiG3y1609WdQ+J+tKxvrEd6A==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
|
@ -36,6 +36,7 @@ import {
|
||||
EmptyPage as TemplateIcon,
|
||||
Archive as TemplatesIcon,
|
||||
User as UserIcon,
|
||||
List as VDCIcon,
|
||||
Shuffle as VRoutersIcons,
|
||||
ModernTv as VmsIcons,
|
||||
MinusPinAlt as ZoneIcon,
|
||||
@ -103,6 +104,10 @@ const CreateDatastores = loadable(
|
||||
ssr: false,
|
||||
}
|
||||
)
|
||||
const DatastoreDetail = loadable(
|
||||
() => import('client/containers/Datastores/Detail'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const Images = loadable(() => import('client/containers/Images'), {
|
||||
ssr: false,
|
||||
@ -199,7 +204,17 @@ const Groups = loadable(() => import('client/containers/Groups'), {
|
||||
const GroupDetail = loadable(() => import('client/containers/Groups/Detail'), {
|
||||
ssr: false,
|
||||
})
|
||||
// const VDCs = loadable(() => import('client/containers/VDCs'), { ssr: false })
|
||||
|
||||
const VDCs = loadable(() => import('client/containers/VDCs'), { ssr: false })
|
||||
|
||||
const VDCDetail = loadable(() => import('client/containers/VDCs/Detail'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
const VDCCreate = loadable(() => import('client/containers/VDCs/Create'), {
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
// const ACLs = loadable(() => import('client/containers/ACLs'), { ssr: false })
|
||||
|
||||
export const PATH = {
|
||||
@ -301,6 +316,11 @@ export const PATH = {
|
||||
LIST: `/${RESOURCE_NAMES.GROUP}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.GROUP}/:id`,
|
||||
},
|
||||
VDCS: {
|
||||
LIST: `/${RESOURCE_NAMES.VDC}`,
|
||||
DETAIL: `/${RESOURCE_NAMES.VDC}/:id`,
|
||||
CREATE: `/${RESOURCE_NAMES.VDC}/create`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -424,6 +444,12 @@ const ENDPOINTS = [
|
||||
path: PATH.STORAGE.DATASTORES.CREATE,
|
||||
Component: CreateDatastores,
|
||||
},
|
||||
{
|
||||
title: T.Datastore,
|
||||
description: (params) => `#${params?.id}`,
|
||||
path: PATH.STORAGE.DATASTORES.DETAIL,
|
||||
Component: DatastoreDetail,
|
||||
},
|
||||
{
|
||||
title: T.Images,
|
||||
path: PATH.STORAGE.IMAGES.LIST,
|
||||
@ -609,6 +635,31 @@ const ENDPOINTS = [
|
||||
path: PATH.SYSTEM.GROUPS.DETAIL,
|
||||
Component: GroupDetail,
|
||||
},
|
||||
{
|
||||
title: (_, state) =>
|
||||
state?.ID !== undefined ? T.UpdateVDC : T.CreateVDC,
|
||||
path: PATH.SYSTEM.VDCS.CREATE,
|
||||
Component: VDCCreate,
|
||||
},
|
||||
{
|
||||
title: T.VDCs,
|
||||
path: PATH.SYSTEM.VDCS.LIST,
|
||||
sidebar: true,
|
||||
icon: VDCIcon,
|
||||
Component: VDCs,
|
||||
},
|
||||
{
|
||||
title: T.VDC,
|
||||
description: (params) => `#${params?.id}`,
|
||||
path: PATH.SYSTEM.VDCS.DETAIL,
|
||||
Component: VDCDetail,
|
||||
},
|
||||
// {
|
||||
// title: T.Group,
|
||||
// description: (params) => `#${params?.id}`,
|
||||
// path: PATH.SYSTEM.GROUPS.DETAIL,
|
||||
// Component: GroupDetail,
|
||||
// },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
@ -0,0 +1,173 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, memo, useMemo } from 'react'
|
||||
|
||||
import { Typography } from '@mui/material'
|
||||
import {
|
||||
Server as ClusterIcon,
|
||||
Db as DatastoreIcon,
|
||||
Group as GroupIcon,
|
||||
HardDrive as HostIcon,
|
||||
Network as VNetIcon,
|
||||
} from 'iconoir-react'
|
||||
|
||||
import MultipleTags from 'client/components/MultipleTags'
|
||||
import { useViews } from 'client/features/Auth'
|
||||
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { StatusCircle } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
|
||||
import { ACTIONS, ALL_SELECTED, RESOURCE_NAMES, T, VDC } from 'client/constants'
|
||||
import COLOR from 'client/constants/color'
|
||||
import { getColorFromString, getUniqueLabels } from 'client/models/Helper'
|
||||
|
||||
const isAllSelected = (resourceArray) =>
|
||||
resourceArray.length === 1 && resourceArray[0] === ALL_SELECTED
|
||||
|
||||
const VirtualDataCenterCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {VDC} props.template - Virtual Data Center resource
|
||||
* @param {object} props.rootProps - Props to root component
|
||||
* @param {function(string):Promise} [props.onClickLabel] - Callback to click label
|
||||
* @param {function(string):Promise} [props.onDeleteLabel] - Callback to delete label
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ template, rootProps, onClickLabel, onDeleteLabel }) => {
|
||||
const classes = rowStyles()
|
||||
const { [RESOURCE_NAMES.VDC]: vdcView } = useViews()
|
||||
|
||||
const enableEditLabels =
|
||||
vdcView?.actions?.[ACTIONS.EDIT_LABELS] === true && !!onDeleteLabel
|
||||
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
GROUPS,
|
||||
CLUSTERS,
|
||||
HOSTS,
|
||||
DATASTORES,
|
||||
VNETS,
|
||||
TEMPLATE: { LABELS },
|
||||
} = template
|
||||
|
||||
const groupsCount = useMemo(() => {
|
||||
const { ID: groupsIds = [] } = GROUPS
|
||||
const groupsArray = Array.isArray(groupsIds) ? groupsIds : [groupsIds]
|
||||
|
||||
return groupsArray.length
|
||||
}, [GROUPS.ID])
|
||||
|
||||
const clustersCount = useMemo(() => {
|
||||
const { CLUSTER: clustersInfo = [] } = CLUSTERS
|
||||
const clustersArray = (
|
||||
Array.isArray(clustersInfo) ? clustersInfo : [clustersInfo]
|
||||
).map((cluster) => cluster.CLUSTER_ID)
|
||||
|
||||
return isAllSelected(clustersArray) ? T.All : clustersArray.length
|
||||
}, [CLUSTERS.CLUSTER])
|
||||
|
||||
const hostsCount = useMemo(() => {
|
||||
const { HOST: hostsInfo = [] } = HOSTS
|
||||
const hostsArray = (
|
||||
Array.isArray(hostsInfo) ? hostsInfo : [hostsInfo]
|
||||
).map((host) => host.HOST_ID)
|
||||
|
||||
return isAllSelected(hostsArray) ? T.All : hostsArray.length
|
||||
}, [HOSTS.HOST])
|
||||
|
||||
const datastoresCount = useMemo(() => {
|
||||
const { DATASTORE: datastoresInfo = [] } = DATASTORES
|
||||
const datastoresArray = (
|
||||
Array.isArray(datastoresInfo) ? datastoresInfo : [datastoresInfo]
|
||||
).map((ds) => ds.DATASTORE_ID)
|
||||
|
||||
return isAllSelected(datastoresArray) ? T.All : datastoresArray.length
|
||||
}, [DATASTORES.DATASTORE])
|
||||
|
||||
const vnetsCount = useMemo(() => {
|
||||
const { VNET: vnetsInfo = [] } = VNETS
|
||||
const vnetsArray = (
|
||||
Array.isArray(vnetsInfo) ? vnetsInfo : [vnetsInfo]
|
||||
).map((vnet) => vnet.VNET_ID)
|
||||
|
||||
return isAllSelected(vnetsArray) ? T.All : vnetsArray.length
|
||||
}, [VNETS.VNET])
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
getUniqueLabels(LABELS).map((label) => ({
|
||||
text: label,
|
||||
stateColor: getColorFromString(label),
|
||||
onClick: onClickLabel,
|
||||
onDelete: enableEditLabels && onDeleteLabel,
|
||||
})),
|
||||
[LABELS, enableEditLabels, onClickLabel, onDeleteLabel]
|
||||
)
|
||||
|
||||
return (
|
||||
<div {...rootProps} data-cy={`vdc-${ID}`}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<StatusCircle color={COLOR.success.main} />
|
||||
<Typography component="span">{NAME}</Typography>
|
||||
<span className={classes.labels}>
|
||||
<MultipleTags tags={labels} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${ID}`}</span>
|
||||
<span title={`${Tr(T.Groups)}: ${groupsCount}`}>
|
||||
<GroupIcon />
|
||||
<span>{` ${groupsCount}`}</span>
|
||||
</span>
|
||||
<span title={`${Tr(T.Clusters)}: ${clustersCount}`}>
|
||||
<ClusterIcon />
|
||||
<span>{` ${clustersCount}`}</span>
|
||||
</span>
|
||||
<span title={`${Tr(T.Hosts)}: ${hostsCount}`}>
|
||||
<HostIcon />
|
||||
<span>{` ${hostsCount}`}</span>
|
||||
</span>
|
||||
<span title={`${Tr(T.Datastores)}: ${datastoresCount}`}>
|
||||
<DatastoreIcon />
|
||||
<span>{` ${datastoresCount}`}</span>
|
||||
</span>
|
||||
<span title={`${Tr(T.VirtualNetworks)}: ${vnetsCount}`}>
|
||||
<VNetIcon />
|
||||
<span>{` ${vnetsCount}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
VirtualDataCenterCard.propTypes = {
|
||||
template: PropTypes.object,
|
||||
rootProps: PropTypes.shape({
|
||||
className: PropTypes.string,
|
||||
}),
|
||||
onClickLabel: PropTypes.func,
|
||||
onDeleteLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
VirtualDataCenterCard.displayName = 'VirtualDataCenterCard'
|
||||
|
||||
export default VirtualDataCenterCard
|
@ -39,6 +39,7 @@ import ServiceCard from 'client/components/Cards/ServiceCard'
|
||||
import ServiceTemplateCard from 'client/components/Cards/ServiceTemplateCard'
|
||||
import SnapshotCard from 'client/components/Cards/SnapshotCard'
|
||||
import TierCard from 'client/components/Cards/TierCard'
|
||||
import VirtualDataCenterCard from 'client/components/Cards/VirtualDataCenterCard'
|
||||
import VirtualMachineCard from 'client/components/Cards/VirtualMachineCard'
|
||||
import VmTemplateCard from 'client/components/Cards/VmTemplateCard'
|
||||
import WavesCard from 'client/components/Cards/WavesCard'
|
||||
@ -70,6 +71,7 @@ export {
|
||||
ServiceTemplateCard,
|
||||
SnapshotCard,
|
||||
TierCard,
|
||||
VirtualDataCenterCard,
|
||||
VirtualMachineCard,
|
||||
VmTemplateCard,
|
||||
WavesCard,
|
||||
|
@ -13,12 +13,13 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useFormContext, useController } from 'react-hook-form'
|
||||
import { memo, useCallback, useEffect, useState } from 'react'
|
||||
import { useController, useFormContext } from 'react-hook-form'
|
||||
|
||||
import Legend from 'client/components/Forms/Legend'
|
||||
import { ErrorHelper } from 'client/components/FormControl'
|
||||
import Legend from 'client/components/Forms/Legend'
|
||||
import { sortStateTables } from 'client/components/Tables/Enhanced/Utils/DataTableUtils'
|
||||
import { generateKey } from 'client/utils'
|
||||
|
||||
const defaultGetRowId = (item) =>
|
||||
@ -44,7 +45,9 @@ const TableController = memo(
|
||||
getRowId = defaultGetRowId,
|
||||
readOnly = false,
|
||||
onConditionChange,
|
||||
fieldProps: { initialState, ...fieldProps } = {},
|
||||
zoneId,
|
||||
dependOf,
|
||||
fieldProps: { initialState, preserveState, ...fieldProps } = {},
|
||||
}) => {
|
||||
const { clearErrors } = useFormContext()
|
||||
|
||||
@ -57,9 +60,15 @@ const TableController = memo(
|
||||
getSelectedRowIds(value)
|
||||
)
|
||||
|
||||
const reSelectRows = (newValues = []) => {
|
||||
const sortedNewValues = sortStateTables(newValues)
|
||||
onChange(sortedNewValues)
|
||||
setInitialRows(getSelectedRowIds(sortedNewValues))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
onChange(singleSelect ? undefined : [])
|
||||
setInitialRows({})
|
||||
onChange(singleSelect ? undefined : preserveState ? value : [])
|
||||
setInitialRows(preserveState ? initialRows : {})
|
||||
}, [Table])
|
||||
|
||||
const handleSelectedRowsChange = useCallback(
|
||||
@ -99,8 +108,12 @@ const TableController = memo(
|
||||
disableRowSelect={readOnly}
|
||||
singleSelect={singleSelect}
|
||||
getRowId={getRowId}
|
||||
zoneId={zoneId}
|
||||
dependOf={dependOf}
|
||||
initialState={{ ...initialState, selectedRowIds: initialRows }}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
value={value ?? []}
|
||||
reSelectRows={reSelectRows}
|
||||
{...fieldProps}
|
||||
/>
|
||||
</>
|
||||
@ -116,6 +129,7 @@ TableController.propTypes = {
|
||||
control: PropTypes.object,
|
||||
cy: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
zoneId: PropTypes.string,
|
||||
singleSelect: PropTypes.bool,
|
||||
Table: PropTypes.any,
|
||||
getRowId: PropTypes.func,
|
||||
@ -124,7 +138,6 @@ TableController.propTypes = {
|
||||
tooltip: PropTypes.any,
|
||||
fieldProps: PropTypes.object,
|
||||
readOnly: PropTypes.bool,
|
||||
onConditionChange: PropTypes.func,
|
||||
}
|
||||
|
||||
TableController.displayName = 'TableController'
|
||||
|
@ -17,19 +17,19 @@
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Box, Button, Typography } from '@mui/material'
|
||||
import Stepper from '@mui/material/Stepper'
|
||||
import Step from '@mui/material/Step'
|
||||
import StepLabel from '@mui/material/StepLabel'
|
||||
import StepButton from '@mui/material/StepButton'
|
||||
import StepIcon, { stepIconClasses } from '@mui/material/StepIcon'
|
||||
import StepConnector, {
|
||||
stepConnectorClasses,
|
||||
} from '@mui/material/StepConnector'
|
||||
import StepIcon, { stepIconClasses } from '@mui/material/StepIcon'
|
||||
import StepLabel from '@mui/material/StepLabel'
|
||||
import Stepper from '@mui/material/Stepper'
|
||||
import { styled } from '@mui/styles'
|
||||
|
||||
import { SubmitButton } from 'client/components/FormControl'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T, SCHEMES } from 'client/constants'
|
||||
import { SCHEMES, T } from 'client/constants'
|
||||
|
||||
const StepperStyled = styled(Stepper)(({ theme }) => ({
|
||||
backdropFilter: 'blur(3px)',
|
||||
|
@ -28,10 +28,10 @@ import { Accordion, AccordionSummary, FormControl, Grid } from '@mui/material'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
|
||||
import * as FC from 'client/components/FormControl'
|
||||
import { useDisableStep } from 'client/components/FormStepper'
|
||||
import Legend from 'client/components/Forms/Legend'
|
||||
import { INPUT_TYPES } from 'client/constants'
|
||||
import { Field } from 'client/utils'
|
||||
import { useDisableStep } from 'client/components/FormStepper'
|
||||
|
||||
const NOT_DEPEND_ATTRIBUTES = [
|
||||
'watcher',
|
||||
@ -230,6 +230,7 @@ const FieldComponent = memo(
|
||||
dependencies: nameOfDependField,
|
||||
name: inputName,
|
||||
type: htmlType === false ? undefined : htmlType,
|
||||
dependOf,
|
||||
onConditionChange: handleConditionChange,
|
||||
...fieldProps,
|
||||
})}
|
||||
|
@ -14,8 +14,8 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
// import { Stack } from '@mui/material'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
import SecurityIcon from 'iconoir-react/dist/HistoricShield'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
|
||||
import { SecurityGroupsTable } from 'client/components/Tables'
|
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 } from '@mui/material'
|
||||
import { useCallback } from 'react'
|
||||
import { useFormContext, useWatch } from 'react-hook-form'
|
||||
import { object } from 'yup'
|
||||
|
||||
import { AttributePanel } from 'client/components/Tabs/Common'
|
||||
import { T } from 'client/constants'
|
||||
import { cleanEmpty, cloneObject, set } from 'client/utils'
|
||||
|
||||
export const STEP_ID = 'custom-variables'
|
||||
|
||||
const Content = () => {
|
||||
const { setValue } = useFormContext()
|
||||
const customVars = useWatch({ name: STEP_ID })
|
||||
|
||||
const handleChangeAttribute = useCallback(
|
||||
(path, newValue) => {
|
||||
const newCustomVars = cloneObject(customVars)
|
||||
set(newCustomVars, path, newValue)
|
||||
setValue(STEP_ID, cleanEmpty(newCustomVars))
|
||||
},
|
||||
[customVars]
|
||||
)
|
||||
|
||||
return (
|
||||
<Box display="grid" gap="1em">
|
||||
<AttributePanel
|
||||
allActionsEnabled
|
||||
handleAdd={handleChangeAttribute}
|
||||
handleEdit={handleChangeAttribute}
|
||||
handleDelete={handleChangeAttribute}
|
||||
attributes={customVars}
|
||||
filtersSpecialAttributes={false}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom variables about VDC Template.
|
||||
*
|
||||
* @returns {object} Custom configuration step
|
||||
*/
|
||||
const CustomVariables = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.CustomVariables,
|
||||
resolver: object(),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default CustomVariables
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
import { FIELDS, SCHEMA } from './schema'
|
||||
|
||||
export const STEP_ID = 'general'
|
||||
|
||||
const Content = () => (
|
||||
<FormWithSchema id={STEP_ID} fields={FIELDS} cy={`${STEP_ID}`} />
|
||||
)
|
||||
|
||||
/**
|
||||
* General configuration about VDC Template.
|
||||
*
|
||||
* @returns {object} General configuration step
|
||||
*/
|
||||
const General = () => ({
|
||||
id: STEP_ID,
|
||||
label: T.Configuration,
|
||||
resolver: SCHEMA,
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: Content,
|
||||
})
|
||||
|
||||
export default General
|
@ -0,0 +1,48 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { ObjectSchema, object, string } from 'yup'
|
||||
|
||||
/** @type {Field} name field */
|
||||
const NAME = {
|
||||
name: 'NAME',
|
||||
label: T.Name,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
validation: string().trim().required(),
|
||||
grid: { xs: 12, md: 12 },
|
||||
}
|
||||
|
||||
/** @type {Field} Description field */
|
||||
const DESCRIPTION = {
|
||||
name: 'DESCRIPTION',
|
||||
label: T.Description,
|
||||
type: INPUT_TYPES.TEXT,
|
||||
multiline: true,
|
||||
validation: string().trim(),
|
||||
grid: { xs: 12, md: 12 },
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = [NAME, DESCRIPTION]
|
||||
|
||||
/**
|
||||
* @param {object} [stepProps] - Step props
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = object(getValidationFromFields(FIELDS))
|
@ -0,0 +1,70 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useFormContext, useWatch } from 'react-hook-form'
|
||||
|
||||
import { GroupsTable } from 'client/components/Tables'
|
||||
import { SCHEMA } from './schema'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
import { Step } from 'client/utils'
|
||||
|
||||
export const STEP_ID = 'groups'
|
||||
|
||||
const Content = () => {
|
||||
const { setValue } = useFormContext()
|
||||
const groups = useWatch({ name: STEP_ID })
|
||||
|
||||
const selectedRowIds =
|
||||
groups?.reduce((res, id) => ({ ...res, [id]: true }), {}) || []
|
||||
|
||||
const handleSelectedRows = (rows) => {
|
||||
const newValue = rows?.map((row) => row.original.ID) || []
|
||||
|
||||
setValue(STEP_ID, newValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<GroupsTable
|
||||
disableGlobalSort
|
||||
displaySelectedRows
|
||||
pageSize={5}
|
||||
initialState={{ selectedRowIds }}
|
||||
onSelectedRowsChange={handleSelectedRows}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Step to select the Group.
|
||||
*
|
||||
* @param {object} app - VDC App resource
|
||||
* @returns {Step} Group step
|
||||
*/
|
||||
const GroupsStep = (app) => ({
|
||||
id: STEP_ID,
|
||||
label: T.SelectGroup,
|
||||
resolver: SCHEMA,
|
||||
content: (props) => Content({ ...props, app }),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
app: PropTypes.object,
|
||||
}
|
||||
|
||||
export default GroupsStep
|
@ -0,0 +1,19 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { ArraySchema, array, string } from 'yup'
|
||||
|
||||
/** @type {ArraySchema} Datastore table schema */
|
||||
export const SCHEMA = array(string().trim()).default(() => [])
|
@ -0,0 +1,35 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FIELDS } from './schema'
|
||||
|
||||
export const STEP_ID = 'clusters'
|
||||
|
||||
const ClusterTable = ({ zones, id }) => (
|
||||
<FormWithSchema id={id} cy={`${STEP_ID}`} fields={FIELDS(zones)} />
|
||||
)
|
||||
|
||||
ClusterTable.propTypes = {
|
||||
zones: PropTypes.arrayOf(PropTypes.object),
|
||||
id: PropTypes.string,
|
||||
}
|
||||
const ClusterResource = {
|
||||
id: STEP_ID,
|
||||
Content: ClusterTable,
|
||||
}
|
||||
|
||||
export default ClusterResource
|
@ -0,0 +1,61 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { ZONE_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ZonesSelect/schema'
|
||||
import { ClustersTable } from 'client/components/Tables'
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { ObjectSchema, array, object, string } from 'yup'
|
||||
|
||||
export const CLUSTER_FIELD_NAME = 'CLUSTER_Z'
|
||||
|
||||
/** @type {Field} Cluster field */
|
||||
const CLUSTER = (zoneId) => ({
|
||||
name: `${CLUSTER_FIELD_NAME}${zoneId}`,
|
||||
dependOf: `$resources.${ZONE_FIELD_NAME}`,
|
||||
htmlType: ([_, selectedZoneId = '0'] = []) =>
|
||||
zoneId !== selectedZoneId && INPUT_TYPES.HIDDEN,
|
||||
label: T.SelectClusters,
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: () => ClustersTable,
|
||||
singleSelect: false,
|
||||
zoneId: zoneId,
|
||||
validation: array(string().trim()).default(() => []),
|
||||
fieldProps: {
|
||||
preserveState: true,
|
||||
},
|
||||
grid: { md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = (zones = []) => {
|
||||
const fields = []
|
||||
|
||||
zones.forEach((zone) => {
|
||||
fields.push(CLUSTER(zone.ID))
|
||||
})
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (zones = []) =>
|
||||
object(getValidationFromFields(FIELDS(zones)))
|
@ -0,0 +1,41 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FIELDS } from './schema'
|
||||
|
||||
export const STEP_ID = 'datastores'
|
||||
|
||||
const DatastoreTable = ({ zones, id, cluster, zone }) => (
|
||||
<FormWithSchema
|
||||
id={id}
|
||||
cy={`${STEP_ID}`}
|
||||
fields={FIELDS(zones, cluster, zone)}
|
||||
/>
|
||||
)
|
||||
|
||||
DatastoreTable.propTypes = {
|
||||
zones: PropTypes.arrayOf(PropTypes.object),
|
||||
id: PropTypes.string,
|
||||
zone: PropTypes.arrayOf(PropTypes.object),
|
||||
cluster: PropTypes.arrayOf(PropTypes.object),
|
||||
}
|
||||
const DatastoreResource = {
|
||||
id: STEP_ID,
|
||||
Content: DatastoreTable,
|
||||
}
|
||||
|
||||
export default DatastoreResource
|
@ -0,0 +1,76 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { CLUSTER_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ClustersTable/schema'
|
||||
import { ZONE_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ZonesSelect/schema'
|
||||
import { DatastoresTable } from 'client/components/Tables'
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { ObjectSchema, array, object, string } from 'yup'
|
||||
|
||||
/** @type {Field} Cluster field */
|
||||
const DATASTORE = (zoneId) => ({
|
||||
name: `DATASTORE_Z${zoneId}`,
|
||||
dependOf: [
|
||||
`$resources.${ZONE_FIELD_NAME}`,
|
||||
`$resources.${CLUSTER_FIELD_NAME}${zoneId}`,
|
||||
],
|
||||
htmlType: ([selectedZoneId = '0'] = []) =>
|
||||
zoneId !== selectedZoneId && INPUT_TYPES.HIDDEN,
|
||||
label: T.SelectDatastores,
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: () => DatastoresTable,
|
||||
singleSelect: false,
|
||||
zoneId: zoneId,
|
||||
validation: array(string().trim()).default(() => []),
|
||||
fieldProps: {
|
||||
preserveState: true,
|
||||
|
||||
// The second parameter of the function filters the results that are found in dependOf
|
||||
filter: (data, [_, clusters]) =>
|
||||
clusters?.length
|
||||
? data.filter((item) => {
|
||||
const dataClusters = item.CLUSTERS.ID
|
||||
if (Array.isArray(dataClusters)) {
|
||||
return dataClusters.some((id) => !clusters.includes(id))
|
||||
} else {
|
||||
return !clusters.includes(dataClusters)
|
||||
}
|
||||
})
|
||||
: data,
|
||||
},
|
||||
grid: { md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = (zones = []) => {
|
||||
const fields = []
|
||||
|
||||
zones.forEach((zone) => {
|
||||
fields.push(DATASTORE(zone.ID))
|
||||
})
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (zones = []) =>
|
||||
object(getValidationFromFields(FIELDS(zones)))
|
@ -0,0 +1,35 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FIELDS } from './schema'
|
||||
|
||||
export const STEP_ID = 'hosts'
|
||||
|
||||
const HostTable = ({ zones, id }) => (
|
||||
<FormWithSchema id={id} cy={`${STEP_ID}`} fields={FIELDS(zones)} />
|
||||
)
|
||||
|
||||
HostTable.propTypes = {
|
||||
zones: PropTypes.arrayOf(PropTypes.object),
|
||||
id: PropTypes.string,
|
||||
}
|
||||
const HostResource = {
|
||||
id: STEP_ID,
|
||||
Content: HostTable,
|
||||
}
|
||||
|
||||
export default HostResource
|
@ -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 { CLUSTER_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ClustersTable/schema'
|
||||
import { ZONE_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ZonesSelect/schema'
|
||||
import { HostsTable } from 'client/components/Tables'
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { ObjectSchema, array, object, string } from 'yup'
|
||||
|
||||
/** @type {Field} Cluster field */
|
||||
const HOST = (zoneId) => ({
|
||||
name: `HOST_Z${zoneId}`,
|
||||
dependOf: [
|
||||
`$resources.${ZONE_FIELD_NAME}`,
|
||||
`$resources.${CLUSTER_FIELD_NAME}${zoneId}`,
|
||||
],
|
||||
htmlType: ([selectedZoneId = '0'] = []) =>
|
||||
zoneId !== selectedZoneId && INPUT_TYPES.HIDDEN,
|
||||
label: T.SelectHosts,
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: () => HostsTable,
|
||||
singleSelect: false,
|
||||
zoneId: zoneId,
|
||||
validation: array(string().trim()).default(() => []),
|
||||
fieldProps: {
|
||||
preserveState: true,
|
||||
// The second parameter of the function filters the results that are found in dependOf
|
||||
filter: (data, [_, clusters]) =>
|
||||
clusters?.length
|
||||
? data.filter((item) => {
|
||||
const dataClusters = item.CLUSTER_ID
|
||||
if (Array.isArray(dataClusters)) {
|
||||
return dataClusters.some((id) => !clusters.includes(id))
|
||||
} else {
|
||||
return !clusters.includes(dataClusters)
|
||||
}
|
||||
})
|
||||
: data,
|
||||
},
|
||||
grid: { md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = (zones = []) => {
|
||||
const fields = []
|
||||
|
||||
zones.forEach((zone) => {
|
||||
fields.push(HOST(zone.ID))
|
||||
})
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (zones = []) =>
|
||||
object(getValidationFromFields(FIELDS(zones)))
|
@ -0,0 +1,35 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import PropTypes from 'prop-types'
|
||||
import { FIELDS } from './schema'
|
||||
|
||||
export const STEP_ID = 'vnets'
|
||||
|
||||
const VnetTable = ({ zones, id }) => (
|
||||
<FormWithSchema id={id} cy={`${STEP_ID}`} fields={FIELDS(zones)} />
|
||||
)
|
||||
|
||||
VnetTable.propTypes = {
|
||||
zones: PropTypes.arrayOf(PropTypes.object),
|
||||
id: PropTypes.string,
|
||||
}
|
||||
const VnetResource = {
|
||||
id: STEP_ID,
|
||||
Content: VnetTable,
|
||||
}
|
||||
|
||||
export default VnetResource
|
@ -0,0 +1,77 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { CLUSTER_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ClustersTable/schema'
|
||||
import { ZONE_FIELD_NAME } from 'client/components/Forms/Vdc/CreateForm/Steps/Resources/ZonesSelect/schema'
|
||||
import { VNetworksTable } from 'client/components/Tables'
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, getValidationFromFields } from 'client/utils'
|
||||
import { ObjectSchema, array, object, string } from 'yup'
|
||||
|
||||
// const ZONE_FIELD_NAME = 'VNET_ZONE_ID'
|
||||
|
||||
/** @type {Field} Cluster field */
|
||||
const VNET = (zoneId) => ({
|
||||
name: `VNET_Z${zoneId}`,
|
||||
dependOf: [
|
||||
`$resources.${ZONE_FIELD_NAME}`,
|
||||
`$resources.${CLUSTER_FIELD_NAME}${zoneId}`,
|
||||
],
|
||||
htmlType: ([selectedZoneId = '0'] = []) =>
|
||||
zoneId !== selectedZoneId && INPUT_TYPES.HIDDEN,
|
||||
label: T.SelectVirtualNetworks,
|
||||
type: INPUT_TYPES.TABLE,
|
||||
Table: () => VNetworksTable,
|
||||
singleSelect: false,
|
||||
zoneId: zoneId,
|
||||
validation: array(string().trim()).default(() => []),
|
||||
fieldProps: {
|
||||
preserveState: true,
|
||||
// The second parameter of the function filters the results that are found in dependOf
|
||||
filter: (data, [_, clusters]) =>
|
||||
clusters?.length
|
||||
? data.filter((item) => {
|
||||
const dataClusters = item.CLUSTERS.ID
|
||||
if (Array.isArray(dataClusters)) {
|
||||
return dataClusters.some((id) => !clusters.includes(id))
|
||||
} else {
|
||||
return !clusters.includes(dataClusters)
|
||||
}
|
||||
})
|
||||
: data,
|
||||
},
|
||||
grid: { md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = (zones = []) => {
|
||||
const fields = []
|
||||
|
||||
zones.forEach((zone) => {
|
||||
fields.push(VNET(zone.ID))
|
||||
})
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (zones = []) =>
|
||||
object(getValidationFromFields(FIELDS(zones)))
|
@ -0,0 +1,38 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 FormWithSchema from 'client/components/Forms/FormWithSchema'
|
||||
import PropTypes from 'prop-types'
|
||||
import { JSXElementConstructor } from 'react'
|
||||
import { FIELDS } from './schema'
|
||||
|
||||
export const STEP_ID = 'zone'
|
||||
|
||||
/**
|
||||
* @param {object} props - props
|
||||
* @param {Array} props.zones - zones
|
||||
* @param {string} props.id - form id
|
||||
* @returns {JSXElementConstructor} Provision App
|
||||
*/
|
||||
const ZoneSelect = ({ zones, id }) => (
|
||||
<FormWithSchema id={id} cy={`${STEP_ID}`} fields={FIELDS(zones)} />
|
||||
)
|
||||
|
||||
ZoneSelect.propTypes = {
|
||||
zones: PropTypes.arrayOf(PropTypes.object),
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
export default ZoneSelect
|
@ -0,0 +1,47 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { INPUT_TYPES, T } from 'client/constants'
|
||||
import { Field, arrayToOptions, getValidationFromFields } from 'client/utils'
|
||||
import { ObjectSchema, object, string } from 'yup'
|
||||
|
||||
export const ZONE_FIELD_NAME = 'ZONE_ID'
|
||||
|
||||
/** @type {Field} Zone Id field */
|
||||
const ZONE = (zones) => ({
|
||||
name: ZONE_FIELD_NAME,
|
||||
label: T.Zone,
|
||||
type: INPUT_TYPES.SELECT,
|
||||
values: arrayToOptions(zones, {
|
||||
addEmpty: false,
|
||||
getText: (zone) => `Zone ${zone.ID} - ${zone.NAME}`,
|
||||
getValue: (zone) => zone.ID,
|
||||
}),
|
||||
validation: string().default(() => '0'),
|
||||
grid: { xs: 12, md: 12 },
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {Field[]} Fields
|
||||
*/
|
||||
export const FIELDS = (zones = []) => [ZONE(zones)]
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - zone objects
|
||||
* @returns {ObjectSchema} Schema
|
||||
*/
|
||||
export const SCHEMA = (zones = []) =>
|
||||
object(getValidationFromFields(FIELDS(zones)))
|
@ -0,0 +1,103 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useViews } from 'client/features/Auth'
|
||||
|
||||
import Cluster from './ClustersTable'
|
||||
import Datastore from './DatastoresTable'
|
||||
import Host from './HostsTable'
|
||||
import Vnets from './VnetsTable'
|
||||
import ZoneSelect from './ZonesSelect'
|
||||
|
||||
import { SCHEMA } from './schema'
|
||||
|
||||
import { RESOURCE_NAMES, T } from 'client/constants'
|
||||
import { getActionsAvailable as getSectionsAvailable } from 'client/models/Helper'
|
||||
|
||||
/**
|
||||
* @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 {function(FieldErrors):boolean} [getError] - Returns `true` if the tab contains an error in form
|
||||
*/
|
||||
|
||||
export const STEP_ID = 'resources'
|
||||
|
||||
/** @type {TabType[]} */
|
||||
export const RESOURCES = [Cluster, Datastore, Host, Vnets]
|
||||
|
||||
const Content = (zones) => {
|
||||
const {
|
||||
formState: { errors },
|
||||
control,
|
||||
} = useFormContext()
|
||||
const { view, getResourceView } = useViews()
|
||||
|
||||
const sectionsAvailable = useMemo(() => {
|
||||
const resource = RESOURCE_NAMES.VDC
|
||||
const dialog = getResourceView(resource)?.dialogs?.create_dialog
|
||||
|
||||
return getSectionsAvailable(dialog)
|
||||
}, [view])
|
||||
|
||||
const totalErrors = Object.keys(errors[STEP_ID] ?? {}).length
|
||||
|
||||
const resources = useMemo(
|
||||
() =>
|
||||
RESOURCES.filter(({ id }) => sectionsAvailable.includes(id)).map(
|
||||
({ Content: TabContent, id }) => (
|
||||
<TabContent key={id} id={STEP_ID} {...{ zones, control }} />
|
||||
)
|
||||
),
|
||||
[totalErrors, view, control]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<ZoneSelect zones={zones} />
|
||||
{resources}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional configuration about VDC.
|
||||
*
|
||||
* @param {Array[object]} zones - Zones available
|
||||
* @returns {object} Optional configuration step
|
||||
*/
|
||||
const Resources = (zones = []) => ({
|
||||
id: STEP_ID,
|
||||
label: T.Resources,
|
||||
resolver: (formData) => SCHEMA(zones),
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: () => Content(zones),
|
||||
})
|
||||
|
||||
Content.propTypes = {
|
||||
data: PropTypes.any,
|
||||
setFormData: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Resources
|
@ -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 { object, ObjectSchema } from 'yup'
|
||||
|
||||
import { SCHEMA as CLUSTER_SCHEMA } from './ClustersTable/schema'
|
||||
import { SCHEMA as DATASTORE_SCHEMA } from './DatastoresTable/schema'
|
||||
import { SCHEMA as HOST_SCHEMA } from './HostsTable/schema'
|
||||
import { SCHEMA as NETWORK_SCHEMA } from './VnetsTable/schema'
|
||||
import { SCHEMA as ZONES_SCHEMA } from './ZonesSelect/schema'
|
||||
|
||||
/**
|
||||
* @param {Array[Object]} zones - Zones objects
|
||||
* @returns {ObjectSchema} Extra configuration schema
|
||||
*/
|
||||
export const SCHEMA = (zones = []) =>
|
||||
object()
|
||||
.concat(ZONES_SCHEMA(zones))
|
||||
.concat(CLUSTER_SCHEMA(zones))
|
||||
.concat(DATASTORE_SCHEMA(zones))
|
||||
.concat(HOST_SCHEMA(zones))
|
||||
.concat(NETWORK_SCHEMA(zones))
|
@ -0,0 +1,141 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { jsonToXml } from 'client/models/Helper'
|
||||
import { createSteps, getUnknownAttributes } from 'client/utils'
|
||||
import CustomAttributes, { STEP_ID as CUSTOM_ID } from './CustomVariables'
|
||||
import General, { STEP_ID as GENERAL_ID } from './General'
|
||||
import Groups, { STEP_ID as GROUPS_ID } from './GroupsTable'
|
||||
import Resources, { STEP_ID as RESOURCES_ID } from './Resources'
|
||||
|
||||
const parseResources = ({ resources, key, newKey }) => {
|
||||
const regex = new RegExp(`^${key}_Z\\d+$`)
|
||||
|
||||
return Object.keys(resources)
|
||||
.filter((internalKey) => regex.test(internalKey))
|
||||
.map((internalKey) => ({
|
||||
zone_id: internalKey.match(/\d+$/)[0],
|
||||
[newKey]: resources[internalKey],
|
||||
}))
|
||||
}
|
||||
|
||||
const parseInitialResources = (resource = [], filterBy, key) => {
|
||||
const data = Array.isArray(resource) ? resource : [resource]
|
||||
|
||||
return data.reduce((acc, obj) => {
|
||||
const zoneId = obj[filterBy]
|
||||
const name = `${key}_Z${zoneId}`
|
||||
|
||||
if (!acc[name]) {
|
||||
acc[name] = []
|
||||
}
|
||||
|
||||
acc[name].push(obj[`${key}_ID`])
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
const Steps = createSteps([General, Groups, Resources, CustomAttributes], {
|
||||
transformInitialValue: (vdcTemplate, schema) => {
|
||||
const groups = vdcTemplate?.GROUPS?.ID
|
||||
const groupsData = groups ? (Array.isArray(groups) ? groups : [groups]) : []
|
||||
|
||||
const datastores = parseInitialResources(
|
||||
vdcTemplate?.DATASTORES?.DATASTORE,
|
||||
'ZONE_ID',
|
||||
'DATASTORE'
|
||||
)
|
||||
|
||||
const knownTemplate = schema.cast(
|
||||
{
|
||||
[GENERAL_ID]: { ...vdcTemplate, ...vdcTemplate.TEMPLATE },
|
||||
[GROUPS_ID]: groupsData,
|
||||
[RESOURCES_ID]: {
|
||||
...parseInitialResources(
|
||||
vdcTemplate?.CLUSTERS?.CLUSTER,
|
||||
'ZONE_ID',
|
||||
'CLUSTER'
|
||||
),
|
||||
...datastores,
|
||||
...parseInitialResources(vdcTemplate?.HOSTS?.HOST, 'ZONE_ID', 'HOST'),
|
||||
...parseInitialResources(vdcTemplate?.VNETS?.VNET, 'ZONE_ID', 'VNET'),
|
||||
},
|
||||
},
|
||||
{
|
||||
stripUnknown: true,
|
||||
}
|
||||
)
|
||||
|
||||
const knownAttributes = {
|
||||
...knownTemplate[GENERAL_ID],
|
||||
}
|
||||
|
||||
// Set the unknown attributes to the custom variables section
|
||||
knownTemplate[CUSTOM_ID] = getUnknownAttributes(
|
||||
vdcTemplate?.TEMPLATE,
|
||||
knownAttributes
|
||||
)
|
||||
|
||||
return knownTemplate
|
||||
},
|
||||
transformBeforeSubmit: (formData) => {
|
||||
const {
|
||||
[GENERAL_ID]: general = {},
|
||||
[CUSTOM_ID]: customAttributes = {},
|
||||
[GROUPS_ID]: groups = [],
|
||||
[RESOURCES_ID]: resources = {},
|
||||
} = formData ?? {}
|
||||
|
||||
const rtn = {
|
||||
template: jsonToXml({
|
||||
...general,
|
||||
...customAttributes,
|
||||
}),
|
||||
}
|
||||
|
||||
if (resources?.ZONE_ID) {
|
||||
rtn.groups = groups
|
||||
|
||||
rtn.clusters = parseResources({
|
||||
resources,
|
||||
key: 'CLUSTER',
|
||||
newKey: 'cluster_id',
|
||||
})
|
||||
|
||||
rtn.datastores = parseResources({
|
||||
resources,
|
||||
key: 'DATASTORE',
|
||||
newKey: 'ds_id',
|
||||
})
|
||||
|
||||
rtn.hosts = parseResources({
|
||||
resources,
|
||||
key: 'HOST',
|
||||
newKey: 'host_id',
|
||||
})
|
||||
|
||||
rtn.vnets = parseResources({
|
||||
resources,
|
||||
key: 'VNET',
|
||||
newKey: 'vnet_id',
|
||||
})
|
||||
}
|
||||
|
||||
return rtn
|
||||
},
|
||||
})
|
||||
|
||||
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 './Steps'
|
27
src/fireedge/src/client/components/Forms/Vdc/index.js
Normal file
27
src/fireedge/src/client/components/Forms/Vdc/index.js
Normal file
@ -0,0 +1,27 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { AsyncLoadForm, ConfigurationProps } from 'client/components/HOC'
|
||||
import { CreateStepsCallback } from 'client/utils/schema'
|
||||
|
||||
/**
|
||||
* @param {ConfigurationProps} configProps - Configuration
|
||||
* @returns {ReactElement|CreateStepsCallback} Asynchronous loaded form
|
||||
*/
|
||||
const CreateForm = (configProps) =>
|
||||
AsyncLoadForm({ formPath: 'Vdc/CreateForm' }, configProps)
|
||||
|
||||
export { CreateForm }
|
@ -35,6 +35,8 @@ const ClustersTable = (props) => {
|
||||
searchProps = {},
|
||||
useQuery = useGetClustersQuery,
|
||||
datastoreId,
|
||||
vdcClusters,
|
||||
zoneId,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
@ -45,18 +47,23 @@ const ClustersTable = (props) => {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery(undefined, {
|
||||
selectFromResult: (result) => ({
|
||||
...result,
|
||||
data: result?.data?.filter((cluster) => {
|
||||
if (datastoreId) {
|
||||
return cluster?.DATASTORES?.ID?.includes(datastoreId)
|
||||
}
|
||||
} = useQuery(
|
||||
{ zone: zoneId },
|
||||
{
|
||||
selectFromResult: (result) => ({
|
||||
...result,
|
||||
data: result?.data?.filter((cluster) => {
|
||||
if (datastoreId) {
|
||||
return cluster?.DATASTORES?.ID?.includes(datastoreId)
|
||||
} else if (vdcClusters) {
|
||||
return vdcClusters.includes(cluster.ID)
|
||||
}
|
||||
|
||||
return true
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
|
@ -13,15 +13,20 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetDatastoresQuery } from 'client/features/OneApi/datastore'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import DatastoreColumns from 'client/components/Tables/Datastores/columns'
|
||||
import DatastoreRow from 'client/components/Tables/Datastores/row'
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import {
|
||||
areArraysEqual,
|
||||
sortStateTables,
|
||||
} from 'client/components/Tables/Enhanced/Utils/DataTableUtils'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
const DEFAULT_DATA_CY = 'datastores'
|
||||
|
||||
@ -33,17 +38,66 @@ const DatastoresTable = (props) => {
|
||||
const {
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
filter = (dataToFilter) => dataToFilter,
|
||||
useQuery = useGetDatastoresQuery,
|
||||
vdcDatastores,
|
||||
zoneId,
|
||||
dependOf,
|
||||
filter,
|
||||
reSelectRows,
|
||||
value,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch } = useGetDatastoresQuery()
|
||||
|
||||
// Filter data with input funcion called "filter"
|
||||
const filteredData = filter(data)
|
||||
let values
|
||||
|
||||
if (typeof filter === 'function') {
|
||||
const { watch } = useFormContext()
|
||||
|
||||
const getDataForDepend = useCallback(
|
||||
(n) => {
|
||||
let dependName = n
|
||||
// removes character '$'
|
||||
if (n.startsWith('$')) dependName = n.slice(1)
|
||||
|
||||
return watch(dependName)
|
||||
},
|
||||
[dependOf]
|
||||
)
|
||||
|
||||
const valuesOfDependField = () => {
|
||||
if (!dependOf) return null
|
||||
|
||||
return Array.isArray(dependOf)
|
||||
? dependOf.map(getDataForDepend)
|
||||
: getDataForDepend(dependOf)
|
||||
}
|
||||
values = valuesOfDependField()
|
||||
}
|
||||
|
||||
const {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery(
|
||||
{ zone: zoneId },
|
||||
{
|
||||
selectFromResult: (result) => {
|
||||
const rtn = { ...result }
|
||||
if (vdcDatastores) {
|
||||
const dataRequest = result.data ?? []
|
||||
rtn.data = dataRequest.filter((ds) => vdcDatastores.includes(ds?.ID))
|
||||
} else if (typeof filter === 'function') {
|
||||
rtn.data = filter(result.data ?? [], values ?? [])
|
||||
}
|
||||
|
||||
return rtn
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
@ -54,16 +108,44 @@ const DatastoresTable = (props) => {
|
||||
[view]
|
||||
)
|
||||
|
||||
const [stateData, setStateData] = useState(data)
|
||||
|
||||
const updateSelectedRows = () => {
|
||||
if (Array.isArray(values) && typeof reSelectRows === 'function') {
|
||||
const datastores = data
|
||||
.filter((dataObject) => value.includes(dataObject.ID))
|
||||
.map((dataObject) => dataObject.ID)
|
||||
|
||||
const sortedDatastores = sortStateTables(datastores)
|
||||
const sortedValue = sortStateTables(value)
|
||||
if (!areArraysEqual(sortedValue, sortedDatastores)) {
|
||||
reSelectRows(sortedDatastores)
|
||||
setStateData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateSelectedRows()
|
||||
}, [dependOf])
|
||||
|
||||
useEffect(() => {
|
||||
if (JSON.stringify(data) !== JSON.stringify(stateData)) {
|
||||
updateSelectedRows()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={useMemo(() => filteredData, [filteredData])}
|
||||
data={useMemo(() => data, [data])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={DatastoreRow}
|
||||
dataDepend={values}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
|
@ -0,0 +1,42 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Sorter data.
|
||||
*
|
||||
* @param {Array | any} value - data to order.
|
||||
* @returns {Array|any} data sorted.
|
||||
*/
|
||||
export const sortStateTables = (value) => {
|
||||
if (!Array.isArray(value)) return value
|
||||
|
||||
return value.sort((a, b) => parseInt(a, 10) - parseInt(b, 10))
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the arrays are equals.
|
||||
*
|
||||
* @param {Array} array1 - array 1.
|
||||
* @param {Array} array2 - array 2.
|
||||
* @returns {boolean} areEquals.
|
||||
*/
|
||||
export const areArraysEqual = (array1 = [], array2 = []) => {
|
||||
if (array1.length !== array2.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
return array1.every((element, index) => element === array2[index])
|
||||
}
|
@ -76,7 +76,7 @@ const GlobalActions = ({
|
||||
{!singleSelect && !disableRowSelect && (
|
||||
<Checkbox
|
||||
{...getToggleAllPageRowsSelectedProps()}
|
||||
title={Tr(T.ToggleAllCurrentPageRowsSelected)}
|
||||
title={Tr(T.ToggleAllSelectedCardsCurrentPage)}
|
||||
indeterminate={getToggleAllRowsSelectedProps().indeterminate}
|
||||
color="secondary"
|
||||
/>
|
||||
|
@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { JSXElementConstructor } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { JSXElementConstructor } from 'react'
|
||||
|
||||
import { TableProps, Row } from 'react-table'
|
||||
import { styled, Chip, Alert, Button, alertClasses } from '@mui/material'
|
||||
import { Alert, Button, Chip, alertClasses, styled } from '@mui/material'
|
||||
import { Row, TableProps } from 'react-table'
|
||||
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
@ -53,7 +53,6 @@ const GlobalSelectedRows = ({
|
||||
toggleAllRowsSelected,
|
||||
state: { selectedRowIds },
|
||||
} = useTableProps
|
||||
|
||||
const selectedRows = preFilteredRows.filter((row) => !!selectedRowIds[row.id])
|
||||
const numberOfRowSelected = selectedRows.length
|
||||
const allSelected = numberOfRowSelected === preFilteredRows.length
|
||||
@ -88,6 +87,7 @@ const GlobalSelectedRows = ({
|
||||
key={row.id}
|
||||
label={row.original?.NAME ?? row.id}
|
||||
onDelete={() => row.toggleRowSelected(false)}
|
||||
data-cy="itemSelected"
|
||||
{...(gotoRowPage && { onClick: () => gotoRowPage(row) })}
|
||||
/>
|
||||
))}
|
||||
|
@ -15,7 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
/* eslint-disable jsdoc/require-jsdoc */
|
||||
import PropTypes from 'prop-types'
|
||||
import { useMemo } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { Alert, Box, Chip, Grid } from '@mui/material'
|
||||
import clsx from 'clsx'
|
||||
@ -74,6 +74,7 @@ const EnhancedTable = ({
|
||||
searchProps = {},
|
||||
noDataMessage,
|
||||
messages = [],
|
||||
dataDepend,
|
||||
}) => {
|
||||
const styles = EnhancedTableStyles()
|
||||
|
||||
@ -93,6 +94,20 @@ const EnhancedTable = ({
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const stateReducer = (newState, action, prevState) => {
|
||||
switch (action.type) {
|
||||
case 'RELOAD_STATE': {
|
||||
const updatedState = {
|
||||
...prevState,
|
||||
selectedRowIds: action.value,
|
||||
}
|
||||
|
||||
return updatedState
|
||||
}
|
||||
default:
|
||||
return newState
|
||||
}
|
||||
}
|
||||
|
||||
const useTableProps = useTable(
|
||||
{
|
||||
@ -115,6 +130,7 @@ const EnhancedTable = ({
|
||||
autoResetGlobalFilter: false,
|
||||
// -------------------------------------
|
||||
initialState: { pageSize, ...initialState },
|
||||
stateReducer,
|
||||
},
|
||||
useGlobalFilter,
|
||||
useFilters,
|
||||
@ -137,8 +153,11 @@ const EnhancedTable = ({
|
||||
setSortBy,
|
||||
setGlobalFilter,
|
||||
state,
|
||||
toggleRowSelected: propsToggleRow,
|
||||
} = useTableProps
|
||||
|
||||
const [stateData, setStateData] = useState(data)
|
||||
|
||||
const gotoRowPage = async (row) => {
|
||||
const pageIdx = Math.floor(row.index / pageSize)
|
||||
|
||||
@ -158,6 +177,25 @@ const EnhancedTable = ({
|
||||
.filter(Boolean)
|
||||
}, [state.selectedRowIds])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
dataDepend &&
|
||||
page.length &&
|
||||
initialState?.selectedRowIds &&
|
||||
JSON.stringify(data) !== JSON.stringify(stateData)
|
||||
) {
|
||||
const initialKeys = Object.keys(initialState.selectedRowIds)
|
||||
page.forEach((row) => {
|
||||
if (!initialKeys.includes(row?.id) && row?.isSelected) {
|
||||
propsToggleRow(row?.id, false)
|
||||
} else if (row?.isSelected) {
|
||||
propsToggleRow(row?.id, false)
|
||||
}
|
||||
})
|
||||
setStateData(data)
|
||||
}
|
||||
}, [dataDepend])
|
||||
|
||||
useMountedLayoutEffect(() => {
|
||||
onSelectedRowsChange?.(
|
||||
selectedRows.map((row) => ({ ...row, gotoPage: () => gotoRowPage(row) }))
|
||||
@ -387,6 +425,7 @@ EnhancedTable.propTypes = {
|
||||
PropTypes.bool,
|
||||
]),
|
||||
messages: PropTypes.array,
|
||||
dataDepend: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
||||
}
|
||||
|
||||
export * from 'client/components/Tables/Enhanced/Utils'
|
||||
|
@ -30,12 +30,27 @@ const DEFAULT_DATA_CY = 'groups'
|
||||
* @returns {ReactElement} Groups table
|
||||
*/
|
||||
const GroupsTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
const { rootProps = {}, searchProps = {}, vdcGroups, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch } = useGetGroupsQuery()
|
||||
const {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useGetGroupsQuery(undefined, {
|
||||
selectFromResult: (result) => ({
|
||||
...result,
|
||||
data: result?.data?.filter((group) => {
|
||||
if (vdcGroups) {
|
||||
return vdcGroups.includes(group.ID)
|
||||
}
|
||||
|
||||
return true
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
|
@ -13,15 +13,20 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import {
|
||||
areArraysEqual,
|
||||
sortStateTables,
|
||||
} from 'client/components/Tables/Enhanced/Utils/DataTableUtils'
|
||||
import HostColumns from 'client/components/Tables/Hosts/columns'
|
||||
import HostRow from 'client/components/Tables/Hosts/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
const DEFAULT_DATA_CY = 'hosts'
|
||||
|
||||
@ -34,13 +39,65 @@ const HostsTable = (props) => {
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
useQuery = useGetHostsQuery,
|
||||
vdcHosts,
|
||||
zoneId,
|
||||
dependOf,
|
||||
filter,
|
||||
reSelectRows,
|
||||
value,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch } = useQuery()
|
||||
|
||||
let values
|
||||
|
||||
if (typeof filter === 'function') {
|
||||
const { watch } = useFormContext()
|
||||
|
||||
const getDataForDepend = useCallback(
|
||||
(n) => {
|
||||
let dependName = n
|
||||
// removes character '$'
|
||||
if (n.startsWith('$')) dependName = n.slice(1)
|
||||
|
||||
return watch(dependName)
|
||||
},
|
||||
[dependOf]
|
||||
)
|
||||
|
||||
const valuesOfDependField = () => {
|
||||
if (!dependOf) return null
|
||||
|
||||
return Array.isArray(dependOf)
|
||||
? dependOf.map(getDataForDepend)
|
||||
: getDataForDepend(dependOf)
|
||||
}
|
||||
values = valuesOfDependField()
|
||||
}
|
||||
|
||||
const {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery(
|
||||
{ zone: zoneId },
|
||||
{
|
||||
selectFromResult: (result) => {
|
||||
const rtn = { ...result }
|
||||
if (vdcHosts) {
|
||||
const dataRequest = result.data ?? []
|
||||
rtn.data = dataRequest.filter((host) => vdcHosts.includes(host?.ID))
|
||||
} else if (typeof filter === 'function') {
|
||||
rtn.data = filter(result.data ?? [], values ?? [])
|
||||
}
|
||||
|
||||
return rtn
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
@ -51,6 +108,33 @@ const HostsTable = (props) => {
|
||||
[view]
|
||||
)
|
||||
|
||||
const [stateData, setStateData] = useState(data)
|
||||
|
||||
const updateSelectedRows = () => {
|
||||
if (Array.isArray(values) && typeof reSelectRows === 'function') {
|
||||
const datastores = data
|
||||
.filter((dataObject) => value.includes(dataObject.ID))
|
||||
.map((dataObject) => dataObject.ID)
|
||||
|
||||
const sortedDatastores = sortStateTables(datastores)
|
||||
const sortedValue = sortStateTables(value)
|
||||
if (!areArraysEqual(sortedValue, sortedDatastores)) {
|
||||
reSelectRows(sortedDatastores)
|
||||
setStateData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateSelectedRows()
|
||||
}, [dependOf])
|
||||
|
||||
useEffect(() => {
|
||||
if (JSON.stringify(data) !== JSON.stringify(stateData)) {
|
||||
updateSelectedRows()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
@ -61,6 +145,7 @@ const HostsTable = (props) => {
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={HostRow}
|
||||
dataDepend={values}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
|
@ -222,7 +222,6 @@ const Actions = () => {
|
||||
form: RecoverForm,
|
||||
onSubmit: (rows) => async (formData) => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
console.log(`RECOVER ${ids}`, formData)
|
||||
await Promise.all(
|
||||
ids.map((id) => recover({ id, ...formData }))
|
||||
)
|
||||
|
@ -13,15 +13,20 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { useMemo, ReactElement } from 'react'
|
||||
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetVNetworksQuery } from 'client/features/OneApi/network'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import {
|
||||
areArraysEqual,
|
||||
sortStateTables,
|
||||
} from 'client/components/Tables/Enhanced/Utils/DataTableUtils'
|
||||
import VNetworkColumns from 'client/components/Tables/VNetworks/columns'
|
||||
import VNetworkRow from 'client/components/Tables/VNetworks/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
import { useFormContext } from 'react-hook-form'
|
||||
|
||||
const DEFAULT_DATA_CY = 'vnets'
|
||||
|
||||
@ -34,13 +39,65 @@ const VNetworksTable = (props) => {
|
||||
rootProps = {},
|
||||
searchProps = {},
|
||||
useQuery = useGetVNetworksQuery,
|
||||
vdcVnets,
|
||||
zoneId,
|
||||
dependOf,
|
||||
filter,
|
||||
reSelectRows,
|
||||
value,
|
||||
...rest
|
||||
} = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch } = useQuery()
|
||||
|
||||
let values
|
||||
|
||||
if (typeof filter === 'function') {
|
||||
const { watch } = useFormContext()
|
||||
|
||||
const getDataForDepend = useCallback(
|
||||
(n) => {
|
||||
let dependName = n
|
||||
// removes character '$'
|
||||
if (n.startsWith('$')) dependName = n.slice(1)
|
||||
|
||||
return watch(dependName)
|
||||
},
|
||||
[dependOf]
|
||||
)
|
||||
|
||||
const valuesOfDependField = () => {
|
||||
if (!dependOf) return null
|
||||
|
||||
return Array.isArray(dependOf)
|
||||
? dependOf.map(getDataForDepend)
|
||||
: getDataForDepend(dependOf)
|
||||
}
|
||||
values = valuesOfDependField()
|
||||
}
|
||||
|
||||
const {
|
||||
data = [],
|
||||
isFetching,
|
||||
refetch,
|
||||
} = useQuery(
|
||||
{ zone: zoneId },
|
||||
{
|
||||
selectFromResult: (result) => {
|
||||
const rtn = { ...result }
|
||||
if (vdcVnets) {
|
||||
const dataRequest = result.data ?? []
|
||||
rtn.data = dataRequest.filter((vnet) => vdcVnets.includes(vnet?.ID))
|
||||
} else if (typeof filter === 'function') {
|
||||
rtn.data = filter(result.data ?? [], values ?? [])
|
||||
}
|
||||
|
||||
return rtn
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
@ -51,6 +108,34 @@ const VNetworksTable = (props) => {
|
||||
[view]
|
||||
)
|
||||
|
||||
const [stateData, setStateData] = useState(data)
|
||||
|
||||
const updateSelectedRows = () => {
|
||||
if (Array.isArray(values) && typeof reSelectRows === 'function') {
|
||||
const datastores = data
|
||||
.filter((dataObject) => value.includes(dataObject.ID))
|
||||
.map((dataObject) => dataObject.ID)
|
||||
|
||||
const sortedDatastores = sortStateTables(datastores)
|
||||
const sortedValue = sortStateTables(value)
|
||||
|
||||
if (!areArraysEqual(sortedValue, sortedDatastores)) {
|
||||
reSelectRows(sortedDatastores)
|
||||
setStateData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateSelectedRows()
|
||||
}, [dependOf])
|
||||
|
||||
useEffect(() => {
|
||||
if (JSON.stringify(data) !== JSON.stringify(stateData)) {
|
||||
updateSelectedRows()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
@ -61,6 +146,7 @@ const VNetworksTable = (props) => {
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={VNetworkRow}
|
||||
dataDepend={values}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
|
@ -0,0 +1,120 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { Typography } from '@mui/material'
|
||||
import { AddCircledOutline, Trash } from 'iconoir-react'
|
||||
import { useMemo } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useRemoveVDCMutation } from 'client/features/OneApi/vdc'
|
||||
|
||||
import {
|
||||
createActions,
|
||||
GlobalAction,
|
||||
} from 'client/components/Tables/Enhanced/Utils'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { RESOURCE_NAMES, T, VDC_ACTIONS } from 'client/constants'
|
||||
|
||||
const ListVDCNames = ({ rows = [] }) =>
|
||||
rows?.map?.(({ id, original }) => {
|
||||
const { ID, NAME } = original
|
||||
|
||||
return (
|
||||
<Typography
|
||||
key={`vdc-${id}`}
|
||||
variant="inherit"
|
||||
component="span"
|
||||
display="block"
|
||||
>
|
||||
{`#${ID} ${NAME}`}
|
||||
</Typography>
|
||||
)
|
||||
})
|
||||
|
||||
const MessageToConfirmAction = (rows, description) => (
|
||||
<>
|
||||
<ListVDCNames rows={rows} />
|
||||
{description && <Translate word={description} />}
|
||||
<Translate word={T.DoYouWantProceed} />
|
||||
</>
|
||||
)
|
||||
|
||||
MessageToConfirmAction.displayName = 'MessageToConfirmAction'
|
||||
|
||||
/**
|
||||
* Generates the actions to operate resources on VM Template table.
|
||||
*
|
||||
* @returns {GlobalAction} - Actions
|
||||
*/
|
||||
const Actions = () => {
|
||||
const history = useHistory()
|
||||
const { view, getResourceView } = useViews()
|
||||
const [remove] = useRemoveVDCMutation()
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
createActions({
|
||||
filters: getResourceView(RESOURCE_NAMES.VDC)?.actions,
|
||||
actions: [
|
||||
{
|
||||
accessor: VDC_ACTIONS.CREATE_DIALOG,
|
||||
tooltip: T.Create,
|
||||
icon: AddCircledOutline,
|
||||
action: () => history.push(PATH.SYSTEM.VDCS.CREATE),
|
||||
},
|
||||
{
|
||||
accessor: VDC_ACTIONS.UPDATE_DIALOG,
|
||||
label: T.Update,
|
||||
tooltip: T.Update,
|
||||
selected: { max: 1 },
|
||||
dataCy: `vdc_${VDC_ACTIONS.UPDATE_DIALOG}`,
|
||||
color: 'secondary',
|
||||
action: (rows) => {
|
||||
const vdcTemplate = rows?.[0]?.original ?? {}
|
||||
history.push(PATH.SYSTEM.VDCS.CREATE, vdcTemplate)
|
||||
},
|
||||
},
|
||||
{
|
||||
accessor: VDC_ACTIONS.DELETE,
|
||||
tooltip: T.Delete,
|
||||
icon: Trash,
|
||||
color: 'error',
|
||||
selected: { min: 1 },
|
||||
dataCy: `vdc_${VDC_ACTIONS.DELETE}`,
|
||||
options: [
|
||||
{
|
||||
isConfirmDialog: true,
|
||||
dialogProps: {
|
||||
title: T.Delete,
|
||||
dataCy: `modal-${VDC_ACTIONS.DELETE}`,
|
||||
children: MessageToConfirmAction,
|
||||
},
|
||||
onSubmit: (rows) => async () => {
|
||||
const ids = rows?.map?.(({ original }) => original?.ID)
|
||||
await Promise.all(ids.map((id) => remove({ id })))
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
}
|
||||
|
||||
export default Actions
|
@ -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 { Column } from 'react-table'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
|
||||
/** @type {Column[]} VM Template columns */
|
||||
const COLUMNS = [
|
||||
{ Header: T.ID, id: 'id', accessor: 'ID', sortType: 'number' },
|
||||
{ Header: T.Name, id: 'name', accessor: 'NAME' },
|
||||
{
|
||||
Header: T.Label,
|
||||
id: 'label',
|
||||
accessor: 'TEMPLATE.LABELS',
|
||||
filter: 'includesSome',
|
||||
},
|
||||
]
|
||||
|
||||
COLUMNS.noFilterIds = ['id', 'name', 'label']
|
||||
|
||||
export default COLUMNS
|
@ -0,0 +1,67 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { useMemo, ReactElement } from 'react'
|
||||
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetVDCsQuery } from 'client/features/OneApi/vdc'
|
||||
|
||||
import EnhancedTable, { createColumns } from 'client/components/Tables/Enhanced'
|
||||
import VDCColumns from 'client/components/Tables/VirtualDataCenters/columns'
|
||||
import VDCRow from 'client/components/Tables/VirtualDataCenters/row'
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
|
||||
const DEFAULT_DATA_CY = 'vdcs'
|
||||
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @returns {ReactElement} VM Templates table
|
||||
*/
|
||||
const VDCsTable = (props) => {
|
||||
const { rootProps = {}, searchProps = {}, ...rest } = props ?? {}
|
||||
rootProps['data-cy'] ??= DEFAULT_DATA_CY
|
||||
searchProps['data-cy'] ??= `search-${DEFAULT_DATA_CY}`
|
||||
|
||||
const { view, getResourceView } = useViews()
|
||||
const { data = [], isFetching, refetch } = useGetVDCsQuery()
|
||||
|
||||
const columns = useMemo(
|
||||
() =>
|
||||
createColumns({
|
||||
filters: getResourceView(RESOURCE_NAMES.VDC)?.filters,
|
||||
columns: VDCColumns,
|
||||
}),
|
||||
[view]
|
||||
)
|
||||
|
||||
return (
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={useMemo(() => data, [data])}
|
||||
rootProps={rootProps}
|
||||
searchProps={searchProps}
|
||||
refetch={refetch}
|
||||
isLoading={isFetching}
|
||||
getRowId={(row) => String(row.ID)}
|
||||
RowComponent={VDCRow}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
VDCsTable.propTypes = { ...EnhancedTable.propTypes }
|
||||
VDCsTable.displayName = 'VDCsTable'
|
||||
|
||||
export default VDCsTable
|
@ -0,0 +1,69 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { memo, useMemo, useCallback } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import vdcApi, { useUpdateVDCMutation } from 'client/features/OneApi/vdc'
|
||||
import { VirtualDataCenterCard } from 'client/components/Cards'
|
||||
import { jsonToXml } from 'client/models/Helper'
|
||||
|
||||
const Row = memo(
|
||||
({ original, value, onClickLabel, ...props }) => {
|
||||
const [update] = useUpdateVDCMutation()
|
||||
|
||||
const state = vdcApi.endpoints.getVDCs.useQueryState(undefined, {
|
||||
selectFromResult: ({ data = [] }) =>
|
||||
data.find((vdc) => +vdc.ID === +original.ID),
|
||||
})
|
||||
|
||||
const memoVdc = useMemo(() => state ?? original, [state, original])
|
||||
|
||||
const handleDeleteLabel = useCallback(
|
||||
(label) => {
|
||||
const currentLabels = memoVdc.TEMPLATE?.LABELS?.split(',')
|
||||
const newLabels = currentLabels.filter((l) => l !== label).join(',')
|
||||
const newUserTemplate = { ...memoVdc.TEMPLATE, LABELS: newLabels }
|
||||
const templateXml = jsonToXml(newUserTemplate)
|
||||
|
||||
update({ id: original.ID, template: templateXml, replace: 0 })
|
||||
},
|
||||
[memoVdc.TEMPLATE?.LABELS, update]
|
||||
)
|
||||
|
||||
return (
|
||||
<VirtualDataCenterCard
|
||||
template={memoVdc}
|
||||
rootProps={props}
|
||||
onClickLabel={onClickLabel}
|
||||
onDeleteLabel={handleDeleteLabel}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.className === next.className
|
||||
)
|
||||
|
||||
Row.propTypes = {
|
||||
original: PropTypes.object,
|
||||
value: PropTypes.object,
|
||||
isSelected: PropTypes.bool,
|
||||
className: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onClickLabel: PropTypes.func,
|
||||
}
|
||||
|
||||
Row.displayName = 'VirtualDataCenterRow'
|
||||
|
||||
export default Row
|
@ -38,6 +38,7 @@ import VNetworksTable from 'client/components/Tables/VNetworks'
|
||||
import VNetworkTemplatesTable from 'client/components/Tables/VNetworkTemplates'
|
||||
import VRoutersTable from 'client/components/Tables/VRouters'
|
||||
import ZonesTable from 'client/components/Tables/Zones'
|
||||
import VDCsTable from 'client/components/Tables/VirtualDataCenters'
|
||||
|
||||
export * from 'client/components/Tables/Enhanced/Utils'
|
||||
|
||||
@ -61,6 +62,7 @@ export {
|
||||
ServicesTable,
|
||||
ServiceTemplatesTable,
|
||||
UsersTable,
|
||||
VDCsTable,
|
||||
VmsTable,
|
||||
VmTemplatesTable,
|
||||
VNetworksTable,
|
||||
|
80
src/fireedge/src/client/components/Tabs/Vdc/Clusters.js
Normal file
80
src/fireedge/src/client/components/Tabs/Vdc/Clusters.js
Normal file
@ -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 PropTypes from 'prop-types'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import { generatePath, useHistory } from 'react-router-dom'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { ClustersTable } from 'client/components/Tables'
|
||||
import SelectZones from 'client/components/Tabs/Vdc/SelecZones'
|
||||
import { useGetVDCQuery } from 'client/features/OneApi/vdc'
|
||||
import { useGetZonesQuery } from 'client/features/OneApi/zone'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Datastore id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const ClustersInfoTab = ({ id }) => {
|
||||
const [selectedZone, setSelectedZone] = useState('0')
|
||||
const path = PATH.INFRASTRUCTURE.CLUSTERS.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
const { data } = useGetVDCQuery({ id })
|
||||
const { data: dataZones } = useGetZonesQuery()
|
||||
|
||||
const vdcData = data.CLUSTERS.CLUSTER
|
||||
? Array.isArray(data.CLUSTERS.CLUSTER)
|
||||
? data.CLUSTERS.CLUSTER
|
||||
: [data.CLUSTERS.CLUSTER]
|
||||
: []
|
||||
|
||||
const vdcClusterIds = vdcData
|
||||
.filter((ds) => ds.ZONE_ID === selectedZone)
|
||||
.map((ds) => ds.CLUSTER_ID)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectZones
|
||||
data={dataZones}
|
||||
handleZone={setSelectedZone}
|
||||
value={selectedZone}
|
||||
/>
|
||||
<ClustersTable
|
||||
disableRowSelect
|
||||
disableGlobalSort
|
||||
vdcClusters={vdcClusterIds}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ClustersInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
ClustersInfoTab.displayName = 'ClustersInfoTab'
|
||||
|
||||
export default ClustersInfoTab
|
81
src/fireedge/src/client/components/Tabs/Vdc/Datastores.js
Normal file
81
src/fireedge/src/client/components/Tabs/Vdc/Datastores.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, useState } from 'react'
|
||||
import { generatePath, useHistory } from 'react-router-dom'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { DatastoresTable } from 'client/components/Tables'
|
||||
import SelectZones from 'client/components/Tabs/Vdc/SelecZones'
|
||||
import { useGetVDCQuery } from 'client/features/OneApi/vdc'
|
||||
import { useGetZonesQuery } from 'client/features/OneApi/zone'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Datastore id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const DatastoresInfoTab = ({ id }) => {
|
||||
const [selectedZone, setSelectedZone] = useState('0')
|
||||
|
||||
const path = PATH.STORAGE.DATASTORES.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
const { data } = useGetVDCQuery({ id })
|
||||
const { data: dataZones } = useGetZonesQuery()
|
||||
|
||||
const vdcData = data.DATASTORES.DATASTORE
|
||||
? Array.isArray(data.DATASTORES.DATASTORE)
|
||||
? data.DATASTORES.DATASTORE
|
||||
: [data.DATASTORES.DATASTORE]
|
||||
: []
|
||||
|
||||
const vdcDatastoresIds = vdcData
|
||||
.filter((ds) => ds.ZONE_ID === selectedZone)
|
||||
.map((ds) => ds.DATASTORE_ID)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectZones
|
||||
data={dataZones}
|
||||
handleZone={setSelectedZone}
|
||||
value={selectedZone}
|
||||
/>
|
||||
<DatastoresTable
|
||||
disableRowSelect
|
||||
disableGlobalSort
|
||||
vdcDatastores={vdcDatastoresIds}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
DatastoresInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
DatastoresInfoTab.displayName = 'DatastoresInfoTab'
|
||||
|
||||
export default DatastoresInfoTab
|
60
src/fireedge/src/client/components/Tabs/Vdc/Groups.js
Normal file
60
src/fireedge/src/client/components/Tabs/Vdc/Groups.js
Normal file
@ -0,0 +1,60 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useHistory, generatePath } from 'react-router-dom'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { GroupsTable } from 'client/components/Tables'
|
||||
import { useGetVDCQuery } from 'client/features/OneApi/vdc'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Datastore id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const GroupsInfoTab = ({ id }) => {
|
||||
const path = PATH.SYSTEM.GROUPS.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
const { data } = useGetVDCQuery({ id })
|
||||
const vdcGroups = data.GROUPS.ID ?? []
|
||||
|
||||
return (
|
||||
<GroupsTable
|
||||
disableRowSelect
|
||||
disableGlobalSort
|
||||
vdcGroups={vdcGroups}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
GroupsInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
GroupsInfoTab.displayName = 'GroupsInfoTab'
|
||||
|
||||
export default GroupsInfoTab
|
80
src/fireedge/src/client/components/Tabs/Vdc/Hosts.js
Normal file
80
src/fireedge/src/client/components/Tabs/Vdc/Hosts.js
Normal file
@ -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 PropTypes from 'prop-types'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import { generatePath, useHistory } from 'react-router-dom'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { HostsTable } from 'client/components/Tables'
|
||||
import SelectZones from 'client/components/Tabs/Vdc/SelecZones'
|
||||
import { useGetVDCQuery } from 'client/features/OneApi/vdc'
|
||||
import { useGetZonesQuery } from 'client/features/OneApi/zone'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Datastore id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const HostsInfoTab = ({ id }) => {
|
||||
const [selectedZone, setSelectedZone] = useState('0')
|
||||
const path = PATH.INFRASTRUCTURE.HOSTS.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
const { data } = useGetVDCQuery({ id })
|
||||
const { data: dataZones } = useGetZonesQuery()
|
||||
|
||||
const vdcData = data.HOSTS.HOST
|
||||
? Array.isArray(data.HOSTS.HOST)
|
||||
? data.HOSTS.HOST
|
||||
: [data.HOSTS.HOST]
|
||||
: []
|
||||
|
||||
const vdcHostsIds = vdcData
|
||||
.filter((ds) => ds.ZONE_ID === selectedZone)
|
||||
.map((ds) => ds.HOST_ID)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectZones
|
||||
data={dataZones}
|
||||
handleZone={setSelectedZone}
|
||||
value={selectedZone}
|
||||
/>
|
||||
<HostsTable
|
||||
disableRowSelect
|
||||
disableGlobalSort
|
||||
vdcHosts={vdcHostsIds}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
HostsInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
HostsInfoTab.displayName = 'HostsInfoTab'
|
||||
|
||||
export default HostsInfoTab
|
99
src/fireedge/src/client/components/Tabs/Vdc/Info/index.js
Normal file
99
src/fireedge/src/client/components/Tabs/Vdc/Info/index.js
Normal file
@ -0,0 +1,99 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Stack } from '@mui/material'
|
||||
|
||||
import {
|
||||
useGetVDCQuery,
|
||||
useUpdateVDCMutation,
|
||||
} from 'client/features/OneApi/vdc'
|
||||
import Information from 'client/components/Tabs/Vdc/Info/information'
|
||||
import {
|
||||
filterAttributes,
|
||||
getActionsAvailable,
|
||||
jsonToXml,
|
||||
} from 'client/models/Helper'
|
||||
import { AttributePanel } from 'client/components/Tabs/Common'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
import { cloneObject, set } from 'client/utils'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.tabProps - Tab information
|
||||
* @param {string} props.id - Template id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const VDCInfoTab = ({ tabProps = {}, id }) => {
|
||||
const {
|
||||
information_panel: informationPanel,
|
||||
attributes_panel: attributesPanel,
|
||||
} = tabProps
|
||||
|
||||
const { data: vdc = {} } = useGetVDCQuery({ id })
|
||||
const { TEMPLATE } = vdc
|
||||
const [updateTemplate] = useUpdateVDCMutation()
|
||||
|
||||
const getActions = (actions) => getActionsAvailable(actions)
|
||||
|
||||
const { attributes } = filterAttributes(TEMPLATE)
|
||||
|
||||
const handleAttributeInXml = async (path, newValue) => {
|
||||
const newTemplate = cloneObject(TEMPLATE)
|
||||
set(newTemplate, path, newValue)
|
||||
|
||||
const xml = jsonToXml(newTemplate)
|
||||
await updateTemplate({ id, template: xml, replace: 0 })
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
display="grid"
|
||||
gap="1em"
|
||||
gridTemplateColumns="repeat(auto-fit, minmax(49%, 1fr))"
|
||||
padding={{ sm: '0.8em' }}
|
||||
>
|
||||
{informationPanel?.enabled && (
|
||||
<Information
|
||||
actions={getActions(informationPanel?.actions)}
|
||||
vdc={vdc}
|
||||
/>
|
||||
)}
|
||||
{attributesPanel?.enabled && (
|
||||
<AttributePanel
|
||||
attributes={attributes}
|
||||
actions={getActions(attributesPanel?.actions)}
|
||||
title={Tr(T.Attributes)}
|
||||
handleAdd={handleAttributeInXml}
|
||||
handleEdit={handleAttributeInXml}
|
||||
handleDelete={handleAttributeInXml}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
VDCInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
VDCInfoTab.displayName = 'VDCInfoTab'
|
||||
|
||||
export default VDCInfoTab
|
@ -0,0 +1,68 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { List } from 'client/components/Tabs/Common'
|
||||
import { useRenameVDCMutation } from 'client/features/OneApi/vdc'
|
||||
|
||||
import { T, VDC_ACTIONS } from 'client/constants'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {object} props.vdc - Template
|
||||
* @param {string[]} props.actions - Available actions to information tab
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const InformationPanel = ({ vdc = {}, actions }) => {
|
||||
const [rename] = useRenameVDCMutation()
|
||||
|
||||
const { ID, NAME } = vdc
|
||||
|
||||
const handleRename = async (_, newName) => {
|
||||
await rename({ id: ID, name: newName })
|
||||
}
|
||||
|
||||
const info = [
|
||||
{ name: T.ID, value: ID, dataCy: 'id' },
|
||||
{
|
||||
name: T.Name,
|
||||
value: NAME,
|
||||
canEdit: actions?.includes?.(VDC_ACTIONS.RENAME),
|
||||
handleEdit: handleRename,
|
||||
dataCy: 'name',
|
||||
},
|
||||
].filter(Boolean)
|
||||
|
||||
return (
|
||||
<List
|
||||
title={T.Information}
|
||||
list={info}
|
||||
containerProps={{ sx: { gridRow: 'span 3' } }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
InformationPanel.displayName = 'InformationPanel'
|
||||
|
||||
InformationPanel.propTypes = {
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
vdc: PropTypes.object,
|
||||
}
|
||||
|
||||
export default InformationPanel
|
51
src/fireedge/src/client/components/Tabs/Vdc/SelecZones.js
Normal file
51
src/fireedge/src/client/components/Tabs/Vdc/SelecZones.js
Normal file
@ -0,0 +1,51 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 { MenuItem, Select } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ReactElement } from 'react'
|
||||
/**
|
||||
* Render zone selector.
|
||||
*
|
||||
* @param {object} props - props
|
||||
* @param {Array} props.data - data for selector
|
||||
* @param {Function} props.handleZone - selector function
|
||||
* @param {string} props.value - value
|
||||
* @returns {ReactElement} - selector zones
|
||||
*/
|
||||
const SelectZones = ({ data = [], handleZone, value }) => {
|
||||
const handleChange = (event) => {
|
||||
handleZone(event.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<Select value={value} onChange={handleChange}>
|
||||
{data.map(({ ID, NAME }) => (
|
||||
<MenuItem key={ID} value={ID}>
|
||||
{NAME}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
SelectZones.propTypes = {
|
||||
data: PropTypes.array,
|
||||
handleZone: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
}
|
||||
|
||||
SelectZones.displayName = 'SelectZones'
|
||||
|
||||
export default SelectZones
|
80
src/fireedge/src/client/components/Tabs/Vdc/Vnets.js
Normal file
80
src/fireedge/src/client/components/Tabs/Vdc/Vnets.js
Normal file
@ -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 PropTypes from 'prop-types'
|
||||
import { ReactElement, useState } from 'react'
|
||||
import { generatePath, useHistory } from 'react-router-dom'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
|
||||
import { VNetworksTable } from 'client/components/Tables'
|
||||
import SelectZones from 'client/components/Tabs/Vdc/SelecZones'
|
||||
import { useGetVDCQuery } from 'client/features/OneApi/vdc'
|
||||
import { useGetZonesQuery } from 'client/features/OneApi/zone'
|
||||
|
||||
/**
|
||||
* Renders mainly information tab.
|
||||
*
|
||||
* @param {object} props - Props
|
||||
* @param {string} props.id - Datastore id
|
||||
* @returns {ReactElement} Information tab
|
||||
*/
|
||||
const VnetsInfoTab = ({ id }) => {
|
||||
const [selectedZone, setSelectedZone] = useState('0')
|
||||
const path = PATH.NETWORK.VNETS.DETAIL
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
const { data } = useGetVDCQuery({ id })
|
||||
const { data: dataZones } = useGetZonesQuery()
|
||||
|
||||
const vdcData = data.VNETS.VNET
|
||||
? Array.isArray(data.VNETS.VNET)
|
||||
? data.VNETS.VNET
|
||||
: [data.VNETS.VNET]
|
||||
: []
|
||||
|
||||
const vdcVnetsIds = vdcData
|
||||
.filter((ds) => ds.ZONE_ID === selectedZone)
|
||||
.map((ds) => ds.VNET_ID)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SelectZones
|
||||
data={dataZones}
|
||||
handleZone={setSelectedZone}
|
||||
value={selectedZone}
|
||||
/>
|
||||
<VNetworksTable
|
||||
disableRowSelect
|
||||
disableGlobalSort
|
||||
vdcVnets={vdcVnetsIds}
|
||||
onRowClick={(row) => handleRowClick(row.ID)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
VnetsInfoTab.propTypes = {
|
||||
tabProps: PropTypes.object,
|
||||
id: PropTypes.string,
|
||||
}
|
||||
|
||||
VnetsInfoTab.displayName = 'VnetsInfoTab'
|
||||
|
||||
export default VnetsInfoTab
|
71
src/fireedge/src/client/components/Tabs/Vdc/index.js
Normal file
71
src/fireedge/src/client/components/Tabs/Vdc/index.js
Normal file
@ -0,0 +1,71 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, LinearProgress } from '@mui/material'
|
||||
import PropTypes from 'prop-types'
|
||||
import { memo, useMemo } from 'react'
|
||||
|
||||
import { RESOURCE_NAMES } from 'client/constants'
|
||||
import { useViews } from 'client/features/Auth'
|
||||
import { useGetVDCQuery } from 'client/features/OneApi/vdc'
|
||||
import { getAvailableInfoTabs } from 'client/models/Helper'
|
||||
|
||||
import Tabs from 'client/components/Tabs'
|
||||
import Datastores from 'client/components/Tabs/Vdc//Datastores'
|
||||
import Groups from 'client/components/Tabs/Vdc//Groups'
|
||||
import Hosts from 'client/components/Tabs/Vdc//Hosts'
|
||||
import Info from 'client/components/Tabs/Vdc//Info'
|
||||
import Vnets from 'client/components/Tabs/Vdc//Vnets'
|
||||
import Clusters from 'client/components/Tabs/Vdc/Clusters'
|
||||
|
||||
const getTabComponent = (tabName) =>
|
||||
({
|
||||
info: Info,
|
||||
groups: Groups,
|
||||
clusters: Clusters,
|
||||
hosts: Hosts,
|
||||
vnets: Vnets,
|
||||
datastores: Datastores,
|
||||
}[tabName])
|
||||
|
||||
const VDCTabs = memo(({ id }) => {
|
||||
const { view, getResourceView } = useViews()
|
||||
const { isError, error, status, data } = useGetVDCQuery({ id })
|
||||
|
||||
const tabsAvailable = useMemo(() => {
|
||||
const resource = RESOURCE_NAMES.VDC
|
||||
const infoTabs = getResourceView(resource)?.['info-tabs'] ?? {}
|
||||
|
||||
return getAvailableInfoTabs(infoTabs, getTabComponent, id)
|
||||
}, [view, id])
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Alert severity="error" variant="outlined">
|
||||
{error.data}
|
||||
</Alert>
|
||||
)
|
||||
}
|
||||
|
||||
if (status === 'fulfilled' || id === data?.ID) {
|
||||
return <Tabs addBorder tabs={tabsAvailable ?? []} />
|
||||
}
|
||||
|
||||
return <LinearProgress color="secondary" sx={{ width: '100%' }} />
|
||||
})
|
||||
VDCTabs.propTypes = { id: PropTypes.string.isRequired }
|
||||
VDCTabs.displayName = 'VDCTabs'
|
||||
|
||||
export default VDCTabs
|
@ -33,7 +33,6 @@ const VmBackupTab = ({ id }) => {
|
||||
const history = useHistory()
|
||||
|
||||
const handleRowClick = (rowId) => {
|
||||
console.log('going to: ', generatePath(path, { id: String(rowId) }))
|
||||
history.push(generatePath(path, { id: String(rowId) }))
|
||||
}
|
||||
|
||||
|
@ -171,6 +171,7 @@ export const RESOURCE_NAMES = {
|
||||
MARKETPLACE: 'marketplace',
|
||||
SEC_GROUP: 'security-group',
|
||||
USER: 'user',
|
||||
VDC: 'virtual-data-center',
|
||||
VROUTER: 'virtual-router',
|
||||
VM_TEMPLATE: 'vm-template',
|
||||
VM: 'vm',
|
||||
@ -202,6 +203,7 @@ export * from 'client/constants/scheduler'
|
||||
export * from 'client/constants/securityGroup'
|
||||
export * from 'client/constants/user'
|
||||
export * from 'client/constants/userInput'
|
||||
export * from 'client/constants/vdc'
|
||||
export * from 'client/constants/vm'
|
||||
export * from 'client/constants/vmTemplate'
|
||||
export * from 'client/constants/zone'
|
||||
|
@ -32,7 +32,8 @@ module.exports = {
|
||||
NoLabels: 'NoLabels',
|
||||
All: 'All',
|
||||
On: 'On',
|
||||
ToggleAllCurrentPageRowsSelected: 'Toggle all current page rows selected',
|
||||
ToggleAllSelectedCardsCurrentPage:
|
||||
'Toggle all selected cards in current page',
|
||||
NumberOfResourcesSelected: 'All %s resources are selected',
|
||||
SelectAllResources: 'Select all %s resources',
|
||||
ClearSelection: 'Clear selection',
|
||||
@ -81,6 +82,8 @@ module.exports = {
|
||||
CreateServiceTemplate: 'Create Service Template',
|
||||
CreateVirtualNetwork: 'Create Virtual Network',
|
||||
CreateVmTemplate: 'Create VM Template',
|
||||
CreateVDC: 'Create VDC',
|
||||
UpdateVDC: 'Update VDC',
|
||||
CurrentGroup: 'Current group: %s',
|
||||
CurrentOwner: 'Current owner: %s',
|
||||
Delete: 'Delete',
|
||||
@ -154,13 +157,17 @@ module.exports = {
|
||||
Search: 'Search',
|
||||
Select: 'Select',
|
||||
SelectCluster: 'Select Cluster',
|
||||
SelectClusters: 'Select Clusters',
|
||||
SelectDatastore: 'Select a Datastore to store the resource',
|
||||
SelectDatastoreImage: 'Select a Datastore',
|
||||
SelectDatastores: 'Select Datastores',
|
||||
SelectDockerHubTag: 'Select DockerHub image tag (default latest)',
|
||||
SelectGroup: 'Select a group',
|
||||
SelectHost: 'Select a host',
|
||||
SelectHosts: 'Select hosts',
|
||||
SelectMarketplace: 'Select Marketplace',
|
||||
SelectNetwork: 'Select a network',
|
||||
SelectVirtualNetworks: 'Select virtual networks',
|
||||
SelectNewCluster: 'Select a new Cluster',
|
||||
SelectRequest: 'Select request',
|
||||
SelectTheNewDatastore: 'Select the new datastore',
|
||||
@ -382,6 +389,10 @@ module.exports = {
|
||||
NoNetworksInMonitoring:
|
||||
'There is currently no network monitoring information associated with this VM',
|
||||
|
||||
/* sections - vdc */
|
||||
Resources: 'Resources',
|
||||
SelectAllResourcesFromZone: 'Select all %s resources from %s zone (Zone #%s)',
|
||||
|
||||
/* sections - storage */
|
||||
Backups: 'Backups',
|
||||
BackupDatastore: 'Backup Datastore',
|
||||
@ -531,6 +542,13 @@ module.exports = {
|
||||
StandaloneQcow2CloneConcept:
|
||||
'Clone qcow2 without a backing chain and no dependencies with Image datastore files',
|
||||
|
||||
/* VDC */
|
||||
AllClustersAreIncludedInThisVDC: 'All clusters are included in this VDC',
|
||||
AllHostsAreIncludedInThisVDC: 'All hosts are included in this VDC',
|
||||
AllDatastoresAreIncludedInThisVDC: 'All datastores are included in this VDC',
|
||||
AllVNetworksAreIncludedInThisVDC:
|
||||
'All virtual networks are included in this VDC',
|
||||
|
||||
/* sections - templates & instances */
|
||||
Instances: 'Instances',
|
||||
VM: 'VM',
|
||||
|
58
src/fireedge/src/client/constants/vdc.js
Normal file
58
src/fireedge/src/client/constants/vdc.js
Normal file
@ -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 * as ACTIONS from 'client/constants/actions'
|
||||
|
||||
/**
|
||||
* @typedef VDC
|
||||
* @property {string|number} ID - Id
|
||||
* @property {string} NAME - Name
|
||||
* @property {object} TEMPLATE - Template information
|
||||
* @property {string} [TEMPLATE.DESCRIPTION] - VDC Description
|
||||
* @property {string} [TEMPLATE.LABELS] - VDC Labels
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef VDCHost
|
||||
* @property {string} ZONE_ID - Host zone id
|
||||
* @property {string} HOST_ID - Host id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef VDCCluster
|
||||
* @property {string} ZONE_ID - Cluster zone id
|
||||
* @property {string} CLUSTER_ID - Cluster id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef VDCDatastore
|
||||
* @property {string} ZONE_ID - Datastore zone id
|
||||
* @property {string} DATASTORE_ID - Datastore id
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef VDCVnet
|
||||
* @property {string} ZONE_ID - Vnet zone id
|
||||
* @property {string} VNET_ID - Vnet id
|
||||
*/
|
||||
|
||||
export const VDC_ACTIONS = {
|
||||
CREATE_DIALOG: 'create_dialog',
|
||||
UPDATE_DIALOG: 'update_dialog',
|
||||
DELETE: 'delete',
|
||||
RENAME: ACTIONS.RENAME,
|
||||
}
|
||||
|
||||
export const ALL_SELECTED = '-10'
|
36
src/fireedge/src/client/containers/Datastores/Detail.js
Normal file
36
src/fireedge/src/client/containers/Datastores/Detail.js
Normal file
@ -0,0 +1,36 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import { useParams, Redirect } from 'react-router-dom'
|
||||
|
||||
import DatastoreTabs from 'client/components/Tabs/Datastore'
|
||||
|
||||
/**
|
||||
* Displays the detail information about a Datastore.
|
||||
*
|
||||
* @returns {ReactElement} Datastore detail component.
|
||||
*/
|
||||
function DatatoreDetail() {
|
||||
const { id } = useParams()
|
||||
|
||||
if (Number.isNaN(+id)) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
return <DatastoreTabs id={id} />
|
||||
}
|
||||
|
||||
export default DatatoreDetail
|
81
src/fireedge/src/client/containers/VDCs/Create.js
Normal file
81
src/fireedge/src/client/containers/VDCs/Create.js
Normal file
@ -0,0 +1,81 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 {
|
||||
useCreateVDCMutation,
|
||||
useUpdateVDCMutation,
|
||||
} from 'client/features/OneApi/vdc'
|
||||
|
||||
import { PATH } from 'client/apps/sunstone/routesOne'
|
||||
import {
|
||||
DefaultFormStepper,
|
||||
SkeletonStepsForm,
|
||||
} from 'client/components/FormStepper'
|
||||
import { CreateForm } from 'client/components/Forms/Vdc'
|
||||
import { useGetZonesQuery } from 'client/features/OneApi/zone'
|
||||
|
||||
/**
|
||||
* Displays the creation or modification form to a VDC Template.
|
||||
*
|
||||
* @returns {ReactElement} VDC Template form
|
||||
*/
|
||||
function CreateVDC() {
|
||||
const history = useHistory()
|
||||
const { state } = useLocation()
|
||||
const { ID: vdcId, NAME } = state ?? {}
|
||||
|
||||
const { data: zones = [] } = useGetZonesQuery()
|
||||
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const [create] = useCreateVDCMutation()
|
||||
const [update] = useUpdateVDCMutation()
|
||||
|
||||
const onSubmit = async (vdc) => {
|
||||
try {
|
||||
if (!vdcId) {
|
||||
const newVDCId = await create(vdc).unwrap()
|
||||
if (newVDCId) {
|
||||
history.push(PATH.SYSTEM.VDCS.LIST)
|
||||
enqueueSuccess(`VDC created - #${newVDCId}`)
|
||||
}
|
||||
} else {
|
||||
const updatedVDC = await update({ id: vdcId, ...vdc }).unwrap()
|
||||
if (updatedVDC) {
|
||||
history.push(PATH.SYSTEM.VDCS.LIST)
|
||||
enqueueSuccess(`VDC updated - #${vdcId} ${NAME}`)
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return zones.length ? (
|
||||
<CreateForm
|
||||
initialValues={state}
|
||||
onSubmit={onSubmit}
|
||||
fallback={<SkeletonStepsForm />}
|
||||
stepProps={zones}
|
||||
>
|
||||
{(config) => <DefaultFormStepper {...config} />}
|
||||
</CreateForm>
|
||||
) : (
|
||||
<SkeletonStepsForm />
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateVDC
|
36
src/fireedge/src/client/containers/VDCs/Detail.js
Normal file
36
src/fireedge/src/client/containers/VDCs/Detail.js
Normal file
@ -0,0 +1,36 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { ReactElement } from 'react'
|
||||
import { useParams, Redirect } from 'react-router-dom'
|
||||
|
||||
import VDCTabs from 'client/components/Tabs/Vdc'
|
||||
|
||||
/**
|
||||
* Displays the detail information about a VDC.
|
||||
*
|
||||
* @returns {ReactElement} VDC detail component.
|
||||
*/
|
||||
function VDCDetail() {
|
||||
const { id } = useParams()
|
||||
|
||||
if (Number.isNaN(+id)) {
|
||||
return <Redirect to="/" />
|
||||
}
|
||||
|
||||
return <VDCTabs id={id} />
|
||||
}
|
||||
|
||||
export default VDCDetail
|
161
src/fireedge/src/client/containers/VDCs/index.js
Normal file
161
src/fireedge/src/client/containers/VDCs/index.js
Normal file
@ -0,0 +1,161 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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, Chip, Stack, Typography } from '@mui/material'
|
||||
import { Cancel, Pin as GotoIcon, RefreshDouble } from 'iconoir-react'
|
||||
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 { VDCsTable } from 'client/components/Tables'
|
||||
import VDCActions from 'client/components/Tables/VirtualDataCenters/actions'
|
||||
import VDCTabs from 'client/components/Tabs/Vdc'
|
||||
import { T, VmTemplate as VdcTemplate } from 'client/constants'
|
||||
import {
|
||||
useLazyGetVDCQuery,
|
||||
useUpdateVDCMutation,
|
||||
} from 'client/features/OneApi/vdc'
|
||||
|
||||
/**
|
||||
* Displays a list of VDCs with a split pane between the list and selected row(s).
|
||||
*
|
||||
* @returns {ReactElement} VDCs list and selected row(s)
|
||||
*/
|
||||
function VirtualDataCenters() {
|
||||
const [selectedRows, onSelectedRowsChange] = useState(() => [])
|
||||
const actions = VDCActions()
|
||||
|
||||
const hasSelectedRows = selectedRows?.length > 0
|
||||
const moreThanOneSelected = selectedRows?.length > 1
|
||||
|
||||
return (
|
||||
<SplitPane gridTemplateRows="1fr auto 1fr">
|
||||
{({ getGridProps, GutterComponent }) => (
|
||||
<Box height={1} {...(hasSelectedRows && getGridProps())}>
|
||||
<VDCsTable
|
||||
onSelectedRowsChange={onSelectedRowsChange}
|
||||
globalActions={actions}
|
||||
useUpdateMutation={useUpdateVDCMutation}
|
||||
/>
|
||||
|
||||
{hasSelectedRows && (
|
||||
<>
|
||||
<GutterComponent direction="row" track={1} />
|
||||
{moreThanOneSelected ? (
|
||||
<GroupedTags tags={selectedRows} />
|
||||
) : (
|
||||
<InfoTabs
|
||||
template={selectedRows[0]?.original}
|
||||
gotoPage={selectedRows[0]?.gotoPage}
|
||||
unselect={() => selectedRows[0]?.toggleRowSelected(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</SplitPane>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays details of a VDC Template.
|
||||
*
|
||||
* @param {VdcTemplate} template - VDC Template id to display
|
||||
* @param {Function} [gotoPage] - Function to navigate to a page of a VDC Template
|
||||
* @param {Function} [unselect] - Function to unselect a VDC Template
|
||||
* @returns {ReactElement} VDC Template details
|
||||
*/
|
||||
const InfoTabs = memo(({ template, gotoPage, unselect }) => {
|
||||
const [getVDC, { data, isFetching }] = useLazyGetVDCQuery()
|
||||
const id = data?.ID ?? template.ID
|
||||
const name = data?.NAME ?? template.NAME
|
||||
|
||||
return (
|
||||
<Stack overflow="auto">
|
||||
<Stack direction="row" alignItems="center" gap={1} mx={1} mb={1}>
|
||||
<Typography color="text.primary" noWrap flexGrow={1}>
|
||||
{`#${id} | ${name}`}
|
||||
</Typography>
|
||||
|
||||
{/* -- ACTIONS -- */}
|
||||
<SubmitButton
|
||||
data-cy="detail-refresh"
|
||||
icon={<RefreshDouble />}
|
||||
tooltip={Tr(T.Refresh)}
|
||||
isSubmitting={isFetching}
|
||||
onClick={() => getVDC({ id })}
|
||||
/>
|
||||
{typeof gotoPage === 'function' && (
|
||||
<SubmitButton
|
||||
data-cy="locate-on-table"
|
||||
icon={<GotoIcon />}
|
||||
tooltip={Tr(T.LocateOnTable)}
|
||||
onClick={() => gotoPage()}
|
||||
/>
|
||||
)}
|
||||
{typeof unselect === 'function' && (
|
||||
<SubmitButton
|
||||
data-cy="unselect"
|
||||
icon={<Cancel />}
|
||||
tooltip={Tr(T.Close)}
|
||||
onClick={() => unselect()}
|
||||
/>
|
||||
)}
|
||||
{/* -- END ACTIONS -- */}
|
||||
</Stack>
|
||||
<VDCTabs id={id} />
|
||||
</Stack>
|
||||
)
|
||||
})
|
||||
|
||||
InfoTabs.propTypes = {
|
||||
template: PropTypes.object,
|
||||
gotoPage: PropTypes.func,
|
||||
unselect: PropTypes.func,
|
||||
}
|
||||
|
||||
InfoTabs.displayName = 'InfoTabs'
|
||||
|
||||
/**
|
||||
* Displays a list of tags that represent the selected rows.
|
||||
*
|
||||
* @param {Row[]} tags - Row(s) to display as tags
|
||||
* @returns {ReactElement} List of tags
|
||||
*/
|
||||
const GroupedTags = memo(({ tags = [] }) => (
|
||||
<Stack direction="row" flexWrap="wrap" gap={1} alignContent="flex-start">
|
||||
<MultipleTags
|
||||
limitTags={10}
|
||||
tags={tags?.map(({ original, id, toggleRowSelected, gotoPage }) => (
|
||||
<Chip
|
||||
key={id}
|
||||
label={original?.NAME ?? id}
|
||||
onClick={gotoPage}
|
||||
onDelete={() => toggleRowSelected(false)}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
</Stack>
|
||||
))
|
||||
|
||||
GroupedTags.propTypes = { tags: PropTypes.array }
|
||||
GroupedTags.displayName = 'GroupedTags'
|
||||
|
||||
export default VirtualDataCenters
|
@ -35,14 +35,16 @@ const clusterApi = oneApi.injectEndpoints({
|
||||
/**
|
||||
* Retrieves information for all the clusters in the pool.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {string} [params.zone] - Zone from where to get the resources
|
||||
* @returns {Cluster[]} List of clusters
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: () => {
|
||||
query: (params) => {
|
||||
const name = Actions.CLUSTER_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { command }
|
||||
return { command, params }
|
||||
},
|
||||
transformResponse: (data) => [data?.CLUSTER_POOL?.CLUSTER ?? []].flat(),
|
||||
providesTags: (clusters) =>
|
||||
|
@ -38,14 +38,16 @@ const datastoreApi = oneApi.injectEndpoints({
|
||||
/**
|
||||
* Retrieves information for all or part of the datastores in the pool.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {string} [params.zone] - Zone from where to get the resources
|
||||
* @returns {Datastore[]} List of datastores
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: () => {
|
||||
query: (params) => {
|
||||
const name = Actions.DATASTORE_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { command }
|
||||
return { command, params }
|
||||
},
|
||||
transformResponse: (data) =>
|
||||
[data?.DATASTORE_POOL?.DATASTORE ?? []].flat(),
|
||||
|
@ -38,14 +38,16 @@ const hostApi = oneApi.injectEndpoints({
|
||||
/**
|
||||
* Retrieves information for all the hosts in the pool.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {string} [params.zone] - Zone from where to get the resources
|
||||
* @returns {Host[]} Get list of hosts
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: () => {
|
||||
query: (params) => {
|
||||
const name = Actions.HOST_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { command }
|
||||
return { command, params }
|
||||
},
|
||||
transformResponse: (data) => [data?.HOST_POOL?.HOST ?? []].flat(),
|
||||
providesTags: (hosts) =>
|
||||
|
@ -16,9 +16,9 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
import { enqueueSnackbar } from 'client/features/General/actions'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
import { requestConfig, generateKey } from 'client/utils'
|
||||
import { generateKey, requestConfig } from 'client/utils'
|
||||
import http from 'client/utils/rest'
|
||||
import { httpCodes } from 'server/utils/constants'
|
||||
|
||||
const ONE_RESOURCES = {
|
||||
ACL: 'ACL',
|
||||
@ -82,13 +82,15 @@ const oneApi = createApi({
|
||||
{ params = {}, command, needStateInMeta = false },
|
||||
{ getState, dispatch, signal }
|
||||
) => {
|
||||
const paramsExtensible = { ...params }
|
||||
|
||||
try {
|
||||
// set filter flag if filter is present in command params
|
||||
if (command?.params?.filter) {
|
||||
params.filter = getState().auth?.filterPool
|
||||
paramsExtensible.filter = getState().auth?.filterPool
|
||||
}
|
||||
|
||||
const config = requestConfig(params, command)
|
||||
const config = requestConfig(paramsExtensible, command)
|
||||
const response = await http.request({ ...config, signal })
|
||||
const state = needStateInMeta ? getState() : {}
|
||||
|
||||
@ -124,11 +126,11 @@ const oneApi = createApi({
|
||||
})
|
||||
|
||||
export {
|
||||
oneApi,
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
DOCUMENT,
|
||||
DOCUMENT_POOL,
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
PROVISION_CONFIG,
|
||||
PROVISION_RESOURCES,
|
||||
oneApi,
|
||||
}
|
||||
|
@ -16,27 +16,27 @@
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/vn'
|
||||
|
||||
import {
|
||||
oneApi,
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
} from 'client/features/OneApi'
|
||||
import {
|
||||
updateResourceOnPool,
|
||||
removeResourceOnPool,
|
||||
updateNameOnResource,
|
||||
updateLockLevelOnResource,
|
||||
removeLockLevelOnResource,
|
||||
updatePermissionOnResource,
|
||||
updateOwnershipOnResource,
|
||||
updateTemplateOnResource,
|
||||
} from 'client/features/OneApi/common'
|
||||
import { UpdateFromSocket } from 'client/features/OneApi/socket'
|
||||
import {
|
||||
LockLevel,
|
||||
FilterFlag,
|
||||
LockLevel,
|
||||
Permission,
|
||||
VirtualNetwork,
|
||||
} from 'client/constants'
|
||||
import {
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
oneApi,
|
||||
} from 'client/features/OneApi'
|
||||
import {
|
||||
removeLockLevelOnResource,
|
||||
removeResourceOnPool,
|
||||
updateLockLevelOnResource,
|
||||
updateNameOnResource,
|
||||
updateOwnershipOnResource,
|
||||
updatePermissionOnResource,
|
||||
updateResourceOnPool,
|
||||
updateTemplateOnResource,
|
||||
} from 'client/features/OneApi/common'
|
||||
import { UpdateFromSocket } from 'client/features/OneApi/socket'
|
||||
|
||||
const { VNET } = ONE_RESOURCES
|
||||
const { VNET_POOL } = ONE_RESOURCES_POOL
|
||||
@ -51,6 +51,7 @@ const vNetworkApi = oneApi.injectEndpoints({
|
||||
* @param {FilterFlag} [params.filter] - Filter flag
|
||||
* @param {number} [params.start] - Range start ID
|
||||
* @param {number} [params.end] - Range end ID
|
||||
* @param {string} [params.zone] - Zone from where to get the resources
|
||||
* @returns {VirtualNetwork[]} List of virtual networks
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
@ -58,7 +59,7 @@ const vNetworkApi = oneApi.injectEndpoints({
|
||||
const name = Actions.VN_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
return { command, params }
|
||||
},
|
||||
transformResponse: (data) => [data?.VNET_POOL?.VNET ?? []].flat(),
|
||||
providesTags: (networks) =>
|
||||
|
455
src/fireedge/src/client/features/OneApi/vdc.js
Normal file
455
src/fireedge/src/client/features/OneApi/vdc.js
Normal file
@ -0,0 +1,455 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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 {
|
||||
Actions as CustomActions,
|
||||
Commands as CustomCommands,
|
||||
} from 'server/routes/api/vdc/routes'
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/vdc'
|
||||
|
||||
import {
|
||||
ONE_RESOURCES,
|
||||
ONE_RESOURCES_POOL,
|
||||
oneApi,
|
||||
} from 'client/features/OneApi'
|
||||
import {
|
||||
removeResourceOnPool,
|
||||
updateNameOnResource,
|
||||
updateResourceOnPool,
|
||||
updateTemplateOnResource,
|
||||
} from 'client/features/OneApi/common'
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import {
|
||||
VDCCluster,
|
||||
VDCDatastore,
|
||||
VDCHost,
|
||||
VDCVnet,
|
||||
} from 'client/constants/vdc'
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
const { VDC } = ONE_RESOURCES
|
||||
const { VDC_POOL } = ONE_RESOURCES_POOL
|
||||
|
||||
const vdcApi = oneApi.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getVDCs: builder.query({
|
||||
/**
|
||||
* Retrieves information for all or part of the Resources in the pool.
|
||||
*
|
||||
* @returns {Array[Object]} List of Virtual Data Centers
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: () => {
|
||||
const name = Actions.VDC_POOL_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { command }
|
||||
},
|
||||
transformResponse: (data) => [data?.VDC_POOL?.VDC ?? []].flat(),
|
||||
providesTags: (vdcs) =>
|
||||
vdcs
|
||||
? [
|
||||
...vdcs.map(({ ID }) => ({
|
||||
type: VDC_POOL,
|
||||
id: `${ID}`,
|
||||
})),
|
||||
VDC_POOL,
|
||||
]
|
||||
: [VDC_POOL],
|
||||
}),
|
||||
getVDC: builder.query({
|
||||
/**
|
||||
* Retrieves information for the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string} params.id - VDC id
|
||||
* @param {boolean} [params.decrypt] - True to decrypt contained secrets (only admin)
|
||||
* @returns {object} Get VDC identified by id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_INFO
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
transformResponse: (data) => data?.VDC ?? {},
|
||||
providesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const { data: resourceFromQuery } = await queryFulfilled
|
||||
|
||||
dispatch(
|
||||
vdcApi.util.updateQueryData(
|
||||
'getVDCs',
|
||||
undefined,
|
||||
updateResourceOnPool({ id, resourceFromQuery })
|
||||
)
|
||||
)
|
||||
} catch {
|
||||
// if the query fails, we want to remove the resource from the pool
|
||||
dispatch(
|
||||
vdcApi.util.updateQueryData(
|
||||
'getVDCs',
|
||||
undefined,
|
||||
removeResourceOnPool({ id })
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
}),
|
||||
createVDC: builder.mutation({
|
||||
/**
|
||||
* Creates a new VDC in OpenNebula.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {string} params.template - A string containing the template on syntax XML
|
||||
* @param {Array[string]} params.groups - List of groups ids
|
||||
* @param {Array[VDCHost]} params.hosts - List of hosts
|
||||
* @param {Array[VDCDatastore]} params.datastores - List of datastores
|
||||
* @param {Array[VDCVnet]} params.vnets - List of vnets
|
||||
* @param {Array[VDCCluster]} params.clusters - List of clusters
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = CustomActions.VDC_CREATE
|
||||
const command = { name, ...CustomCommands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
}),
|
||||
removeVDC: builder.mutation({
|
||||
/**
|
||||
* Deletes the given VDC from the pool.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {number|string} params.id - VDC id
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_DELETE
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: [VDC_POOL],
|
||||
}),
|
||||
updateVDC: builder.mutation({
|
||||
/**
|
||||
* Replaces the template contents.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @param {number|string} params.id - VDC id
|
||||
* @param {string} params.template - The new template contents
|
||||
* @param {0|1} params.replace
|
||||
* - Update type:
|
||||
* ``0``: Replace the whole template.
|
||||
* ``1``: Merge new template with the existing one.
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = CustomActions.VDC_UPDATE
|
||||
const command = { name, ...CustomCommands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchVDC = dispatch(
|
||||
vdcApi.util.updateQueryData(
|
||||
'getVDC',
|
||||
{ id: params.id },
|
||||
updateTemplateOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchVDCs = dispatch(
|
||||
vdcApi.util.updateQueryData(
|
||||
'getVDCs',
|
||||
undefined,
|
||||
updateTemplateOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchVDC.undo()
|
||||
patchVDCs.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
renameVDC: builder.mutation({
|
||||
/**
|
||||
* Renames a Virtual Data Center.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {string} params.name - The new name
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_RENAME
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
async onQueryStarted(params, { dispatch, queryFulfilled }) {
|
||||
try {
|
||||
const patchVDC = dispatch(
|
||||
vdcApi.util.updateQueryData(
|
||||
'getVDC',
|
||||
{ id: params.id },
|
||||
updateNameOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
const patchVDCs = dispatch(
|
||||
vdcApi.util.updateQueryData(
|
||||
'getVDCs',
|
||||
undefined,
|
||||
updateNameOnResource(params)
|
||||
)
|
||||
)
|
||||
|
||||
queryFulfilled.catch(() => {
|
||||
patchVDC.undo()
|
||||
patchVDCs.undo()
|
||||
})
|
||||
} catch {}
|
||||
},
|
||||
}),
|
||||
addGroupToVDC: builder.mutation({
|
||||
/**
|
||||
* Adds a group to the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {string} params.group - Group id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_ADDGROUP
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
removeGroupFromVDC: builder.mutation({
|
||||
/**
|
||||
* Removes a group from the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {string} params.group - Group id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_DELGROUP
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
addClusterToVDC: builder.mutation({
|
||||
/**
|
||||
* Adds a Cluster to the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.cluster - Cluster id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_ADDCLUSTER
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
removeClusterFromVDC: builder.mutation({
|
||||
/**
|
||||
* Removes a Cluster from the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.cluster - Cluster id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_DELCLUSTER
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
addHostToVDC: builder.mutation({
|
||||
/**
|
||||
* Adds a Host to the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.host - Host id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_ADDHOST
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
removeHostFromVDC: builder.mutation({
|
||||
/**
|
||||
* Removes a Host from the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.host - Host id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_DELHOST
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
addDatastoreToVDC: builder.mutation({
|
||||
/**
|
||||
* Adds a Datastore to the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.datastore - Datastore id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_ADDDATASTORE
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
removeDatastoreFromVDC: builder.mutation({
|
||||
/**
|
||||
* Removes a Datastore from the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.datastore - Datastore id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_DELDATASTORE
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
addVNetToVDC: builder.mutation({
|
||||
/**
|
||||
* Adds a VNet to the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.vnet - VNet id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_ADDVNET
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
removeVNetFromVDC: builder.mutation({
|
||||
/**
|
||||
* Removes a VNet from the VDC.
|
||||
*
|
||||
* @param {object} params - Request parameters
|
||||
* @param {string|number} params.id - VDC id
|
||||
* @param {number} params.zone - Zone id
|
||||
* @param {string} params.vnet - VNet id to be added to the VDC
|
||||
* @returns {number} VDC id
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = Actions.VDC_DELVNET
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
invalidatesTags: (_, __, { id }) => [{ type: VDC, id }],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const {
|
||||
// Queries
|
||||
useGetVDCsQuery,
|
||||
useLazyGetVDCsQuery,
|
||||
useGetVDCQuery,
|
||||
useLazyGetVDCQuery,
|
||||
|
||||
// Mutations
|
||||
useCreateVDCMutation,
|
||||
useRemoveVDCMutation,
|
||||
useUpdateVDCMutation,
|
||||
useRenameVDCMutation,
|
||||
useAddGroupToVDCMutation,
|
||||
useRemoveGroupFromVDCMutation,
|
||||
useAddClusterToVDCMutation,
|
||||
useRemoveClusterFromVDCMutation,
|
||||
useAddHostToVDCMutation,
|
||||
useRemoveHostFromVDCMutation,
|
||||
useAddDatastoreToVDCMutation,
|
||||
useRemoveDatastoreFromVDCMutation,
|
||||
useAddVNetToVDCMutation,
|
||||
useRemoveVNetFromVDCMutation,
|
||||
} = vdcApi
|
||||
|
||||
export default vdcApi
|
@ -49,6 +49,7 @@ const routes = [
|
||||
'sunstone',
|
||||
'system',
|
||||
'support',
|
||||
'vdc',
|
||||
]
|
||||
|
||||
const serverRoutes = []
|
||||
|
701
src/fireedge/src/server/routes/api/vdc/functions.js
Normal file
701
src/fireedge/src/server/routes/api/vdc/functions.js
Normal file
@ -0,0 +1,701 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { defaults, httpCodes } = require('server/utils/constants')
|
||||
const { httpResponse } = require('server/utils/server')
|
||||
const { Actions: vdcActions } = require('server/utils/constants/commands/vdc')
|
||||
|
||||
const { ok, badRequest } = httpCodes
|
||||
const { defaultEmptyFunction } = defaults
|
||||
|
||||
const {
|
||||
VDC_ALLOCATE,
|
||||
VDC_ADDHOST,
|
||||
VDC_ADDCLUSTER,
|
||||
VDC_ADDGROUP,
|
||||
VDC_ADDDATASTORE,
|
||||
VDC_ADDVNET,
|
||||
VDC_UPDATE,
|
||||
VDC_INFO,
|
||||
VDC_DELGROUP,
|
||||
VDC_DELCLUSTER,
|
||||
VDC_DELDATASTORE,
|
||||
VDC_DELHOST,
|
||||
VDC_DELVNET,
|
||||
} = vdcActions
|
||||
|
||||
const resourcesForDelete = (xmlData, externalData, xmlKey, externalKey) => {
|
||||
const resourceVDC = xmlData
|
||||
? Array.isArray(xmlData)
|
||||
? xmlData
|
||||
: [xmlData]
|
||||
: []
|
||||
|
||||
return resourceVDC.filter((item) => {
|
||||
const { ZONE_ID } = item
|
||||
const found = externalData.find(
|
||||
(resource) =>
|
||||
resource.zone_id === ZONE_ID &&
|
||||
resource[externalKey].includes(item[xmlKey])
|
||||
)
|
||||
|
||||
return !found
|
||||
})
|
||||
}
|
||||
|
||||
const resourcesForAdd = (xmlData, externalData, xmlKey, externalKey) => {
|
||||
const resourceVDC = xmlData
|
||||
? Array.isArray(xmlData)
|
||||
? xmlData
|
||||
: [xmlData]
|
||||
: []
|
||||
|
||||
return externalData.map((obj) => {
|
||||
const { zone_id: zoneId } = obj
|
||||
const newItems = obj[externalKey].filter((val) => {
|
||||
const exist = resourceVDC.some(
|
||||
(item) => item.ZONE_ID === zoneId && item[xmlKey] === val
|
||||
)
|
||||
|
||||
return !exist
|
||||
})
|
||||
|
||||
return { zone_id: zoneId, [externalKey]: newItems }
|
||||
})
|
||||
}
|
||||
|
||||
const limitResourceAdd = (data, key) =>
|
||||
data.reduce((suma, obj) => suma + (obj?.[key]?.length || 0), 0)
|
||||
|
||||
const groupsForVDC = (externalData, xmlData, del = true) => {
|
||||
const arrayXmlData = xmlData
|
||||
? Array.isArray(xmlData)
|
||||
? xmlData
|
||||
: [xmlData]
|
||||
: []
|
||||
|
||||
return del
|
||||
? arrayXmlData.filter((value) => !externalData.includes(value))
|
||||
: externalData.filter((value) => !arrayXmlData.includes(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create VDC.
|
||||
*
|
||||
* @param {object} res - http response
|
||||
* @param {Function} next - express stepper
|
||||
* @param {object} params - params of http request
|
||||
* @param {string} params.hosts - vdc hosts
|
||||
* @param {string} params.datastores - vdc datastores
|
||||
* @param {string} params.vnets - vdc vnets
|
||||
* @param {string} params.groups - vdc groups
|
||||
* @param {string} params.clusters - vdc clusterts
|
||||
* @param {string} params.template - vdc template
|
||||
* @param {object} userData - user of http request
|
||||
* @param {Function} xmlrpc - XML-RPC function
|
||||
*/
|
||||
const createVdc = (
|
||||
res = {},
|
||||
next = defaultEmptyFunction,
|
||||
params = {},
|
||||
userData = {},
|
||||
xmlrpc = defaultEmptyFunction
|
||||
) => {
|
||||
const { user, password } = userData
|
||||
const { hosts, datastores, vnets, groups, clusters, template } = params
|
||||
|
||||
const clustersArray = clusters || []
|
||||
const hostsArray = hosts || []
|
||||
const datastoresArray = datastores || []
|
||||
const vnetsArray = vnets || []
|
||||
const groupsArray = groups || []
|
||||
|
||||
const oneClient = xmlrpc(user, password)
|
||||
|
||||
oneClient({
|
||||
action: VDC_ALLOCATE,
|
||||
parameters: [template],
|
||||
callback: (vdcInfoErr, vdcId) => {
|
||||
if (vdcInfoErr || Number.isNaN(Number(vdcId))) {
|
||||
res.locals.httpCode = httpResponse(badRequest, vdcInfoErr)
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const vdcErrors = {
|
||||
hosts: [],
|
||||
datastores: [],
|
||||
vnets: [],
|
||||
clusters: [],
|
||||
groups: [],
|
||||
}
|
||||
|
||||
// Send Cluster to VDC
|
||||
// Cluster format should be [ { zone_id: 0, cluster_id: 0 } ]
|
||||
clustersArray.forEach(
|
||||
({ zone_id: zoneId, cluster_id: internalClusters }) => {
|
||||
internalClusters.forEach((clusterId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDCLUSTER,
|
||||
parameters: [
|
||||
parseInt(vdcId, 10),
|
||||
parseInt(zoneId, 10),
|
||||
parseInt(clusterId, 10),
|
||||
],
|
||||
callback: (err, id) => {
|
||||
if (err || !id) {
|
||||
vdcErrors.clusters.push(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Send hosts to VDC
|
||||
// Host format should be [ { zone_id: 0, host_id: 0 } ]
|
||||
hostsArray.forEach(({ zone_id: zoneId, host_id: internalHosts }) => {
|
||||
internalHosts.forEach((hostId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDHOST,
|
||||
parameters: [
|
||||
parseInt(vdcId, 10),
|
||||
parseInt(zoneId, 10),
|
||||
parseInt(hostId, 10),
|
||||
],
|
||||
callback: (err, id) => {
|
||||
if (err || !id) {
|
||||
vdcErrors.hosts.push(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Send datastores to VDC
|
||||
// Datastore format should be [ { zone_id: 0, ds_id: 0 } ]
|
||||
datastoresArray.forEach(
|
||||
({ zone_id: zoneId, ds_id: internalDatastores }) => {
|
||||
internalDatastores.forEach((dsId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDDATASTORE,
|
||||
parameters: [
|
||||
parseInt(vdcId, 10),
|
||||
parseInt(zoneId, 10),
|
||||
parseInt(dsId, 10),
|
||||
],
|
||||
callback: (err, id) => {
|
||||
if (err || !id) {
|
||||
vdcErrors.datastores.push(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// Send Virtual Networks to VDC
|
||||
// VNet format should be [ { zone_id: 0, vnet_id: 0 } ]
|
||||
vnetsArray.forEach(({ zone_id: zoneId, vnet_id: internalDatastores }) => {
|
||||
internalDatastores.forEach((vnetId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDVNET,
|
||||
parameters: [
|
||||
parseInt(vdcId, 10),
|
||||
parseInt(zoneId, 10),
|
||||
parseInt(vnetId, 10),
|
||||
],
|
||||
callback: (err, id) => {
|
||||
if (err || !id) {
|
||||
vdcErrors.vnets.push(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Send Groups to VDC
|
||||
groupsArray.forEach((groupId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDGROUP,
|
||||
parameters: [parseInt(vdcId, 10), parseInt(groupId, 10)],
|
||||
callback: (err, id) => {
|
||||
if (err || !id) {
|
||||
vdcErrors.groups.push(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// check if any of the errors is not empty
|
||||
const hasErrors = Object.values(vdcErrors).some(
|
||||
(errorArray) => errorArray.length > 0
|
||||
)
|
||||
|
||||
if (hasErrors) {
|
||||
let errorMessage = ''
|
||||
Object.entries(vdcErrors).forEach(([resource, errors]) => {
|
||||
if (errors.length > 0) {
|
||||
errorMessage += `${resource}: ${errors.join(', ')}\n`
|
||||
}
|
||||
})
|
||||
|
||||
res.locals.httpCode = httpResponse(ok, errorMessage)
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
res.locals.httpCode = httpResponse(ok, vdcId)
|
||||
next()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update VDC.
|
||||
*
|
||||
* @param {object} res - http response
|
||||
* @param {Function} next - express stepper
|
||||
* @param {object} params - params of http request
|
||||
* @param {string} params.id - vdc ID
|
||||
* @param {string} params.hosts - vdc hosts
|
||||
* @param {string} params.datastores - vdc datastores
|
||||
* @param {string} params.vnets - vdc vnets
|
||||
* @param {string} params.groups - vdc groups
|
||||
* @param {string} params.clusters - vdc clusterts
|
||||
* @param {string} params.template - vdc template
|
||||
* @param {object} userData - user of http request
|
||||
* @param {Function} xmlrpc - XML-RPC function
|
||||
*/
|
||||
const updateVdc = (
|
||||
res = {},
|
||||
next = defaultEmptyFunction,
|
||||
params = {},
|
||||
userData = {},
|
||||
xmlrpc = defaultEmptyFunction
|
||||
) => {
|
||||
const { user, password } = userData
|
||||
const { hosts, datastores, vnets, groups, clusters, template, id } = params
|
||||
|
||||
if (!id) {
|
||||
res.locals.httpCode = httpResponse(badRequest)
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const vdcID = parseInt(id, 10)
|
||||
const oneClient = xmlrpc(user, password)
|
||||
|
||||
const clustersArray = clusters || []
|
||||
const datastoresArray = datastores || []
|
||||
const hostsArray = hosts || []
|
||||
const vnetsArray = vnets || []
|
||||
const groupsArray = groups || []
|
||||
|
||||
const vdcErrors = {
|
||||
clusters: { add: [], del: [] },
|
||||
datastores: { add: [], del: [] },
|
||||
hosts: { add: [], del: [] },
|
||||
vnets: { add: [], del: [] },
|
||||
groups: { add: [], del: [] },
|
||||
}
|
||||
|
||||
const results = {
|
||||
groups: { add: 0, delete: 0 },
|
||||
clusters: { add: 0, delete: 0 },
|
||||
datastores: { add: 0, delete: 0 },
|
||||
hosts: { add: 0, delete: 0 },
|
||||
vnets: { add: 0, delete: 0 },
|
||||
}
|
||||
|
||||
const callbackRequest = ({
|
||||
groups: groupsProcessed,
|
||||
clusters: clustersProcessed,
|
||||
datastores: datastoresProcessed,
|
||||
hosts: hostsProcessed,
|
||||
vnets: vnetsProcessed,
|
||||
}) => {
|
||||
if (
|
||||
groupsProcessed.add.limit === groupsProcessed.add.process &&
|
||||
clustersProcessed.add.limit === clustersProcessed.add.process &&
|
||||
datastoresProcessed.add.limit === datastoresProcessed.add.process &&
|
||||
hostsProcessed.add.limit === hostsProcessed.add.process &&
|
||||
vnetsProcessed.add.limit === vnetsProcessed.add.process &&
|
||||
groupsProcessed.delete.limit === groupsProcessed.delete.process &&
|
||||
clustersProcessed.delete.limit === clustersProcessed.delete.process &&
|
||||
datastoresProcessed.delete.limit === datastoresProcessed.delete.process &&
|
||||
hostsProcessed.delete.limit === hostsProcessed.delete.process &&
|
||||
vnetsProcessed.delete.limit === vnetsProcessed.delete.process
|
||||
) {
|
||||
const hasAddErrors = Object.values(vdcErrors).some(
|
||||
(errorArray) => errorArray.add.length > 0
|
||||
)
|
||||
const hasDelErrors = Object.values(vdcErrors).some(
|
||||
(errorArray) => errorArray.del.length > 0
|
||||
)
|
||||
|
||||
if (hasAddErrors || hasDelErrors) {
|
||||
let errorMessage = ''
|
||||
Object.entries(vdcErrors).forEach(([resource, errors]) => {
|
||||
if (errors.add.length > 0) {
|
||||
errorMessage += `add ${resource}: ${errors.join(', ')}\n`
|
||||
}
|
||||
})
|
||||
Object.entries(vdcErrors).forEach(([resource, errors]) => {
|
||||
if (errors.del.length > 0) {
|
||||
errorMessage += `delete ${resource}: ${errors.join(', ')}\n`
|
||||
}
|
||||
})
|
||||
res.locals.httpCode = httpResponse(ok, errorMessage)
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
res.locals.httpCode = httpResponse(ok, id)
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
const callbackInfo = (vdcInfo) => {
|
||||
const groupsForDelete = groupsForVDC(groupsArray, vdcInfo?.GROUPS?.ID)
|
||||
const groupsForAdd = groupsForVDC(groupsArray, vdcInfo?.GROUPS?.ID, false)
|
||||
const clustersForDelete = resourcesForDelete(
|
||||
vdcInfo?.CLUSTERS?.CLUSTER,
|
||||
clustersArray,
|
||||
'CLUSTER_ID',
|
||||
'cluster_id'
|
||||
)
|
||||
const clustersForAdd = resourcesForAdd(
|
||||
vdcInfo?.CLUSTERS?.CLUSTER,
|
||||
clustersArray,
|
||||
'CLUSTER_ID',
|
||||
'cluster_id'
|
||||
)
|
||||
const datastoresForDelete = resourcesForDelete(
|
||||
vdcInfo?.DATASTORES?.DATASTORE,
|
||||
datastoresArray,
|
||||
'DATASTORE_ID',
|
||||
'ds_id'
|
||||
)
|
||||
const datastoresForAdd = resourcesForAdd(
|
||||
vdcInfo?.DATASTORES?.DATASTORE,
|
||||
datastoresArray,
|
||||
'DATASTORE_ID',
|
||||
'ds_id'
|
||||
)
|
||||
const hostsForDelete = resourcesForDelete(
|
||||
vdcInfo?.HOSTS?.HOST,
|
||||
hostsArray,
|
||||
'HOST_ID',
|
||||
'host_id'
|
||||
)
|
||||
const hostsForAdd = resourcesForAdd(
|
||||
vdcInfo?.HOSTS?.HOST,
|
||||
hostsArray,
|
||||
'HOST_ID',
|
||||
'host_id'
|
||||
)
|
||||
|
||||
const vnetsForDelete = resourcesForDelete(
|
||||
vdcInfo?.VNETS?.VNET,
|
||||
vnetsArray,
|
||||
'VNET_ID',
|
||||
'vnet_id'
|
||||
)
|
||||
|
||||
const vnetsForAdd = resourcesForAdd(
|
||||
vdcInfo?.VNETS?.VNET,
|
||||
vnetsArray,
|
||||
'VNET_ID',
|
||||
'vnet_id'
|
||||
)
|
||||
|
||||
const generateResult = () => {
|
||||
callbackRequest({
|
||||
groups: {
|
||||
add: {
|
||||
limit: groupsForAdd.length,
|
||||
process: results.groups.add,
|
||||
},
|
||||
delete: {
|
||||
limit: groupsForDelete.length,
|
||||
process: results.groups.delete,
|
||||
},
|
||||
},
|
||||
clusters: {
|
||||
add: {
|
||||
limit: limitResourceAdd(clustersForAdd, 'cluster_id'),
|
||||
process: results.clusters.add,
|
||||
},
|
||||
delete: {
|
||||
limit: clustersForDelete.length,
|
||||
process: results.clusters.delete,
|
||||
},
|
||||
},
|
||||
datastores: {
|
||||
add: {
|
||||
limit: limitResourceAdd(datastoresForAdd, 'ds_id'),
|
||||
process: results.datastores.add,
|
||||
},
|
||||
delete: {
|
||||
limit: datastoresForDelete.length,
|
||||
process: results.datastores.delete,
|
||||
},
|
||||
},
|
||||
hosts: {
|
||||
add: {
|
||||
limit: limitResourceAdd(hostsForAdd, 'host_id'),
|
||||
process: results.hosts.add,
|
||||
},
|
||||
delete: {
|
||||
limit: hostsForDelete.length,
|
||||
process: results.hosts.delete,
|
||||
},
|
||||
},
|
||||
vnets: {
|
||||
add: {
|
||||
limit: limitResourceAdd(vnetsForAdd, 'vnet_id'),
|
||||
process: results.vnets.add,
|
||||
},
|
||||
delete: {
|
||||
limit: vnetsForDelete.length,
|
||||
process: results.vnets.delete,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Delete resources from VDC */
|
||||
// GROUPS
|
||||
groupsForDelete.forEach((idGroup) => {
|
||||
const group = parseInt(idGroup, 10)
|
||||
if (!Number.isNaN(Number(group))) {
|
||||
oneClient({
|
||||
action: VDC_DELGROUP,
|
||||
parameters: [vdcID, group],
|
||||
callback: (err, idDeletedGroup) => {
|
||||
results.groups.delete = results.groups.delete + 1
|
||||
if (err || Number.isNaN(Number(idDeletedGroup))) {
|
||||
vdcErrors.groups.del.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// CLUSTERS
|
||||
clustersForDelete.forEach((resource) => {
|
||||
const zone = parseInt(resource?.ZONE_ID, 10)
|
||||
const cluster = parseInt(resource?.CLUSTER_ID, 10)
|
||||
if (!Number.isNaN(Number(zone)) && !Number.isNaN(Number(cluster))) {
|
||||
oneClient({
|
||||
action: VDC_DELCLUSTER,
|
||||
parameters: [vdcID, zone, cluster],
|
||||
callback: (err, idDeletedClusters) => {
|
||||
results.clusters.delete = results.clusters.delete + 1
|
||||
if (err || Number.isNaN(Number(idDeletedClusters))) {
|
||||
vdcErrors.clusters.del.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// DATASTORES
|
||||
datastoresForDelete.forEach((resource) => {
|
||||
const zone = parseInt(resource?.ZONE_ID, 10)
|
||||
const ds = parseInt(resource?.DATASTORE_ID, 10)
|
||||
if (!Number.isNaN(Number(zone)) && !Number.isNaN(Number(ds))) {
|
||||
oneClient({
|
||||
action: VDC_DELDATASTORE,
|
||||
parameters: [vdcID, zone, ds],
|
||||
callback: (err, idDeletedDS) => {
|
||||
results.datastores.delete = results.datastores.delete + 1
|
||||
if (err || Number.isNaN(Number(idDeletedDS))) {
|
||||
vdcErrors.datastores.del.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// HOSTS
|
||||
hostsForDelete.forEach((resource) => {
|
||||
const zone = parseInt(resource?.ZONE_ID, 10)
|
||||
const host = parseInt(resource?.HOST_ID, 10)
|
||||
if (!Number.isNaN(Number(zone)) && !Number.isNaN(Number(host))) {
|
||||
oneClient({
|
||||
action: VDC_DELHOST,
|
||||
parameters: [vdcID, zone, host],
|
||||
callback: (err, idDeletedHosts) => {
|
||||
results.hosts.delete = results.hosts.delete + 1
|
||||
if (err || Number.isNaN(Number(idDeletedHosts))) {
|
||||
vdcErrors.hosts.del.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// VNETS
|
||||
vnetsForDelete.forEach((resource) => {
|
||||
const zone = parseInt(resource?.ZONE_ID, 10)
|
||||
const vnet = parseInt(resource?.VNET_ID, 10)
|
||||
if (!Number.isNaN(Number(zone)) && !Number.isNaN(Number(vnet))) {
|
||||
oneClient({
|
||||
action: VDC_DELVNET,
|
||||
parameters: [vdcID, zone, vnet],
|
||||
callback: (err, idDeletedVnets) => {
|
||||
results.vnets.delete = results.vnets.delete + 1
|
||||
if (err || Number.isNaN(Number(idDeletedVnets))) {
|
||||
vdcErrors.vnets.del.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/** Add resources from VDC */
|
||||
// GROUPS
|
||||
groupsForAdd.forEach((groupId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDGROUP,
|
||||
parameters: [vdcID, parseInt(groupId, 10)],
|
||||
callback: (err, idGroup) => {
|
||||
results.groups.add = results.groups.add + 1
|
||||
if (err || Number.isNaN(Number(idGroup))) {
|
||||
vdcErrors.groups.add.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// CLUSTERS
|
||||
clustersForAdd.forEach(
|
||||
({ zone_id: zoneId, cluster_id: internalClusters }) => {
|
||||
internalClusters.forEach((clusterId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDCLUSTER,
|
||||
parameters: [vdcID, parseInt(zoneId, 10), parseInt(clusterId, 10)],
|
||||
callback: (err, idCluster) => {
|
||||
results.clusters.add = results.clusters.add + 1
|
||||
if (err || Number.isNaN(Number(idCluster))) {
|
||||
vdcErrors.clusters.add.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// DATASTORES
|
||||
datastoresForAdd.forEach(
|
||||
({ zone_id: zoneId, ds_id: internalDatastores }) => {
|
||||
internalDatastores.forEach((dsId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDDATASTORE,
|
||||
parameters: [vdcID, parseInt(zoneId, 10), parseInt(dsId, 10)],
|
||||
callback: (err, idDs) => {
|
||||
results.datastores.add = results.datastores.add + 1
|
||||
if (err || Number.isNaN(Number(idDs))) {
|
||||
vdcErrors.datastores.add.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// HOSTS
|
||||
hostsForAdd.forEach(({ zone_id: zoneId, host_id: internalHosts }) => {
|
||||
internalHosts.forEach((hostId) => {
|
||||
oneClient({
|
||||
action: VDC_ADDHOST,
|
||||
parameters: [vdcID, parseInt(zoneId, 10), parseInt(hostId, 10)],
|
||||
callback: (err, idHost) => {
|
||||
results.hosts.add = results.hosts.add + 1
|
||||
if (err || Number.isNaN(Number(idHost))) {
|
||||
vdcErrors.hosts.add.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// VNETS
|
||||
vnetsForAdd.forEach(({ zone_id: zoneId, vnet_id: internalDatastores }) => {
|
||||
internalDatastores.forEach((vnetId, index) => {
|
||||
oneClient({
|
||||
action: VDC_ADDVNET,
|
||||
parameters: [vdcID, parseInt(zoneId, 10), parseInt(vnetId, 10)],
|
||||
callback: (err, idVnet) => {
|
||||
results.vnets.add = results.vnets.add + 1
|
||||
if (err || Number.isNaN(Number(idVnet))) {
|
||||
vdcErrors.vnets.add.push(err)
|
||||
}
|
||||
generateResult()
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
generateResult()
|
||||
}
|
||||
|
||||
oneClient({
|
||||
action: VDC_UPDATE,
|
||||
parameters: [vdcID, template],
|
||||
callback: (vdcInfoErr, vdcId) => {
|
||||
if (vdcInfoErr || Number.isNaN(Number(vdcId))) {
|
||||
res.locals.httpCode = httpResponse(badRequest, vdcInfoErr)
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
oneClient({
|
||||
action: VDC_INFO,
|
||||
parameters: [vdcID],
|
||||
callback: (err, vdcInfo) => {
|
||||
if (err || !vdcInfo?.VDC) {
|
||||
res.locals.httpCode = httpResponse(badRequest, err)
|
||||
next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
callbackInfo(vdcInfo?.VDC)
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createVdc,
|
||||
updateVdc,
|
||||
}
|
31
src/fireedge/src/server/routes/api/vdc/index.js
Normal file
31
src/fireedge/src/server/routes/api/vdc/index.js
Normal file
@ -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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { Actions, Commands } = require('server/routes/api/vdc/routes')
|
||||
const { createVdc, updateVdc } = require('server/routes/api/vdc/functions')
|
||||
|
||||
const { VDC_CREATE, VDC_UPDATE } = Actions
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
...Commands[VDC_CREATE],
|
||||
action: createVdc,
|
||||
},
|
||||
{
|
||||
...Commands[VDC_UPDATE],
|
||||
action: updateVdc,
|
||||
},
|
||||
]
|
91
src/fireedge/src/server/routes/api/vdc/routes.js
Normal file
91
src/fireedge/src/server/routes/api/vdc/routes.js
Normal file
@ -0,0 +1,91 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* 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. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const {
|
||||
httpMethod,
|
||||
from: fromData,
|
||||
} = require('../../../utils/constants/defaults')
|
||||
|
||||
const basepath = '/vdc'
|
||||
const { POST, PUT } = httpMethod
|
||||
const { resource, postBody } = fromData
|
||||
|
||||
const VDC_CREATE = 'vdc.create'
|
||||
const VDC_UPDATE = 'vdc.updateVdc'
|
||||
|
||||
const Actions = {
|
||||
VDC_CREATE,
|
||||
VDC_UPDATE,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Actions,
|
||||
Commands: {
|
||||
[VDC_CREATE]: {
|
||||
path: `${basepath}/create`,
|
||||
httpMethod: POST,
|
||||
auth: true,
|
||||
params: {
|
||||
hosts: {
|
||||
from: postBody,
|
||||
},
|
||||
datastores: {
|
||||
from: postBody,
|
||||
},
|
||||
vnets: {
|
||||
from: postBody,
|
||||
},
|
||||
groups: {
|
||||
from: postBody,
|
||||
},
|
||||
clusters: {
|
||||
from: postBody,
|
||||
},
|
||||
template: {
|
||||
from: postBody,
|
||||
},
|
||||
},
|
||||
},
|
||||
[VDC_UPDATE]: {
|
||||
path: `${basepath}/updateVdc/:id`,
|
||||
httpMethod: PUT,
|
||||
auth: true,
|
||||
params: {
|
||||
id: {
|
||||
from: resource,
|
||||
},
|
||||
hosts: {
|
||||
from: postBody,
|
||||
},
|
||||
datastores: {
|
||||
from: postBody,
|
||||
},
|
||||
vnets: {
|
||||
from: postBody,
|
||||
},
|
||||
groups: {
|
||||
from: postBody,
|
||||
},
|
||||
clusters: {
|
||||
from: postBody,
|
||||
},
|
||||
template: {
|
||||
from: postBody,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
@ -202,7 +202,12 @@ module.exports = {
|
||||
[CLUSTER_POOL_INFO]: {
|
||||
// inspected
|
||||
httpMethod: GET,
|
||||
params: {},
|
||||
params: {
|
||||
zone: {
|
||||
from: query,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -195,7 +195,12 @@ module.exports = {
|
||||
[DATASTORE_POOL_INFO]: {
|
||||
// inspected
|
||||
httpMethod: GET,
|
||||
params: {},
|
||||
params: {
|
||||
zone: {
|
||||
from: query,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -149,7 +149,12 @@ module.exports = {
|
||||
[HOST_POOL_INFO]: {
|
||||
// inspected
|
||||
httpMethod: GET,
|
||||
params: {},
|
||||
params: {
|
||||
zone: {
|
||||
from: query,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
[HOST_POOL_MONITORING]: {
|
||||
// inspected
|
||||
|
@ -66,10 +66,6 @@ module.exports = {
|
||||
from: postBody,
|
||||
default: '',
|
||||
},
|
||||
cluster: {
|
||||
from: postBody,
|
||||
default: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
[VDC_DELETE]: {
|
||||
|
@ -348,6 +348,10 @@ module.exports = {
|
||||
from: query,
|
||||
default: -1,
|
||||
},
|
||||
zone: {
|
||||
from: query,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -127,7 +127,7 @@ const opennebulaConnect = (username = '', password = '', zoneURL = '') => {
|
||||
fillHookResource = true,
|
||||
parseXML = true,
|
||||
}) => {
|
||||
if (action && parameters && Array.isArray(parameters) && callback) {
|
||||
if (action && Array.isArray(parameters) && callback) {
|
||||
// user config
|
||||
const appConfig = getFireedgeConfig()
|
||||
const namespace = appConfig.namespace || defaultNamespace
|
||||
@ -142,7 +142,7 @@ const opennebulaConnect = (username = '', password = '', zoneURL = '') => {
|
||||
callback(undefined, data)
|
||||
}
|
||||
|
||||
if (err && err.body) {
|
||||
if (err?.body) {
|
||||
parseXML
|
||||
? xml2json(err.body, (error, result) => {
|
||||
if (error) {
|
||||
@ -173,7 +173,7 @@ const opennebulaConnect = (username = '', password = '', zoneURL = '') => {
|
||||
: success(err.body)
|
||||
|
||||
return
|
||||
} else if (value && value[0] && value[1]) {
|
||||
} else if (value?.[0] && value?.[1]) {
|
||||
let messageCall
|
||||
if (Array.isArray(value)) {
|
||||
messageCall = value[1]
|
||||
|
@ -536,10 +536,10 @@ const getSunstoneAuth = () => {
|
||||
const getDataZone = (zone = '0', configuredZones) => {
|
||||
let rtn
|
||||
const zones = global?.zones || configuredZones
|
||||
if (zones && Array.isArray(zones)) {
|
||||
if (Array.isArray(zones)) {
|
||||
rtn = zones[0]
|
||||
if (Number.isInteger(parseInt(zone, 10))) {
|
||||
rtn = zones.find((zn) => zn && zn.id && String(zn.id) === String(zone))
|
||||
rtn = zones.find((zn) => zn?.id && String(zn.id) === String(zone))
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user