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

F OpenNebula/one#5422: Add category filters

This commit is contained in:
Sergio Betanzos 2021-06-28 15:52:49 +02:00
parent 761f2d5bdf
commit 566345747a
No known key found for this signature in database
GPG Key ID: E3E704F097737136
23 changed files with 498 additions and 228 deletions

View File

@ -6,16 +6,18 @@ import { Typography } from '@material-ui/core'
import { rowStyles } from 'client/components/Tables/styles'
const Row = ({ value, ...props }) => {
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, HOSTS, DATASTORES, VNETS, PROVIDER_NAME } = value
return (
<div {...props}>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
</Typography>
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
</div>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span title={`Total Hosts: ${HOSTS}`}>
@ -41,6 +43,7 @@ const Row = ({ value, ...props }) => {
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func

View File

@ -1,3 +1,4 @@
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
import * as DatastoreModel from 'client/models/Datastore'
export default [
@ -8,7 +9,14 @@ export default [
{
Header: 'State',
id: 'STATE',
accessor: row => DatastoreModel.getState(row)
accessor: row => DatastoreModel.getState(row)?.name,
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'State'
}),
filter: 'includesValue'
},
{
Header: 'Type',

View File

@ -9,25 +9,29 @@ import { rowStyles } from 'client/components/Tables/styles'
import * as DatastoreModel from 'client/models/Datastore'
const Row = ({ value, ...props }) => {
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, UNAME, GNAME, STATE, TYPE, CLUSTERS, LOCK, PROVISION_ID } = value
const { ID, NAME, UNAME, GNAME, TYPE, CLUSTERS, LOCK, PROVISION_ID } = value
const { percentOfUsed, percentLabel } = DatastoreModel.getCapacityInfo(value)
const { color: stateColor, name: stateName } = DatastoreModel.getState(original)
return (
<div {...props}>
<div>
<StatusCircle color={STATE?.color} tooltip={STATE?.name} />
<StatusCircle color={stateColor} tooltip={stateName} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
<span className={classes.labels}>
{LOCK && <Lock size={20} />}
<StatusChip stateColor={'#c6c6c6'} text={TYPE?.name} />
</span>
</Typography>
</div>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span title={`Owner: ${UNAME}`}>
@ -56,6 +60,7 @@ const Row = ({ value, ...props }) => {
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func

View File

@ -0,0 +1,111 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { List, ListSubheader, ListItem, Typography, IconButton } from '@material-ui/core'
import { Cancel } from 'iconoir-react'
import { Tr } from 'client/components/HOC'
// FILTER FUNCTION
export const categoryFilterFn = (accessorOption, rows, columnsId, filterValue) =>
rows.filter(row =>
columnsId.some(id => {
const val = row.values[id][accessorOption] ?? row.values[id]
return filterValue.includes(val)
})
)
categoryFilterFn.autoRemove = val => !val || !val.length
// ***************************************************************
const CategoryFilter = ({ title, column, accessorOption, multiple }) => {
/** @type {import('react-table').UseFiltersInstanceProps} */
const {
setFilter,
id,
preFilteredRows,
filterValue = multiple ? [] : undefined
} = column
React.useEffect(() => () => setFilter(multiple ? [] : undefined), [])
// Calculate the options for filtering using the preFilteredRows
const options = React.useMemo(() => {
const options = new Set()
preFilteredRows?.forEach(row => {
options.add(row.values[id])
})
return [...options.values()]
}, [id])
const handleSelect = value => setFilter(
multiple ? [...filterValue, value] : value
)
const handleUnselect = value => setFilter(
multiple ? filterValue.filter(v => v !== value) : undefined
)
const handleClear = () => setFilter(multiple ? [] : undefined)
const isFiltered = React.useMemo(() => (
multiple ? filterValue?.length > 0 : filterValue !== undefined
), [filterValue])
if (options.length === 0) {
return null
}
return (
<List dense disablePadding>
{title && (
<ListSubheader disableSticky disableGutters
title={Tr(title)}
style={{ display: 'flex', alignItems: 'center' }}
>
{Tr(title)}
{isFiltered && (
<IconButton disableRipple disablePadding size='small' onClick={handleClear}>
<Cancel/>
</IconButton>
)}
</ListSubheader>
)}
{options.map((option, i) => {
const value = option[accessorOption] ?? option
const isSelected = multiple
? filterValue?.includes?.(value)
: value === filterValue
return (
<ListItem key={i} button
selected={isSelected}
onClick={() =>
isSelected ? handleUnselect(value) : handleSelect(value)
}
>
<Typography noWrap variant='subtitle2' title={value}>
{value}
</Typography>
</ListItem>
)
})}
</List>
)
}
CategoryFilter.propTypes = {
column: PropTypes.object,
accessorOption: PropTypes.string,
icon: PropTypes.node,
title: PropTypes.string,
multiple: PropTypes.bool
}
export default CategoryFilter

View File

@ -1,4 +1,4 @@
import React from 'react'
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, fade, debounce, InputBase } from '@material-ui/core'
@ -14,7 +14,6 @@ const useStyles = makeStyles(({ spacing, palette, shape, breakpoints }) => ({
},
width: '100%',
[breakpoints.up('sm')]: {
marginLeft: spacing(1),
width: 'auto'
}
},
@ -38,14 +37,14 @@ const useStyles = makeStyles(({ spacing, palette, shape, breakpoints }) => ({
}
}))
const GlobalFilter = props => {
const GlobalFilter = ({ useTableProps }) => {
const classes = useStyles()
/**
* @type {import('react-table').UseGlobalFiltersInstanceProps &
* import('react-table').UseGlobalFiltersState}
* { state: import('react-table').UseGlobalFiltersState }}
*/
const { globalFilter, setGlobalFilter } = props
const classes = useStyles()
const { setGlobalFilter, state: { globalFilter } } = useTableProps
const [value, setValue] = React.useState(globalFilter)
@ -77,9 +76,7 @@ const GlobalFilter = props => {
}
GlobalFilter.propTypes = {
preGlobalFilteredRows: PropTypes.array.isRequired,
globalFilter: PropTypes.string,
setGlobalFilter: PropTypes.func.isRequired
useTableProps: PropTypes.object.isRequired
}
export default GlobalFilter

View File

@ -1,4 +1,4 @@
import React from 'react'
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, MenuItem, MenuList, Chip } from '@material-ui/core'
@ -7,24 +7,26 @@ import { SortDown, ArrowDown, ArrowUp } from 'iconoir-react'
import HeaderPopover from 'client/components/Header/Popover'
import { T } from 'client/constants'
const useStyles = makeStyles(theme => ({
const useStyles = makeStyles({
root: {
display: 'flex',
flexWrap: 'wrap',
gap: 6,
alignItems: 'center'
}
}))
})
const GlobalSort = props => {
const GlobalSort = ({ useTableProps }) => {
const classes = useStyles()
React.useEffect(() => () => setSortBy([]), [])
/**
* @type {import('react-table').TableInstance &
* import('react-table').UseSortByInstanceProps &
* import('react-table').UseSortByState}
* @type {import('react-table').UseSortByInstanceProps &
* import('react-table').TableInstance &
* { state: import('react-table').UseSortByState }}
*/
const { headers, sortBy, setSortBy } = props
const { headers, setSortBy, state: { sortBy } } = useTableProps
const headersNotSorted = React.useMemo(() =>
headers.filter(({ isSorted, canSort, isVisible }) =>
@ -95,10 +97,7 @@ const GlobalSort = props => {
}
GlobalSort.propTypes = {
headers: PropTypes.array.isRequired,
preSortedRows: PropTypes.array.isRequired,
sortBy: PropTypes.array.isRequired,
setSortBy: PropTypes.func.isRequired
useTableProps: PropTypes.object.isRequired
}
export default GlobalSort

View File

@ -0,0 +1,75 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import clsx from 'clsx'
import { makeStyles, useMediaQuery, Card, CardContent } from '@material-ui/core'
import GlobalFilter from 'client/components/Tables/Enhanced/Utils/GlobalFilter'
const useToolbarStyles = makeStyles({
root: {
display: 'flex'
},
rootNoFilters: {
gridColumn: '1/3',
'& ~ div': {
gridColumn: '1/3'
}
},
content: {
flexGrow: 1
},
contentWithFilter: {
display: 'flex',
flexDirection: 'column',
gap: '1em'
},
filters: {
flexGrow: 1,
overflow: 'auto'
}
})
const Filters = ({ useTableProps }) => {
const classes = useToolbarStyles()
const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm'))
/** @type {import('react-table').UseTableInstanceProps} */
const { rows, columns } = useTableProps
const filters = React.useMemo(() => (
columns
.filter(({ canFilter }) => canFilter)
.map(column => column.canFilter ? column.render('Filter') : null)
), [rows])
if (isMobile) {
return <GlobalFilter useTableProps={useTableProps} />
}
const noFilters = filters.length === 0
return (
<Card variant='outlined'
className={clsx(classes.root, { [classes.rootNoFilters]: noFilters })}
>
<CardContent
className={clsx(classes.content, { [classes.contentWithFilter]: !noFilters })}
>
<GlobalFilter useTableProps={useTableProps} />
{!noFilters && <div className={classes.filters}>{filters}</div>}
</CardContent>
</Card>
)
}
Filters.propTypes = {
useTableProps: PropTypes.object
}
Filters.defaultProps = {
useTableProps: {}
}
export default Filters

View File

@ -1,9 +1,11 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Box, LinearProgress } from '@material-ui/core'
import { InfoEmpty } from 'iconoir-react'
import { Box, LinearProgress, Typography } from '@material-ui/core'
import {
useGlobalFilter,
useFilters,
usePagination,
useRowSelect,
useSortBy,
@ -13,63 +15,12 @@ import {
import SplitPane from 'client/components/SplitPane'
import Toolbar from 'client/components/Tables/Enhanced/toolbar'
import Pagination from 'client/components/Tables/Enhanced/pagination'
import Filters from 'client/components/Tables/Enhanced/filters'
import DefaultFilter from 'client/components/Table/Filters/DefaultFilter'
import EnhancedTableStyles from 'client/components/Tables/Enhanced/styles'
import { addOpacityToColor } from 'client/utils'
const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({
root: {
height: '100%',
display: 'flex',
flexDirection: 'column'
},
body: {
overflow: 'auto',
display: 'grid',
gap: '1em',
gridTemplateColumns: '1fr',
'& > [role=row]': {
padding: '0.8em',
cursor: 'pointer',
color: palette.text.primary,
backgroundColor: palette.background.paper,
fontWeight: typography.fontWeightMedium,
fontSize: '1em',
borderRadius: 6,
display: 'flex',
gap: 8,
'&:hover': {
backgroundColor: palette.action.hover
},
'&.selected': {
backgroundColor: addOpacityToColor(palette.secondary.main, 0.2),
border: `1px solid ${palette.secondary.main}`
}
}
},
toolbar: {
...typography.body1,
marginBottom: 16,
display: 'flex',
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'
},
loading: {
transition: '200ms'
}
}))
import { Tr } from 'client/components/HOC'
import { T } from 'client/constants'
const EnhancedTable = ({
canFetchMore,
@ -84,10 +35,11 @@ const EnhancedTable = ({
RowComponent,
showPageCount
}) => {
const classes = useStyles()
const classes = EnhancedTableStyles()
const defaultColumn = React.useMemo(() => ({
// Filter: DefaultFilter,
Filter: DefaultFilter,
disableFilters: true
}), [])
const sortTypes = React.useMemo(() => ({
@ -118,6 +70,7 @@ const EnhancedTable = ({
}
},
useGlobalFilter,
useFilters,
useSortBy,
usePagination,
useRowSelect
@ -143,7 +96,7 @@ const EnhancedTable = ({
const canNextPage = pageCount === -1 ? page.length >= pageSize : newPage < pageCount - 1
newPage > pageIndex && canFetchMore && !canNextPage && fetchMore()
newPage > pageIndex && canFetchMore && !canNextPage && fetchMore?.()
}
return (
@ -167,30 +120,44 @@ const EnhancedTable = ({
<LinearProgress size='1em' color='secondary' className={classes.loading} />
)}
<div className={classes.body}>
{page.map(row => {
prepareRow(row)
<div className={classes.table}>
<Filters useTableProps={useTableProps} />
/** @type {import('react-table').UseRowSelectRowProps} */
const { getRowProps, values, toggleRowSelected, isSelected } = row
const { key, ...rowProps } = getRowProps()
<div className={classes.body}>
{/* NO DATA MESSAGE */}
{((!isLoading && data.length === 0) || page?.length === 0) && (
<span className={classes.noDataMessage}>
<InfoEmpty />
{Tr(T.NoDataAvailable)}
</span>
)}
return (
<RowComponent
{...rowProps}
key={key}
value={values}
className={isSelected ? 'selected' : ''}
onClick={() => toggleRowSelected(!isSelected)}
/>
)
})}
{/* DATALIST PER PAGE */}
{page.map(row => {
prepareRow(row)
/** @type {import('react-table').UseRowSelectRowProps} */
const { getRowProps, original, values, toggleRowSelected, isSelected } = row
const { key, ...rowProps } = getRowProps()
return (
<RowComponent
{...rowProps}
key={key}
original={original}
value={values}
className={isSelected ? 'selected' : ''}
onClick={() => toggleRowSelected(!isSelected)}
/>
)
})}
</div>
</div>
</Box>
<div style={{ display: 'flex', flexDirection: 'column', overflow: 'auto' }}>
{selectedRows?.length === 1 && renderDetail
? renderDetail(selectedRows[0]?.values)
? renderDetail?.(selectedRows[0]?.values)
: renderAllSelected && (
<pre>
<code>

View File

@ -0,0 +1,73 @@
import { makeStyles } from '@material-ui/core'
import { addOpacityToColor } from 'client/utils'
export default makeStyles(({ palette, typography, breakpoints }) => ({
root: {
height: '100%',
display: 'flex',
flexDirection: 'column'
},
toolbar: {
...typography.body1,
marginBottom: 16,
display: 'flex',
gap: '1em',
alignItems: 'start',
justifyContent: 'space-between',
'& > div:first-child': {
flexGrow: 1
}
},
pagination: {
flexShrink: 0,
display: 'flex',
alignItems: 'center',
gap: '1em'
},
loading: {
transition: '200ms'
},
table: {
display: 'grid',
gridTemplateColumns: 'minmax(auto, 300px) 1fr',
gap: 8,
overflow: 'auto',
[breakpoints.down('sm')]: {
gridTemplateColumns: 'minmax(0, 1fr)'
}
},
body: {
overflow: 'auto',
display: 'grid',
gap: '1em',
gridTemplateColumns: 'minmax(0, 1fr)',
gridAutoRows: 'max-content',
'& > [role=row]': {
padding: '0.8em',
cursor: 'pointer',
color: palette.text.primary,
backgroundColor: palette.background.paper,
fontWeight: typography.fontWeightMedium,
fontSize: '1em',
borderRadius: 6,
display: 'flex',
gap: 8,
'&:hover': {
backgroundColor: palette.action.hover
},
'&.selected': {
backgroundColor: addOpacityToColor(palette.secondary.main, 0.2),
border: `1px solid ${palette.secondary.main}`
}
}
},
noDataMessage: {
...typography.h6,
color: palette.text.hint,
display: 'inline-flex',
alignItems: 'center',
gap: 6,
padding: '1em'
}
}))

View File

@ -1,77 +1,26 @@
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 { makeStyles, useMediaQuery } from '@material-ui/core'
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(() => ({
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
const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm'))
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}
/>
{!isMobile && <GlobalSort useTableProps={useTableProps} />}
</div>
)
}

View File

@ -1,3 +1,4 @@
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
import * as HostModel from 'client/models/Host'
const getTotalOfResources = resources => [resources?.ID ?? []].flat().length || 0
@ -12,7 +13,14 @@ export default [
{
Header: 'State',
id: 'STATE',
accessor: row => HostModel.getState(row)
accessor: row => HostModel.getState(row)?.name,
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'State'
}),
filter: 'includesValue'
},
{ Header: 'Cluster', accessor: 'CLUSTER' },
{ Header: 'IM MAD', accessor: 'IM_MAD' },

View File

@ -9,11 +9,11 @@ import { rowStyles } from 'client/components/Tables/styles'
import * as HostModel from 'client/models/Host'
const Row = ({ value, ...props }) => {
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const {
ID, NAME, IM_MAD, VM_MAD, STATE,
RUNNING_VMS, TOTAL_VMS, CLUSTER, TEMPLATE
ID, NAME, IM_MAD, VM_MAD, RUNNING_VMS,
TOTAL_VMS, CLUSTER, TEMPLATE
} = value
const {
@ -23,22 +23,26 @@ const Row = ({ value, ...props }) => {
percentMemLabel
} = HostModel.getAllocatedInfo(value)
const { color: stateColor, name: stateName } = HostModel.getState(original)
const labels = [...new Set([IM_MAD, VM_MAD])]
return (
<div {...props}>
<div>
<StatusCircle color={STATE?.color} tooltip={STATE?.name} />
<StatusCircle color={stateColor} tooltip={stateName} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{TEMPLATE?.NAME ?? NAME}
<div className={classes.title}>
<Typography className={classes.titleText} noWrap component='span'>
{TEMPLATE?.NAME ?? NAME}
</Typography>
<span className={classes.labels}>
{labels.map(label => (
<StatusChip key={label} stateColor={'#c6c6c6'} text={label} />
))}
</span>
</Typography>
</div>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span title={`Cluster: ${CLUSTER}`}>
@ -60,6 +64,7 @@ const Row = ({ value, ...props }) => {
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func

View File

@ -1,3 +1,4 @@
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
import * as ImageModel from 'client/models/Image'
const getTotalOfResources = resources => [resources?.ID ?? []].flat().length || 0
@ -10,7 +11,14 @@ export default [
{
Header: 'State',
id: 'STATE',
accessor: row => ImageModel.getState(row)
accessor: row => ImageModel.getState(row)?.name,
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'State'
}),
filter: 'includesValue'
},
{
Header: 'Type',

View File

@ -7,14 +7,15 @@ import { Typography } from '@material-ui/core'
import { StatusCircle, StatusChip } from 'client/components/Status'
import { rowStyles } from 'client/components/Tables/styles'
import * as ImageModel from 'client/models/Image'
import * as Helper from 'client/models/Helper'
const Row = ({ value, ...props }) => {
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const {
ID, NAME, UNAME, GNAME, REGTIME,
STATE, TYPE, DISK_TYPE, PERSISTENT,
LOCK, DATASTORE, VMS, RUNNING_VMS
ID, NAME, UNAME, GNAME, REGTIME, TYPE,
DISK_TYPE, PERSISTENT, LOCK, DATASTORE,
VMS, RUNNING_VMS
} = value
const usedByVms = [VMS?.ID ?? []].flat().length || 0
@ -22,24 +23,28 @@ const Row = ({ value, ...props }) => {
const labels = [...new Set([
PERSISTENT && 'PERSISTENT', TYPE, DISK_TYPE])].filter(Boolean)
const { color: stateColor, name: stateName } = ImageModel.getState(original)
const time = Helper.timeFromMilliseconds(+REGTIME)
const timeAgo = `registered ${time.toRelative()}`
return (
<div {...props}>
<div>
<StatusCircle color={STATE?.color} tooltip={STATE?.name} />
<StatusCircle color={stateColor} tooltip={stateName} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
{LOCK && <Lock size={20} />}
<span className={classes.labels}>
{labels.map(label => (
<StatusChip key={label} stateColor={'#c6c6c6'} text={label} />
))}
</span>
</Typography>
</div>
<div className={classes.caption}>
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
@ -68,6 +73,7 @@ const Row = ({ value, ...props }) => {
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func

View File

@ -1,3 +1,4 @@
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
import * as MarketplaceAppModel from 'client/models/MarketplaceApp'
export default [
@ -8,7 +9,14 @@ export default [
{
Header: 'State',
id: 'STATE',
accessor: row => MarketplaceAppModel.getState(row)
accessor: row => MarketplaceAppModel.getState(row)?.name,
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'State'
}),
filter: 'includesValue'
},
{
Header: 'Type',
@ -17,6 +25,16 @@ export default [
},
{ Header: 'Size', accessor: 'SIZE' },
{ Header: 'Registration Time', accessor: 'REGTIME' },
{ Header: 'Marketplace', accessor: 'MARKETPLACE' },
{
Header: 'Marketplace',
accessor: 'MARKETPLACE',
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'Marketplace'
}),
filter: 'includesValue'
},
{ Header: 'Zone ID', accessor: 'ZONE_ID' }
]

View File

@ -7,32 +7,37 @@ import { Typography } from '@material-ui/core'
import { StatusCircle, StatusChip } from 'client/components/Status'
import { rowStyles } from 'client/components/Tables/styles'
import * as MarketplaceAppModel from 'client/models/MarketplaceApp'
import * as Helper from 'client/models/Helper'
import { prettyBytes } from 'client/utils'
const Row = ({ value, ...props }) => {
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const {
ID, NAME, UNAME, GNAME, LOCK, TYPE, STATE,
ID, NAME, UNAME, GNAME, LOCK, TYPE,
REGTIME, MARKETPLACE, ZONE_ID, SIZE
} = value
const { color: stateColor, name: stateName } = MarketplaceAppModel.getState(original)
const time = Helper.timeFromMilliseconds(+REGTIME)
const timeAgo = `registered ${time.toRelative()}`
return (
<div {...props}>
<div>
<StatusCircle color={STATE?.color} tooltip={STATE?.name} />
<StatusCircle color={stateColor} tooltip={stateName} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
{LOCK && <Lock size={20} />}
<span className={classes.labels}>
<StatusChip stateColor={'#c6c6c6'} text={TYPE} />
</span>
</Typography>
</div>
<div className={classes.caption}>
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
@ -62,6 +67,7 @@ const Row = ({ value, ...props }) => {
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func

View File

@ -1,3 +1,4 @@
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
import * as MarketplaceModel from 'client/models/Datastore'
const getTotalOfResources = resources => [resources?.ID ?? []].flat().length || 0
@ -10,7 +11,14 @@ export default [
{
Header: 'State',
id: 'STATE',
accessor: row => MarketplaceModel.getState(row)
accessor: row => MarketplaceModel.getState(row)?.name,
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'State'
}),
filter: 'includesValue'
},
{ Header: 'Market', accessor: 'MARKET_MAD' },
{ Header: 'Total Capacity', accessor: 'TOTAL_MB' },

View File

@ -9,24 +9,28 @@ import { rowStyles } from 'client/components/Tables/styles'
import * as MarketplaceModel from 'client/models/Datastore'
const Row = ({ value, ...props }) => {
const Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const { ID, NAME, UNAME, GNAME, STATE, MARKET_MAD, TOTAL_APPS } = value
const { ID, NAME, UNAME, GNAME, MARKET_MAD, TOTAL_APPS } = value
const { name: stateName, color: stateColor } = MarketplaceModel.getState(original)
const { percentOfUsed, percentLabel } = MarketplaceModel.getCapacityInfo(value)
return (
<div {...props}>
<div>
<StatusCircle color={STATE?.color} tooltip={STATE?.name} />
<StatusCircle color={stateColor} tooltip={stateName} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
<span className={classes.labels}>
<StatusChip stateColor={'#c6c6c6'} text={MARKET_MAD} />
</span>
</Typography>
</div>
<div className={classes.caption}>
<span>{`#${ID}`}</span>
<span title={`Owner: ${UNAME}`}>
@ -52,6 +56,7 @@ const Row = ({ value, ...props }) => {
Row.propTypes = {
value: PropTypes.object,
original: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func
}

View File

@ -1,3 +1,4 @@
import CategoryFilter from 'client/components/Tables/Enhanced/Utils/CategoryFilter'
import * as VirtualMachineModel from 'client/models/VirtualMachine'
export default [
@ -6,7 +7,14 @@ export default [
{
Header: 'State',
id: 'STATE',
accessor: row => VirtualMachineModel.getState(row)
accessor: row => VirtualMachineModel.getState(row)?.name,
disableFilters: false,
Filter: ({ column }) => CategoryFilter({
column,
multiple: true,
title: 'State'
}),
filter: 'includesValue'
},
{ Header: 'Owner', accessor: 'UNAME' },
{ Header: 'Group', accessor: 'GNAME' },
@ -22,6 +30,6 @@ export default [
{
Header: 'Hostname',
id: 'HOSTNAME',
accessor: row => VirtualMachineModel.getLastHistory(row)?.HOSTNAME ?? '--'
accessor: row => VirtualMachineModel.getLastHistory(row)?.HOSTNAME
}
]

View File

@ -16,20 +16,22 @@ const Multiple = ({ tags, limitTags = 1 }) => {
<StatusChip key={tag} text={tag} stateColor='#ececec' />
))
return [
...Tags,
(more > 0 && (
<Tooltip arrow
title={tags.map(tag => (
<Typography key={tag} variant='subtitle2'>{tag}</Typography>
))}
>
<span style={{ marginLeft: 6 }}>
{`+${more} more`}
</span>
</Tooltip>
))
]
return (
<div style={{ display: 'flex', flexWrap: 'wrap' }}>
{Tags}
{more > 0 && (
<Tooltip arrow
title={tags.map(tag => (
<Typography key={tag} variant='subtitle2'>{tag}</Typography>
))}
>
<span style={{ marginLeft: 6 }}>
{`+${more} more`}
</span>
</Tooltip>
)}
</div>
)
}
Multiple.propTypes = {

View File

@ -8,28 +8,32 @@ 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 Row = ({ original, value, ...props }) => {
const classes = rowStyles()
const {
ID, NAME, UNAME, GNAME, STATE,
IPS, STIME, ETIME, HOSTNAME, LOCK
} = value
const { ID, NAME, UNAME, GNAME, IPS, STIME, ETIME, HOSTNAME = '--', LOCK } = value
const time = Helper.timeFromMilliseconds(+ETIME || +STIME)
const timeAgo = `${+ETIME ? 'done' : 'started'} ${time.toRelative()}`
const { color: stateColor, name: stateName } = VirtualMachineModel.getState(original)
return (
<div {...props}>
<div>
<StatusCircle color={STATE?.color} tooltip={STATE?.name} />
<StatusCircle color={stateColor} tooltip={stateName} />
</div>
<div className={classes.main}>
<Typography className={classes.title} component='span'>
{NAME}
{LOCK && <Lock size={20} />}
</Typography>
<div className={classes.title}>
<Typography className={classes.titleText} component='span'>
{NAME}
</Typography>
<span className={classes.labels}>
{LOCK && <Lock size={20} />}
</span>
</div>
<div className={classes.caption}>
<span title={time.toFormat('ff')}>
{`#${ID} ${timeAgo}`}
@ -56,6 +60,7 @@ const Row = ({ value, ...props }) => {
}
Row.propTypes = {
original: PropTypes.object,
value: PropTypes.object,
isSelected: PropTypes.bool,
handleClick: PropTypes.func

View File

@ -3,17 +3,20 @@ import { makeStyles } from '@material-ui/core'
export const rowStyles = makeStyles(
({ palette, typography, breakpoints }) => ({
main: {
flex: 'auto'
flex: 'auto',
overflow: 'hidden'
},
title: {
color: palette.text.primary,
display: 'flex',
alignItems: 'center'
gap: 6,
alignItems: 'center',
flexWrap: 'wrap',
marginBottom: 8
},
labels: {
display: 'inline-flex',
gap: 6,
marginLeft: 6
gap: 6
},
caption: {
...typography.caption,

View File

@ -49,6 +49,7 @@ module.exports = {
NotFound: 'Not found',
None: 'None',
Empty: 'Empty',
NoDataAvailable: 'There is no data available',
/* steps form */
/* steps form - flow */