From fc7ca2830b7b7aa4ffa1ecd80d0b3fe3c26d469e Mon Sep 17 00:00:00 2001 From: Sergio Betanzos Date: Wed, 16 Jun 2021 15:33:49 +0200 Subject: [PATCH] M ~#: Add vms, datastores & hosts tables --- .../src/client/components/Header/Group.js | 2 +- .../src/client/components/Status/Badge.js | 17 +- .../Status/LinearProgressWithLabel.js | 6 +- .../client/components/Table/EnhancedTable.js | 3 +- .../components/Tables/Datastores/columns.js | 31 ++- .../components/Tables/Datastores/index.js | 35 +-- .../components/Tables/Enhanced/index.js | 228 ++++++++++++++++++ .../components/Tables/Enhanced/pagination.js | 94 ++++++++ .../client/components/Tables/Enhanced/row.js | 34 +++ .../client/components/Tables/Hosts/columns.js | 26 +- .../client/components/Tables/Hosts/index.js | 35 +-- .../components/Tables/Virtualized/header.js | 42 +--- .../components/Tables/Virtualized/index.js | 47 ++-- .../components/Tables/Virtualized/toolbar.js | 2 +- .../client/components/Tables/Vms/columns.js | 26 +- .../src/client/components/Tables/Vms/index.js | 31 +-- .../src/client/components/Tables/index.js | 12 +- .../src/client/containers/Login/schema.js | 2 +- .../src/client/containers/Newstone/index.js | 15 +- src/fireedge/src/client/features/One/utils.js | 4 +- .../src/client/features/One/vm/actions.js | 7 +- 21 files changed, 519 insertions(+), 180 deletions(-) create mode 100644 src/fireedge/src/client/components/Tables/Enhanced/index.js create mode 100644 src/fireedge/src/client/components/Tables/Enhanced/pagination.js create mode 100644 src/fireedge/src/client/components/Tables/Enhanced/row.js diff --git a/src/fireedge/src/client/components/Header/Group.js b/src/fireedge/src/client/components/Header/Group.js index 7756d5f7b0..b76f413a15 100644 --- a/src/fireedge/src/client/components/Header/Group.js +++ b/src/fireedge/src/client/components/Header/Group.js @@ -37,7 +37,7 @@ const Group = () => { }} > {NAME} - {isSelected && } + {isSelected && } ) } diff --git a/src/fireedge/src/client/components/Status/Badge.js b/src/fireedge/src/client/components/Status/Badge.js index 55941cb16d..2438edc0c0 100644 --- a/src/fireedge/src/client/components/Status/Badge.js +++ b/src/fireedge/src/client/components/Status/Badge.js @@ -7,7 +7,8 @@ const useStyles = makeStyles(theme => ({ badge: { backgroundColor: ({ stateColor }) => stateColor, color: ({ stateColor }) => stateColor, - boxShadow: `0 0 0 2px ${theme.palette.background.paper}`, + transform: ({ customTransform }) => customTransform, + boxShadow: '0 0 0 2px transparent', '&::after': { position: 'absolute', top: 0, @@ -32,26 +33,25 @@ const useStyles = makeStyles(theme => ({ } })) -const StatusBadge = memo(({ stateColor, children, ...props }) => { - const classes = useStyles({ stateColor }) +const StatusBadge = memo(({ stateColor, children, customTransform, ...props }) => { + const classes = useStyles({ stateColor, customTransform }) return ( {children} ) -}, -(prev, next) => prev.stateColor === next.stateColor -) +}, (prev, next) => prev.stateColor === next.stateColor) StatusBadge.propTypes = { stateColor: PropTypes.string, + customTransform: PropTypes.string, children: PropTypes.oneOfType([ PropTypes.string, PropTypes.node @@ -60,6 +60,7 @@ StatusBadge.propTypes = { StatusBadge.defaultProps = { stateColor: undefined, + customTransform: undefined, children: '' } diff --git a/src/fireedge/src/client/components/Status/LinearProgressWithLabel.js b/src/fireedge/src/client/components/Status/LinearProgressWithLabel.js index b924281e9a..62efbb09f6 100644 --- a/src/fireedge/src/client/components/Status/LinearProgressWithLabel.js +++ b/src/fireedge/src/client/components/Status/LinearProgressWithLabel.js @@ -10,17 +10,17 @@ const BorderLinearProgress = withStyles(({ palette }) => ({ borderRadius: 5 }, colorPrimary: { - backgroundColor: palette.grey[palette.type === 'light' ? 200 : 700] + backgroundColor: palette.grey[palette.type === 'light' ? 400 : 700] }, bar: { borderRadius: 5, - backgroundColor: palette.secondary.main + backgroundColor: palette.primary.main } }))(LinearProgress) const LinearProgressWithLabel = memo(({ value, label }) => (
- {label} + {label}
), (prev, next) => prev.value === next.value && prev.label === next.label) diff --git a/src/fireedge/src/client/components/Table/EnhancedTable.js b/src/fireedge/src/client/components/Table/EnhancedTable.js index 1b784317ba..6ac1301035 100644 --- a/src/fireedge/src/client/components/Table/EnhancedTable.js +++ b/src/fireedge/src/client/components/Table/EnhancedTable.js @@ -156,7 +156,8 @@ EnhancedTable.propTypes = { } EnhancedTable.defaultProps = { - skipPageReset: false + skipPageReset: false, + filterTypes: [] } export default EnhancedTable diff --git a/src/fireedge/src/client/components/Tables/Datastores/columns.js b/src/fireedge/src/client/components/Tables/Datastores/columns.js index 671141114f..8369703a36 100644 --- a/src/fireedge/src/client/components/Tables/Datastores/columns.js +++ b/src/fireedge/src/client/components/Tables/Datastores/columns.js @@ -1,6 +1,6 @@ import * as React from 'react' -import { LinearProgressWithLabel } from 'client/components/Status' +import { LinearProgressWithLabel, StatusBadge } from 'client/components/Status' import * as DatastoreModel from 'client/models/Datastore' export default [ @@ -24,24 +24,39 @@ export default [ ) }, */ { - Header: '#', accessor: 'ID' - }, - { Header: 'Name', accessor: 'NAME' }, - { - Header: 'State', + Header: '', id: 'STATE', - accessor: row => DatastoreModel.getState(row)?.name + width: 50, + accessor: row => { + const state = DatastoreModel.getState(row) + + return ( + + ) + } }, + { Header: '#', accessor: 'ID', width: 45 }, + { Header: 'Name', accessor: 'NAME' }, { Header: 'Type', id: 'TYPE', + width: 100, accessor: row => DatastoreModel.getType(row)?.name }, { Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` }, - { Header: 'Cluster', accessor: 'CLUSTER' }, + { + Header: 'Clusters', + id: 'CLUSTERS', + width: 100, + accessor: row => [row.CLUSTERS?.ID ?? []].flat().join(',') + }, { Header: 'Allocated CPU', accessor: row => { diff --git a/src/fireedge/src/client/components/Tables/Datastores/index.js b/src/fireedge/src/client/components/Tables/Datastores/index.js index c9768643f4..a29be318a3 100644 --- a/src/fireedge/src/client/components/Tables/Datastores/index.js +++ b/src/fireedge/src/client/components/Tables/Datastores/index.js @@ -1,51 +1,30 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect } from 'react' import { useAuth } from 'client/features/Auth' import { useFetch } from 'client/hooks' import { useDatastore, useDatastoreApi } from 'client/features/One' -import { VirtualizedTable } from 'client/components/Tables' +import { EnhancedTable } from 'client/components/Tables' +import { DatastoreCard } from 'client/components/Cards' import Columns from 'client/components/Tables/Datastores/columns' -const INITIAL_ELEMENT = 0 -const NUMBER_OF_INTERVAL = 20 - const DatastoresTable = () => { - const [{ start, end }, setPage] = useState({ - start: INITIAL_ELEMENT, - end: -NUMBER_OF_INTERVAL - }) - const columns = React.useMemo(() => Columns, []) const datastores = useDatastore() const { getDatastores } = useDatastoreApi() const { filterPool } = useAuth() - const { data, fetchRequest, loading, reloading, error } = useFetch(getDatastores) + const { fetchRequest, loading, reloading } = useFetch(getDatastores) - useEffect(() => { fetchRequest({ start, end }) }, [filterPool]) - - const fetchMore = () => { - setPage(prevState => { - const newStart = prevState.start + NUMBER_OF_INTERVAL - const newEnd = prevState.end - NUMBER_OF_INTERVAL - - fetchRequest({ start: newStart, end: newEnd }) - - return { start: newStart, end: newEnd } - }) - } - - const canFetchMore = error || data?.datastores?.length < NUMBER_OF_INTERVAL + useEffect(() => { fetchRequest() }, [filterPool]) return ( - ) } diff --git a/src/fireedge/src/client/components/Tables/Enhanced/index.js b/src/fireedge/src/client/components/Tables/Enhanced/index.js new file mode 100644 index 0000000000..ba88848698 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/index.js @@ -0,0 +1,228 @@ +/* eslint-disable react/prop-types */ +import * as React from 'react' + +import { makeStyles, Box, CircularProgress, useMediaQuery } from '@material-ui/core' +import { + useGlobalFilter, + usePagination, + useRowSelect, + useTable +} from 'react-table' + +import Toolbar from 'client/components/Tables/Virtualized/toolbar' +import Header from 'client/components/Tables/Virtualized/header' +import Row from 'client/components/Tables/Enhanced/row' +import Pagination from 'client/components/Tables/Enhanced/pagination' + +import { addOpacityToColor } from 'client/utils' + +const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({ + root: { + height: '100%', + display: 'flex', + flexDirection: 'column' + }, + table: { + height: '100%', + overflow: 'auto', + [breakpoints.up('md')]: { + border: `1px solid ${palette.action.disabledBackground}`, + borderRadius: 6 + }, + // includes header row + '& *[role=row]': { + padding: '0.8em', + textAlign: 'start', + overflowWrap: 'break-word', + lineHeight: '1rem', + color: palette.text.primary, + [breakpoints.up('md')]: { + display: 'grid', + gridAutoFlow: 'column', + gridTemplateColumns: ({ columnsWidth }) => columnsWidth.join(' ') + } + } + }, + header: { + position: 'sticky', + top: 0, + zIndex: 2, + + textTransform: 'uppercase', + fontSize: '0.9em', + fontWeight: 700, + letterSpacing: '0.05em', + borderBottom: 'transparent', + backgroundColor: palette.grey[palette.type === 'light' ? 200 : 600], + + [breakpoints.down('sm')]: { + display: 'none' + } + }, + body: { + [breakpoints.only('sm')]: { + display: 'grid', + gap: '1em', + gridTemplateColumns: '1fr 1fr' + }, + [breakpoints.only('xm')]: { + display: 'grid', + gap: '1em', + gridTemplateColumns: '1fr' + }, + '& *[role=row]': { + fontSize: '1em', + fontWeight: typography.fontWeightMedium, + borderTop: `1px solid ${palette.action.disabledBackground}`, + backgroundColor: palette.background.paper, + '&:hover': { + backgroundColor: palette.action.hover + }, + '&:first-of-type': { + borderTopColor: 'transparent' + }, + '&.selected': { + backgroundColor: addOpacityToColor(palette.secondary.main, 0.7) + } + }, + '& *[role=cell]': { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } + }, + toolbar: { + ...typography.body1, + color: palette.text.hint, + marginBottom: 16, + + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + gap: '1em' + }, + total: { + display: 'flex', + alignItems: 'center', + gap: '1em', + transition: 200 + } +})) + +const DefaultCell = React.memo(({ value }) => value ?? '--') +DefaultCell.displayName = 'DefaultCell' + +const EnhancedTable = ({ + data, + columns, + pageSize = 10, + isLoading, + showPageCount, + fetchMore, + canFetchMore, + MobileComponentRow +}) => { + const columnsWidth = React.useMemo(() => + columns.map(({ width = '1fr' }) => { + return Number.isInteger(width) ? `${width}px` : width + }), []) + + const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm')) + const classes = useStyles({ columnsWidth }) + + const defaultColumn = React.useMemo(() => ({ + // Filter: DefaultFilter, + Cell: DefaultCell + }), []) + + const useTableProps = useTable( + { + columns, + data, + defaultColumn, + autoResetPage: false, + initialState: { + pageSize + } + }, + useGlobalFilter, + usePagination, + useRowSelect + ) + + const { + getTableProps, + prepareRow, + rows, + page, + gotoPage, + pageCount, + state: { pageIndex } + } = useTableProps + + const handleChangePage = newPage => { + gotoPage(newPage) + + const canNextPage = pageCount === -1 ? page.length >= pageSize : newPage < pageCount - 1 + + newPage > pageIndex && canFetchMore && !canNextPage && fetchMore() + } + + return ( + + +
+ +
+ {isLoading && } + Total loaded: {rows.length} +
+
+ +
+
+
+
+ +
+ { + page.map(row => { + prepareRow(row) + + /** @type {import('react-table').UseRowSelectRowProps} */ + const { getRowProps, original, toggleRowSelected, isSelected } = row + + const key = getRowProps().key + const handleSelect = () => toggleRowSelected(!isSelected) + + return isMobile && MobileComponentRow ? ( + + ) : ( + + ) + })} +
+
+ + {page?.length > 0 && ( + + )} +
+ ) +} + +export default EnhancedTable diff --git a/src/fireedge/src/client/components/Tables/Enhanced/pagination.js b/src/fireedge/src/client/components/Tables/Enhanced/pagination.js new file mode 100644 index 0000000000..dfbcc89dce --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/pagination.js @@ -0,0 +1,94 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import { makeStyles, IconButton } from '@material-ui/core' + +import { + FastArrowLeft as FirstPageIcon, + NavArrowLeft as PreviousPageIcon, + NavArrowRight as NextPageIcon, + FastArrowRight as LastPageIcon +} from 'iconoir-react' + +const useStyles = makeStyles(theme => ({ + root: { + margin: '10px auto' + } +})) + +const Pagination = ({ + count = 0, + handleChangePage, + useTableProps, + showPageCount = true +}) => { + const classes = useStyles() + + /** @type {import('react-table').UsePaginationState} */ + const { pageIndex, pageSize } = useTableProps.state + + const pageCount = React.useMemo(() => Math.ceil(count / pageSize), [count]) + + const handleFirstPageButtonClick = () => { + handleChangePage(0) + } + + const handleBackButtonClick = () => { + handleChangePage(pageIndex - 1) + } + + const handleNextButtonClick = () => { + handleChangePage(pageIndex + 1) + } + + const handleLastPageButtonClick = () => { + handleChangePage(Math.max(0, pageCount - 1)) + } + + return ( +
+ {/* + + */} + + + + {showPageCount && + + {`${pageIndex + 1} / ${pageCount}`} + + } + = Math.ceil(count / pageSize) - 1} + aria-label="next page" + > + + + {/* = Math.ceil(count / pageSize) - 1} + aria-label="last page" + > + + */} +
+ ) +} + +Pagination.propTypes = { + handleChangePage: PropTypes.func.isRequired, + useTableProps: PropTypes.object.isRequired, + count: PropTypes.number.isRequired, + showPageCount: PropTypes.bool +} + +export default Pagination diff --git a/src/fireedge/src/client/components/Tables/Enhanced/row.js b/src/fireedge/src/client/components/Tables/Enhanced/row.js new file mode 100644 index 0000000000..222bc89fc4 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/Enhanced/row.js @@ -0,0 +1,34 @@ +import * as React from 'react' +import PropTypes from 'prop-types' + +const Row = ({ row, handleClick }) => { + /** @type {import('react-table').Row} */ + const { getRowProps, cells, isSelected } = row + + const renderCell = React.useCallback(cell => ( +
+ {cell.render('Cell')} +
+ ), []) + + return ( +
+ {cells?.map(renderCell)} +
+ ) +} + +Row.propTypes = { + row: PropTypes.object, + handleClick: PropTypes.func +} + +Row.defaultProps = { + row: {}, + handleClick: undefined +} + +export default Row diff --git a/src/fireedge/src/client/components/Tables/Hosts/columns.js b/src/fireedge/src/client/components/Tables/Hosts/columns.js index 67dd9425eb..0205d5adde 100644 --- a/src/fireedge/src/client/components/Tables/Hosts/columns.js +++ b/src/fireedge/src/client/components/Tables/Hosts/columns.js @@ -1,6 +1,6 @@ import * as React from 'react' -import { LinearProgressWithLabel } from 'client/components/Status' +import { LinearProgressWithLabel, StatusBadge } from 'client/components/Status' import * as HostModel from 'client/models/Host' export default [ @@ -24,21 +24,31 @@ export default [ ) }, */ { - Header: '#', accessor: 'ID' + Header: '', + id: 'STATE', + width: 50, + accessor: row => { + const state = HostModel.getState(row) + + return ( + + ) + } }, + { Header: '#', accessor: 'ID', width: 45 }, { Header: 'Name', accessor: 'NAME' }, - { - Header: 'State', - id: 'STATE', - accessor: row => HostModel.getState(row)?.name - }, { Header: 'IM/VM', + width: 100, accessor: ({ IM_MAD, VM_MAD }) => IM_MAD === VM_MAD ? IM_MAD : `${IM_MAD}/${VM_MAD}` }, { Header: 'Cluster', accessor: 'CLUSTER' }, - { Header: 'RVMs', accessor: 'HOST_SHARE.RUNNING_VMS' }, + { Header: 'RVMs', accessor: 'HOST_SHARE.RUNNING_VMS', width: 100 }, { Header: 'Allocated CPU', accessor: row => { diff --git a/src/fireedge/src/client/components/Tables/Hosts/index.js b/src/fireedge/src/client/components/Tables/Hosts/index.js index 1cb26550ef..d55c565734 100644 --- a/src/fireedge/src/client/components/Tables/Hosts/index.js +++ b/src/fireedge/src/client/components/Tables/Hosts/index.js @@ -1,51 +1,30 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect } from 'react' import { useAuth } from 'client/features/Auth' import { useFetch } from 'client/hooks' import { useHost, useHostApi } from 'client/features/One' -import { VirtualizedTable } from 'client/components/Tables' +import { EnhancedTable } from 'client/components/Tables' +import { HostCard } from 'client/components/Cards' import Columns from 'client/components/Tables/Hosts/columns' -const INITIAL_ELEMENT = 0 -const NUMBER_OF_INTERVAL = 20 - const HostsTable = () => { - const [{ start, end }, setPage] = useState({ - start: INITIAL_ELEMENT, - end: -NUMBER_OF_INTERVAL - }) - const columns = React.useMemo(() => Columns, []) const hosts = useHost() const { getHosts } = useHostApi() const { filterPool } = useAuth() - const { data, fetchRequest, loading, reloading, error } = useFetch(getHosts) + const { fetchRequest, loading, reloading } = useFetch(getHosts) - useEffect(() => { fetchRequest({ start, end }) }, [filterPool]) - - const fetchMore = () => { - setPage(prevState => { - const newStart = prevState.start + NUMBER_OF_INTERVAL - const newEnd = prevState.end - NUMBER_OF_INTERVAL - - fetchRequest({ start: newStart, end: newEnd }) - - return { start: newStart, end: newEnd } - }) - } - - const canFetchMore = error || data?.hosts?.length < NUMBER_OF_INTERVAL + useEffect(() => { fetchRequest() }, [filterPool]) return ( - ) } diff --git a/src/fireedge/src/client/components/Tables/Virtualized/header.js b/src/fireedge/src/client/components/Tables/Virtualized/header.js index 5c4e0be31f..7d6e04f8ab 100644 --- a/src/fireedge/src/client/components/Tables/Virtualized/header.js +++ b/src/fireedge/src/client/components/Tables/Virtualized/header.js @@ -1,55 +1,23 @@ import * as React from 'react' import PropTypes from 'prop-types' -import { makeStyles, Box } from '@material-ui/core' - -const useStyles = makeStyles(theme => ({ - root: { - textTransform: 'uppercase', - fontSize: '0.9em', - fontWeight: 700, - lineHeight: '1rem', - letterSpacing: '0.05em', - - overflowWrap: 'break-word', - textAlign: 'start', - padding: '1em', - - color: theme.palette.text.primary, - backgroundColor: theme.palette.action.hover, - border: `1px solid ${theme.palette.action.disabledBackground}`, - borderBottom: 'transparent', - - borderLeftWidth: 1, - borderRightWidth: 1, - borderTopLeftRadius: 6, - borderTopRightRadius: 6 - } -})) - const Header = ({ useTableProps }) => { - const classes = useStyles() - /** @type {import('react-table').UseTableInstanceProps} */ const { headerGroups } = useTableProps const renderHeaderColumn = React.useCallback(column => ( - +
{column.render('Header')} - +
), []) const renderHeaderGroup = React.useCallback(headerGroup => ( - +
{headerGroup.headers.map(renderHeaderColumn)} - +
), []) - return ( - - {headerGroups.map(renderHeaderGroup)} - - ) + return headerGroups.map(renderHeaderGroup) } Header.propTypes = { diff --git a/src/fireedge/src/client/components/Tables/Virtualized/index.js b/src/fireedge/src/client/components/Tables/Virtualized/index.js index b7cef9985c..3b404ef02b 100644 --- a/src/fireedge/src/client/components/Tables/Virtualized/index.js +++ b/src/fireedge/src/client/components/Tables/Virtualized/index.js @@ -23,11 +23,11 @@ const useStyles = makeStyles(theme => ({ }, table: { height: '100%', - overflow: 'hidden', - borderTop: 0, + overflow: 'auto', border: `1px solid ${theme.palette.action.disabledBackground}`, - borderRadius: '0 0 6px 6px', - + borderRadius: 6 + }, + body: { '& *[role=row]': { fontSize: '1em', fontWeight: theme.typography.fontWeightMedium, @@ -48,7 +48,7 @@ const useStyles = makeStyles(theme => ({ } } }, - header: { + toolbar: { ...theme.typography.body1, color: theme.palette.text.hint, marginBottom: 16, @@ -89,7 +89,7 @@ const VirtualizedTable = ({ data, columns, isLoading, canFetchMore, fetchMore }) return ( -
+
{isLoading && } @@ -97,23 +97,26 @@ const VirtualizedTable = ({ data, columns, isLoading, canFetchMore, fetchMore })
-
- - {virtualItems => virtualItems?.map(virtualRow => ( - - )) - } - +
+ +
+ + {virtualItems => virtualItems?.map(virtualRow => ( + + )) + } + +
) diff --git a/src/fireedge/src/client/components/Tables/Virtualized/toolbar.js b/src/fireedge/src/client/components/Tables/Virtualized/toolbar.js index b5e97b6759..688393434c 100644 --- a/src/fireedge/src/client/components/Tables/Virtualized/toolbar.js +++ b/src/fireedge/src/client/components/Tables/Virtualized/toolbar.js @@ -33,7 +33,7 @@ const Toolbar = ({ useTableProps }) => {