1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00
This commit is contained in:
Sergio Betanzos 2021-06-22 09:18:25 +02:00
parent 185b761303
commit 4c8ed9da76
No known key found for this signature in database
GPG Key ID: E3E704F097737136
14 changed files with 615 additions and 94 deletions

View File

@ -0,0 +1,44 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { Tooltip, Typography } from '@material-ui/core'
const StatusCircle = ({ color, tooltip, size }) => (
<Tooltip arrow placement='right-end'
title={<Typography variant='subtitle2'>{tooltip}</Typography>}
>
<svg
viewBox='0 0 100 100'
version='1.1'
width={size}
height={size}
aria-hidden='true'
style={{
color,
fill: 'currentColor',
verticalAlign: 'text-bottom'
}}
>
<circle cx='50' cy='50' r='50' />
</svg>
</Tooltip>
)
StatusCircle.propTypes = {
tooltip: PropTypes.string,
color: PropTypes.string,
size: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}
StatusCircle.defaultProps = {
tooltip: undefined,
color: undefined,
size: 12
}
StatusCircle.displayName = 'StatusCircle'
export default StatusCircle

View File

@ -1,9 +1,11 @@
import StatusBadge from 'client/components/Status/Badge'
import StatusChip from 'client/components/Status/Chip'
import StatusCircle from 'client/components/Status/Circle'
import LinearProgressWithLabel from 'client/components/Status/LinearProgressWithLabel'
export {
StatusBadge,
StatusChip,
StatusCircle,
LinearProgressWithLabel
}

View File

@ -1,63 +1,8 @@
import * as React from 'react'
import { StatusBadge } from 'client/components/Status'
import * as VirtualMachineModel from 'client/models/VirtualMachine'
export default [
/* {
id: 'selection',
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox.
// Pagination is a problem since this will select all rows even though
// not all rows are on the current page.
// The solution should be server side pagination.
// For one, the clients should not download all rows in most cases.
// The client should only download data for the current page.
// In that case, getToggleAllRowsSelectedProps works fine.
Header: ({ getToggleAllRowsSelectedProps }) => (
<CheckboxCell {...getToggleAllRowsSelectedProps()} />
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<CheckboxCell {...row.getToggleRowSelectedProps()} />
)
}, */
{
Header: '',
id: 'STATE',
width: 50,
accessor: row => {
const state = VirtualMachineModel.getState(row)
return (
<StatusBadge
title={state?.name}
stateColor={state?.color}
customTransform='translate(150%, 50%)'
/>
)
}
// Cell: ({ value: { name, color } = {} }) => name && (
// <StatusChip stateColor={color} text={name} />
// ),
// Filter: ({ column }) => (
// <SelectFilter column={column} accessorOption='name' />
// ),
// filter: (rows, id, filterValue) =>
// rows.filter(row => row.values[id]?.name === filterValue)
},
{ Header: '#', accessor: 'ID', width: 45 },
{ Header: 'Name', accessor: 'NAME' },
{
Header: 'Owner/Group',
accessor: row => `${row.UNAME}/${row.GNAME}`
},
{
Header: 'Ips',
accessor: row => VirtualMachineModel.getIps(row).join(',')
// Cell: ({ value }) => value.map(nic => (
// <StatusChip key={nic} stateColor={Colors.debug.light} text={nic} />
// ))
}
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'STATE' },
{ Header: '', accessor: 'LCM_STATE' },
{ Header: '', accessor: 'UID' },
{ Header: '', accessor: 'GID' }
]

View File

