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

F OpenNebula/one#5422: Add styles to some vm tabs

This commit is contained in:
Sergio Betanzos 2021-07-07 18:16:05 +02:00
parent 93aa66d301
commit cb181ea413
No known key found for this signature in database
GPG Key ID: E3E704F097737136
27 changed files with 704 additions and 205 deletions

View File

@ -12,7 +12,7 @@ const Action = memo(({ handleClick, icon, cy, ...props }) => {
return (
<SubmitButton
data-cy={cy}
icon
icon={!!icon}
isSubmitting={loading}
label={icon}
onClick={fetchRequest}

View File

@ -52,13 +52,18 @@ const Row = ({ original, value, ...props }) => {
</span>
</div>
</div>
<div className={classes.secondary}>
{!!IPS?.length && (
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'end' }}>
{!!IPS?.length && (
<div className={classes.secondary}>
<div style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'end',
alignItems: 'center'
}}>
<Multiple tags={IPS.split(',')} />
</div>
)}
</div>
</div>
)}
</div>
)
}

View File

@ -1,7 +1,21 @@
import { makeStyles } from '@material-ui/core'
export const rowStyles = makeStyles(
({ palette, typography, breakpoints }) => ({
({ palette, typography, breakpoints, shadows }) => ({
root: {
padding: '0.8em',
color: palette.text.primary,
backgroundColor: palette.background.paper,
fontWeight: typography.fontWeightMedium,
fontSize: '1em',
borderRadius: 6,
display: 'flex',
gap: 8,
boxShadow: shadows[1],
[breakpoints.down('md')]: {
flexWrap: 'wrap'
}
},
main: {
flex: 'auto',
overflow: 'hidden'
@ -38,6 +52,10 @@ export const rowStyles = makeStyles(
flexShrink: 0,
whiteSpace: 'nowrap'
}
},
actions: {
flexShrink: 0
}
})
)

View File

@ -6,15 +6,17 @@ import { makeStyles, List as MList, ListItem, Typography, Paper } from '@materia
import { Tr } from 'client/components/HOC'
const useStyles = makeStyles(theme => ({
list: {
...theme.typography.body2,
'& > * > *': {
width: '50%'
}
},
title: {
fontWeight: theme.typography.fontWeightBold,
borderBottom: `1px solid ${theme.palette.divider}`
},
item: {
'& > $typo': {
width: '50%'
}
},
typo: {
...theme.typography.body2
}
}))
@ -32,9 +34,17 @@ const List = ({ title, list = [], ...props }) => {
)}
{/* LIST */}
{list.map(({ key, value }, idx) => (
<ListItem key={`${key}-${idx}`}>
<Typography noWrap title={key}>{Tr(key)}</Typography>
<Typography noWrap title={value}>{value}</Typography>
<ListItem key={`${key}-${idx}`} className={classes.item}>
<Typography className={classes.typo} noWrap title={key}>
{Tr(key)}
</Typography>
<Typography
noWrap
className={classes.typo}
title={typeof value === 'string' ? value : undefined}
>
{value}
</Typography>
</ListItem>
))}
</MList>
@ -46,10 +56,7 @@ List.propTypes = {
title: PropTypes.string,
list: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
])
value: PropTypes.any
}))
}

View File

