1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-02-10 13:57:22 +03:00

F OpenNebula/one#5422: Add styles to nics tabs

This commit is contained in:
Sergio Betanzos 2021-07-13 17:56:24 +02:00
parent 05e706b2b5
commit b75e5d7181
No known key found for this signature in database
GPG Key ID: E3E704F097737136
19 changed files with 431 additions and 162 deletions

View File

@ -19,9 +19,15 @@ import PropTypes from 'prop-types'
import useFetch from 'client/hooks/useFetch'
import { SubmitButton } from 'client/components/FormControl'
const Action = memo(({ handleClick, icon, cy, ...props }) => {
const Action = memo(({
cy,
handleClick,
icon,
stopPropagation,
...props
}) => {
const { fetchRequest, data, loading } = useFetch(
() => Promise.resolve(handleClick?.())
e => Promise.resolve(handleClick?.(e))
)
return (
@ -30,7 +36,10 @@ const Action = memo(({ handleClick, icon, cy, ...props }) => {
icon={!!icon}
isSubmitting={loading}
label={icon}
onClick={fetchRequest}
onClick={evt => {
stopPropagation && evt?.stopPropagation?.()
fetchRequest()
}}
disabled={!!data}
{...props}
/>
@ -38,9 +47,10 @@ const Action = memo(({ handleClick, icon, cy, ...props }) => {
})
Action.propTypes = {
cy: PropTypes.string,
handleClick: PropTypes.func.isRequired,
icon: PropTypes.node.isRequired,
cy: PropTypes.string
stopPropagation: PropTypes.bool
}
Action.defaultProps = {

View File

@ -28,17 +28,24 @@ const Multiple = ({ tags, limitTags = 1 }) => {
const more = tags.length - limitTags
const Tags = tags.splice(0, limitTags).map(tag => (
<StatusChip key={tag} text={tag} />
))
const Tags = tags
.splice(0, limitTags)
.map((tag, idx) => <StatusChip key={`${idx}-${tag}`} text={tag} />)
return (
<>
{Tags}
{more > 0 && (
<Tooltip arrow
title={tags.map(tag => (
<Typography key={tag} variant='subtitle2'>{tag}</Typography>
<Tooltip
arrow
title={tags.map((tag, idx) => (
<Typography
key={`${idx}-${tag}`}
variant='subtitle2'
style={{ height: 'max-content' }}
>
{tag}
</Typography>
))}
>
<span style={{ marginLeft: 6 }}>

View File

@ -23,9 +23,7 @@ import { prettyBytes } from 'client/utils'
const useStyles = makeStyles(theme => ({
root: {
padding: '1em'
},
grid: {
marginBlock: '0.8em',
padding: '1em',
display: 'grid',
gap: '1em',
@ -80,30 +78,28 @@ const VmCapacityTab = data => {
].filter(Boolean)
return (
<div className={classes.root}>
<Paper variant='outlined' className={classes.grid}>
<div className={classes.actions}>
<Action
cy='resize'
icon={false}
label={'Resize'}
size='small'
color='secondary'
handleClick={() => undefined}
/>
<Paper variant='outlined' className={classes.root}>
<div className={classes.actions}>
<Action
cy='resize'
icon={false}
label={'Resize'}
size='small'
color='secondary'
handleClick={() => undefined}
/>
</div>
{capacity.map(({ key, value }) => (
<div key={key} className={classes.item}>
<Typography className={classes.title} noWrap title={key}>
{key}
</Typography>
<Typography variant='body2' noWrap title={value}>
{value}
</Typography>
</div>
{capacity.map(({ key, value }) => (
<div key={key} className={classes.item}>
<Typography className={classes.title} noWrap title={key}>
{key}
</Typography>
<Typography variant='body2' noWrap title={value}>
{value}
</Typography>
</div>
))}
</Paper>
</div>
))}
</Paper>
)
}

View File

@ -28,7 +28,7 @@ const VmInfoTab = ({ tabProps, ...data }) => {
display: 'grid',
gap: '1em',
gridTemplateColumns: 'repeat(auto-fit, minmax(480px, 1fr))',
padding: '1em'
padding: '0.8em'
}}>
{tabProps?.information_panel?.enabled &&
<Information {...data} />

View File

@ -18,69 +18,159 @@ import * as React from 'react'
import PropTypes from 'prop-types'
import { Trash } from 'iconoir-react'
import { Typography } from '@material-ui/core'
import {
withStyles,
makeStyles,
Typography,
Accordion,
AccordionSummary as MAccordionSummary,
AccordionDetails,
useMediaQuery,
Paper
} from '@material-ui/core'
// import { useVmApi } from 'client/features/One'
import { useVmApi } from 'client/features/One'
import { Action } from 'client/components/Cards/SelectCard'
import { StatusChip } from 'client/components/Status'
import { rowStyles } from 'client/components/Tables/styles'
import Multiple from 'client/components/Tables/Vms/multiple'
import { VM_ACTIONS } from 'client/constants'
import { T, VM_ACTIONS } from 'client/constants'
import { Tr } from 'client/components/HOC'
const NetworkItem = ({ nic = {}, actions }) => {
const classes = rowStyles()
const AccordionSummary = withStyles({
root: {
backgroundColor: 'rgba(0, 0, 0, .03)',
borderBottom: '1px solid rgba(0, 0, 0, .125)',
marginBottom: -1,
minHeight: 56,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, .07)'
},
'&$expanded': {
backgroundColor: 'rgba(0, 0, 0, .07)',
minHeight: 56
}
},
content: {
overflow: 'hidden',
'&$expanded': {
margin: '12px 0'
}
},
expanded: {}
})(MAccordionSummary)
const {
NIC_ID,
NETWORK = '-',
BRIDGE,
IP,
MAC,
PCI_ID,
ALIAS
} = nic
const useStyles = makeStyles(({
row: {
width: '100%',
display: 'flex',
alignItems: 'center',
flexWrap: 'nowrap'
},
labels: {
display: 'inline-flex',
gap: '0.5em',
alignItems: 'center'
},
details: {
marginLeft: '1em',
flexDirection: 'column',
gap: '0.5em'
},
securityGroups: {
display: 'flex',
flexDirection: 'column',
gap: '0.5em',
padding: '0.8em'
}
}))
const NetworkItem = ({ vmId, nic = {}, actions }) => {
const classes = useStyles()
const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm'))
const { detachNic, getVm } = useVmApi()
const { NIC_ID, NETWORK = '-', BRIDGE, IP, MAC, PCI_ID, ALIAS, SECURITY_GROUPS } = nic
const hasDetails = React.useMemo(() =>
!!ALIAS.length || !!SECURITY_GROUPS?.length,
[ALIAS.length, SECURITY_GROUPS?.length]
)
const detachAction = () => actions.includes(VM_ACTIONS.DETACH_NIC) && (
<Action
cy={`${VM_ACTIONS.DETACH_NIC}-${NIC_ID}`}
icon={<Trash size={18} />}
handleClick={() => undefined}
stopPropagation
handleClick={async () => {
const response = await detachNic(vmId, NIC_ID)
String(response) === String(vmId) && getVm(vmId)
}}
/>
)
const renderLabels = labels => labels
?.filter(Boolean)
?.map(label => (
<StatusChip key={label} text={label} style={{ marginInline: '0.5em' }}/>
))
return (
<div className={classes.root}>
<div className={classes.main}>
<div style={{ borderBottom: ALIAS.length ? '1px solid #c6c6c6' : '' }}>
<Typography className={classes.titleText}>
<Accordion variant='outlined'>
<AccordionSummary>
<div className={classes.row}>
<Typography noWrap>
{`${NIC_ID} | ${NETWORK}`}
{renderLabels([IP, MAC, BRIDGE && `BRIDGE - ${BRIDGE}`, PCI_ID])}
{detachAction()}
</Typography>
<span className={classes.labels}>
<Multiple
limitTags={isMobile ? 1 : 4}
tags={[IP, MAC, BRIDGE && `BRIDGE - ${BRIDGE}`, PCI_ID].filter(Boolean)}
/>
</span>
{!isMobile && detachAction()}
</div>
<div style={{ marginLeft: '1em' }}>
</AccordionSummary>
{hasDetails && (
<AccordionDetails className={classes.details}>
{ALIAS?.map(({ NIC_ID, NETWORK = '-', BRIDGE, IP, MAC }) => (
<Typography variant='body2' key={NIC_ID}>
{`${NIC_ID} | ${NETWORK}`}
{renderLabels([IP, MAC, BRIDGE && `BRIDGE - ${BRIDGE}`])}
{detachAction()}
</Typography>
<div key={NIC_ID} className={classes.row}>
<Typography noWrap variant='body2'>
{`Alias ${NIC_ID} | ${NETWORK}`}
</Typography>
<span className={classes.labels}>
<Multiple
limitTags={isMobile ? 1 : 4}
tags={[IP, MAC, BRIDGE && `BRIDGE - ${BRIDGE}`].filter(Boolean)}
/>
</span>
{!isMobile && detachAction()}
</div>
))}
</div>
</div>
</div>
{!!SECURITY_GROUPS?.length && (
<Paper variant='outlined' className={classes.securityGroups}>
<Typography variant='body1'>{Tr(T.SecurityGroups)}</Typography>
{SECURITY_GROUPS
?.map(({ ID, NAME, PROTOCOL, RULE_TYPE, ICMP_TYPE, RANGE, NETWORK_ID }, idx) => (
<div key={`${idx}-${NAME}`} className={classes.row}>
<Typography noWrap variant='body2'>
{`${ID} | ${NAME}`}
</Typography>
<span className={classes.labels}>
<Multiple
limitTags={isMobile ? 2 : 5}
tags={[PROTOCOL, RULE_TYPE, RANGE, NETWORK_ID, ICMP_TYPE].filter(Boolean)}
/>
</span>
</div>
))}
</Paper>
)}
</AccordionDetails>
)}
</Accordion>
)
}
NetworkItem.propTypes = {
nic: PropTypes.object,
actions: PropTypes.arrayOf(PropTypes.string)
actions: PropTypes.arrayOf(PropTypes.string),
vmId: PropTypes.string,
nic: PropTypes.object
}
NetworkItem.displayName = 'NetworkItem'

View File

@ -19,7 +19,7 @@ import PropTypes from 'prop-types'
import NetworkItem from 'client/components/Tabs/Vm/Network/Item'
const NetworkList = ({ nics, actions }) => (
const NetworkList = ({ vmId, nics, actions }) => (
<div style={{
display: 'flex',
flexDirection: 'column',
@ -27,14 +27,15 @@ const NetworkList = ({ nics, actions }) => (
paddingBlock: '0.8em'
}}>
{nics.map((nic, idx) => (
<NetworkItem key={idx} nic={nic} actions={actions} />
<NetworkItem key={idx} vmId={vmId} nic={nic} actions={actions} />
))}
</div>
)
NetworkList.propTypes = {
nics: PropTypes.array,
actions: PropTypes.object
actions: PropTypes.arrayOf(PropTypes.string),
vmId: PropTypes.string,
nics: PropTypes.array
}
NetworkList.displayName = 'NetworkList'

View File

@ -1,50 +0,0 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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 jsdoc/require-jsdoc */
import * as React from 'react'
import * as VirtualMachine from 'client/models/VirtualMachine'
const VmNetworkTab = data => {
const nics = VirtualMachine.getNics(data, true)
// const { nics, alias } = VirtualMachine.splitNicAlias(data)
console.log(nics)
return (
<div>
<div>
<p>VM NICS</p>
{nics.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-', PCI_ID = '', ALIAS }) => (
<div key={NIC_ID}>
<p>
{`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC} | ${PCI_ID}`}
</p>
{ALIAS?.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-' }) => (
<p key={NIC_ID} style={{ marginLeft: '1em' }}>
{`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC}`}
</p>
))}
</div>
))}
</div>
</div>
)
}
VmNetworkTab.displayName = 'VmNetworkTab'
export default VmNetworkTab

View File

@ -25,7 +25,11 @@ import * as Helper from 'client/models/Helper'
const VmNetworkTab = ({ tabProps, ...vm }) => {
const { actions = [] } = tabProps
const nics = VirtualMachine.getNics(vm, { groupAlias: true })
const nics = VirtualMachine.getNics(vm, {
groupAlias: true,
securityGroupsFromTemplate: true
})
const hypervisor = VirtualMachine.getHypervisor(vm)
const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor)

View File

@ -21,7 +21,7 @@ import {
DatabaseSettings, Folder, ModernTv,
Trash, SaveActionFloppy, Camera, Expand
} from 'iconoir-react'
import { Typography } from '@material-ui/core'
import { Typography, Paper } from '@material-ui/core'
// import { useVmApi } from 'client/features/One'
import { Action } from 'client/components/Cards/SelectCard'
@ -70,7 +70,7 @@ const StorageItem = ({ disk, actions = [] }) => {
])].filter(Boolean)
return (
<div className={classes.root}>
<Paper variant='outlined' className={classes.root}>
<div className={classes.main}>
<div className={classes.title}>
<Typography component='span'>
@ -136,7 +136,7 @@ const StorageItem = ({ disk, actions = [] }) => {
)}
</div>
)}
</div>
</Paper>
)
}

View File

@ -20,12 +20,7 @@ import PropTypes from 'prop-types'
import StorageItem from 'client/components/Tabs/Vm/Storage/Item'
const StorageList = ({ disks, actions }) => (
<div style={{
display: 'grid',
gap: '1em',
gridTemplateColumns: 'repeat(auto-fill, minmax(49%, 1fr))',
paddingBlock: '0.8em'
}}>
<div style={{ display: 'grid', gap: '1em', paddingBlock: '0.8em' }}>
{disks.map((disk, idx) => (
<StorageItem
key={idx}
@ -38,7 +33,7 @@ const StorageList = ({ disks, actions }) => (
StorageList.propTypes = {
disks: PropTypes.array,
actions: PropTypes.object
actions: PropTypes.arrayOf(PropTypes.string)
}
StorageList.displayName = 'StorageList'

View File

@ -20,6 +20,7 @@ module.exports = {
Next: 'Next',
SortBy: 'Sort by',
Filter: 'Filter',
All: 'All',
/* actions */
Accept: 'Accept',
@ -123,6 +124,8 @@ module.exports = {
VirtualNetworks: 'Virtual networks',
NetworkTopology: 'Network topology',
NetworksTopologies: 'Networks topologies',
SecurityGroup: 'Security group',
SecurityGroups: 'Security groups',
/* sections - storage */
Datastore: 'Datastore',
@ -188,5 +191,14 @@ module.exports = {
/* ownership */
Ownership: 'Ownership',
Owner: 'Owner',
Other: 'Other'
Other: 'Other',
/* security group schema */
TCP: 'TCP',
UDP: 'UDP',
ICMP: 'ICMP',
ICMPV6: 'ICMPv6',
IPSEC: 'IPsec',
Outbound: 'Outbound',
Inbound: 'Inbound'
}

View File

@ -140,13 +140,13 @@ export const eventApi = createAsyncThunk(
*/
export const eventUpdateResourceState = createAsyncThunk(
'socket/event-state',
({ data } = {}) => {
(data = {}) => {
const { name, value } = getResourceFromEventState(data)
return { type: name, data: value }
},
{
condition: ({ data } = {}) => {
condition: (data = {}) => {
const { name, value } = getResourceFromEventState(data)
return (

View File

@ -41,3 +41,4 @@ export const terminateVm = createAction(
)
export const changePermissions = createAction('vm/chmod', vmService.changePermissions)
export const detachNic = createAction('vm/detach/nic', vmService.detachNic)

View File

@ -36,6 +36,8 @@ export const useVmApi = () => {
getVm: id => unwrapDispatch(actions.getVm({ id })),
getVms: options => unwrapDispatch(actions.getVms(options)),
terminateVm: id => unwrapDispatch(actions.terminateVm({ id })),
changePermissions: (id, data) => unwrapDispatch(actions.changePermissions({ id, data }))
changePermissions: (id, permissions) =>
unwrapDispatch(actions.changePermissions({ id, permissions })),
detachNic: (id, nic) => unwrapDispatch(actions.detachNic({ id, nic }))
}
}

View File

@ -102,7 +102,7 @@ export const vmService = ({
* Changes the permission bits of a virtual machine.
*
* @param {object} params - Request parameters
* @param {string} params.id - Virtual machine id
* @param {string|number} params.id - Virtual machine id
* @param {{
* ownerUse: number,
* ownerManage: number,
@ -113,19 +113,40 @@ export const vmService = ({
* otherUse: number,
* otherManage: number,
* otherAdmin: number
* }} params.data - Permissions data
* @returns {Response} Response
* }} params.permissions - Permissions data
* @returns {number} Virtual machine id
* @throws Fails when response isn't code 200
*/
changePermissions: async ({ id, data }) => {
changePermissions: async ({ id, permissions }) => {
const name = Actions.VM_CHMOD
const command = { name, ...Commands[name] }
const config = requestConfig({ id, data }, command)
const config = requestConfig({ id, ...permissions }, command)
const res = await RestClient.request(config)
if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data
return res
return res?.data
},
/**
* Detaches a network interface from a virtual machine.
*
* @param {object} params - Request parameters
* @param {string|number} params.id - Virtual machine id
* @param {string|number} params.nic - NIC id
* @returns {number} Virtual machine id
* @throws Fails when response isn't code 200
*/
detachNic: async ({ id, nic }) => {
const name = Actions.VM_NIC_DETACH
const command = { name, ...Commands[name] }
const config = requestConfig({ id, nic }, command)
const res = await RestClient.request(config)
if (!res?.id || res?.id !== httpCodes.ok.id) throw res?.data
return res?.data
}
})

View File

@ -96,7 +96,8 @@ const useFetch = (request, socket) => {
useEffect(() => {
isFetched && socket?.connect({
dataFromFetch: state.data,
callback: socketData => dispatch({ type: ACTIONS.SUCCESS, payload: socketData })
callback: socketData =>
socketData && dispatch({ type: ACTIONS.SUCCESS, payload: socketData })
})
return () => {

View File

@ -64,7 +64,7 @@ const useSocket = () => {
dispatch(updateResourceFromFetch({ data: dataFromFetch, resource }))
})
socket.on(SOCKETS.HOOKS, data => {
socket.on(SOCKETS.HOOKS, ({ data } = {}) => {
// update the list on redux state
dispatch(eventUpdateResourceState(data))
// return data from event

View File

@ -0,0 +1,160 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2021, 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 { T } from 'client/constants'
/**
* ICMP Codes for each ICMP type as in:
* http://www.iana.org/assignments/icmp-parameters/
*
* @enum {string} Security group ICMP type
*/
const ICMP_STRING = {
'': 'All',
0: '0: Echo Reply',
3: '3: Destination Unreachable',
4: '4: Source Quench', // Deprecated
5: '5: Redirect',
6: '6: Alternate Host Address', // Deprecated
8: '8: Echo',
9: '9: Router Advertisement',
10: '10: Router Selection',
11: '11: Time Exceeded',
12: '12: Parameter Problem',
13: '13: Timestamp',
14: '14: Timestamp Reply',
15: '15: Information Request', // Deprecated
16: '16: Information Reply', // Deprecated
17: '17: Address Mask Request', // Deprecated
18: '18: Address Mask Reply', // Deprecated
30: '30: Traceroute', // Deprecated
31: '31: Datagram Conversion Error', // Deprecated
32: '32: Mobile Host Redirect', // Deprecated
33: '33: IPv6 Where-Are-You', // Deprecated
34: '34: IPv6 I-Am-Here', // Deprecated
35: '35: Mobile Registration Request', // Deprecated
36: '36: Mobile Registration Reply', // Deprecated
37: '37: Domain Name Request', // Deprecated
38: '38: Domain Name Reply', // Deprecated
39: '39: SKIP', // Deprecated
40: '40: Photuris',
41: '41: ICMP messages utilized by experimental mobility protocols such as Seamoby',
253: '253: RFC3692-style Experiment 1',
254: '254: RFC3692-style Experiment 2'
}
/**
* ICMPv6 Codes for each ICMPv6 type as in:
* http://www.iana.org/assignments/icmpv6-parameters/
*
* @enum {string} Security group ICMP v6 type
*/
const ICMP_V6_STRING = {
'': 'All',
1: '1: Destination Unreachable',
2: '2/0: Packet too big',
3: '3: Time exceeded',
4: '4: Parameter problem',
128: '128/0: Echo request',
129: '129/0: Echo reply'
}
/** @enum {string} Security group protocol */
const PROTOCOL_STRING = {
TCP: T.TCP,
UDP: T.UDP,
ICMP: T.ICMP,
ICMPV6: T.ICMPV6,
IPSEC: T.IPSEC,
ALL: T.All
}
/** @enum {string} Security group rule type */
const RULE_TYPE_STRING = {
OUTBOUND: T.Outbound,
INBOUND: T.Inbound
}
/**
* Converts a security group attributes into a readable format.
*
* @param {object} securityGroup - Security group
* @param {number|string} securityGroup.SECURITY_GROUP_ID - Id
* @param {string} securityGroup.SECURITY_GROUP_NAME - Name
* @param {string} securityGroup.PROTOCOL - Protocol
* @param {string} securityGroup.RULE_TYPE - Rule type
* @param {number|string} securityGroup.ICMP_TYPE - ICMP type
* @param {number|string} securityGroup.ICMPv6_TYPE - ICMP v6 type
* @param {number|string} securityGroup.RANGE - Range
* @param {number|string} securityGroup.NETWORK_ID - Network id
* @param {number|string} securityGroup.SIZE - Network size
* @param {string} securityGroup.IP - Network IP
* @param {string} securityGroup.MAC - Network MAC
* @returns {{
* SECURITY_GROUP_ID: number|string,
* SECURITY_GROUP_NAME: string,
* PROTOCOL: PROTOCOL_STRING,
* RULE_TYPE: RULE_TYPE_STRING,
* ICMP_TYPE: ICMP_STRING,
* ICMPv6_TYPE: ICMP_V6_STRING,
* RANGE: string,
* NETWORK_ID: number|string,
* SIZE: number|string,
* IP: string,
* MAC: string
* }} Readable attributes
*/
export const prettySecurityGroup = ({
SECURITY_GROUP_ID: ID,
SECURITY_GROUP_NAME: NAME,
PROTOCOL: protocol,
RULE_TYPE: ruleType,
ICMP_TYPE: icmpType,
ICMPv6_TYPE: icmpv6Type,
RANGE: range,
NETWORK_ID,
SIZE,
IP,
MAC
}) => ({
ID,
NAME,
PROTOCOL: PROTOCOL_STRING[String(protocol).toUpperCase()],
RULE_TYPE: RULE_TYPE_STRING[String(ruleType).toUpperCase()],
ICMP_TYPE: ICMP_STRING[+icmpType] ?? '',
ICMPv6_TYPE: ICMP_V6_STRING[+icmpv6Type] ?? '',
RANGE: range || T.All,
NETWORK_ID,
SIZE,
IP,
MAC
})
/**
* Selects security groups from OpenNebula resource.
*
* @param {object} resource - OpenNebula resource
* @param {string|string[]} securityGroups - List of security group
* @returns {object[]} List of security groups
*/
export const getSecurityGroupsFromResource = (resource, securityGroups) => {
const rules = [resource?.TEMPLATE?.SECURITY_GROUP_RULE ?? []].flat()
const groups = Array.isArray(securityGroups)
? securityGroups
: securityGroups.split(',')
return rules.filter(({ SECURITY_GROUP_ID }) => groups.includes?.(SECURITY_GROUP_ID))
}

View File

@ -15,6 +15,7 @@
* ------------------------------------------------------------------------- */
import { STATES, VM_STATES, VM_LCM_STATES, StateInfo } from 'client/constants'
import { getSecurityGroupsFromResource, prettySecurityGroup } from 'client/models/SecurityGroup'
/* const EXTERNAL_IP_ATTRS = [
'GUEST_IP',
@ -124,11 +125,14 @@ export const getDisks = vm => {
/**
* @param {object} vm - Virtual machine
* @param {object} [options] - Options
* @param {boolean} [options.groupAlias] - Map ALIAS_IDS attribute with NIC_ALIAS
* @returns {Array} List of nics from resource
* @param {boolean} [options.groupAlias]
* - Create ALIAS attribute with result to mapping NIC_ALIAS and ALIAS_IDS
* @param {boolean} [options.securityGroupsFromTemplate]
* - Create SECURITY_GROUPS attribute with rules from TEMPLATE.SECURITY_GROUP_RULE
* @returns {object[]} List of nics from resource
*/
export const getNics = (vm, options = {}) => {
const { groupAlias = false } = options
const { groupAlias = false, securityGroupsFromTemplate = false } = options
const { TEMPLATE = {}, MONITORING = {} } = vm ?? {}
const { NIC = [], NIC_ALIAS = [], PCI = [] } = TEMPLATE
@ -138,14 +142,29 @@ export const getNics = (vm, options = {}) => {
.filter(Boolean)
.map(ip => ({ NIC_ID: '-', IP: ip, NETWORK: 'Additional IP', BRIDGE: '-' }))
const nics = [NIC, PCI, extraIps].flat().filter(Boolean)
let nics = [NIC, NIC_ALIAS, PCI, extraIps].flat().filter(Boolean)
return groupAlias
? nics.map(({ ALIAS_IDS, ...nic }) => ({
if (groupAlias) {
nics = nics
.filter(({ PARENT }) => PARENT === undefined)
.map(({ ALIAS_IDS, ...nic }) => ({
...nic,
ALIAS: [NIC_ALIAS]
.flat()
.filter(({ NIC_ID }) => ALIAS_IDS?.split(',')?.includes?.(NIC_ID))
}))
}
if (securityGroupsFromTemplate) {
nics = nics.map(({ SECURITY_GROUPS, ...nic }) => ({
...nic,
ALIAS: NIC_ALIAS?.filter(({ NIC_ID }) => ALIAS_IDS?.includes(NIC_ID))
SECURITY_GROUPS:
getSecurityGroupsFromResource(vm, SECURITY_GROUPS)
?.map(prettySecurityGroup)
}))
: nics.concat(NIC_ALIAS)
}
return nics
}
/**