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

F OpenNebula/one#5422: Add view condition to vm detail

This commit is contained in:
Sergio Betanzos 2021-07-05 14:56:06 +02:00
parent 5feacf8d56
commit d5b2e8a286
No known key found for this signature in database
GPG Key ID: E3E704F097737136
15 changed files with 342 additions and 186 deletions

View File

@ -1,10 +1,6 @@
import { CategoryFilter } from 'client/components/Tables/Enhanced/Utils'
export const createColumns = ({ view, resource, columns }) => {
const filters = view
?.find(({ resource_name: name }) => name === resource)
?.filters ?? {}
export const createColumns = ({ filters = {}, columns = [] }) => {
if (Object.keys(filters).length === 0) return columns
return columns.map(column => {

View File

@ -1,27 +1,24 @@
import React, { useEffect } from 'react'
import * as React from 'react'
import PropTypes from 'prop-types'
import { LinearProgress, Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core'
import Tabs from 'client/components/Tabs'
import { StatusBadge } from 'client/components/Status'
import { LinearProgress } from '@material-ui/core'
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'
import VmTabs from 'client/components/Tabs/Vm'
const NavArrowDown = <span style={{ writingMode: 'vertical-rl' }}>{'>'}</span>
const VmDetail = ({ id }) => {
const VmDetail = React.memo(({ id, view = {} }) => {
const { getHooksSocket } = useSocket()
const socket = getHooksSocket({ resource: 'vm', id })
const { getVm } = useVmApi()
const { data, fetchRequest, loading, error } = useFetch(getVm, socket)
useEffect(() => {
const {
data,
fetchRequest,
loading,
error
} = useFetch(getVm, getHooksSocket({ resource: 'vm', id }))
React.useEffect(() => {
fetchRequest(id)
}, [id])
@ -33,168 +30,20 @@ const VmDetail = ({ id }) => {
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 ? `#${hostId} ${hostname}` : ''}</p>
<p>Cluster: {clusterId ? `#${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>
)
}
]
const tabs = Object.entries(view['info-tabs'] ?? {})
?.map(([tabName, { enabled } = {}]) => !!enabled && tabName)
?.filter(Boolean)
return (
<Tabs tabs={tabs} />
<VmTabs data={data} tabs={tabs} view={view} />
)
}
})
VmDetail.propTypes = {
id: PropTypes.string.isRequired
id: PropTypes.string.isRequired,
view: PropTypes.object.isRequired
}
VmDetail.displayName = 'VmDetail'
export default VmDetail

View File

@ -1,4 +1,4 @@
import React, { useEffect } from 'react'
import * as React from 'react'
import { useAuth } from 'client/features/Auth'
import { useFetch } from 'client/hooks'
@ -18,16 +18,19 @@ const VmsTable = () => {
const { getVms } = useVmApi()
const { view, views, filterPool } = useAuth()
const viewSelected = React.useMemo(() => views[view], [view])
const resourceView = viewSelected?.find(({ resource_name: name }) => name === 'VM')
const columns = React.useMemo(() => createColumns({
view: views[view],
resource: 'VM',
filters: resourceView?.filters,
columns: VmColumns
}), [view])
const { status, data, fetchRequest, loading, reloading, error, STATUS } = useFetch(getVms)
const { INIT, PENDING } = STATUS
useEffect(() => {
React.useEffect(() => {
const requests = {
INIT: () => fetchRequest({
start: INITIAL_ELEMENT,
@ -60,7 +63,7 @@ const VmsTable = () => {
isLoading={loading || reloading}
getRowId={row => String(row.ID)}
RowComponent={VmRow}
renderDetail={row => <VmDetail id={row.ID} />}
renderDetail={row => <VmDetail id={row.ID} view={resourceView} />}
/>
)
}

View File

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

@ -0,0 +1,41 @@
import * as React from 'react'
import { Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core'
const NavArrowDown = <span style={{ writingMode: 'vertical-rl' }}>{'>'}</span>
const VmConfigurationTab = data => {
const { TEMPLATE, USER_TEMPLATE } = data
return (
<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>
)
}
VmConfigurationTab.displayName = 'VmConfigurationTab'
export default VmConfigurationTab

View File

@ -0,0 +1,52 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import Tabs from 'client/components/Tabs'
const stringToCamelCase = s =>
s.replace(
/([-_][a-z])/ig,
$1 => $1.toUpperCase()
.replace('-', '')
.replace('_', '')
)
const stringToCamelSpace = s => s.replace(/([a-z])([A-Z])/g, '$1 $2')
const VmTabs = ({ data, tabs }) => {
const [renderTabs, setTabs] = React.useState(() => [])
React.useEffect(() => {
const loadTab = async tabKey => {
try {
const camelCaseKey = stringToCamelCase(tabKey)
// dynamic import => client/components/Tabs/Vm
const tabComponent = await import(`./${camelCaseKey}`)
setTabs(prev => prev.concat([{
name: stringToCamelSpace(camelCaseKey),
renderContent: tabComponent.default(data)
}]))
} catch (error) {}
}
// reset
setTabs([])
tabs?.forEach(loadTab)
}, [tabs?.length])
return <Tabs tabs={renderTabs} />
}
VmTabs.propTypes = {
data: PropTypes.object.isRequired,
tabs: PropTypes.arrayOf(
PropTypes.string
).isRequired
}
VmTabs.displayName = 'VmTabs'
export default VmTabs

View File

@ -0,0 +1,47 @@
import * as React from 'react'
import { StatusBadge } from 'client/components/Status'
import * as VirtualMachine from 'client/models/VirtualMachine'
import * as Helper from 'client/models/Helper'
const VmInfoTab = data => {
const { ID, NAME, UNAME, GNAME, RESCHED, STIME, ETIME, LOCK, DEPLOY_ID } = 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)
return (
<div>
<p>
<StatusBadge
title={stateName}
stateColor={stateColor}
customTransform='translate(150%, 50%)'
/>
<span style={{ marginLeft: 20 }}>
{`#${ID} - ${NAME}`}
</span>
</p>
<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 ? `#${hostId} ${hostname}` : ''}</p>
<p>Cluster: {clusterId ? `#${clusterId} ${clusterName}` : ''}</p>
<p>Deploy ID: {DEPLOY_ID}</p>
</div>
</div>
)
}
VmInfoTab.displayName = 'VmInfoTab'
export default VmInfoTab

View File

@ -0,0 +1,13 @@
import * as React from 'react'
const VmLogTab = data => {
return (
<div>
<p>WIP - LOG</p>
</div>
)
}
VmLogTab.displayName = 'VmLogTab'
export default VmLogTab

View File

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

@ -0,0 +1,13 @@
import * as React from 'react'
const VmPlacementTab = data => {
return (
<div>
<p>WIP - PLACEMENT</p>
</div>
)
}
VmPlacementTab.displayName = 'VmPlacementTab'
export default VmPlacementTab

View File

@ -0,0 +1,13 @@
import * as React from 'react'
const VmSchedulingTab = data => {
return (
<div>
<p>WIP - SCHED ACTIONS</p>
</div>
)
}
VmSchedulingTab.displayName = 'VmSchedulingTab'
export default VmSchedulingTab

View File

@ -0,0 +1,13 @@
import * as React from 'react'
const VmSnapshotTab = data => {
return (
<div>
<p>WIP - SNAPSHOT</p>
</div>
)
}
VmSnapshotTab.displayName = 'VmSnapshotTab'
export default VmSnapshotTab

View File

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

@ -36,7 +36,7 @@ const Tabs = ({ tabs = [], renderHiddenTabs = false }) => {
/>
)}
</MTabs>
), [tabSelected])
), [tabs.length, tabSelected])
const renderAllHiddenTabContents = useMemo(() =>
tabs.map((tabProps, idx) => {

View File

@ -5,7 +5,7 @@ import { Container, Box, Grid } from '@material-ui/core'
import { useAuth } from 'client/features/Auth'
import { useFetchAll } from 'client/hooks'
import { useVmApi, useUserApi, useImageApi, useVNetworkApi } from 'client/features/One'
import { useUserApi, useImageApi, useVNetworkApi } from 'client/features/One'
import * as Widgets from 'client/components/Widgets'
import dashboardStyles from 'client/containers/Dashboard/Provision/styles'
@ -14,7 +14,6 @@ function Dashboard () {
const { status, fetchRequestAll, STATUS } = useFetchAll()
const { INIT, PENDING } = STATUS
const { getVms } = useVmApi()
const { getUsers } = useUserApi()
const { getImages } = useImageApi()
const { getVNetworks } = useVNetworkApi()
@ -26,7 +25,6 @@ function Dashboard () {
React.useEffect(() => {
fetchRequestAll([
getVms(),
getUsers(),
getImages(),
getVNetworks()