@ -0,0 +1,207 @@
import React, { useEffect } from 'react'
import { LinearProgress, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core'
import Tabs from 'client/components/Tabs'
import { StatusBadge } from 'client/components/Status'
import { useFetch, useSocket } from 'client/hooks'
import { useVmApi } from 'client/features/One'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
import { prettyBytes } from 'client/utils'
const NavArrowDown = <span style={{ writingMode: 'vertical-rl' }}>{'>'}</span>
const VmDetail = ({ id }) => {
const { getVm } = useVmApi()
const { getHooksSocketTemporal } = useSocket()
const socketTemporal = getHooksSocketTemporal({ resource: 'vm', id })
const { data, fetchRequest, loading, error } = useFetch(getVm)
const isLoading = (!data && !error) || loading
useEffect(() => {
fetchRequest(id)
}, [id])
useEffect(() => {
if (!isLoading && data) {
console.log('connect???')
socketTemporal.connect(console.log)
}
return () => {
socketTemporal.disconnect()
}
}, [isLoading, data])
if (isLoading) {
return <LinearProgress color='secondary' style={{ width: '100%' }} />
}
if (error) {
return <div>{error}</div>
}
const { ID, NAME, UNAME, GNAME, RESCHED, STIME, ETIME, LOCK, DEPLOY_ID, TEMPLATE, USER_TEMPLATE } = data
const isVCenter = VirtualMachine.isVCenter(data)
const { name: stateName, color: stateColor } = VirtualMachine.getState(data)
const { HID: hostId, HOSTNAME: hostname = '--', CID: clusterId } = VirtualMachine.getLastHistory(data)
const clusterName = clusterId === '-1' ? 'default' : '--' // TODO: get from cluster list
const ips = VirtualMachine.getIps(data)
const { nics, alias } = VirtualMachine.splitNicAlias(data)
const disks = VirtualMachine.getDisks(data)
const tabs = [
{
name: 'info',
renderContent: (
<div>
<div>
<StatusBadge
title={stateName}
stateColor={stateColor}
customTransform='translate(150%, 50%)'
/>
<span style={{ marginLeft: 20 }}>
{`#${ID} - ${NAME}`}
</span>
</div>
<div>
<p>Owner: {UNAME}</p>
<p>Group: {GNAME}</p>
<p>Reschedule: {Helper.booleanToString(+RESCHED)}</p>
<p>Locked: {Helper.levelLockToString(LOCK?.LOCKED)}</p>
<p>IP: {ips.join(', ') || '--'}</p>
<p>Start time: {Helper.timeToString(STIME)}</p>
<p>End time: {Helper.timeToString(ETIME)}</p>
<p>Host: {`#${hostId} ${hostname}`}</p>
<p>Cluster: {`#${clusterId} ${clusterName}`}</p>
<p>Deploy ID: {DEPLOY_ID}</p>
</div>
</div>
)
},
{
name: 'capacity',
renderContent: (
<div>
<p>Physical CPU: {TEMPLATE?.CPU}</p>
<p>Virtual CPU: {TEMPLATE?.VCPU ?? '-'}</p>
{isVCenter && (
<p>Virtual Cores: {`
Cores x ${TEMPLATE?.TOPOLOGY?.CORES || '-'} |
Sockets ${TEMPLATE?.TOPOLOGY?.SOCKETS || '-'}
`}</p>
)}
<p>Memory: {prettyBytes(+TEMPLATE?.MEMORY, 'MB')}</p>
<p>Cost / CPU: {TEMPLATE?.CPU_COST}</p>
<p>Cost / MByte: {TEMPLATE?.MEMORY_COST}</p>
</div>
)
},
{
name: 'storage',
renderContent: (
<div>
<p>VM DISKS</p>
{disks.map(({
DISK_ID,
DATASTORE = '-',
TARGET = '-',
IMAGE,
TYPE,
FORMAT,
SIZE,
MONITOR_SIZE,
READONLY,
SAVE = 'No',
CLONE
}) => {
const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-'
const monitorSize = +MONITOR_SIZE ? prettyBytes(+MONITOR_SIZE, 'MB') : '-'
const type = String(TYPE).toLowerCase()
const image = IMAGE ?? ({
fs: `${FORMAT} - ${size}`,
swap: size
}[type])
return (
<p key={DISK_ID}>
{`${DISK_ID} | ${DATASTORE} | ${TARGET} | ${image} | ${monitorSize}/${size} | ${type} | ${READONLY} | ${SAVE} | ${CLONE}`}
</p>
)
})}
</div>
)
},
{
name: 'network',
renderContent: (
<div>
<div>
<p>VM NICS</p>
{nics.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-', PCI_ID = '' }) => (
<p key={NIC_ID}>
{`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC} | ${PCI_ID}`}
</p>
))}
</div>
<hr />
<div>
<p>VM ALIAS</p>
{alias.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-' }) => (
<p key={NIC_ID}>
{`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC}`}
</p>
))}
</div>
</div>
)
},
{
name: 'template',
renderContent: (
<div>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={NavArrowDown}>
User Template
</AccordionSummary>
<AccordionDetails>
<pre>
<code>
{JSON.stringify(USER_TEMPLATE, null, 2)}
</code>
</pre>
</AccordionDetails>
</Accordion>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={NavArrowDown}>
Template
</AccordionSummary>
<AccordionDetails>
<pre>
<code>
{JSON.stringify(TEMPLATE, null, 2)}
</code>
</pre>
</AccordionDetails>
</Accordion>
</div>
)
}
]
return (
<Tabs tabs={tabs} />
)
}
export default VmDetail

