mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
M ~#: Add vms, datastores & hosts tables
This commit is contained in:
parent
daadc63f20
commit
fc7ca2830b
@ -37,7 +37,7 @@ const Group = () => {
|
||||
}}
|
||||
>
|
||||
{NAME}
|
||||
{isSelected && <SelectIcon size='1rem' />}
|
||||
{isSelected && <SelectIcon size='1em' />}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ const useStyles = makeStyles(theme => ({
|
||||
badge: {
|
||||
backgroundColor: ({ stateColor }) => stateColor,
|
||||
color: ({ stateColor }) => stateColor,
|
||||
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
|
||||
transform: ({ customTransform }) => customTransform,
|
||||
boxShadow: '0 0 0 2px transparent',
|
||||
'&::after': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
@ -32,26 +33,25 @@ const useStyles = makeStyles(theme => ({
|
||||
}
|
||||
}))
|
||||
|
||||
const StatusBadge = memo(({ stateColor, children, ...props }) => {
|
||||
const classes = useStyles({ stateColor })
|
||||
const StatusBadge = memo(({ stateColor, children, customTransform, ...props }) => {
|
||||
const classes = useStyles({ stateColor, customTransform })
|
||||
|
||||
return (
|
||||
<Badge
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
classes={{ badge: classes.badge }}
|
||||
overlap="circle"
|
||||
variant="dot"
|
||||
overlap='circle'
|
||||
variant='dot'
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Badge>
|
||||
)
|
||||
},
|
||||
(prev, next) => prev.stateColor === next.stateColor
|
||||
)
|
||||
}, (prev, next) => prev.stateColor === next.stateColor)
|
||||
|
||||
StatusBadge.propTypes = {
|
||||
stateColor: PropTypes.string,
|
||||
customTransform: PropTypes.string,
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.node
|
||||
@ -60,6 +60,7 @@ StatusBadge.propTypes = {
|
||||
|
||||
StatusBadge.defaultProps = {
|
||||
stateColor: undefined,
|
||||
customTransform: undefined,
|
||||
children: ''
|
||||
}
|
||||
|
||||
|
@ -10,17 +10,17 @@ const BorderLinearProgress = withStyles(({ palette }) => ({
|
||||
borderRadius: 5
|
||||
},
|
||||
colorPrimary: {
|
||||
backgroundColor: palette.grey[palette.type === 'light' ? 200 : 700]
|
||||
backgroundColor: palette.grey[palette.type === 'light' ? 400 : 700]
|
||||
},
|
||||
bar: {
|
||||
borderRadius: 5,
|
||||
backgroundColor: palette.secondary.main
|
||||
backgroundColor: palette.primary.main
|
||||
}
|
||||
}))(LinearProgress)
|
||||
|
||||
const LinearProgressWithLabel = memo(({ value, label }) => (
|
||||
<div style={{ textAlign: 'end' }}>
|
||||
<Typography component='span' variant='body2'>{label}</Typography>
|
||||
<Typography component='span' variant='body2' noWrap>{label}</Typography>
|
||||
<BorderLinearProgress variant='determinate' value={value} />
|
||||
</div>
|
||||
), (prev, next) => prev.value === next.value && prev.label === next.label)
|
||||
|
@ -156,7 +156,8 @@ EnhancedTable.propTypes = {
|
||||
}
|
||||
|
||||
EnhancedTable.defaultProps = {
|
||||
skipPageReset: false
|
||||
skipPageReset: false,
|
||||
filterTypes: []
|
||||
}
|
||||
|
||||
export default EnhancedTable
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import { LinearProgressWithLabel, StatusBadge } from 'client/components/Status'
|
||||
import * as DatastoreModel from 'client/models/Datastore'
|
||||
|
||||
export default [
|
||||
@ -24,24 +24,39 @@ export default [
|
||||
)
|
||||
}, */
|
||||
{
|
||||
Header: '#', accessor: 'ID'
|
||||
},
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'State',
|
||||
Header: '',
|
||||
id: 'STATE',
|
||||
accessor: row => DatastoreModel.getState(row)?.name
|
||||
width: 50,
|
||||
accessor: row => {
|
||||
const state = DatastoreModel.getState(row)
|
||||
|
||||
return (
|
||||
<StatusBadge
|
||||
title={state?.name}
|
||||
stateColor={state?.color}
|
||||
customTransform='translate(150%, 50%)'
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
{ Header: '#', accessor: 'ID', width: 45 },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'Type',
|
||||
id: 'TYPE',
|
||||
width: 100,
|
||||
accessor: row => DatastoreModel.getType(row)?.name
|
||||
},
|
||||
{
|
||||
Header: 'Owner/Group',
|
||||
accessor: row => `${row.UNAME}/${row.GNAME}`
|
||||
},
|
||||
{ Header: 'Cluster', accessor: 'CLUSTER' },
|
||||
{
|
||||
Header: 'Clusters',
|
||||
id: 'CLUSTERS',
|
||||
width: 100,
|
||||
accessor: row => [row.CLUSTERS?.ID ?? []].flat().join(',')
|
||||
},
|
||||
{
|
||||
Header: 'Allocated CPU',
|
||||
accessor: row => {
|
||||
|
@ -1,51 +1,30 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useDatastore, useDatastoreApi } from 'client/features/One'
|
||||
|
||||
import { VirtualizedTable } from 'client/components/Tables'
|
||||
import { EnhancedTable } from 'client/components/Tables'
|
||||
import { DatastoreCard } from 'client/components/Cards'
|
||||
import Columns from 'client/components/Tables/Datastores/columns'
|
||||
|
||||
const INITIAL_ELEMENT = 0
|
||||
const NUMBER_OF_INTERVAL = 20
|
||||
|
||||
const DatastoresTable = () => {
|
||||
const [{ start, end }, setPage] = useState({
|
||||
start: INITIAL_ELEMENT,
|
||||
end: -NUMBER_OF_INTERVAL
|
||||
})
|
||||
|
||||
const columns = React.useMemo(() => Columns, [])
|
||||
|
||||
const datastores = useDatastore()
|
||||
const { getDatastores } = useDatastoreApi()
|
||||
const { filterPool } = useAuth()
|
||||
|
||||
const { data, fetchRequest, loading, reloading, error } = useFetch(getDatastores)
|
||||
const { fetchRequest, loading, reloading } = useFetch(getDatastores)
|
||||
|
||||
useEffect(() => { fetchRequest({ start, end }) }, [filterPool])
|
||||
|
||||
const fetchMore = () => {
|
||||
setPage(prevState => {
|
||||
const newStart = prevState.start + NUMBER_OF_INTERVAL
|
||||
const newEnd = prevState.end - NUMBER_OF_INTERVAL
|
||||
|
||||
fetchRequest({ start: newStart, end: newEnd })
|
||||
|
||||
return { start: newStart, end: newEnd }
|
||||
})
|
||||
}
|
||||
|
||||
const canFetchMore = error || data?.datastores?.length < NUMBER_OF_INTERVAL
|
||||
useEffect(() => { fetchRequest() }, [filterPool])
|
||||
|
||||
return (
|
||||
<VirtualizedTable
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={datastores}
|
||||
isLoading={loading || reloading}
|
||||
canFetchMore={canFetchMore}
|
||||
fetchMore={fetchMore}
|
||||
MobileComponentRow={DatastoreCard}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
228
src/fireedge/src/client/components/Tables/Enhanced/index.js
Normal file
228
src/fireedge/src/client/components/Tables/Enhanced/index.js
Normal file
@ -0,0 +1,228 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import * as React from 'react'
|
||||
|
||||
import { makeStyles, Box, CircularProgress, useMediaQuery } from '@material-ui/core'
|
||||
import {
|
||||
useGlobalFilter,
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
useTable
|
||||
} from 'react-table'
|
||||
|
||||
import Toolbar from 'client/components/Tables/Virtualized/toolbar'
|
||||
import Header from 'client/components/Tables/Virtualized/header'
|
||||
import Row from 'client/components/Tables/Enhanced/row'
|
||||
import Pagination from 'client/components/Tables/Enhanced/pagination'
|
||||
|
||||
import { addOpacityToColor } from 'client/utils'
|
||||
|
||||
const useStyles = makeStyles(({ palette, typography, breakpoints }) => ({
|
||||
root: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
table: {
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
[breakpoints.up('md')]: {
|
||||
border: `1px solid ${palette.action.disabledBackground}`,
|
||||
borderRadius: 6
|
||||
},
|
||||
// includes header row
|
||||
'& *[role=row]': {
|
||||
padding: '0.8em',
|
||||
textAlign: 'start',
|
||||
overflowWrap: 'break-word',
|
||||
lineHeight: '1rem',
|
||||
color: palette.text.primary,
|
||||
[breakpoints.up('md')]: {
|
||||
display: 'grid',
|
||||
gridAutoFlow: 'column',
|
||||
gridTemplateColumns: ({ columnsWidth }) => columnsWidth.join(' ')
|
||||
}
|
||||
}
|
||||
},
|
||||
header: {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 2,
|
||||
|
||||
textTransform: 'uppercase',
|
||||
fontSize: '0.9em',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.05em',
|
||||
borderBottom: 'transparent',
|
||||
backgroundColor: palette.grey[palette.type === 'light' ? 200 : 600],
|
||||
|
||||
[breakpoints.down('sm')]: {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
body: {
|
||||
[breakpoints.only('sm')]: {
|
||||
display: 'grid',
|
||||
gap: '1em',
|
||||
gridTemplateColumns: '1fr 1fr'
|
||||
},
|
||||
[breakpoints.only('xm')]: {
|
||||
display: 'grid',
|
||||
gap: '1em',
|
||||
gridTemplateColumns: '1fr'
|
||||
},
|
||||
'& *[role=row]': {
|
||||
fontSize: '1em',
|
||||
fontWeight: typography.fontWeightMedium,
|
||||
borderTop: `1px solid ${palette.action.disabledBackground}`,
|
||||
backgroundColor: palette.background.paper,
|
||||
'&:hover': {
|
||||
backgroundColor: palette.action.hover
|
||||
},
|
||||
'&:first-of-type': {
|
||||
borderTopColor: 'transparent'
|
||||
},
|
||||
'&.selected': {
|
||||
backgroundColor: addOpacityToColor(palette.secondary.main, 0.7)
|
||||
}
|
||||
},
|
||||
'& *[role=cell]': {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
},
|
||||
toolbar: {
|
||||
...typography.body1,
|
||||
color: palette.text.hint,
|
||||
marginBottom: 16,
|
||||
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
gap: '1em'
|
||||
},
|
||||
total: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1em',
|
||||
transition: 200
|
||||
}
|
||||
}))
|
||||
|
||||
const DefaultCell = React.memo(({ value }) => value ?? '--')
|
||||
DefaultCell.displayName = 'DefaultCell'
|
||||
|
||||
const EnhancedTable = ({
|
||||
data,
|
||||
columns,
|
||||
pageSize = 10,
|
||||
isLoading,
|
||||
showPageCount,
|
||||
fetchMore,
|
||||
canFetchMore,
|
||||
MobileComponentRow
|
||||
}) => {
|
||||
const columnsWidth = React.useMemo(() =>
|
||||
columns.map(({ width = '1fr' }) => {
|
||||
return Number.isInteger(width) ? `${width}px` : width
|
||||
}), [])
|
||||
|
||||
const isMobile = useMediaQuery(theme => theme.breakpoints.down('sm'))
|
||||
const classes = useStyles({ columnsWidth })
|
||||
|
||||
const defaultColumn = React.useMemo(() => ({
|
||||
// Filter: DefaultFilter,
|
||||
Cell: DefaultCell
|
||||
}), [])
|
||||
|
||||
const useTableProps = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
autoResetPage: false,
|
||||
initialState: {
|
||||
pageSize
|
||||
}
|
||||
},
|
||||
useGlobalFilter,
|
||||
usePagination,
|
||||
useRowSelect
|
||||
)
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
prepareRow,
|
||||
rows,
|
||||
page,
|
||||
gotoPage,
|
||||
pageCount,
|
||||
state: { pageIndex }
|
||||
} = useTableProps
|
||||
|
||||
const handleChangePage = newPage => {
|
||||
gotoPage(newPage)
|
||||
|
||||
const canNextPage = pageCount === -1 ? page.length >= pageSize : newPage < pageCount - 1
|
||||
|
||||
newPage > pageIndex && canFetchMore && !canNextPage && fetchMore()
|
||||
}
|
||||
|
||||
return (
|
||||
<Box {...getTableProps()} className={classes.root}>
|
||||
|
||||
<div className={classes.toolbar}>
|
||||
<Toolbar useTableProps={useTableProps} />
|
||||
<div className={classes.total}>
|
||||
{isLoading && <CircularProgress size='1em' color='secondary' />}
|
||||
Total loaded: {rows.length}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.table}>
|
||||
<div className={classes.header}>
|
||||
<Header useTableProps={useTableProps} />
|
||||
</div>
|
||||
|
||||
<div className={classes.body}>
|
||||
{
|
||||
page.map(row => {
|
||||
prepareRow(row)
|
||||
|
||||
/** @type {import('react-table').UseRowSelectRowProps} */
|
||||
const { getRowProps, original, toggleRowSelected, isSelected } = row
|
||||
|
||||
const key = getRowProps().key
|
||||
const handleSelect = () => toggleRowSelected(!isSelected)
|
||||
|
||||
return isMobile && MobileComponentRow ? (
|
||||
<MobileComponentRow
|
||||
key={key}
|
||||
value={original}
|
||||
isSelected={isSelected}
|
||||
handleClick={handleSelect}
|
||||
/>
|
||||
) : (
|
||||
<Row
|
||||
key={key}
|
||||
row={row}
|
||||
handleClick={handleSelect}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{page?.length > 0 && (
|
||||
<Pagination
|
||||
handleChangePage={handleChangePage}
|
||||
useTableProps={useTableProps}
|
||||
count={rows.length}
|
||||
showPageCount={showPageCount}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default EnhancedTable
|
@ -0,0 +1,94 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, IconButton } from '@material-ui/core'
|
||||
|
||||
import {
|
||||
FastArrowLeft as FirstPageIcon,
|
||||
NavArrowLeft as PreviousPageIcon,
|
||||
NavArrowRight as NextPageIcon,
|
||||
FastArrowRight as LastPageIcon
|
||||
} from 'iconoir-react'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
margin: '10px auto'
|
||||
}
|
||||
}))
|
||||
|
||||
const Pagination = ({
|
||||
count = 0,
|
||||
handleChangePage,
|
||||
useTableProps,
|
||||
showPageCount = true
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
||||
/** @type {import('react-table').UsePaginationState} */
|
||||
const { pageIndex, pageSize } = useTableProps.state
|
||||
|
||||
const pageCount = React.useMemo(() => Math.ceil(count / pageSize), [count])
|
||||
|
||||
const handleFirstPageButtonClick = () => {
|
||||
handleChangePage(0)
|
||||
}
|
||||
|
||||
const handleBackButtonClick = () => {
|
||||
handleChangePage(pageIndex - 1)
|
||||
}
|
||||
|
||||
const handleNextButtonClick = () => {
|
||||
handleChangePage(pageIndex + 1)
|
||||
}
|
||||
|
||||
const handleLastPageButtonClick = () => {
|
||||
handleChangePage(Math.max(0, pageCount - 1))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
{/* <IconButton
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={pageIndex === 0}
|
||||
aria-label="first page"
|
||||
>
|
||||
<FirstPageIcon />
|
||||
</IconButton> */}
|
||||
<IconButton
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={pageIndex === 0}
|
||||
aria-label="previous page"
|
||||
>
|
||||
<PreviousPageIcon />
|
||||
</IconButton>
|
||||
{showPageCount &&
|
||||
<span style={{ marginInline: '1em' }}>
|
||||
{`${pageIndex + 1} / ${pageCount}`}
|
||||
</span>
|
||||
}
|
||||
<IconButton
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={pageIndex >= Math.ceil(count / pageSize) - 1}
|
||||
aria-label="next page"
|
||||
>
|
||||
<NextPageIcon />
|
||||
</IconButton>
|
||||
{/* <IconButton
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={pageIndex >= Math.ceil(count / pageSize) - 1}
|
||||
aria-label="last page"
|
||||
>
|
||||
<LastPageIcon />
|
||||
</IconButton> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Pagination.propTypes = {
|
||||
handleChangePage: PropTypes.func.isRequired,
|
||||
useTableProps: PropTypes.object.isRequired,
|
||||
count: PropTypes.number.isRequired,
|
||||
showPageCount: PropTypes.bool
|
||||
}
|
||||
|
||||
export default Pagination
|
34
src/fireedge/src/client/components/Tables/Enhanced/row.js
Normal file
34
src/fireedge/src/client/components/Tables/Enhanced/row.js
Normal file
@ -0,0 +1,34 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const Row = ({ row, handleClick }) => {
|
||||
/** @type {import('react-table').Row} */
|
||||
const { getRowProps, cells, isSelected } = row
|
||||
|
||||
const renderCell = React.useCallback(cell => (
|
||||
<div {...cell.getCellProps()} data-header={cell.column.Header}>
|
||||
{cell.render('Cell')}
|
||||
</div>
|
||||
), [])
|
||||
|
||||
return (
|
||||
<div {...getRowProps()}
|
||||
className={isSelected ? 'selected' : ''}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{cells?.map(renderCell)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Row.propTypes = {
|
||||
row: PropTypes.object,
|
||||
handleClick: PropTypes.func
|
||||
}
|
||||
|
||||
Row.defaultProps = {
|
||||
row: {},
|
||||
handleClick: undefined
|
||||
}
|
||||
|
||||
export default Row
|
@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import { LinearProgressWithLabel, StatusBadge } from 'client/components/Status'
|
||||
import * as HostModel from 'client/models/Host'
|
||||
|
||||
export default [
|
||||
@ -24,21 +24,31 @@ export default [
|
||||
)
|
||||
}, */
|
||||
{
|
||||
Header: '#', accessor: 'ID'
|
||||
Header: '',
|
||||
id: 'STATE',
|
||||
width: 50,
|
||||
accessor: row => {
|
||||
const state = HostModel.getState(row)
|
||||
|
||||
return (
|
||||
<StatusBadge
|
||||
title={state?.name}
|
||||
stateColor={state?.color}
|
||||
customTransform='translate(150%, 50%)'
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
{ Header: '#', accessor: 'ID', width: 45 },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'State',
|
||||
id: 'STATE',
|
||||
accessor: row => HostModel.getState(row)?.name
|
||||
},
|
||||
{
|
||||
Header: 'IM/VM',
|
||||
width: 100,
|
||||
accessor: ({ IM_MAD, VM_MAD }) =>
|
||||
IM_MAD === VM_MAD ? IM_MAD : `${IM_MAD}/${VM_MAD}`
|
||||
},
|
||||
{ Header: 'Cluster', accessor: 'CLUSTER' },
|
||||
{ Header: 'RVMs', accessor: 'HOST_SHARE.RUNNING_VMS' },
|
||||
{ Header: 'RVMs', accessor: 'HOST_SHARE.RUNNING_VMS', width: 100 },
|
||||
{
|
||||
Header: 'Allocated CPU',
|
||||
accessor: row => {
|
||||
|
@ -1,51 +1,30 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useHost, useHostApi } from 'client/features/One'
|
||||
|
||||
import { VirtualizedTable } from 'client/components/Tables'
|
||||
import { EnhancedTable } from 'client/components/Tables'
|
||||
import { HostCard } from 'client/components/Cards'
|
||||
import Columns from 'client/components/Tables/Hosts/columns'
|
||||
|
||||
const INITIAL_ELEMENT = 0
|
||||
const NUMBER_OF_INTERVAL = 20
|
||||
|
||||
const HostsTable = () => {
|
||||
const [{ start, end }, setPage] = useState({
|
||||
start: INITIAL_ELEMENT,
|
||||
end: -NUMBER_OF_INTERVAL
|
||||
})
|
||||
|
||||
const columns = React.useMemo(() => Columns, [])
|
||||
|
||||
const hosts = useHost()
|
||||
const { getHosts } = useHostApi()
|
||||
const { filterPool } = useAuth()
|
||||
|
||||
const { data, fetchRequest, loading, reloading, error } = useFetch(getHosts)
|
||||
const { fetchRequest, loading, reloading } = useFetch(getHosts)
|
||||
|
||||
useEffect(() => { fetchRequest({ start, end }) }, [filterPool])
|
||||
|
||||
const fetchMore = () => {
|
||||
setPage(prevState => {
|
||||
const newStart = prevState.start + NUMBER_OF_INTERVAL
|
||||
const newEnd = prevState.end - NUMBER_OF_INTERVAL
|
||||
|
||||
fetchRequest({ start: newStart, end: newEnd })
|
||||
|
||||
return { start: newStart, end: newEnd }
|
||||
})
|
||||
}
|
||||
|
||||
const canFetchMore = error || data?.hosts?.length < NUMBER_OF_INTERVAL
|
||||
useEffect(() => { fetchRequest() }, [filterPool])
|
||||
|
||||
return (
|
||||
<VirtualizedTable
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={hosts}
|
||||
isLoading={loading || reloading}
|
||||
canFetchMore={canFetchMore}
|
||||
fetchMore={fetchMore}
|
||||
MobileComponentRow={HostCard}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,55 +1,23 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, Box } from '@material-ui/core'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
textTransform: 'uppercase',
|
||||
fontSize: '0.9em',
|
||||
fontWeight: 700,
|
||||
lineHeight: '1rem',
|
||||
letterSpacing: '0.05em',
|
||||
|
||||
overflowWrap: 'break-word',
|
||||
textAlign: 'start',
|
||||
padding: '1em',
|
||||
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
border: `1px solid ${theme.palette.action.disabledBackground}`,
|
||||
borderBottom: 'transparent',
|
||||
|
||||
borderLeftWidth: 1,
|
||||
borderRightWidth: 1,
|
||||
borderTopLeftRadius: 6,
|
||||
borderTopRightRadius: 6
|
||||
}
|
||||
}))
|
||||
|
||||
const Header = ({ useTableProps }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
/** @type {import('react-table').UseTableInstanceProps} */
|
||||
const { headerGroups } = useTableProps
|
||||
|
||||
const renderHeaderColumn = React.useCallback(column => (
|
||||
<Box {...column.getHeaderProps()}>
|
||||
<div {...column.getHeaderProps()}>
|
||||
{column.render('Header')}
|
||||
</Box>
|
||||
</div>
|
||||
), [])
|
||||
|
||||
const renderHeaderGroup = React.useCallback(headerGroup => (
|
||||
<Box {...headerGroup.getHeaderGroupProps()}>
|
||||
<div {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(renderHeaderColumn)}
|
||||
</Box>
|
||||
</div>
|
||||
), [])
|
||||
|
||||
return (
|
||||
<Box className={classes.root}>
|
||||
{headerGroups.map(renderHeaderGroup)}
|
||||
</Box>
|
||||
)
|
||||
return headerGroups.map(renderHeaderGroup)
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
|
@ -23,11 +23,11 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
table: {
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
borderTop: 0,
|
||||
overflow: 'auto',
|
||||
border: `1px solid ${theme.palette.action.disabledBackground}`,
|
||||
borderRadius: '0 0 6px 6px',
|
||||
|
||||
borderRadius: 6
|
||||
},
|
||||
body: {
|
||||
'& *[role=row]': {
|
||||
fontSize: '1em',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
@ -48,7 +48,7 @@ const useStyles = makeStyles(theme => ({
|
||||
}
|
||||
}
|
||||
},
|
||||
header: {
|
||||
toolbar: {
|
||||
...theme.typography.body1,
|
||||
color: theme.palette.text.hint,
|
||||
marginBottom: 16,
|
||||
@ -89,7 +89,7 @@ const VirtualizedTable = ({ data, columns, isLoading, canFetchMore, fetchMore })
|
||||
return (
|
||||
<Box {...getTableProps()} className={classes.root}>
|
||||
|
||||
<div className={classes.header}>
|
||||
<div className={classes.toolbar}>
|
||||
<Toolbar useTableProps={useTableProps} />
|
||||
<div className={classes.total}>
|
||||
{isLoading && <CircularProgress size='1em' color='secondary' />}
|
||||
@ -97,23 +97,26 @@ const VirtualizedTable = ({ data, columns, isLoading, canFetchMore, fetchMore })
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Header useTableProps={useTableProps} />
|
||||
<div className={classes.table}>
|
||||
<ListVirtualized
|
||||
containerProps={{ ...getTableBodyProps() }}
|
||||
canFetchMore={canFetchMore}
|
||||
data={rows}
|
||||
isLoading={isLoading}
|
||||
fetchMore={fetchMore}
|
||||
>
|
||||
{virtualItems => virtualItems?.map(virtualRow => (
|
||||
<Row key={virtualRow.index}
|
||||
virtualRow={virtualRow}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ListVirtualized>
|
||||
<Header useTableProps={useTableProps} />
|
||||
|
||||
<div className={classes.body}>
|
||||
<ListVirtualized
|
||||
containerProps={{ ...getTableBodyProps() }}
|
||||
canFetchMore={canFetchMore}
|
||||
data={rows}
|
||||
isLoading={isLoading}
|
||||
fetchMore={fetchMore}
|
||||
>
|
||||
{virtualItems => virtualItems?.map(virtualRow => (
|
||||
<Row key={virtualRow.index}
|
||||
virtualRow={virtualRow}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ListVirtualized>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
|
@ -33,7 +33,7 @@ const Toolbar = ({ useTableProps }) => {
|
||||
<div className={classes.filterWrapper}>
|
||||
<Button
|
||||
variant='outlined'
|
||||
startIcon={<FilterIcon size='1rem' />}
|
||||
startIcon={<FilterIcon size='1em' />}
|
||||
className={classes.filterButton}
|
||||
>
|
||||
Filters
|
||||
|
@ -1,3 +1,6 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { StatusBadge } from 'client/components/Status'
|
||||
import * as VirtualMachineModel from 'client/models/VirtualMachine'
|
||||
|
||||
export default [
|
||||
@ -21,15 +24,20 @@ export default [
|
||||
)
|
||||
}, */
|
||||
{
|
||||
Header: '#', accessor: 'ID'
|
||||
// Cell: ({ value }) =>
|
||||
// <StatusChip stateColor={Colors.debug.light} text={`#${value}`} />
|
||||
},
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'State',
|
||||
Header: '',
|
||||
id: 'STATE',
|
||||
accessor: row => VirtualMachineModel.getState(row)?.name
|
||||
width: 50,
|
||||
accessor: row => {
|
||||
const state = VirtualMachineModel.getState(row)
|
||||
|
||||
return (
|
||||
<StatusBadge
|
||||
title={state?.name}
|
||||
stateColor={state?.color}
|
||||
customTransform='translate(150%, 50%)'
|
||||
/>
|
||||
)
|
||||
}
|
||||
// Cell: ({ value: { name, color } = {} }) => name && (
|
||||
// <StatusChip stateColor={color} text={name} />
|
||||
// ),
|
||||
@ -39,6 +47,8 @@ export default [
|
||||
// filter: (rows, id, filterValue) =>
|
||||
// rows.filter(row => row.values[id]?.name === filterValue)
|
||||
},
|
||||
{ Header: '#', accessor: 'ID', width: 45 },
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'Owner/Group',
|
||||
accessor: row => `${row.UNAME}/${row.GNAME}`
|
||||
|
@ -1,20 +1,18 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState, useCallback } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useVm, useVmApi } from 'client/features/One'
|
||||
|
||||
import { VirtualizedTable } from 'client/components/Tables'
|
||||
import { EnhancedTable } from 'client/components/Tables'
|
||||
import { VirtualMachineCard } from 'client/components/Cards'
|
||||
import Columns from 'client/components/Tables/Vms/columns'
|
||||
|
||||
const INITIAL_ELEMENT = 0
|
||||
const NUMBER_OF_INTERVAL = 20
|
||||
const NUMBER_OF_INTERVAL = 6
|
||||
|
||||
const VmsTable = () => {
|
||||
const [{ start, end }, setPage] = useState({
|
||||
start: INITIAL_ELEMENT,
|
||||
end: -NUMBER_OF_INTERVAL
|
||||
})
|
||||
const [[start, end], setPage] = useState([INITIAL_ELEMENT, -NUMBER_OF_INTERVAL])
|
||||
|
||||
const columns = React.useMemo(() => Columns, [])
|
||||
|
||||
@ -26,26 +24,29 @@ const VmsTable = () => {
|
||||
|
||||
useEffect(() => { fetchRequest({ start, end }) }, [filterPool])
|
||||
|
||||
const fetchMore = () => {
|
||||
setPage(prevState => {
|
||||
const newStart = prevState.start + NUMBER_OF_INTERVAL
|
||||
const newEnd = prevState.end - NUMBER_OF_INTERVAL
|
||||
const fetchMore = useCallback(() => {
|
||||
setPage(([prevStart, prevEnd]) => {
|
||||
const newStart = prevStart + NUMBER_OF_INTERVAL
|
||||
const newEnd = prevEnd - NUMBER_OF_INTERVAL
|
||||
|
||||
fetchRequest({ start: newStart, end: newEnd })
|
||||
|
||||
return { start: newStart, end: newEnd }
|
||||
return [newStart, newEnd]
|
||||
})
|
||||
}
|
||||
}, [start, end])
|
||||
|
||||
const canFetchMore = error || data?.vms?.length < NUMBER_OF_INTERVAL
|
||||
const canFetchMore = !error && data?.vms?.length % NUMBER_OF_INTERVAL === 0
|
||||
|
||||
return (
|
||||
<VirtualizedTable
|
||||
<EnhancedTable
|
||||
columns={columns}
|
||||
data={vms}
|
||||
pageSize={NUMBER_OF_INTERVAL / 2}
|
||||
isLoading={loading || reloading}
|
||||
showPageCount={false}
|
||||
canFetchMore={canFetchMore}
|
||||
fetchMore={fetchMore}
|
||||
MobileComponentRow={VirtualMachineCard}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import DatastoresTable from 'client/components/Tables/Datastores'
|
||||
import EnhancedTable from 'client/components/Tables/Enhanced'
|
||||
import HostsTable from 'client/components/Tables/Hosts'
|
||||
import VirtualizedTable from 'client/components/Tables/Virtualized'
|
||||
import VmsTable from 'client/components/Tables/Vms'
|
||||
import DatastoresTable from 'client/components/Tables/Datastores'
|
||||
import HostsTable from 'client/components/Tables/Hosts'
|
||||
|
||||
export {
|
||||
VirtualizedTable,
|
||||
VmsTable,
|
||||
DatastoresTable,
|
||||
HostsTable
|
||||
EnhancedTable,
|
||||
HostsTable,
|
||||
VirtualizedTable,
|
||||
VmsTable
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ export const GROUP = {
|
||||
const formatGroups = sortedGroupsById.map(({ ID, NAME }) => {
|
||||
const markAsPrimary = user?.GID === ID ? (
|
||||
<span style={{ marginLeft: 16 }}>
|
||||
<SelectIcon size='1rem' />
|
||||
<SelectIcon size='1em' />
|
||||
</span>
|
||||
) : null
|
||||
|
||||
|
@ -47,6 +47,15 @@ const AntTab = withStyles(theme => ({
|
||||
selected: {}
|
||||
}))(props => <Tab disableRipple {...props} />)
|
||||
|
||||
const AntContainer = withStyles({
|
||||
root: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingInline: 0
|
||||
}
|
||||
})(Container)
|
||||
|
||||
const Newstone = () => {
|
||||
const history = useHistory()
|
||||
const { resource } = useParams()
|
||||
@ -70,10 +79,10 @@ const Newstone = () => {
|
||||
), [resource])
|
||||
|
||||
return (
|
||||
<Container disableGutters>
|
||||
<AntContainer>
|
||||
{Object.values(TABS).includes(history.location.pathname) && renderTabs}
|
||||
|
||||
<Box py={2}>
|
||||
<Box py={2} overflow='auto'>
|
||||
<Switch>
|
||||
<Route path={TABS.vms} component={VmsTable} />
|
||||
<Route path={TABS.datastores} component={DatastoresTable} />
|
||||
@ -82,7 +91,7 @@ const Newstone = () => {
|
||||
<Route component={() => <Redirect to={TABS.vms} />} />
|
||||
</Switch>
|
||||
</Box>
|
||||
</Container>
|
||||
</AntContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,14 @@ import { requestParams, RestClient } from 'client/utils'
|
||||
export const createAction = (type, service, wrapResult) =>
|
||||
createAsyncThunk(type, async (payload, { getState, rejectWithValue }) => {
|
||||
try {
|
||||
const { filterPool } = getState().auth
|
||||
const { auth: { filterPool }, one } = getState()
|
||||
|
||||
const response = await service({
|
||||
...payload,
|
||||
filter: filterPool
|
||||
})
|
||||
|
||||
return wrapResult ? wrapResult(response) : response
|
||||
return wrapResult ? wrapResult(response, one) : response
|
||||
} catch (err) {
|
||||
return rejectWithValue(typeof err === 'string' ? err : err?.response?.data)
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
import { createAction } from 'client/features/One/utils'
|
||||
import { vmService } from 'client/features/One/vm/services'
|
||||
import { filterBy } from 'client/utils'
|
||||
|
||||
export const getVm = createAction('vm', vmService.getVm)
|
||||
|
||||
export const getVms = createAction(
|
||||
'vm/pool',
|
||||
vmService.getVms,
|
||||
response => ({ vms: response })
|
||||
(response, { vms: currentVms }) => {
|
||||
const vms = filterBy([...currentVms, ...response], 'ID')
|
||||
|
||||
return { vms }
|
||||
}
|
||||
)
|
||||
|
||||
export const terminateVm = createAction(
|
||||
|
Loading…
x
Reference in New Issue
Block a user