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:
parent
e989154f03
commit
f03ef61352
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
]
|
||||
|
@ -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>
|
||||
|
@ -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' }
|
||||
]
|
||||
|
@ -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>
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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' }
|
||||
]
|
||||
|
@ -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>
|
||||
|
@ -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' }
|
||||
]
|
||||
|
@ -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>
|
||||
|
@ -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' }
|
||||
]
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
]
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -3,6 +3,8 @@ module.exports = {
|
||||
Back: 'Back',
|
||||
Previous: 'Previous',
|
||||
Next: 'Next',
|
||||
SortBy: 'Sort by',
|
||||
Filter: 'Filter',
|
||||
|
||||
/* actions */
|
||||
Accept: 'Accept',
|
||||
|
Loading…
x
Reference in New Issue
Block a user