View File

@ -5,16 +5,17 @@ import { useFetch } from 'client/hooks'
import { useVm, useVmApi } from 'client/features/One'
import { EnhancedTable } from 'client/components/Tables'
import { VirtualMachineCard } from 'client/components/Cards'
import Columns from 'client/components/Tables/Vms/columns'
import VmColumns from 'client/components/Tables/Vms/columns'
import VmRow from 'client/components/Tables/Vms/row'
import VmDetail from 'client/components/Tables/Vms/detail'
const INITIAL_ELEMENT = 0
const NUMBER_OF_INTERVAL = 6
const NUMBER_OF_INTERVAL = 10
const VmsTable = () => {
const [[start, end], setPage] = useState([INITIAL_ELEMENT, -NUMBER_OF_INTERVAL])
const columns = React.useMemo(() => Columns, [])
const columns = React.useMemo(() => VmColumns, [])
const vms = useVm()
const { getVms } = useVmApi()
@ -44,9 +45,11 @@ const VmsTable = () => {
pageSize={NUMBER_OF_INTERVAL / 2}
isLoading={loading || reloading}
showPageCount={false}
getRowId={row => String(row.ID)}
RowComponent={VmRow}
renderDetail={row => <VmDetail id={row.ID} />}
canFetchMore={canFetchMore}
fetchMore={fetchMore}
MobileComponentRow={VirtualMachineCard}
/>
)
}

View File

@ -0,0 +1,40 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { Tooltip, Typography } from '@material-ui/core'
import { StatusChip } from 'client/components/Status'
const Multiple = ({ tags, limitTags = 1 }) => {
if (tags?.length === 0) {
return null
}
const more = tags.length - limitTags
const Tags = tags.splice(0, limitTags).map(tag => (
<StatusChip key={tag} text={tag} stateColor='#ececec' />
))
return [
...Tags,
(more > 0 && (
<Tooltip arrow
title={tags.map(tag => (
<Typography key={tag} variant='subtitle2'>{tag}</Typography>
))}
>
<span style={{ marginLeft: 6 }}>
{`+${more} more`}
</span>
</Tooltip>
))
]
}
Multiple.propTypes = {
tags: PropTypes.array,
limitTags: PropTypes.number
}
export default Multiple

View File

@ -0,0 +1,66 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { User, Group, Lock, HardDrive } from 'iconoir-react'
import { Typography } from '@material-ui/core'
import { StatusCircle } from 'client/components/Status'
import Multiple from 'client/components/Tables/Vms/multiple'
import { rowStyles } from 'client/components/Tables/styles'
import * as VirtualMachineModel from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
const Row = ({ value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, UNAME, GNAME, STIME, ETIME, LOCK } = value
const state = VirtualMachineModel.getState(value)
const ips = VirtualMachineModel.getIps(value)
const { HOSTNAME = '--' } = VirtualMachineModel.getLastHistory(value)
const time = Helper.timeFromMilliseconds(+ETIME || +STIME)
const timeAgo = `${+ETIME ? 'done' : 'started'} ${time.toRelative()}`
return (
<div {...props}>
<div>
<StatusCircle color={state?.color} tooltip={state?.name} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
{LOCK && <Lock size={20} />}
</Typography>
<div className={classes.caption}>
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
</span>
<span>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
<span>
<HardDrive size={16} />
<span>{` ${HOSTNAME}`}</span>
</span>
</div>
</div>
<div className={classes.secondary}>
<Multiple tags={ips} limitTags={1} />
</div>
</div>
)
}
Row.propTypes = {
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func
}
export default Row

