mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
F OpenNebula/one#5422: Add vms table
This commit is contained in:
parent
185b761303
commit
4c8ed9da76
44
src/fireedge/src/client/components/Status/Circle.js
Normal file
44
src/fireedge/src/client/components/Status/Circle.js
Normal 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
|
@ -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
|
||||
}
|
||||
|
@ -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' }
|
||||
]
|
||||
|
207
src/fireedge/src/client/components/Tables/Vms/detail.js
Normal file
207
src/fireedge/src/client/components/Tables/Vms/detail.js
Normal 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
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
40
src/fireedge/src/client/components/Tables/Vms/multiple.js
Normal file
40
src/fireedge/src/client/components/Tables/Vms/multiple.js
Normal 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
|
66
src/fireedge/src/client/components/Tables/Vms/row.js
Normal file
66
src/fireedge/src/client/components/Tables/Vms/row.js
Normal 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
|
@ -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
|
||||
}
|
||||
|
40
src/fireedge/src/client/components/Tables/styles.js
Normal file
40
src/fireedge/src/client/components/Tables/styles.js
Normal 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'
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
69
src/fireedge/src/client/components/Tabs/index.js
Normal file
69
src/fireedge/src/client/components/Tabs/index.js
Normal 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
|
@ -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'
|
||||
|
@ -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',
|
||||
|
17
src/fireedge/src/client/models/Helper.js
Normal file
17
src/fireedge/src/client/models/Helper.js
Normal 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] || '-')
|
@ -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: [] })
|
||||
|
Loading…
x
Reference in New Issue
Block a user