diff --git a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js
index 8f31815d0c..c1644ac579 100644
--- a/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js
+++ b/src/fireedge/src/client/components/Tables/Enhanced/Utils/utils.js
@@ -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 => {
diff --git a/src/fireedge/src/client/components/Tables/Vms/detail.js b/src/fireedge/src/client/components/Tables/Vms/detail.js
index 0d28dfcaa7..1eb88e940a 100644
--- a/src/fireedge/src/client/components/Tables/Vms/detail.js
+++ b/src/fireedge/src/client/components/Tables/Vms/detail.js
@@ -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 = {'>'}
-
-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
{error}
}
- 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: (
-
-
-
-
- {`#${ID} - ${NAME}`}
-
-
-
-
Owner: {UNAME}
-
Group: {GNAME}
-
Reschedule: {Helper.booleanToString(+RESCHED)}
-
Locked: {Helper.levelLockToString(LOCK?.LOCKED)}
-
IP: {ips.join(', ') || '--'}
-
Start time: {Helper.timeToString(STIME)}
-
End time: {Helper.timeToString(ETIME)}
-
Host: {hostId ? `#${hostId} ${hostname}` : ''}
-
Cluster: {clusterId ? `#${clusterId} ${clusterName}` : ''}
-
Deploy ID: {DEPLOY_ID}
-
-
- )
- },
- {
- name: 'capacity',
- renderContent: (
-
-
Physical CPU: {TEMPLATE?.CPU}
-
Virtual CPU: {TEMPLATE?.VCPU ?? '-'}
- {isVCenter && (
-
Virtual Cores: {`
- Cores x ${TEMPLATE?.TOPOLOGY?.CORES || '-'} |
- Sockets ${TEMPLATE?.TOPOLOGY?.SOCKETS || '-'}
- `}
- )}
-
Memory: {prettyBytes(+TEMPLATE?.MEMORY, 'MB')}
-
Cost / CPU: {TEMPLATE?.CPU_COST}
-
Cost / MByte: {TEMPLATE?.MEMORY_COST}
-
- )
- },
- {
- name: 'storage',
- renderContent: (
-
-
VM DISKS
- {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 (
-
- {`${DISK_ID} | ${DATASTORE} | ${TARGET} | ${image} | ${monitorSize}/${size} | ${type} | ${READONLY} | ${SAVE} | ${CLONE}`}
-
- )
- })}
-
- )
- },
- {
- name: 'network',
- renderContent: (
-
-
-
VM NICS
- {nics.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-', PCI_ID = '' }) => (
-
- {`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC} | ${PCI_ID}`}
-
- ))}
-
-
-
-
VM ALIAS
- {alias.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-' }) => (
-
- {`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC}`}
-
- ))}
-
-
- )
- },
- {
- name: 'template',
- renderContent: (
-
-
-
- User Template
-
-
-
-
- {JSON.stringify(USER_TEMPLATE, null, 2)}
-
-
-
-
-
-
- Template
-
-
-
-
- {JSON.stringify(TEMPLATE, null, 2)}
-
-
-
-
-
- )
- }
- ]
+ const tabs = Object.entries(view['info-tabs'] ?? {})
+ ?.map(([tabName, { enabled } = {}]) => !!enabled && tabName)
+ ?.filter(Boolean)
return (
-
+
)
-}
+})
VmDetail.propTypes = {
- id: PropTypes.string.isRequired
+ id: PropTypes.string.isRequired,
+ view: PropTypes.object.isRequired
}
+VmDetail.displayName = 'VmDetail'
+
export default VmDetail
diff --git a/src/fireedge/src/client/components/Tables/Vms/index.js b/src/fireedge/src/client/components/Tables/Vms/index.js
index 38425446f7..1516072f27 100644
--- a/src/fireedge/src/client/components/Tables/Vms/index.js
+++ b/src/fireedge/src/client/components/Tables/Vms/index.js
@@ -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 => }
+ renderDetail={row => }
/>
)
}
diff --git a/src/fireedge/src/client/components/Tabs/Vm/capacity.js b/src/fireedge/src/client/components/Tabs/Vm/capacity.js
new file mode 100644
index 0000000000..218d31c0a7
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/capacity.js
@@ -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 (
+
+
Physical CPU: {TEMPLATE?.CPU}
+
Virtual CPU: {TEMPLATE?.VCPU ?? '-'}
+ {isVCenter && (
+
Virtual Cores: {`
+ Cores x ${TEMPLATE?.TOPOLOGY?.CORES || '-'} |
+ Sockets ${TEMPLATE?.TOPOLOGY?.SOCKETS || '-'}
+ `}
+ )}
+
Memory: {prettyBytes(+TEMPLATE?.MEMORY, 'MB')}
+
Cost / CPU: {TEMPLATE?.CPU_COST}
+
Cost / MByte: {TEMPLATE?.MEMORY_COST}
+
+ )
+}
+
+VmCapacityTab.displayName = 'VmCapacityTab'
+
+export default VmCapacityTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/configuration.js b/src/fireedge/src/client/components/Tabs/Vm/configuration.js
new file mode 100644
index 0000000000..412a6eb350
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/configuration.js
@@ -0,0 +1,41 @@
+import * as React from 'react'
+import { Accordion, AccordionSummary, AccordionDetails } from '@material-ui/core'
+
+const NavArrowDown = {'>'}
+
+const VmConfigurationTab = data => {
+ const { TEMPLATE, USER_TEMPLATE } = data
+
+ return (
+
+
+
+ {'User Template'}
+
+
+
+
+ {JSON.stringify(USER_TEMPLATE, null, 2)}
+
+
+
+
+
+
+ {'Template'}
+
+
+
+
+ {JSON.stringify(TEMPLATE, null, 2)}
+
+
+
+
+
+ )
+}
+
+VmConfigurationTab.displayName = 'VmConfigurationTab'
+
+export default VmConfigurationTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/index.js b/src/fireedge/src/client/components/Tabs/Vm/index.js
new file mode 100644
index 0000000000..c1e3f3c06a
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/index.js
@@ -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
+}
+
+VmTabs.propTypes = {
+ data: PropTypes.object.isRequired,
+ tabs: PropTypes.arrayOf(
+ PropTypes.string
+ ).isRequired
+}
+
+VmTabs.displayName = 'VmTabs'
+
+export default VmTabs
diff --git a/src/fireedge/src/client/components/Tabs/Vm/info.js b/src/fireedge/src/client/components/Tabs/Vm/info.js
new file mode 100644
index 0000000000..e2459eb002
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/info.js
@@ -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 (
+
+
+
+
+ {`#${ID} - ${NAME}`}
+
+
+
+
Owner: {UNAME}
+
Group: {GNAME}
+
Reschedule: {Helper.booleanToString(+RESCHED)}
+
Locked: {Helper.levelLockToString(LOCK?.LOCKED)}
+
IP: {ips.join(', ') || '--'}
+
Start time: {Helper.timeToString(STIME)}
+
End time: {Helper.timeToString(ETIME)}
+
Host: {hostId ? `#${hostId} ${hostname}` : ''}
+
Cluster: {clusterId ? `#${clusterId} ${clusterName}` : ''}
+
Deploy ID: {DEPLOY_ID}
+
+
+ )
+}
+
+VmInfoTab.displayName = 'VmInfoTab'
+
+export default VmInfoTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/log.js b/src/fireedge/src/client/components/Tabs/Vm/log.js
new file mode 100644
index 0000000000..9f3c523ede
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/log.js
@@ -0,0 +1,13 @@
+import * as React from 'react'
+
+const VmLogTab = data => {
+ return (
+
+ )
+}
+
+VmLogTab.displayName = 'VmLogTab'
+
+export default VmLogTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/network.js b/src/fireedge/src/client/components/Tabs/Vm/network.js
new file mode 100644
index 0000000000..1b77cb1fa5
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/network.js
@@ -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 (
+
+
+
VM NICS
+ {nics.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-', PCI_ID = '' }) => (
+
+ {`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC} | ${PCI_ID}`}
+
+ ))}
+
+
+
+
VM ALIAS
+ {alias.map(({ NIC_ID, NETWORK = '-', BRIDGE = '-', IP = '-', MAC = '-' }) => (
+
+ {`${NIC_ID} | ${NETWORK} | ${BRIDGE} | ${IP} | ${MAC}`}
+
+ ))}
+
+
+ )
+}
+
+VmNetworkTab.displayName = 'VmNetworkTab'
+
+export default VmNetworkTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/placement.js b/src/fireedge/src/client/components/Tabs/Vm/placement.js
new file mode 100644
index 0000000000..205a7f9b6e
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/placement.js
@@ -0,0 +1,13 @@
+import * as React from 'react'
+
+const VmPlacementTab = data => {
+ return (
+
+ )
+}
+
+VmPlacementTab.displayName = 'VmPlacementTab'
+
+export default VmPlacementTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/schedActions.js b/src/fireedge/src/client/components/Tabs/Vm/schedActions.js
new file mode 100644
index 0000000000..04ac3ea0fe
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/schedActions.js
@@ -0,0 +1,13 @@
+import * as React from 'react'
+
+const VmSchedulingTab = data => {
+ return (
+
+ )
+}
+
+VmSchedulingTab.displayName = 'VmSchedulingTab'
+
+export default VmSchedulingTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/snapshot.js b/src/fireedge/src/client/components/Tabs/Vm/snapshot.js
new file mode 100644
index 0000000000..6c7913ae8f
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/snapshot.js
@@ -0,0 +1,13 @@
+import * as React from 'react'
+
+const VmSnapshotTab = data => {
+ return (
+
+ )
+}
+
+VmSnapshotTab.displayName = 'VmSnapshotTab'
+
+export default VmSnapshotTab
diff --git a/src/fireedge/src/client/components/Tabs/Vm/storage.js b/src/fireedge/src/client/components/Tabs/Vm/storage.js
new file mode 100644
index 0000000000..61eb84a91a
--- /dev/null
+++ b/src/fireedge/src/client/components/Tabs/Vm/storage.js
@@ -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 (
+
+
VM DISKS
+ {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 (
+
+ {`${DISK_ID} |
+ ${DATASTORE} |
+ ${TARGET} |
+ ${image} |
+ ${monitorSize}/${size} |
+ ${type} |
+ ${READONLY} |
+ ${SAVE} |
+ ${CLONE}`}
+
+ )
+ })}
+
+ )
+}
+
+VmStorageTab.displayName = 'VmStorageTab'
+
+export default VmStorageTab
diff --git a/src/fireedge/src/client/components/Tabs/index.js b/src/fireedge/src/client/components/Tabs/index.js
index 5d6f3d01b7..43987152ce 100644
--- a/src/fireedge/src/client/components/Tabs/index.js
+++ b/src/fireedge/src/client/components/Tabs/index.js
@@ -36,7 +36,7 @@ const Tabs = ({ tabs = [], renderHiddenTabs = false }) => {
/>
)}
- ), [tabSelected])
+ ), [tabs.length, tabSelected])
const renderAllHiddenTabContents = useMemo(() =>
tabs.map((tabProps, idx) => {
diff --git a/src/fireedge/src/client/containers/Dashboard/Sunstone/index.js b/src/fireedge/src/client/containers/Dashboard/Sunstone/index.js
index 4000f4bb30..d2dbdd880f 100644
--- a/src/fireedge/src/client/containers/Dashboard/Sunstone/index.js
+++ b/src/fireedge/src/client/containers/Dashboard/Sunstone/index.js
@@ -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()