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:
parent
93aa66d301
commit
cb181ea413
@ -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}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
})
|
||||
|
||||
)
|
||||
|
@ -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
|
||||
}))
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
96
src/fireedge/src/client/components/Tabs/Vm/Capacity.js
Normal file
96
src/fireedge/src/client/components/Tabs/Vm/Capacity.js
Normal 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
|
36
src/fireedge/src/client/components/Tabs/Vm/Info/index.js
Normal file
36
src/fireedge/src/client/components/Tabs/Vm/Info/index.js
Normal 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
|
@ -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
|
72
src/fireedge/src/client/components/Tabs/Vm/Network/Item.js
Normal file
72
src/fireedge/src/client/components/Tabs/Vm/Network/Item.js
Normal 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
|
26
src/fireedge/src/client/components/Tabs/Vm/Network/List.js
Normal file
26
src/fireedge/src/client/components/Tabs/Vm/Network/List.js
Normal 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
|
@ -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
|
30
src/fireedge/src/client/components/Tabs/Vm/Network/index.js
Normal file
30
src/fireedge/src/client/components/Tabs/Vm/Network/index.js
Normal 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
|
134
src/fireedge/src/client/components/Tabs/Vm/Storage/Item.js
Normal file
134
src/fireedge/src/client/components/Tabs/Vm/Storage/Item.js
Normal 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
|
30
src/fireedge/src/client/components/Tabs/Vm/Storage/List.js
Normal file
30
src/fireedge/src/client/components/Tabs/Vm/Storage/List.js
Normal 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
|
30
src/fireedge/src/client/components/Tabs/Vm/Storage/index.js
Normal file
30
src/fireedge/src/client/components/Tabs/Vm/Storage/index.js
Normal 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
|
@ -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
|
@ -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} />
|
||||
|
@ -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
|
@ -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
|
@ -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'
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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: [] })
|
||||
|
Loading…
x
Reference in New Issue
Block a user