View File

@ -1,13 +1,20 @@
import DatastoresTable from 'client/components/Tables/Datastores'
import EnhancedTable from 'client/components/Tables/Enhanced'
import HostsTable from 'client/components/Tables/Hosts'
import ImagesTable from 'client/components/Tables/Images'
import MarketplaceAppsTable from 'client/components/Tables/MarketplaceApps'
import MarketplacesTable from 'client/components/Tables/Marketplaces'
import VirtualizedTable from 'client/components/Tables/Virtualized'
import VmsTable from 'client/components/Tables/Vms'
export {
DatastoresTable,
EnhancedTable,
HostsTable,
VirtualizedTable,
DatastoresTable,
HostsTable,
ImagesTable,
MarketplaceAppsTable,
MarketplacesTable,
VmsTable
}

View File

@ -0,0 +1,40 @@
import { makeStyles } from '@material-ui/core'
export const rowStyles = makeStyles(
({ palette, typography, breakpoints }) => ({
main: {
flex: 'auto'
},
title: {
color: palette.text.primary,
display: 'flex',
alignItems: 'center'
},
labels: {
display: 'inline-flex',
gap: 6,
marginLeft: 6
},
caption: {
...typography.caption,
color: palette.text.secondary,
marginTop: 4,
display: 'flex',
gap: 8,
wordWrap: 'break-word'
},
secondary: {
width: '25%',
flexShrink: 0,
whiteSpace: 'nowrap',
textAlign: 'right',
[breakpoints.down('sm')]: {
display: 'none'
},
'& > *': {
flexShrink: 0,
whiteSpace: 'nowrap'
}
}
})
)

View File

@ -0,0 +1,69 @@
import React, { useState, useMemo, memo } from 'react'
import PropTypes from 'prop-types'
import { Tabs as MTabs, Tab as MTab } from '@material-ui/core'
const Content = memo(({ name, renderContent, hidden }) => (
<div key={`tab-${name}`}
style={{
padding: 2,
height: '100%',
overflow: 'auto',
display: hidden ? 'none' : 'block'
}}
>
{typeof renderContent === 'function' ? renderContent() : renderContent}
</div>
), (prev, next) => prev.hidden === next.hidden)
const Tabs = ({ tabs = [] }) => {
const [tabSelected, setTab] = useState(0)
const renderTabs = useMemo(() => (
<MTabs
value={tabSelected}
variant="scrollable"
scrollButtons='auto'
onChange={(_, tab) => setTab(tab)}
>
{tabs.map(({ value, name, icon: Icon }, idx) =>
<MTab
key={`tab-${name}`}
id={`tab-${name}`}
icon={Icon && <Icon />}
value={value ?? idx}
label={String(name).toUpperCase()}
/>
)}
</MTabs>
), [tabSelected])
const renderTabContent = useMemo(() =>
tabs.map((tabProps, idx) => {
const { name, value = idx } = tabProps
const hidden = tabSelected !== value
return <Content key={`tab-${name}`} {...tabProps} hidden={hidden} />
}), [tabSelected])
return (
<>
{renderTabs}
{renderTabContent}
</>
)
}
Tabs.displayName = 'Tabs'
Content.displayName = 'Content'
Content.propTypes = {
name: PropTypes.string,
renderContent: PropTypes.oneOfType([
PropTypes.object,
PropTypes.func
]),
hidden: PropTypes.bool
}
export default Tabs

View File