@ -19,7 +19,6 @@ const CATEGORIES = [
const useStyles = makeStyles(theme => ({
list: {
...theme.typography.body2,
'& > * > *': {
width: '25%'
}
@ -69,9 +68,11 @@ const Permissions = React.memo(({
<Typography noWrap>{Tr(T.Admin)}</Typography>
</ListItem>
{CATEGORIES.map(({ title, category }) => (
<ListItem key={category}>
<ListItem key={category} className={classes.item} dense>
{/* TITLE */}
<Typography noWrap>{Tr(title)}</Typography>
<Typography variant='body2' noWrap title={title}>
{Tr(title)}
</Typography>
{/* PERMISSIONS */}
{Object.entries(permissions)
@ -81,7 +82,9 @@ const Permissions = React.memo(({
<Action
cy={`permission-${key}`}
disabled={permission === undefined}
icon={+permission ? <CheckIcon /> : <BlankSquareIcon />}
icon={
+permission ? <CheckIcon size={18} /> : <BlankSquareIcon size={18} />
}
handleClick={() => handleChange(key, permission)}
/>
</span>

View File

@ -0,0 +1,96 @@
import * as React from 'react'
import { makeStyles, Paper, Typography } from '@material-ui/core'
import { Action } from 'client/components/Cards/SelectCard'
import * as VirtualMachine from 'client/models/VirtualMachine'
import { prettyBytes } from 'client/utils'
const useStyles = makeStyles(theme => ({
root: {
padding: '1em'
},
grid: {
padding: '1em',
display: 'grid',
gap: '1em',
gridAutoFlow: 'column',
[theme.breakpoints.down('sm')]: {
gridAutoFlow: 'initial'
}
},
item: {
[theme.breakpoints.down('sm')]: {
display: 'flex',
gap: '1em',
'& > *': {
width: '50%'
}
}
},
actions: {
[theme.breakpoints.down('sm')]: {
borderBottom: `1px solid ${theme.palette.divider}`,
padding: '1em'
},
[theme.breakpoints.up('md')]: {
order: 1,
textAlign: 'end'
}
},
title: {
fontWeight: theme.typography.fontWeightBold
}
}))
const VmCapacityTab = data => {
const classes = useStyles()
const { TEMPLATE } = data
const isVCenter = VirtualMachine.isVCenter(data)
const capacity = [
{ key: 'Physical CPU', value: TEMPLATE?.CPU },
{ key: 'Virtual CPU', value: TEMPLATE?.VCPU ?? '-' },
(isVCenter && {
key: 'Virtual Cores',
value: `
Cores x ${TEMPLATE?.TOPOLOGY?.CORES || '-'} |
Sockets ${TEMPLATE?.TOPOLOGY?.SOCKETS || '-'}`
}),
{ key: 'Memory', value: prettyBytes(+TEMPLATE?.MEMORY, 'MB') },
{ key: 'Cost / CPU', value: TEMPLATE?.CPU_COST || 0 },
{ key: 'Cost / MByte', value: TEMPLATE?.MEMORY_COST || 0 }
].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}
/>
</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>
)
}
VmCapacityTab.displayName = 'VmCapacityTab'
export default VmCapacityTab

View File

@ -0,0 +1,36 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { Permissions, Ownership } from 'client/components/Tabs/Common'
import Information from 'client/components/Tabs/Vm/Info/information'
const VmInfoTab = ({ tabProps, ...data }) => {
const { ID, UNAME, GNAME, PERMISSIONS } = data
return (
<div style={{
display: 'grid',
gap: '1em',
gridTemplateColumns: 'repeat(auto-fit, minmax(480px, 1fr))',
padding: '1em'
}}>
{tabProps?.information_panel?.enabled &&
<Information {...data} />
}
{tabProps?.permissions_panel?.enabled &&
<Permissions id={ID} {...PERMISSIONS} />
}
{tabProps?.ownership_panel?.enabled &&
<Ownership userName={UNAME} groupName={GNAME} />
}
</div>
)
}
VmInfoTab.propTypes = {
tabProps: PropTypes.object
}
VmInfoTab.displayName = 'VmInfoTab'
export default VmInfoTab

View File

@ -1,15 +1,15 @@
import * as React from 'react'
import { StatusChip } from 'client/components/Status'
import { List, Permissions, Ownership } from 'client/components/Tabs/Common'
import { List } from 'client/components/Tabs/Common'
import Multiple from 'client/components/Tables/Vms/multiple'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
import { T } from 'client/constants'
const VmInfoTab = data => {
const { ID, NAME, UNAME, GNAME, RESCHED, STIME, ETIME, LOCK, DEPLOY_ID, PERMISSIONS } = data
const InformationPanel = data => {
const { ID, NAME, RESCHED, STIME, ETIME, LOCK, DEPLOY_ID } = data
const { name: stateName, color: stateColor } = VirtualMachine.getState(data)
@ -19,60 +19,51 @@ const VmInfoTab = data => {
const ips = VirtualMachine.getIps(data)
const info = [
{ key: [T.ID], value: ID },
{ key: [T.Name], value: NAME },
{ key: T.ID, value: ID },
{ key: T.Name, value: NAME },
{
key: [T.State],
key: T.State,
value: <StatusChip text={stateName} stateColor={stateColor} />
},
{
key: [T.Reschedule],
key: T.Reschedule,
value: Helper.booleanToString(+RESCHED)
},
{
key: [T.Locked],
key: T.Locked,
value: Helper.levelLockToString(LOCK?.LOCKED)
},
{
key: [T.IP],
key: T.IP,
value: ips?.length ? <Multiple tags={ips} /> : '--'
},
{
key: [T.StartTime],
key: T.StartTime,
value: Helper.timeToString(STIME)
},
{
key: [T.EndTime],
key: T.EndTime,
value: Helper.timeToString(ETIME)
},
{
key: [T.Host],
key: T.Host,
value: hostId ? `#${hostId} ${hostname}` : ''
},
{
key: [T.Cluster],
key: T.Cluster,
value: clusterId ? `#${clusterId} ${clusterName}` : ''
},
{
key: [T.DeployID],
key: T.DeployID,
value: DEPLOY_ID
}
]
return (
<div style={{
display: 'grid',
gap: '1em',
gridTemplateColumns: 'repeat(auto-fit, minmax(480px, 1fr))',
padding: '1em'
}}>
<List title={T.Information} list={info} style={{ gridRow: 'span 3' }} />
<Permissions id={ID} {...PERMISSIONS} />
<Ownership userName={UNAME} groupName={GNAME} />
</div>
<List title={T.Information} list={info} style={{ gridRow: 'span 3' }} />
)
}
VmInfoTab.displayName = 'VmInfoTab'
InformationPanel.displayName = 'InformationPanel'
export default VmInfoTab
export default InformationPanel

View File

@ -0,0 +1,72 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { Trash } from 'iconoir-react'
import { Typography } from '@material-ui/core'
// 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 { VM_ACTIONS } from 'client/constants'
const NetworkItem = ({ nic = {}, actions }) => {
const classes = rowStyles()
const {
NIC_ID,
NETWORK = '-',
BRIDGE,
IP,
MAC,
PCI_ID,
ALIAS
} = nic
const detachAction = () => actions.includes(VM_ACTIONS.DETACH_NIC) && (
<Action
cy={`${VM_ACTIONS.DETACH_NIC}-${NIC_ID}`}
icon={<Trash size={18} />}
handleClick={() => undefined}
/>
)
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}>
{`${NIC_ID} | ${NETWORK}`}
{renderLabels([IP, MAC, BRIDGE && `BRIDGE - ${BRIDGE}`, PCI_ID])}
{detachAction()}
</Typography>
</div>
<div style={{ marginLeft: '1em' }}>
{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>
</div>
</div>
)
}
NetworkItem.propTypes = {
nic: PropTypes.object,
actions: PropTypes.arrayOf(PropTypes.string)
}
NetworkItem.displayName = 'NetworkItem'
export default NetworkItem

View File

@ -0,0 +1,26 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import NetworkItem from 'client/components/Tabs/Vm/Network/Item'
const NetworkList = ({ nics, actions }) => (
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '1em',
paddingBlock: '0.8em'
}}>
{nics.map((nic, idx) => (
<NetworkItem key={idx} nic={nic} actions={actions} />
))}
</div>
)
NetworkList.propTypes = {
nics: PropTypes.array,
actions: PropTypes.object
}
NetworkList.displayName = 'NetworkList'
export default NetworkList

View File

@ -0,0 +1,34 @@
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

@ -0,0 +1,30 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import NetworkList from 'client/components/Tabs/Vm/Network/List'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
const VmNetworkTab = ({ tabProps, ...data }) => {
const { actions = [] } = tabProps
const nics = VirtualMachine.getNics(data, { groupAlias: true })
const hypervisor = VirtualMachine.getHypervisor(data)
const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor)
return (
<NetworkList actions={actionsAvailable} nics={nics} />
)
}
VmNetworkTab.propTypes = {
tabProps: PropTypes.shape({
actions: PropTypes.object
}),
actions: PropTypes.array
}
VmNetworkTab.displayName = 'VmNetworkTab'
export default VmNetworkTab

View File

@ -0,0 +1,134 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import {
DatabaseSettings, Folder, ModernTv,
Trash, SaveActionFloppy, Camera, Expand
} from 'iconoir-react'
import { Typography } from '@material-ui/core'
// 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 * as Helper from 'client/models/Helper'
import { prettyBytes } from 'client/utils'
import { VM_ACTIONS } from 'client/constants'
const StorageItem = ({ disk, actions = [] }) => {
const classes = rowStyles()
const {
DISK_ID,
DATASTORE,
TARGET,
IMAGE,
TYPE,
FORMAT,
SIZE,
MONITOR_SIZE,
READONLY,
PERSISTENT,
SAVE,
CLONE,
IS_CONTEXT
} = disk
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])
const labels = [...new Set([
TYPE,
Helper.stringToBoolean(PERSISTENT) && 'PERSISTENT',
Helper.stringToBoolean(READONLY) && 'READONLY',
Helper.stringToBoolean(SAVE) && 'SAVE',
Helper.stringToBoolean(CLONE) && 'CLONE'
])].filter(Boolean)
return (
<div className={classes.root}>
<div className={classes.main}>
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{image}
</Typography>
<span className={classes.labels}>
{labels.map(label => (
<StatusChip key={label} text={label} />
))}
</span>
</div>
<div className={classes.caption}>
<span>
{`#${DISK_ID}`}
</span>
{TARGET && (
<span title={`Target: ${TARGET}`}>
<DatabaseSettings size={16} />
<span>{` ${TARGET}`}</span>
</span>
)}
{DATASTORE && (
<span title={`Datastore Name: ${DATASTORE}`}>
<Folder size={16} />
<span>{` ${DATASTORE}`}</span>
</span>
)}
<span title={`Monitor Size / Disk Size: ${monitorSize}/${size}`}>
<ModernTv size={16} />
<span>{` ${monitorSize}/${size}`}</span>
</span>
</div>
</div>
{!IS_CONTEXT && !!actions.length && (
<div className={classes.actions}>
{actions.includes(VM_ACTIONS.DISK_SAVEAS) && (
<Action
cy={`${VM_ACTIONS.DISK_SAVEAS}-${DISK_ID}`}
icon={<SaveActionFloppy size={18} />}
handleClick={() => undefined}
/>
)}
{actions.includes(VM_ACTIONS.SNAPSHOT_DISK_CREATE) && (
<Action
cy={`${VM_ACTIONS.SNAPSHOT_DISK_CREATE}-${DISK_ID}`}
icon={<Camera size={18} />}
handleClick={() => undefined}
/>
)}
{actions.includes(VM_ACTIONS.RESIZE_DISK) && (
<Action
cy={`${VM_ACTIONS.RESIZE_DISK}-${DISK_ID}`}
icon={<Expand size={18} />}
handleClick={() => undefined}
/>
)}
{actions.includes(VM_ACTIONS.DETACH_DISK) && (
<Action
cy={`${VM_ACTIONS.DETACH_DISK}-${DISK_ID}`}
icon={<Trash size={18} />}
handleClick={() => undefined}
/>
)}
</div>
)}
</div>
)
}
StorageItem.propTypes = {
disk: PropTypes.object.isRequired,
actions: PropTypes.arrayOf(PropTypes.string)
}
StorageItem.displayName = 'StorageItem'
export default StorageItem

View File

@ -0,0 +1,30 @@
import * as React from 'react'
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'
}}>
{disks.map((disk, idx) => (
<StorageItem
key={idx}
disk={disk}
actions={actions}
/>
))}
</div>
)
StorageList.propTypes = {
disks: PropTypes.array,
actions: PropTypes.object
}
StorageList.displayName = 'StorageList'
export default StorageList

View File

@ -0,0 +1,30 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import StorageList from 'client/components/Tabs/Vm/Storage/List'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
const VmStorageTab = ({ tabProps, ...data }) => {
const { actions = [] } = tabProps
const disks = VirtualMachine.getDisks(data)
const hypervisor = VirtualMachine.getHypervisor(data)
const actionsAvailable = Helper.getActionsAvailable(actions, hypervisor)
return (
<StorageList actions={actionsAvailable} disks={disks} />
)
}
VmStorageTab.propTypes = {
tabProps: PropTypes.shape({
actions: PropTypes.object
}),
actions: PropTypes.array
}
VmStorageTab.displayName = 'VmStorageTab'
export default VmStorageTab

View File

@ -1,30 +0,0 @@
import * as React from 'react'
import * as VirtualMachine from 'client/models/VirtualMachine'
import { prettyBytes } from 'client/utils'
const VmCapacityTab = data => {
const { TEMPLATE } = data
const isVCenter = VirtualMachine.isVCenter(data)
return (
<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>
)
}
VmCapacityTab.displayName = 'VmCapacityTab'
export default VmCapacityTab

View File

@ -1,21 +1,20 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import loadable from '@loadable/component'
import { useAuth } from 'client/features/Auth'
import Tabs from 'client/components/Tabs'
import { stringToCamelCase, stringToCamelSpace } from 'client/utils'
const Capacity = loadable(() => import('client/components/Tabs/Vm/capacity'))
const Configuration = loadable(() => import('client/components/Tabs/Vm/configuration'))
const Info = loadable(() => import('client/components/Tabs/Vm/info'))
const Log = loadable(() => import('client/components/Tabs/Vm/log'))
const Network = loadable(() => import('client/components/Tabs/Vm/network'))
const Placement = loadable(() => import('client/components/Tabs/Vm/placement'))
const SchedActions = loadable(() => import('client/components/Tabs/Vm/schedActions'))
const Snapshot = loadable(() => import('client/components/Tabs/Vm/snapshot'))
const Storage = loadable(() => import('client/components/Tabs/Vm/storage'))
import Capacity from 'client/components/Tabs/Vm/Capacity'
import Configuration from 'client/components/Tabs/Vm/Configuration'
import Info from 'client/components/Tabs/Vm/Info'
import Log from 'client/components/Tabs/Vm/Log'
import Network from 'client/components/Tabs/Vm/Network'
import Placement from 'client/components/Tabs/Vm/Placement'
import SchedActions from 'client/components/Tabs/Vm/SchedActions'
import Snapshot from 'client/components/Tabs/Vm/Snapshot'
import Storage from 'client/components/Tabs/Vm/Storage'
const loadTab = tabName => ({
capacity: Capacity,
@ -36,19 +35,18 @@ const VmTabs = ({ data }) => {
React.useEffect(() => {
const infoTabs = getResourceView('VM')?.['info-tabs'] ?? {}
const tabs = Object.entries(infoTabs)
?.map(([tabName, { enabled } = {}]) => !!enabled && tabName)
?.filter(Boolean)
setTabs(() => Object.entries(infoTabs)
?.filter(([_, { enabled } = {}]) => !!enabled)
?.map(([tabName, tabProps]) => {
const nameSanitize = stringToCamelCase(tabName)
const TabContent = loadTab(nameSanitize)
setTabs(() => tabs.map(tabName => {
const nameSanitize = stringToCamelCase(tabName)
const TabContent = loadTab(nameSanitize)
return TabContent && {
name: stringToCamelSpace(nameSanitize),
renderContent: props => TabContent.render({ ...props })
}
}).filter(Boolean))
return TabContent && {
name: stringToCamelSpace(nameSanitize),
renderContent: props => TabContent({ ...props, tabProps })
}
})
?.filter(Boolean))
}, [view])
return <Tabs tabs={tabsAvailable} data={data} />

View File

@ -1,33 +0,0 @@
import * as React from 'react'
import * as VirtualMachine from 'client/models/VirtualMachine'
const VmNetworkTab = data => {
const { nics, alias } = VirtualMachine.splitNicAlias(data)
return (
<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>
)
}
VmNetworkTab.displayName = 'VmNetworkTab'
export default VmNetworkTab

View File

@ -1,55 +0,0 @@
import * as React from 'react'
import * as VirtualMachine from 'client/models/VirtualMachine'
import { prettyBytes } from 'client/utils'
const VmStorageTab = data => {
const disks = VirtualMachine.getDisks(data)
return (
<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>
)
}
VmStorageTab.displayName = 'VmStorageTab'
export default VmStorageTab

View File

@ -411,3 +411,78 @@ export const VM_LCM_STATES = [
meaning: ''
}
]
export const VM_ACTIONS = {
CREATE_DIALOG: 'create_dialog',
DEPLOY: 'deploy',
MIGRATE: 'migrate',
MIGRATE_LIVE: 'migrate_live',
MIGRATE_POFF: 'migrate_poff',
MIGRATE_POFF_HARD: 'migrate_poff_hard',
HOLD: 'hold',
RELEASE: 'release',
SUSPEND: 'suspend',
RESUME: 'resume',
STOP: 'stop',
RECOVER: 'recover',
REBOOT: 'reboot',
REBOOT_HARD: 'reboot_hard',
POWEROFF: 'poweroff',
POWEROFF_HARD: 'poweroff_hard',
UNDEPLOY: 'undeploy',
UNDEPLOY_HARD: 'undeploy_hard',
TERMINATE: 'terminate',
TERMINATE_HARD: 'terminate_hard',
RESCHED: 'resched',
UNRESCHED: 'unresched',
SAVE_AS_TEMPLATE: 'save_as_template',
LOCK: 'lockU',
UNLOCK: 'unlock',
STAR_TVNC: 'startvnc',
STAR_TVMRC: 'startvmrc',
STAR_TSPICE: 'startspice',
VNC: 'vnc',
SSH: 'ssh',
RDP: 'rdp',
SAVE_RDP: 'save_rdp',
SAVE_VIRT_VIEWER: 'save_virt_viewer',
// INFORMATION
RENAME: 'rename',
// PERMISSION
CHMOD: 'chmod',
// OWNERSHIP
CHOWN: 'chown',
CHANGE_GROUP: 'chgrp',
// CAPACITY
RESIZE_CAPACITY: 'resize_capacity',
// STORAGE
ATTACH_DISK: 'attach_disk',
DETACH_DISK: 'detach_disk',
SNAPSHOT_DISK_CREATE: 'snapshot_disk_create',
SNAPSHOT_DISK_REVERT: 'snapshot_disk_revert',
SNAPSHOT_DISK_DELETE: 'snapshot_disk_delete',
RESIZE_DISK: 'resize_disk',
DISK_SAVEAS: 'disk_saveas',
// NETWORK
ATTACH_NIC: 'attach_nic',
DETACH_NIC: 'detach_nic',
// SNAPSHOT
SNAPSHOT_CREATE: 'snapshot_create',
SNAPSHOT_REVERT: 'snapshot_revert',
SNAPSHOT_DELETE: 'snapshot_delete',
// SCHEDULING ACTION
SCHED_ACTION_CREATE: 'sched_action_create',
SCHED_ACTION_DELETE: 'sched_action_delete',
CHARTER_CREATE: 'charter_create',
// CONFIGURATION
UPDATE_CONF: 'update_configuration'
}

View File

@ -38,3 +38,19 @@ export const permissionsToOctal = permissions => {
[OTHER_U, OTHER_M, OTHER_A]
].map(getCategoryValue).join('')
}
/**
* @param {Object} actions Actions from view yaml
* @param {String} [hypervisor] Resource hypervisor
* @returns {String[]} List of actions available for the resource
*/
export const getActionsAvailable = (actions = {}, hypervisor = '') =>
Object.entries(actions)
.filter(([_, action]) => {
if (typeof action === 'boolean') return !!action
const { enabled = false, not_on: notOn = [] } = action || {}
return !!enabled && !notOn?.includes?.(hypervisor)
})
.map(([actionName, _]) => actionName)

View File

@ -72,12 +72,17 @@ export const getState = ({ STATE, LCM_STATE } = {}) => {
* @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,
export const getDisks = vm => {
const { TEMPLATE = {}, MONITORING = {} } = vm ?? {}
const { DISK, CONTEXT } = TEMPLATE
const { DISK_SIZE = [] } = MONITORING
const contextDisk = CONTEXT && !isVCenter(vm) && {
...CONTEXT,
IMAGE: 'CONTEXT',
IS_CONTEXT: true,
DATASTORE: '-',
TYPE: '-',
READONLY: '-',
SAVE: '-',
CLONE: '-',
@ -87,11 +92,12 @@ export const getDisks = ({ TEMPLATE = {}, MONITORING = {}, ...vm } = {}) => {
const addMonitoringData = disk => ({
...disk,
// get monitoring data
MONITOR_SIZE: MONITORING.DISK_SIZE
MONITOR_SIZE: [DISK_SIZE ?? []]
?.flat()
?.find(({ ID }) => ID === disk.DISK_ID)?.SIZE || '-'
})
return [TEMPLATE.DISK, contextDisk]
return [DISK, contextDisk]
.flat()
.filter(Boolean)
.map(addMonitoringData)
@ -99,9 +105,13 @@ export const getDisks = ({ TEMPLATE = {}, MONITORING = {}, ...vm } = {}) => {
/**
* @param {Object} vm Virtual machine
* @param {Boolean} [options.groupAlias] Map ALIAS_IDS attribute with NIC_ALIAS
* @returns {Array} List of nics from resource
*/
export const getNics = ({ TEMPLATE = {}, MONITORING = {} } = {}) => {
export const getNics = (vm, options = {}) => {
const { groupAlias = false } = options
const { TEMPLATE = {}, MONITORING = {} } = vm ?? {}
const { NIC = [], NIC_ALIAS = [], PCI = [] } = TEMPLATE
const { GUEST_IP, GUEST_IP_ADDRESSES = '' } = MONITORING
@ -109,7 +119,14 @@ export const getNics = ({ TEMPLATE = {}, MONITORING = {} } = {}) => {
.filter(Boolean)
.map(ip => ({ NIC_ID: '-', IP: ip, NETWORK: 'Additional IP', BRIDGE: '-' }))
return [NIC, NIC_ALIAS, PCI, extraIps].flat().filter(Boolean)
const nics = [NIC, PCI, extraIps].flat().filter(Boolean)
return groupAlias
? nics.map(({ ALIAS_IDS, ...nic }) => ({
...nic,
ALIAS: NIC_ALIAS?.filter(({ NIC_ID }) => ALIAS_IDS?.includes(NIC_ID))
}))
: nics.concat(NIC_ALIAS)
}
/**
@ -123,13 +140,12 @@ export const getIps = vm => {
}
/**
* @type {{nics: Array, alias: Array}} Nics&Alias
*
* @param {Object} vm Virtual machine
* @returns {Nics&Alias} Lists of nics and alias from resource
* @returns {{ nics: Array, alias: Array }} Lists of nics and alias from resource
*/
export const splitNicAlias = vm => getNics(vm).reduce((result, nic) => {
result[nic?.PARENT !== undefined ? 'alias' : 'nics'].push(nic)
export const splitNicAlias = vm =>
getNics(vm).reduce((result, nic) => {
result[nic?.PARENT !== undefined ? 'alias' : 'nics'].push(nic)
return result
}, { nics: [], alias: [] })
return result
}, { nics: [], alias: [] })