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

F OpenNebula/one#5422: Add sortby to tables

This commit is contained in:
Sergio Betanzos 2021-06-24 16:39:34 +02:00
parent e989154f03
commit f03ef61352
No known key found for this signature in database
GPG Key ID: E3E704F097737136
21 changed files with 483 additions and 173 deletions

View File

@ -22,6 +22,7 @@ const HeaderPopover = ({
buttonProps,
headerTitle,
disablePadding,
popoverProps,
children
}) => {
const classes = headerStyles()
@ -70,6 +71,7 @@ const HeaderPopover = ({
vertical: 'top',
horizontal: 'right'
}}
{...popoverProps}
>
{(headerTitle || isMobile) && (
<Box className={classes.header}>
@ -98,6 +100,7 @@ HeaderPopover.propTypes = {
buttonProps: PropTypes.objectOf(PropTypes.any),
headerTitle: PropTypes.string,
disablePadding: PropTypes.bool,
popoverProps: PropTypes.objectOf(PropTypes.any),
children: PropTypes.func
}
@ -108,6 +111,7 @@ HeaderPopover.defaultProps = {
buttonProps: {},
headerTitle: null,
disablePadding: false,
popoverProps: {},
children: () => undefined
}

View File

@ -1,7 +1,21 @@
const getNumberOfResources = resources => [resources?.ID ?? []].flat().length || 0
export default [
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'HOSTS' },
{ Header: '', accessor: 'DATASTORES' },
{ Header: '', accessor: 'VNETS' }
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', accessor: 'NAME' },
{
Header: 'Number of Hosts',
id: 'HOSTS',
accessor: row => getNumberOfResources(row?.HOSTS)
},
{
Header: 'Number of Datastores',
id: 'DATASTORES',
accessor: row => getNumberOfResources(row?.DATASTORES)
},
{
Header: 'Number of VNets',
id: 'VNETS',
accessor: row => getNumberOfResources(row?.VNETS)
}
]

View File

@ -14,6 +14,8 @@ const Row = ({ value, ...props }) => {
const datastores = [DATASTORES?.ID ?? []].flat().length || 0
const virtualNetworks = [VNETS?.ID ?? []].flat().length || 0
const providerName = TEMPLATE?.PROVISION?.PROVIDER_NAME
return (
<div {...props}>
<div className={classes.main}>
@ -22,21 +24,21 @@ const Row = ({ value, ...props }) => {
</Typography>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span>
<span title={`Number of Hosts: ${hosts}`}>
<HardDrive size={16} />
<span>{` ${hosts}`}</span>
</span>
<span>
<span title={`Number of Datastores: ${datastores}`}>
<Folder size={16} />
<span>{` ${datastores}`}</span>
</span>
<span>
<span title={`Number of VNets: ${virtualNetworks}`}>
<NetworkAlt size={16} />
<span>{` ${virtualNetworks}`}</span>
</span>
{TEMPLATE?.PROVISION && <span>
{TEMPLATE?.PROVISION && <span title={`Provider: ${providerName}`}>
<Cloud size={16} />
<span>{` ${TEMPLATE?.PROVISION?.PROVIDER_NAME}`}</span>
<span>{` ${providerName}`}</span>
</span>}
</div>
</div>

View File

@ -1,8 +1,16 @@
const getNumberOfResources = resources => [resources?.ID ?? []].flat().length || 0
export default [
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'STATE' },
{ Header: '', accessor: 'TYPE' },
{ Header: '', accessor: 'CLUSTERS' },
{ Header: '', accessor: 'ALLOCATED_CPU' }
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'State', accessor: 'STATE' },
{ Header: 'Type', accessor: 'TYPE' },
{
Header: 'Number of Clusters',
id: 'CLUSTERS',
accessor: row => getNumberOfResources(row?.CLUSTERS)
},
{ Header: 'Allocated CPU', accessor: 'ALLOCATED_CPU' }
]

View File

@ -17,8 +17,9 @@ const Row = ({ value, ...props }) => {
const state = DatastoreModel.getState(value)
const type = DatastoreModel.getType(value)
const clusters = [CLUSTERS?.ID ?? []].flat()
const clusters = [CLUSTERS?.ID ?? []].flat().join(',')
const { percentOfUsed, percentLabel } = DatastoreModel.getCapacityInfo(value)
const provisionId = PROVISION?.ID
return (
<div {...props}>
@ -35,21 +36,21 @@ const Row = ({ value, ...props }) => {
</Typography>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span>
<span title={`Owner: ${UNAME}`}>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
{PROVISION?.ID && <span>
{provisionId && <span title={`Provision ID: #${provisionId}`}>
<Cloud size={16} />
<span>{` ${PROVISION.ID}`}</span>
<span>{` ${provisionId}`}</span>
</span>}
<span>
<span title={`Cluster IDs: ${clusters}`}>
<Server size={16} />
<span>{` ${clusters.join(',')}`}</span>
<span>{` ${clusters}`}</span>
</span>
</div>
</div>

View File

@ -0,0 +1,106 @@
import React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, fade, debounce, InputBase } from '@material-ui/core'
import { Search as SearchIcon } from 'iconoir-react'
const useStyles = makeStyles(({ spacing, palette, shape, breakpoints }) => ({
search: {
position: 'relative',
borderRadius: shape.borderRadius,
backgroundColor: fade(palette.divider, 0.15),
'&:hover': {
backgroundColor: fade(palette.divider, 0.25)
},
width: '100%',
[breakpoints.up('sm')]: {
marginLeft: spacing(1),
width: 'auto'
}
},
searchIcon: {
padding: spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
inputRoot: {
color: 'inherit'
},
inputInput: {
padding: spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${spacing(4)}px)`,
width: '100%'
}
}))
const GlobalFilter = props => {
/**
* @type {import('react-table').UseGlobalFiltersInstanceProps &
* import('react-table').UseGlobalFiltersState}
*/
const { preGlobalFilteredRows, globalFilter, setGlobalFilter } = props
const classes = useStyles()
const count = preGlobalFilteredRows.length
const [value, setValue] = React.useState(globalFilter)
const handleChange = React.useCallback(
// Set undefined to remove the filter entirely
debounce(value => { setGlobalFilter(value || undefined) }, 200)
)
// Global filter only works with pagination from the first page.
// This may not be a problem for server side pagination when
// only the current page is downloaded.
/* <Box className={classes.search}>
<Box className={classes.searchIcon}>
<SearchIcon />
</Box>
<InputBase
type='search'
onChange={searchProps.handleChange}
fullWidth
placeholder={`${T.Search}...`}
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
/>
</Box> */
return (
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
value={value ?? ''}
onChange={event => {
setValue(event.target.value)
handleChange(event.target.value)
}}
placeholder={'Search...'}
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
inputProps={{ 'aria-label': 'search' }}
/>
</div>
)
}
GlobalFilter.propTypes = {
preGlobalFilteredRows: PropTypes.array.isRequired,
globalFilter: PropTypes.string,
setGlobalFilter: PropTypes.func.isRequired
}
export default GlobalFilter

View File

@ -0,0 +1,105 @@
import React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, MenuItem, MenuList, Chip } from '@material-ui/core'
import { SortDown, ArrowDown, ArrowUp } from 'iconoir-react'
import HeaderPopover from 'client/components/Header/Popover'
import { T } from 'client/constants'
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
gap: 6,
alignItems: 'center'
},
}))
const GlobalSort = props => {
const classes = useStyles()
/**
* @type {import('react-table').TableInstance &
* import('react-table').UseSortByInstanceProps &
* import('react-table').UseSortByState}
*/
const { headers, sortBy, setSortBy } = props
const sortAvailable = React.useMemo(() => {
const flatSorters = sortBy.map(({ id }) => id)
return headers.filter(({ id }) => !flatSorters.includes(id))
}, [sortBy.length])
const handleClick = (id, name) => {
setSortBy([{ id, desc: false, name }, ...sortBy])
}
const handleDelete = removeId => {
setSortBy(sortBy.filter(({id}) => id !== removeId))
}
const handleToggle = (id, desc) => {
setSortBy(sortBy.map(sort => sort.id === id ? ({ ...sort, desc }) : sort))
}
return (
<div className={classes.root}>
{React.useMemo(() => (
<HeaderPopover
id='sort-by-button'
icon={<SortDown />}
buttonLabel={T.SortBy}
buttonProps={{
'data-cy': 'sort-by-button',
disabled: sortAvailable.length === 0,
variant: 'outlined'
}}
popoverProps= {{
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left'
},
transformOrigin: {
vertical: 'top',
horizontal: 'left'
}
}}
>
{() => (
<MenuList disablePadding>
{sortAvailable.length
? sortAvailable?.map(({ id, Header: name }) => (
<MenuItem key={id} onClick={() => { handleClick(id, name) }}>
{name}
</MenuItem>
))
: <span>{T.Empty}</span>
}
</MenuList>
)}
</HeaderPopover>
), [sortAvailable.length])}
{React.useMemo(() => sortBy?.map(({ name, id, desc }) => (
<Chip
key={`${id}-${desc ? 'desc' : 'asc'}`}
icon={desc ? <ArrowUp /> : <ArrowDown />}
label={name ?? id}
onClick={() => handleToggle(id, !desc)}
onDelete={() => handleDelete(id)}
/>
)), [sortBy.length, handleToggle])}
</div>
)
}
GlobalSort.propTypes = {
headers: PropTypes.array.isRequired,
preSortedRows: PropTypes.array.isRequired,
sortBy: PropTypes.array.isRequired,
setSortBy: PropTypes.func.isRequired
}
export default GlobalSort

View File

@ -1,21 +1,22 @@
/* eslint-disable react/prop-types */
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Box, LinearProgress } from '@material-ui/core'
import {
useGlobalFilter,
usePagination,
useRowSelect,
useSortBy,
useTable
} from 'react-table'
import SplitPane from 'client/components/SplitPane'
import Toolbar from 'client/components/Tables/Virtualized/toolbar'
import Toolbar from 'client/components/Tables/Enhanced/toolbar'
import Pagination from 'client/components/Tables/Enhanced/pagination'
import { addOpacityToColor } from 'client/utils'
const useStyles = makeStyles(({ palette, typography }) => ({
const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({
root: {
height: '100%',
display: 'flex',
@ -47,37 +48,38 @@ const useStyles = makeStyles(({ palette, typography }) => ({
},
toolbar: {
...typography.body1,
color: palette.text.hint,
marginBottom: 16,
display: 'flex',
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'space-between',
gap: '1em',
alignItems: 'start',
justifyContent: 'space-between',
'& > div:first-child': {
flexGrow: 1
},
[breakpoints.down('sm')]: {
flexWrap: 'wrap'
}
},
pagination: {
flexShrink: 0,
display: 'flex',
alignItems: 'center',
gap: '1em',
transition: '200ms'
gap: '1em'
}
}))
const EnhancedTable = ({
data,
canFetchMore,
columns,
pageSize = 10,
isLoading,
showPageCount,
getRowId,
RowComponent,
renderDetail,
renderAllSelected = true,
data,
fetchMore,
canFetchMore
getRowId,
isLoading,
pageSize = 10,
renderAllSelected = true,
renderDetail,
RowComponent,
showPageCount
}) => {
const classes = useStyles()
@ -92,20 +94,21 @@ const EnhancedTable = ({
defaultColumn,
getRowId,
// When table has update, disable all of the auto resetting
autoResetExpanded: false,
autoResetFilters: false,
autoResetGroupBy: false,
autoResetPage: false,
autoResetRowState: false,
autoResetSelectedRow: false,
autoResetExpanded: false,
autoResetGroupBy: false,
autoResetSelectedRows: false,
autoResetSortBy: false,
autoResetFilters: false,
// -------------------------------------
initialState: {
pageSize
}
},
useGlobalFilter,
useSortBy,
usePagination,
useRowSelect
)
@ -187,4 +190,21 @@ const EnhancedTable = ({
)
}
EnhancedTable.propTypes = {
canFetchMore: PropTypes.bool,
columns: PropTypes.array,
data: PropTypes.array,
fetchMore: PropTypes.func,
getRowId: PropTypes.func,
isLoading: PropTypes.bool,
pageSize: PropTypes.number,
renderAllSelected: PropTypes.bool,
renderDetail: PropTypes.func,
RowComponent: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
]),
showPageCount: PropTypes.bool
}
export default EnhancedTable

View File

@ -0,0 +1,87 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Button } from '@material-ui/core'
import { Filter as FilterIcon } from 'iconoir-react'
import GlobalFilter from 'client/components/Tables/Enhanced/Utils/GlobalFilter'
import GlobalSort from 'client/components/Tables/Enhanced/Utils/GlobalSort'
import { Tr } from 'client/components/HOC'
import { T } from 'client/constants'
const useToolbarStyles = makeStyles(() => ({
root: {
display: 'flex',
flexDirection: 'column',
alignItems: 'start',
gap: '1em'
},
filterWrapper: {
flexGrow: 1,
display: 'flex',
gap: '1em'
},
filterButton: {
minWidth: 123,
textAlign: 'left'
}
}))
const Toolbar = ({ useTableProps }) => {
const classes = useToolbarStyles()
/**
* @type {import('react-table').UseGlobalFiltersInstanceProps &
* import('react-table').UseSortByInstanceProps &
* import('react-table').TableInstance &
* { state: import('react-table').UseGlobalFiltersState &
* import('react-table').TableState
* import('react-table').UseSortByState }}
*/
const {
headers,
preGlobalFilteredRows,
setGlobalFilter,
preSortedRows,
setSortBy,
state: { globalFilter, sortBy }
} = useTableProps
// const numSelected = Object.keys(selectedRowIds).length
return (
<div className={classes.root}>
<div className={classes.filterWrapper}>
<Button variant='outlined'
color='inherit'
startIcon={<FilterIcon />}
className={classes.filterButton}
>
{Tr(T.Filter)}
</Button>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={globalFilter}
setGlobalFilter={setGlobalFilter}
/>
</div>
<GlobalSort
headers={headers}
preSortedRows={preSortedRows}
sortBy={sortBy}
setSortBy={setSortBy}
/>
</div>
)
}
Toolbar.propTypes = {
useTableProps: PropTypes.object
}
Toolbar.defaultProps = {
useTableProps: {}
}
export default Toolbar

View File

@ -1,76 +1,13 @@
import * as React from 'react'
import { LinearProgressWithLabel, StatusBadge } from 'client/components/Status'
import * as HostModel from 'client/models/Host'
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 = HostModel.getState(row)
return (
<StatusBadge
title={state?.name}
stateColor={state?.color}
customTransform='translate(150%, 50%)'
/>
)
}
},
{ Header: '#', accessor: 'ID', width: 45 },
{ Header: 'Name', accessor: 'NAME' },
{
Header: 'IM/VM',
width: 100,
accessor: ({ IM_MAD, VM_MAD }) =>
IM_MAD === VM_MAD ? IM_MAD : `${IM_MAD}/${VM_MAD}`
},
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', id: 'NAME', accessor: row => row?.TEMPLATE?.NAME ?? row.NAME },
{ Header: 'State', accessor: 'STATE' },
{ Header: 'Cluster', accessor: 'CLUSTER' },
{ Header: 'RVMs', accessor: 'HOST_SHARE.RUNNING_VMS', width: 100 },
{
Header: 'Allocated CPU',
accessor: row => {
const { percentCpuUsed, percentCpuLabel } = HostModel.getAllocatedInfo(row)
return (
<div style={{ paddingRight: '1em' }}>
<LinearProgressWithLabel value={percentCpuUsed} label={percentCpuLabel} />
</div>
)
}
},
{
Header: 'Allocated MEM',
accessor: row => {
const { percentMemUsed, percentMemLabel } = HostModel.getAllocatedInfo(row)
return (
<div style={{ paddingRight: '1em' }}>
<LinearProgressWithLabel value={percentMemUsed} label={percentMemLabel} />
</div>
)
}
}
{ Header: 'IM MAD', accessor: 'IM_MAD' },
{ Header: 'VM MAD', accessor: 'VM_MAD' },
{ Header: 'Running VMs', accessor: 'HOST_SHARE.RUNNING_VMS' },
{ Header: 'CPU Usage', accessor: 'CPU_USAGE' },
{ Header: 'CPU Total', accessor: 'TOTAL_CPU' },
{ Header: 'MEM Usage', accessor: 'MEM_USAGE' },
{ Header: 'MEM Total', accessor: 'TOTAL_MEM' }
]

View File

@ -11,7 +11,7 @@ import * as HostModel from 'client/models/Host'
const Row = ({ value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, IM_MAD, VM_MAD, VMS, CLUSTER } = value
const { ID, NAME, IM_MAD, VM_MAD, VMS, CLUSTER, TEMPLATE } = value
const {
percentCpuUsed,
@ -32,7 +32,7 @@ const Row = ({ value, ...props }) => {
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
{TEMPLATE?.NAME ?? NAME}
<span className={classes.labels}>
{labels.map(label => (
<StatusChip key={label} stateColor={'#c6c6c6'} text={label} />
@ -41,11 +41,11 @@ const Row = ({ value, ...props }) => {
</Typography>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span>
<span title={`Cluster: ${CLUSTER}`}>
<Server size={16} />
<span>{` ${CLUSTER}`}</span>
</span>
<span>
<span title={`Running VMs: ${runningVms}`}>
<ModernTv size={16} />
<span>{` ${runningVms}`}</span>
</span>

View File

@ -1,11 +1,10 @@
export default [
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'UNAME' },
{ Header: '', accessor: 'GNAME' },
{ Header: '', accessor: 'STATE' },
{ Header: '', accessor: 'TYPE' },
{ Header: '', accessor: 'DISK_TYPE' },
{ Header: '', accessor: 'DATASTORE_ID' },
{ Header: '', accessor: 'DATASTORE' }
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'State', accessor: 'STATE' },
{ Header: 'Type', accessor: 'TYPE' },
{ Header: 'Disk Type', accessor: 'DISK_TYPE' },
{ Header: 'Datastore', accessor: 'DATASTORE' }
]

View File

@ -45,19 +45,19 @@ const Row = ({ value, ...props }) => {
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
</span>
<span>
<span title={`Owner: ${UNAME}`}>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
<span>
<span title={`Datastore: ${DATASTORE}`}>
<Folder size={16} />
<span>{` ${DATASTORE}`}</span>
</span>
<span>
<span title={`Running / Used VMs: ${RUNNING_VMS} / ${usedByVms}`}>
<ModernTv size={16} />
<span>{` ${RUNNING_VMS} / ${usedByVms}`}</span>
</span>

View File

@ -1,12 +1,12 @@
export default [
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'UNAME' },
{ Header: '', accessor: 'GNAME' },
{ Header: '', accessor: 'REGTIME' },
{ Header: '', accessor: 'MARKETPLACE' },
{ Header: '', accessor: 'STATE' },
{ Header: '', accessor: 'TYPE' },
{ Header: '', accessor: 'ZONE_ID' },
{ Header: '', accessor: 'SIZE' }
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'State', accessor: 'STATE' },
{ Header: 'Type', accessor: 'TYPE' },
{ Header: 'Registration Time', accessor: 'REGTIME' },
{ Header: 'Marketplace', accessor: 'MARKETPLACE' },
{ Header: 'Zone ID', accessor: 'ZONE_ID' },
{ Header: 'Size', accessor: 'SIZE' }
]

View File

@ -38,15 +38,15 @@ const Row = ({ value, ...props }) => {
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
</span>
<span>
<span title={`Owner: ${UNAME}`}>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
<span>
<span title={`Marketplace: ${MARKETPLACE}`}>
<Cart size={16} />
<span>{` ${MARKETPLACE}`}</span>
</span>

View File

@ -1,9 +1,15 @@
const getNumberOfResources = resources => [resources?.ID ?? []].flat().length || 0
export default [
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'UNAME' },
{ Header: '', accessor: 'GNAME' },
{ Header: '', accessor: 'MARKET_MAD' },
{ Header: '', accessor: 'ZONE_ID' },
{ Header: '', accessor: 'MARKETPLACEAPPS' }
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'Market', accessor: 'MARKET_MAD' },
{ Header: 'Zone ID', accessor: 'ZONE_ID' },
{
Header: 'Number of Apps',
id: 'MARKETPLACEAPPS',
accessor: row => getNumberOfResources(row?.MARKETPLACEAPPS)
}
]

View File

@ -1,7 +1,7 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { User, Group } from 'iconoir-react'
import { User, Group, CloudDownload } from 'iconoir-react'
import { Typography } from '@material-ui/core'
import { StatusCircle, LinearProgressWithLabel, StatusChip } from 'client/components/Status'
@ -32,16 +32,16 @@ const Row = ({ value, ...props }) => {
</Typography>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span>
<span title={`Owner: ${UNAME}`}>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
<span>
<Group size={16} />
<span title={`Number of Apps: ${apps}`}>
<CloudDownload size={16} />
<span>{` ${apps}`}</span>
</span>
</div>

View File

@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import { makeStyles, Button } from '@material-ui/core'
import { Filter as FilterIcon } from 'iconoir-react'
// import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
const useToolbarStyles = makeStyles(theme => ({
filterWrapper: {
@ -24,9 +24,9 @@ const Toolbar = ({ useTableProps }) => {
const classes = useToolbarStyles()
/** @type {import('react-table').UseGlobalFiltersInstanceProps} */
// const { preGlobalFilteredRows, setGlobalFilter, state } = useTableProps
const { preGlobalFilteredRows, setGlobalFilter, state } = useTableProps
// const { selectedRowIds, globalFilter } = state
const { globalFilter } = state
// const numSelected = Object.keys(selectedRowIds).length
return (
@ -38,7 +38,12 @@ const Toolbar = ({ useTableProps }) => {
>
Filters
</Button>
<span>No filters selected</span>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={globalFilter}
setGlobalFilter={setGlobalFilter}
/>
{/* <span>No filters selected</span> */}
</div>
)

View File

@ -1,8 +1,22 @@
import * as VirtualMachineModel from 'client/models/VirtualMachine'
export default [
{ Header: '', accessor: 'ID' },
{ Header: '', accessor: 'NAME' },
{ Header: '', accessor: 'STATE' },
{ Header: '', accessor: 'LCM_STATE' },
{ Header: '', accessor: 'UID' },
{ Header: '', accessor: 'GID' }
{ Header: 'ID', accessor: 'ID' },
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'State', accessor: 'STATE' },
{ Header: 'LCM State', accessor: 'LCM_STATE' },
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
{ Header: 'Start Time', accessor: 'STIME' },
{ Header: 'End Time', accessor: 'ETIME' },
{
Header: 'Ips',
id: 'IPS',
accessor: row => VirtualMachineModel.getIps(row).join(',')
},
{
Header: 'Hostname',
id: 'HOSTNAME',
accessor: row => VirtualMachineModel.getLastHistory(row)?.HOSTNAME
}
]

View File

@ -36,22 +36,22 @@ const Row = ({ value, ...props }) => {
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
</span>
<span>
<span title={`Owner: ${UNAME}`}>
<User size={16} />
<span>{` ${UNAME}`}</span>
</span>
<span>
<span title={`Group: ${GNAME}`}>
<Group size={16} />
<span>{` ${GNAME}`}</span>
</span>
<span>
<span title={`Hostname: ${HOSTNAME}`}>
<HardDrive size={16} />
<span>{` ${HOSTNAME}`}</span>
</span>
</div>
</div>
<div className={classes.secondary}>
<Multiple tags={ips} limitTags={1} />
<Multiple tags={ips} />
</div>
</div>
)

View File

@ -3,6 +3,8 @@ module.exports = {
Back: 'Back',
Previous: 'Previous',
Next: 'Next',
SortBy: 'Sort by',
Filter: 'Filter',
/* actions */
Accept: 'Accept',