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:
parent
761f2d5bdf
commit
566345747a
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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>
|
||||
|
73
src/fireedge/src/client/components/Tables/Enhanced/styles.js
Normal file
73
src/fireedge/src/client/components/Tables/Enhanced/styles.js
Normal 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'
|
||||
}
|
||||
}))
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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' },
|
||||
|
@ -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
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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' }
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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' },
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -49,6 +49,7 @@ module.exports = {
|
||||
NotFound: 'Not found',
|
||||
None: 'None',
|
||||
Empty: 'Empty',
|
||||
NoDataAvailable: 'There is no data available',
|
||||
|
||||
/* steps form */
|
||||
/* steps form - flow */
|
||||
|
Loading…
x
Reference in New Issue
Block a user