mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-21 14:50:08 +03:00
parent
b12b55945a
commit
265ccea559
@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo } from 'react'
|
||||
import { ReactElement, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { DatabaseSettings, Folder, ModernTv } from 'iconoir-react'
|
||||
import { DatabaseSettings, Folder, PlugTypeC } from 'iconoir-react'
|
||||
import { Box, Typography, Paper } from '@mui/material'
|
||||
|
||||
import DiskSnapshotCard from 'client/components/Cards/DiskSnapshotCard'
|
||||
@ -26,126 +26,140 @@ import { Tr } from 'client/components/HOC'
|
||||
import { getDiskName, getDiskType } from 'client/models/Image'
|
||||
import { stringToBoolean } from 'client/models/Helper'
|
||||
import { prettyBytes, sentenceCase } from 'client/utils'
|
||||
import { T, Disk } from 'client/constants'
|
||||
import { T, Disk, DiskSnapshot } from 'client/constants'
|
||||
|
||||
const DiskCard = memo(({ disk = {}, actions = [], snapshotActions = [] }) => {
|
||||
const classes = rowStyles()
|
||||
const DiskCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {Disk} props.disk - Disk
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @param {function({ snapshot: DiskSnapshot }):ReactElement} [props.snapshotActions] - Snapshot actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ disk = {}, actions, snapshotActions }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
/** @type {Disk} */
|
||||
const {
|
||||
DISK_ID,
|
||||
DATASTORE,
|
||||
TARGET,
|
||||
TYPE,
|
||||
SIZE,
|
||||
MONITOR_SIZE,
|
||||
READONLY,
|
||||
PERSISTENT,
|
||||
SAVE,
|
||||
CLONE,
|
||||
IS_CONTEXT,
|
||||
SNAPSHOTS,
|
||||
} = disk
|
||||
const {
|
||||
DISK_ID,
|
||||
DATASTORE,
|
||||
TARGET,
|
||||
TYPE,
|
||||
SIZE,
|
||||
MONITOR_SIZE,
|
||||
READONLY,
|
||||
PERSISTENT,
|
||||
SAVE,
|
||||
CLONE,
|
||||
IS_CONTEXT,
|
||||
SNAPSHOTS,
|
||||
} = disk
|
||||
|
||||
const size = +SIZE ? prettyBytes(+SIZE, 'MB') : '-'
|
||||
const monitorSize = +MONITOR_SIZE ? prettyBytes(+MONITOR_SIZE, 'MB') : '-'
|
||||
const size = useMemo(() => (+SIZE ? prettyBytes(+SIZE, 'MB') : '-'), [SIZE])
|
||||
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ label: getDiskType(disk), dataCy: 'type' },
|
||||
{
|
||||
label: stringToBoolean(PERSISTENT) && T.Persistent,
|
||||
dataCy: 'persistent',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(READONLY) && T.ReadOnly,
|
||||
dataCy: 'readonly',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(SAVE) && T.Save,
|
||||
dataCy: 'save',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(CLONE) && T.Clone,
|
||||
dataCy: 'clone',
|
||||
},
|
||||
].filter(({ label } = {}) => Boolean(label)),
|
||||
[TYPE, PERSISTENT, READONLY, SAVE, CLONE]
|
||||
)
|
||||
const monitorSize = useMemo(
|
||||
() => (+MONITOR_SIZE ? prettyBytes(+MONITOR_SIZE, 'MB') : '-'),
|
||||
[MONITOR_SIZE]
|
||||
)
|
||||
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
className={classes.root}
|
||||
sx={{ flexWrap: 'wrap', alignContent: 'start' }}
|
||||
data-cy={`disk-${DISK_ID}`}
|
||||
>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span" data-cy="name">
|
||||
{getDiskName(disk)}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{labels.map(({ label, dataCy }) => (
|
||||
<StatusChip
|
||||
key={label}
|
||||
text={sentenceCase(Tr(label))}
|
||||
{...(dataCy && { dataCy: dataCy })}
|
||||
const labels = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ label: getDiskType(disk), dataCy: 'type' },
|
||||
{
|
||||
label: stringToBoolean(PERSISTENT) && T.Persistent,
|
||||
dataCy: 'persistent',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(READONLY) && T.ReadOnly,
|
||||
dataCy: 'readonly',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(SAVE) && T.Save,
|
||||
dataCy: 'save',
|
||||
},
|
||||
{
|
||||
label: stringToBoolean(CLONE) && T.Clone,
|
||||
dataCy: 'clone',
|
||||
},
|
||||
].filter(({ label } = {}) => Boolean(label)),
|
||||
[TYPE, PERSISTENT, READONLY, SAVE, CLONE]
|
||||
)
|
||||
|
||||
return (
|
||||
<Paper
|
||||
variant="outlined"
|
||||
className={classes.root}
|
||||
sx={{ flexWrap: 'wrap', alignContent: 'start' }}
|
||||
data-cy={`disk-${DISK_ID}`}
|
||||
>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span" data-cy="name">
|
||||
{getDiskName(disk)}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{labels.map(({ label, dataCy }) => (
|
||||
<StatusChip
|
||||
key={label}
|
||||
text={sentenceCase(Tr(label))}
|
||||
{...(dataCy && { dataCy: dataCy })}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${DISK_ID}`}</span>
|
||||
{TARGET && (
|
||||
<span title={`${Tr(T.TargetDevice)}: ${TARGET}`}>
|
||||
<PlugTypeC />
|
||||
<span data-cy="target">{` ${TARGET}`}</span>
|
||||
</span>
|
||||
)}
|
||||
{DATASTORE && (
|
||||
<span title={`${Tr(T.Datastore)}: ${DATASTORE}`}>
|
||||
<DatabaseSettings />
|
||||
<span data-cy="datastore">{` ${DATASTORE}`}</span>
|
||||
</span>
|
||||
)}
|
||||
{+MONITOR_SIZE ? (
|
||||
<span
|
||||
title={`${Tr(T.Monitoring)} / ${Tr(
|
||||
T.DiskSize
|
||||
)}: ${monitorSize}/${size}`}
|
||||
>
|
||||
<Folder />
|
||||
<span data-cy="monitorsize">{` ${monitorSize}/${size}`}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span title={`${Tr(T.DiskSize)}: ${size}`}>
|
||||
<Folder />
|
||||
<span data-cy="disksize">{` ${size}`}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!IS_CONTEXT && !!actions && (
|
||||
<div className={classes.actions}>{actions}</div>
|
||||
)}
|
||||
{!!SNAPSHOTS?.length && (
|
||||
<Box flexBasis="100%">
|
||||
{SNAPSHOTS?.map((snapshot) => (
|
||||
<DiskSnapshotCard
|
||||
key={`${DISK_ID}-${snapshot.ID}`}
|
||||
snapshot={snapshot}
|
||||
actions={snapshotActions}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span>{`#${DISK_ID}`}</span>
|
||||
{TARGET && (
|
||||
<span title={`Target: ${TARGET}`}>
|
||||
<DatabaseSettings />
|
||||
<span data-cy="target">{` ${TARGET}`}</span>
|
||||
</span>
|
||||
)}
|
||||
{DATASTORE && (
|
||||
<span title={`Datastore Name: ${DATASTORE}`}>
|
||||
<Folder />
|
||||
<span data-cy="datastore">{` ${DATASTORE}`}</span>
|
||||
</span>
|
||||
)}
|
||||
{+MONITOR_SIZE ? (
|
||||
<span title={`Monitor Size / Disk Size: ${monitorSize}/${size}`}>
|
||||
<ModernTv />
|
||||
<span data-cy="monitorsize">{` ${monitorSize}/${size}`}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span title={`Disk Size: ${size}`}>
|
||||
<ModernTv />
|
||||
<span data-cy="disksize">{` ${size}`}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!IS_CONTEXT && !!actions && (
|
||||
<div className={classes.actions}>{actions}</div>
|
||||
)}
|
||||
{!!SNAPSHOTS?.length && (
|
||||
<Box flexBasis="100%">
|
||||
{SNAPSHOTS?.map((snapshot) => (
|
||||
<DiskSnapshotCard
|
||||
key={`${DISK_ID}-${snapshot.ID}`}
|
||||
snapshot={snapshot}
|
||||
actions={snapshotActions}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
})
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
DiskCard.propTypes = {
|
||||
disk: PropTypes.object.isRequired,
|
||||
actions: PropTypes.any,
|
||||
extraActionProps: PropTypes.object,
|
||||
extraSnapshotActionProps: PropTypes.object,
|
||||
snapshotActions: PropTypes.any,
|
||||
}
|
||||
|
||||
|
@ -13,72 +13,85 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo } from 'react'
|
||||
import { ReactElement, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { ModernTv } from 'iconoir-react'
|
||||
import { Typography, Paper } from '@mui/material'
|
||||
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
import { rowStyles } from 'client/components/Tables/styles'
|
||||
import { Translate } from 'client/components/HOC'
|
||||
import { Tr, Translate } from 'client/components/HOC'
|
||||
|
||||
import * as Helper from 'client/models/Helper'
|
||||
import { stringToBoolean, timeFromMilliseconds } from 'client/models/Helper'
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { T, DiskSnapshot } from 'client/constants'
|
||||
|
||||
const DiskSnapshotCard = memo(({ snapshot = {}, actions = [] }) => {
|
||||
const classes = rowStyles()
|
||||
const DiskSnapshotCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {DiskSnapshot} props.snapshot - Disk snapshot
|
||||
* @param {function({ snapshot: DiskSnapshot }):ReactElement} [props.actions] - Actions
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({ snapshot = {}, actions }) => {
|
||||
const classes = rowStyles()
|
||||
|
||||
/** @type {DiskSnapshot} */
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
ACTIVE,
|
||||
DATE,
|
||||
SIZE: SNAPSHOT_SIZE,
|
||||
MONITOR_SIZE: SNAPSHOT_MONITOR_SIZE,
|
||||
} = snapshot
|
||||
const {
|
||||
ID,
|
||||
NAME,
|
||||
ACTIVE,
|
||||
DATE,
|
||||
SIZE: SNAPSHOT_SIZE,
|
||||
MONITOR_SIZE: SNAPSHOT_MONITOR_SIZE,
|
||||
} = snapshot
|
||||
|
||||
const isActive = Helper.stringToBoolean(ACTIVE)
|
||||
const time = Helper.timeFromMilliseconds(+DATE)
|
||||
const timeAgo = `created ${time.toRelative()}`
|
||||
const isActive = useMemo(() => stringToBoolean(ACTIVE), [ACTIVE])
|
||||
const time = useMemo(() => timeFromMilliseconds(+DATE), [DATE])
|
||||
const timeFormat = useMemo(() => time.toFormat('ff'), [DATE])
|
||||
const timeAgo = useMemo(() => `created ${time.toRelative()}`, [DATE])
|
||||
|
||||
const size = +SNAPSHOT_SIZE ? prettyBytes(+SNAPSHOT_SIZE, 'MB') : '-'
|
||||
const monitorSize = +SNAPSHOT_MONITOR_SIZE
|
||||
? prettyBytes(+SNAPSHOT_MONITOR_SIZE, 'MB')
|
||||
: '-'
|
||||
const sizeInfo = useMemo(() => {
|
||||
const size = +SNAPSHOT_SIZE ? prettyBytes(+SNAPSHOT_SIZE, 'MB') : '-'
|
||||
const monitorSize = +SNAPSHOT_MONITOR_SIZE
|
||||
? prettyBytes(+SNAPSHOT_MONITOR_SIZE, 'MB')
|
||||
: '-'
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{isActive && <StatusChip text={<Translate word={T.Active} />} />}
|
||||
<StatusChip text={<Translate word={T.Snapshot} />} />
|
||||
</span>
|
||||
return `${monitorSize}/${size}`
|
||||
}, [SNAPSHOT_SIZE, SNAPSHOT_MONITOR_SIZE])
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.root}>
|
||||
<div className={classes.main}>
|
||||
<div className={classes.title}>
|
||||
<Typography noWrap component="span">
|
||||
{NAME}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{isActive && <StatusChip text={<Translate word={T.Active} />} />}
|
||||
<StatusChip text={<Translate word={T.Snapshot} />} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={timeFormat}>{`#${ID} ${timeAgo}`}</span>
|
||||
<span
|
||||
title={`${Tr(T.Monitoring)} / ${Tr(T.DiskSize)}: ${sizeInfo}`}
|
||||
>
|
||||
<ModernTv />
|
||||
<span>{` ${sizeInfo}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span title={time.toFormat('ff')}>{`#${ID} ${timeAgo}`}</span>
|
||||
<span title={`Monitor Size / Disk Size: ${monitorSize}/${size}`}>
|
||||
<ModernTv />
|
||||
<span>{` ${monitorSize}/${size}`}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{typeof actions === 'function' && (
|
||||
<div className={classes.actions}>{actions({ snapshot })}</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
})
|
||||
{typeof actions === 'function' && (
|
||||
<div className={classes.actions}>{actions({ snapshot })}</div>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
DiskSnapshotCard.propTypes = {
|
||||
snapshot: PropTypes.object.isRequired,
|
||||
extraActionProps: PropTypes.object,
|
||||
actions: PropTypes.arrayOf(PropTypes.string),
|
||||
actions: PropTypes.func,
|
||||
}
|
||||
|
||||
DiskSnapshotCard.displayName = 'DiskSnapshotCard'
|
||||
|
@ -13,9 +13,10 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { memo, useMemo } from 'react'
|
||||
import { ReactElement, memo, useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Network } from 'iconoir-react'
|
||||
import {
|
||||
useMediaQuery,
|
||||
Typography,
|
||||
@ -37,6 +38,16 @@ import { groupBy } from 'client/utils'
|
||||
import { T, Nic, NicAlias, PrettySecurityGroupRule } from 'client/constants'
|
||||
|
||||
const NicCard = memo(
|
||||
/**
|
||||
* @param {object} props - Props
|
||||
* @param {Nic|NicAlias} props.nic - NIC
|
||||
* @param {ReactElement} [props.actions] - Actions
|
||||
* @param {function({ alias: NicAlias }):ReactElement} [props.aliasActions] - Alias actions
|
||||
* @param {function({ securityGroupId: string }):ReactElement} [props.securityGroupActions] - Security group actions
|
||||
* @param {boolean} [props.showParents] -
|
||||
* @param {boolean} [props.clipboardOnTags] -
|
||||
* @returns {ReactElement} - Card
|
||||
*/
|
||||
({
|
||||
nic = {},
|
||||
actions,
|
||||
@ -48,7 +59,6 @@ const NicCard = memo(
|
||||
const classes = rowStyles()
|
||||
const isMobile = useMediaQuery((theme) => theme.breakpoints.down('md'))
|
||||
|
||||
/** @type {Nic|NicAlias} */
|
||||
const {
|
||||
NIC_ID,
|
||||
NETWORK = '-',
|
||||
@ -65,24 +75,32 @@ const NicCard = memo(
|
||||
|
||||
const isAlias = !!PARENT?.length
|
||||
const isPciDevice = PCI_ID !== undefined
|
||||
const isAdditionalIp = NIC_ID !== undefined || NETWORK === 'Additional IP'
|
||||
const isAdditionalIp = NIC_ID === undefined || NETWORK === 'Additional IP'
|
||||
|
||||
const dataCy = isAlias ? 'alias' : 'nic'
|
||||
|
||||
const noClipboardTags = [
|
||||
{ text: stringToBoolean(RDP) && 'RDP', dataCy: `${dataCy}-rdp` },
|
||||
{ text: stringToBoolean(SSH) && 'SSH', dataCy: `${dataCy}-ssh` },
|
||||
showParents && {
|
||||
text: isAlias ? `PARENT: ${PARENT}` : false,
|
||||
dataCy: `${dataCy}-parent`,
|
||||
},
|
||||
].filter(({ text } = {}) => Boolean(text))
|
||||
const noClipboardTags = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ text: stringToBoolean(RDP) && 'RDP', dataCy: `${dataCy}-rdp` },
|
||||
{ text: stringToBoolean(SSH) && 'SSH', dataCy: `${dataCy}-ssh` },
|
||||
showParents && {
|
||||
text: isAlias ? `PARENT: ${PARENT}` : false,
|
||||
dataCy: `${dataCy}-parent`,
|
||||
},
|
||||
].filter(({ text } = {}) => Boolean(text)),
|
||||
[RDP, SSH, showParents, PARENT]
|
||||
)
|
||||
|
||||
const tags = [
|
||||
{ text: IP, dataCy: `${dataCy}-ip` },
|
||||
{ text: MAC, dataCy: `${dataCy}-mac` },
|
||||
{ text: ADDRESS, dataCy: `${dataCy}-address` },
|
||||
].filter(({ text } = {}) => Boolean(text))
|
||||
const tags = useMemo(
|
||||
() =>
|
||||
[
|
||||
{ text: IP, dataCy: `${dataCy}-ip` },
|
||||
{ text: MAC, dataCy: `${dataCy}-mac` },
|
||||
{ text: ADDRESS, dataCy: `${dataCy}-address` },
|
||||
].filter(({ text } = {}) => Boolean(text)),
|
||||
[IP, MAC, ADDRESS]
|
||||
)
|
||||
|
||||
return (
|
||||
<Paper
|
||||
@ -96,13 +114,8 @@ const NicCard = memo(
|
||||
{...(!isAlias && !showParents && { pl: '1em' })}
|
||||
>
|
||||
<div className={classes.title}>
|
||||
<Typography
|
||||
noWrap
|
||||
component="span"
|
||||
fontWeight="bold"
|
||||
data-cy={`${dataCy}-name`}
|
||||
>
|
||||
{`${NIC_ID} | ${NETWORK}`}
|
||||
<Typography noWrap component="span" data-cy={`${dataCy}-name`}>
|
||||
{NETWORK}
|
||||
</Typography>
|
||||
<span className={classes.labels}>
|
||||
{isAlias && <StatusChip stateColor="info" text={'ALIAS'} />}
|
||||
@ -113,11 +126,24 @@ const NicCard = memo(
|
||||
dataCy={tag.dataCy}
|
||||
/>
|
||||
))}
|
||||
<MultipleTags
|
||||
clipboard={clipboardOnTags}
|
||||
limitTags={isMobile ? 1 : 3}
|
||||
tags={tags}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
{`#${NIC_ID}`}
|
||||
<span>
|
||||
<Network />
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
gap="0.5em"
|
||||
>
|
||||
<MultipleTags
|
||||
tags={tags}
|
||||
clipboard={clipboardOnTags}
|
||||
limitTags={isMobile ? 1 : 3}
|
||||
/>
|
||||
</Stack>
|
||||
</span>
|
||||
</div>
|
||||
</Box>
|
||||
|
@ -66,7 +66,7 @@ const VirtualMachineCard = memo(
|
||||
ETIME,
|
||||
LOCK,
|
||||
USER_TEMPLATE: { LABELS } = {},
|
||||
TEMPLATE: { CPU, MEMORY } = {},
|
||||
TEMPLATE: { VCPU = '-', MEMORY } = {},
|
||||
} = vm
|
||||
|
||||
const { HOSTNAME = '--', VM_MAD: hypervisor } = useMemo(
|
||||
@ -74,10 +74,11 @@ const VirtualMachineCard = memo(
|
||||
[vm.HISTORY_RECORDS]
|
||||
)
|
||||
|
||||
const time = useMemo(
|
||||
() => timeFromMilliseconds(+ETIME || +STIME),
|
||||
[ETIME, STIME]
|
||||
)
|
||||
const [time, timeFormat] = useMemo(() => {
|
||||
const fromMill = timeFromMilliseconds(+ETIME || +STIME)
|
||||
|
||||
return [fromMill, fromMill.toFormat('ff')]
|
||||
}, [ETIME, STIME])
|
||||
|
||||
const { color: stateColor, name: stateName } = getState(vm)
|
||||
const error = useMemo(() => getErrorMessage(vm), [vm])
|
||||
@ -121,27 +122,24 @@ const VirtualMachineCard = memo(
|
||||
</div>
|
||||
<div className={classes.caption}>
|
||||
<span data-cy="id">{`#${ID}`}</span>
|
||||
<span title={useMemo(() => time.toFormat('ff'), [ETIME, STIME])}>
|
||||
<span title={timeFormat}>
|
||||
{`${+ETIME ? T.Done : T.Started} `}
|
||||
<Timer initial={time} />
|
||||
</span>
|
||||
<span title={`${Tr(T.PhysicalCpu)}: ${CPU}`}>
|
||||
<span title={`${Tr(T.VirtualCpu)}: ${VCPU}`}>
|
||||
<Cpu />
|
||||
<span data-cy="cpu">{CPU}</span>
|
||||
<span data-cy="vcpu">{VCPU}</span>
|
||||
</span>
|
||||
<span title={`${Tr(T.Memory)}: ${memValue}`}>
|
||||
<MemoryIcon width={20} height={20} />
|
||||
<span data-cy="memory">{memValue}</span>
|
||||
</span>
|
||||
<span
|
||||
className={classes.captionItem}
|
||||
title={`${Tr(T.Hostname)}: ${HOSTNAME}`}
|
||||
>
|
||||
<span title={`${Tr(T.Hostname)}: ${HOSTNAME}`}>
|
||||
<HardDrive />
|
||||
<span data-cy="hostname">{HOSTNAME}</span>
|
||||
</span>
|
||||
{!!ips?.length && (
|
||||
<span className={classes.captionItem}>
|
||||
<span title={`${Tr(T.IP)}`}>
|
||||
<Network />
|
||||
<Stack direction="row" justifyContent="end" alignItems="center">
|
||||
<MultipleTags tags={ips} clipboard />
|
||||
|
@ -90,6 +90,7 @@ const SelectController = memo(
|
||||
label={labelCanBeTranslated(label) ? Tr(label) : label}
|
||||
InputLabelProps={{ shrink: needShrink }}
|
||||
InputProps={{
|
||||
...(multiple && { sx: { paddingTop: '0.5em' } }),
|
||||
readOnly,
|
||||
startAdornment:
|
||||
(optionSelected && renderValue?.(optionSelected)) ||
|
||||
|
@ -120,7 +120,11 @@ const AttributeList = ({
|
||||
{/* TITLE */}
|
||||
{title && (
|
||||
<TitleElement>
|
||||
<Typography noWrap>{Tr(title)}</Typography>
|
||||
{typeof title === 'string' ? (
|
||||
<Typography noWrap>{Tr(title)}</Typography>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</TitleElement>
|
||||
)}
|
||||
<DetailsElement>
|
||||
|
@ -462,6 +462,7 @@ module.exports = {
|
||||
Snapshot: 'Snapshot',
|
||||
SnapshotName: 'Snapshot name',
|
||||
DiskSnapshot: 'Disk snapshot',
|
||||
DiskSize: 'Disk size',
|
||||
NewImageName: 'New Image name',
|
||||
NewImageNameConcept: 'Name for the new Image where the disk will be saved',
|
||||
/* VM schema - network */
|
||||
|
Loading…
x
Reference in New Issue
Block a user