diff --git a/src/fireedge/src/client/components/Status/Circle.js b/src/fireedge/src/client/components/Status/Circle.js new file mode 100644 index 0000000000..5df405de29 --- /dev/null +++ b/src/fireedge/src/client/components/Status/Circle.js @@ -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}} + > + + +) + +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 diff --git a/src/fireedge/src/client/components/Status/index.js b/src/fireedge/src/client/components/Status/index.js index 3ac225cdd1..1a1a5153dd 100644 --- a/src/fireedge/src/client/components/Status/index.js +++ b/src/fireedge/src/client/components/Status/index.js @@ -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 } diff --git a/src/fireedge/src/client/components/Tables/Vms/columns.js b/src/fireedge/src/client/components/Tables/Vms/columns.js index 4d3959f146..4528662152 100644 --- a/src/fireedge/src/client/components/Tables/Vms/columns.js +++ b/src/fireedge/src/client/components/Tables/Vms/columns.js @@ -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 }) => ( - - ), - // The cell can use the individual row's getToggleRowSelectedProps method - // to the render a checkbox - Cell: ({ row }) => ( - - ) - }, */ - { - Header: '', - id: 'STATE', - width: 50, - accessor: row => { - const state = VirtualMachineModel.getState(row) - - return ( - - ) - } - // Cell: ({ value: { name, color } = {} }) => name && ( - // - // ), - // Filter: ({ column }) => ( - // - // ), - // 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 => ( - // - // )) - } + { Header: '', accessor: 'ID' }, + { Header: '', accessor: 'NAME' }, + { Header: '', accessor: 'STATE' }, + { Header: '', accessor: 'LCM_STATE' }, + { Header: '', accessor: 'UID' }, + { Header: '', accessor: 'GID' } ] diff --git a/src/fireedge/src/client/components/Tables/Vms/detail.js b/src/fireedge/src/client/components/Tables/Vms/detail.js new file mode 100644 index 0000000000..448db43ceb --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Vms/detail.js @@ -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 = {'>'} + +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 + } + + if (error) { + 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} ${hostname}`}

+

Cluster: {`#${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)}
+                
+              
+
+
+
+ ) + } + ] + + return ( + + ) +} + +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 31a8382866..e835d479d9 100644 --- a/src/fireedge/src/client/components/Tables/Vms/index.js +++ b/src/fireedge/src/client/components/Tables/Vms/index.js @@ -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 => } canFetchMore={canFetchMore} fetchMore={fetchMore} - MobileComponentRow={VirtualMachineCard} /> ) } diff --git a/src/fireedge/src/client/components/Tables/Vms/multiple.js b/src/fireedge/src/client/components/Tables/Vms/multiple.js new file mode 100644 index 0000000000..369d26ab67 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Vms/multiple.js @@ -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 => ( + + )) + + return [ + ...Tags, + (more > 0 && ( + ( + {tag} + ))} + > + + {`+${more} more`} + + + )) + ] +} + +Multiple.propTypes = { + tags: PropTypes.array, + limitTags: PropTypes.number +} + +export default Multiple diff --git a/src/fireedge/src/client/components/Tables/Vms/row.js b/src/fireedge/src/client/components/Tables/Vms/row.js new file mode 100644 index 0000000000..fe14da171f --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Vms/row.js @@ -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 ( +
+
+ +
+
+ + {NAME} + {LOCK && } + +
+ + {`#${ID} ${timeAgo}`} + + + + {` ${UNAME}`} + + + + {` ${GNAME}`} + + + + {` ${HOSTNAME}`} + +
+
+
+ +
+
+ ) +} + +Row.propTypes = { + value: PropTypes.object, + isSelected: PropTypes.bool, + handleClick: PropTypes.func +} + +export default Row diff --git a/src/fireedge/src/client/components/Tables/index.js b/src/fireedge/src/client/components/Tables/index.js index 59a66bad8f..d6ca395ab1 100644 --- a/src/fireedge/src/client/components/Tables/index.js +++ b/src/fireedge/src/client/components/Tables/index.js @@ -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 } diff --git a/src/fireedge/src/client/components/Tables/styles.js b/src/fireedge/src/client/components/Tables/styles.js new file mode 100644 index 0000000000..a23634c8d8 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/styles.js @@ -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' + } + } + }) +) diff --git a/src/fireedge/src/client/components/Tabs/index.js b/src/fireedge/src/client/components/Tabs/index.js new file mode 100644 index 0000000000..9bbb330086 --- /dev/null +++ b/src/fireedge/src/client/components/Tabs/index.js @@ -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 }) => ( + +), (prev, next) => prev.hidden === next.hidden) + +const Tabs = ({ tabs = [] }) => { + const [tabSelected, setTab] = useState(0) + + const renderTabs = useMemo(() => ( + setTab(tab)} + > + {tabs.map(({ value, name, icon: Icon }, idx) => + } + value={value ?? idx} + label={String(name).toUpperCase()} + /> + )} + + ), [tabSelected]) + + const renderTabContent = useMemo(() => + tabs.map((tabProps, idx) => { + const { name, value = idx } = tabProps + const hidden = tabSelected !== value + + return