@ -13,10 +13,12 @@ export const BOOT_UNKNOWN = 'BOOT_UNKNOWN'
export const CANCEL = 'CANCEL'
export const CLEANUP_DELETE = 'CLEANUP_DELETE'
export const CLEANUP_RESUBMIT = 'CLEANUP_RESUBMIT'
export const CLONE = 'CLONE'
export const CLONING = 'CLONING'
export const CLONING_FAILURE = 'CLONING_FAILURE'
export const CONFIGURING = 'CONFIGURING'
export const COOLDOWN = 'COOLDOWN'
export const DELETE = 'DELETE'
export const DELETING = 'DELETING'
export const DEPLOYING = 'DEPLOYING'
export const DISABLED = 'DISABLED'
@ -60,6 +62,9 @@ export const HOTPLUG_SAVEAS_UNDEPLOYED = 'HOTPLUG_SAVEAS_UNDEPLOYED'
export const HOTPLUG_SNAPSHOT = 'HOTPLUG_SNAPSHOT'
export const INIT = 'INIT'
export const LCM_INIT = 'LCM_INIT'
export const LOCKED = 'LOCKED'
export const LOCKED_USED = 'LOCKED_USED'
export const LOCKED_USED_PERS = 'LOCKED_USED_PERS'
export const MIGRATE = 'MIGRATE'
export const MONITORED = 'MONITORED'
export const MONITORING_DISABLED = 'MONITORING_DISABLED'
@ -94,7 +99,9 @@ export const SHUTDOWN_POWEROFF = 'SHUTDOWN_POWEROFF'
export const SHUTDOWN_UNDEPLOY = 'SHUTDOWN_UNDEPLOY'
export const STOPPED = 'STOPPED'
export const SUSPENDED = 'SUSPENDED'
export const UNKNOWN = 'UNKNOWN'
export const UNDEPLOYED = 'UNDEPLOYED'
export const UNDEPLOYING = 'UNDEPLOYING'
export const UNKNOWN = 'UNKNOWN'
export const USED = 'USED'
export const USED_PERS = 'USED_PERS'
export const WARNING = 'WARNING'

View File

@ -1,6 +1,7 @@
module.exports = {
/* pagination / stepper */
Back: 'Back',
Previous: 'Previous',
Next: 'Next',
/* actions */
@ -41,6 +42,8 @@ module.exports = {
/* errors */
CannotConnectOneFlow: 'Cannot connect to OneFlow server',
CannotConnectOneProvision: 'Cannot connect to OneProvision server',
ErrorOneProvisionGUI: 'FireEdge is not correctly configured to operate the OneProvision GUI',
ContactYourAdmin: 'Please contact your system administrator',
NotFound: 'Not found',
None: 'None',
Empty: 'Empty',

View File

@ -0,0 +1,17 @@
import { DateTime } from 'luxon'
export const booleanToString = bool => bool ? 'Yes' : 'No'
export const timeToString = time =>
+time ? new Date(+time * 1000).toLocaleString() : '-'
export const timeFromMilliseconds = time =>
DateTime.fromMillis(+time * 1000)
export const levelLockToString = level => ({
0: 'None',
1: 'Use',
2: 'Manage',
3: 'Admin',
4: 'All'
}[level] || '-')

View File

@ -20,45 +20,116 @@ const NIC_ALIAS_IP_ATTRS = [
'VROUTER_IP6_ULA'
]
/**
* @param {Array} vms List of virtual machines
* @returns {Array} Clean list of vms with done state
*/
export const filterDoneVms = (vms = []) =>
vms.filter(({ STATE }) => VM_STATES[STATE]?.name !== STATES.DONE)
/**
* @param {Object} vm Virtual machine
* @returns {Object} Last history record from resource
*/
export const getLastHistory = vm => {
const history = vm?.HISTORY_RECORDS?.HISTORY ?? {}
return Array.isArray(history) ? history[history.length - 1] : history
}
/**
* @param {Object} vm Virtual machine
* @returns {String} Resource type: VR, FLOW or VM
*/
export const getType = vm => vm.TEMPLATE?.VROUTER_ID
? 'VR' : vm?.USER_TEMPLATE?.USER_TEMPLATE?.SERVICE_ID ? 'FLOW' : 'VM'
/**
* @param {Object} vm Virtual machine
* @returns {String} Resource hypervisor
*/
export const getHypervisor = vm => String(getLastHistory(vm)?.VM_MAD).toLowerCase()
/**
* @param {Object} vm Virtual machine
* @returns {Boolean} If the hypervisor is vCenter
*/
export const isVCenter = vm => getHypervisor(vm) === 'vcenter'
/**
* @type {{color: string, name: string, meaning: string}} StateInfo
*
* @param {Object} vm Virtual machine
* @returns {StateInfo} State information from resource
*/
export const getState = ({ STATE, LCM_STATE } = {}) => {
const state = VM_STATES[+STATE]
return state?.name === STATES.ACTIVE ? VM_LCM_STATES[+LCM_STATE] : state
}
export const getIps = ({ TEMPLATE = {} } = {}) => {
const { NIC = [], PCI = [] } = TEMPLATE
// TODO: add monitoring ips
/**
* @param {Object} vm Virtual machine
* @returns {Array} List of disks from resource
*/
export const getDisks = ({ TEMPLATE = {}, MONITORING = {}, ...vm } = {}) => {
const contextDisk = TEMPLATE.CONTEXT && !isVCenter(vm) && {
...TEMPLATE.CONTEXT,
IMAGE: 'CONTEXT',
DATASTORE: '-',
TYPE: '-',
READONLY: '-',
SAVE: '-',
CLONE: '-',
SAVE_AS: '-'
}
const nics = [NIC, PCI].flat()
const addMonitoringData = disk => ({
...disk,
// get monitoring data
MONITOR_SIZE: MONITORING.DISK_SIZE
?.find(({ ID }) => ID === disk.DISK_ID)?.SIZE || '-'
})
return nics
.map(nic => NIC_ALIAS_IP_ATTRS.map(attr => nic[attr]).filter(Boolean))
return [TEMPLATE.DISK, contextDisk]
.flat()
.filter(Boolean)
.map(addMonitoringData)
}
const getNicsFromMonitoring = ({ ID }) => {
const monitoringPool = {} // _getMonitoringPool()
const monitoringVM = monitoringPool[ID]
/**
* @param {Object} vm Virtual machine
* @returns {Array} List of nics from resource
*/
export const getNics = ({ TEMPLATE = {}, MONITORING = {} } = {}) => {
const { NIC = [], NIC_ALIAS = [], PCI = [] } = TEMPLATE
const { GUEST_IP, GUEST_IP_ADDRESSES = '' } = MONITORING
if (!monitoringPool || Object.keys(monitoringPool).length === 0 || !monitoringVM) return []
const extraIps = [GUEST_IP, ...GUEST_IP_ADDRESSES?.split(',')]
.filter(Boolean)
.map(ip => ({ NIC_ID: '-', IP: ip, NETWORK: 'Additional IP', BRIDGE: '-' }))
return EXTERNAL_IP_ATTRS.reduce(function (externalNics, attr) {
const monitoringValues = monitoringVM[attr]
if (monitoringValues) {
monitoringValues.split(',').forEach((_, ip) => {
const exists = externalNics.some(nic => nic.IP === ip)
if (!exists) {
externalNics.push({ NIC_ID: '_', IP: ip })
}
})
}
return externalNics
}, [])
return [NIC, NIC_ALIAS, PCI, extraIps].flat().filter(Boolean)
}
/**
* @param {Object} vm Virtual machine
* @returns {Array} List of ips from resource
*/
export const getIps = vm => {
const getIpsFromNic = nic => NIC_ALIAS_IP_ATTRS.map(attr => nic[attr]).filter(Boolean)
return getNics(vm).map(getIpsFromNic).flat()
}
/**
* @type {{nics: Array, alias: Array}} Nics&Alias
*
* @param {Object} vm Virtual machine
* @returns {Nics&Alias} Lists of nics and alias from resource
*/
export const splitNicAlias = vm => getNics(vm).reduce((result, nic) => {
result[nic?.PARENT !== undefined ? 'alias' : 'nics'].push(nic)
return result
}, { nics: [], alias: [] })