1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-16 22:50:10 +03:00
This commit is contained in:
Sergio Betanzos 2021-06-10 22:15:05 +02:00
parent 694280f5f6
commit 8a65245d54
No known key found for this signature in database
GPG Key ID: E3E704F097737136
8 changed files with 350 additions and 200 deletions

View File

@ -2,65 +2,79 @@ import * as React from 'react'
import PropTypes from 'prop-types'
import { useVirtual } from 'react-virtual'
import { Box } from '@material-ui/core'
import { debounce, Box, LinearProgress } from '@material-ui/core'
const ListVirtualized = ({ list = [] }) => {
const parentRef = React.useRef()
import { useNearScreen } from 'client/hooks'
const rowVirtualizer = useVirtual({
size: list.length,
parentRef,
overscan: 20,
estimateSize: React.useCallback(() => 35, []),
keyExtractor: index => list[index]?.ID
const ListVirtualized = ({
canFetchMore,
containerProps,
data,
isLoading,
fetchMore,
children
}) => {
// OBSERVER
const loaderRef = React.useRef()
const { isNearScreen } = useNearScreen({
distance: '100px',
externalRef: isLoading ? null : loaderRef,
once: false
})
// VIRTUALIZER
const parentRef = React.useRef()
const rowVirtualizer = useVirtual({
size: data.length,
parentRef,
overscan: 20,
estimateSize: React.useCallback(() => 40, []),
keyExtractor: index => data[index]?.id
})
const debounceHandleNextPage = React.useCallback(debounce(fetchMore, 200), [])
React.useEffect(() => {
if (isNearScreen && !canFetchMore) debounceHandleNextPage()
}, [isNearScreen, canFetchMore, debounceHandleNextPage])
return (
<Box>
<div
ref={parentRef}
style={{
height: '150px',
overflow: 'auto'
}}
<Box ref={parentRef} height={1} overflow='auto'>
<Box {...containerProps}
height={`${rowVirtualizer.totalSize}px`}
width={1}
position='relative'
>
<div
style={{
height: `${rowVirtualizer.totalSize}px`,
width: '100%',
position: 'relative'
}}
>
{rowVirtualizer.virtualItems.map(virtualRow => {
console.log(virtualRow)
return (
<div
key={virtualRow.index}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
Row {virtualRow.index}
</div>
)
})}
</div>
</div>
{children(rowVirtualizer.virtualItems)}
</Box>
{!canFetchMore && (
<LinearProgress
ref={loaderRef}
color='secondary'
style={{ width: '100%', marginTop: 10 }}
/>
)}
</Box>
)
}
ListVirtualized.propTypes = {
list: PropTypes.arrayOf(PropTypes.any)
canFetchMore: PropTypes.bool,
containerProps: PropTypes.object,
data: PropTypes.arrayOf(PropTypes.any),
isLoading: PropTypes.bool,
fetchMore: PropTypes.func,
children: PropTypes.func
}
ListVirtualized.defaultProps = {
list: []
canFetchMore: false,
containerProps: undefined,
data: [],
isLoading: false,
fetchMore: () => undefined,
children: () => undefined
}
export default ListVirtualized

View File

@ -8,20 +8,19 @@ const useStyles = makeStyles(theme => ({
search: {
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.common.white, 0.15),
backgroundColor: fade(theme.palette.primary.dark, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.common.white, 0.25)
backgroundColor: fade(theme.palette.primary.dark, 0.25)
},
marginRight: theme.spacing(2),
marginLeft: 0,
margin: theme.spacing(1, 0),
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(3),
marginLeft: theme.spacing(1),
width: 'auto'
}
},
searchIcon: {
width: theme.spacing(7),
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
@ -33,12 +32,10 @@ const useStyles = makeStyles(theme => ({
color: 'inherit'
},
inputInput: {
padding: theme.spacing(1, 1, 1, 7),
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('md')]: {
width: 200
}
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
width: '100%'
}
}))
@ -59,6 +56,22 @@ const GlobalFilter = props => {
// This may not be a problem for server side pagination when
// only the current page is downloaded.
/* <Box className={classes.search}>
<Box className={classes.searchIcon}>
<SearchIcon />
</Box>
<InputBase
type='search'
onChange={searchProps.handleChange}
fullWidth
placeholder={`${T.Search}...`}
classes={{
root: classes.inputRoot,
input: classes.inputInput
}}
/>
</Box> */
return (
<div className={classes.search}>
<div className={classes.searchIcon}>

View File

@ -33,7 +33,6 @@ export default [
<StatusChip stateColor={Colors.debug.light} text={`#${value}`} />
},
{ Header: 'Name', accessor: 'NAME' },
{ Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` },
{
Header: 'State',
id: 'STATE',
@ -47,6 +46,7 @@ export default [
filter: (rows, id, filterValue) =>
rows.filter(row => row.values[id]?.name === filterValue)
},
{ Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` },
{
Header: 'Ips',
accessor: row => VirtualMachineModel.getIps(row),

View File

@ -0,0 +1,57 @@
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: '#4A5568',
backgroundColor: '#e6e8f7',
borderBlock: '0.5px solid #EDF2F7'
}
}))
const Header = ({ useTableProps }) => {
const classes = useStyles()
/** @type {import('react-table').UseTableInstanceProps} */
const { headerGroups } = useTableProps
const renderHeaderColumn = React.useCallback(column => (
<Box {...column.getHeaderProps()}>
{column.render('Header')}
</Box>
), [])
const renderHeaderGroup = React.useCallback(headerGroup => (
<Box {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(renderHeaderColumn)}
</Box>
), [])
return (
<Box className={classes.root}>
{headerGroups.map(renderHeaderGroup)}
</Box>
)
}
Header.propTypes = {
useTableProps: PropTypes.object
}
Header.defaultProps = {
useTableProps: {}
}
export default Header

View File

@ -2,155 +2,79 @@
/* eslint-disable react/jsx-key */
import * as React from 'react'
import { Paper, debounce, LinearProgress, CircularProgress } from '@material-ui/core'
import { useVirtual } from 'react-virtual'
import { Box, CircularProgress } from '@material-ui/core'
import {
useTable,
useGlobalFilter,
useSortBy,
useRowSelect,
useFilters,
usePagination,
useFlexLayout
} from 'react-table'
import { useNearScreen } from 'client/hooks'
import { EnhancedTable, DefaultFilter } from 'client/components/Table'
import { ListVirtualized } from 'client/components/List'
import Toolbar from 'client/components/Tables/VmTable/toolbar'
import Header from 'client/components/Tables/VmTable/header'
import Row from 'client/components/Tables/VmTable/row'
import Columns from 'client/components/Tables/VmTable/columns'
import { console } from 'window-or-global'
const VmTable = ({ data, isLoading, finish, getNextData }) => {
const parentRef = React.useRef()
// <----------- USE TABLE ----------->
const VmTable = ({ data, isLoading, canFetchMore, fetchMore }) => {
const columns = React.useMemo(() => Columns, [])
const defaultColumn = React.useMemo(() => ({
Filter: DefaultFilter
// Filter: DefaultFilter,
Cell: React.memo(({ value }) => value ?? '--')
}), [])
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
totalColumnsWidth,
prepareRow
} = useTable(
{
columns,
data,
defaultColumn
},
const useTableProps = useTable(
{ columns, data, defaultColumn },
useRowSelect,
useFlexLayout
useFlexLayout,
useGlobalFilter
)
// <----------- FINISH USE TABLE ----------->
// <----------- VIRTUALIZER ----------->
const rowVirtualizer = useVirtual({
size: rows.length,
parentRef,
overscan: 10,
estimateSize: React.useCallback(() => 50, []),
keyExtractor: index => rows[index]?.id
})
// <----------- FINISH VIRTUALIZER ----------->
// <----------- OBSERVER ----------->
const loaderRef = React.useRef()
const { isNearScreen } = useNearScreen({
distance: '100px',
externalRef: isLoading ? null : loaderRef,
once: false
})
const debounceHandleNextPage = React.useCallback(debounce(getNextData, 200), [])
React.useEffect(() => {
if (isNearScreen && !finish) debounceHandleNextPage()
}, [isNearScreen, finish, debounceHandleNextPage])
// <----------- FINISH OBSERVER ----------->
const RenderRow = React.useCallback(({ row, virtualRow }) => (
<div
{...row.getRowProps()}
ref={virtualRow.measureRef}
style={{
display: 'flex',
alignItems: 'center',
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
>
{row.cells.map(cell => (
<div {...cell.getCellProps()}>
{cell.render('Cell')}
</div>
))}
</div>
), [prepareRow, rows])
const { getTableProps, getTableBodyProps, rows } = useTableProps
return (
<Paper style={{ height: '100%', overflow: 'hidden' }}>
<div
{...getTableProps()}
style={{ height: '100%', display: 'flex', flexFlow: 'column' }}
<Box {...getTableProps()}
height={1}
display='flex'
flexDirection='column'
border='1px solid #d7dde3'
borderRadius={5}
>
<Toolbar useTableProps={useTableProps} />
<Header useTableProps={useTableProps} />
<ListVirtualized
containerProps={{ ...getTableBodyProps() }}
canFetchMore={canFetchMore}
data={rows}
isLoading={isLoading}
fetchMore={fetchMore}
>
<div>
{headerGroups.map(headerGroup => (
<div {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<div {...column.getHeaderProps()}>
{column.render('Header')}
</div>
))}
</div>
))}
</div>
{virtualItems => virtualItems?.map(virtualRow => (
<Row key={virtualRow.index}
virtualRow={virtualRow}
useTableProps={useTableProps}
/>
))
}
</ListVirtualized>
<div ref={parentRef} style={{ height: '100%', overflow: 'auto' }}>
<div
{...getTableBodyProps()}
style={{
height: `${rowVirtualizer.totalSize}px`,
width: '100%',
position: 'relative'
}}
>
{rowVirtualizer.virtualItems?.map(virtualRow => {
const row = rows[virtualRow.index]
prepareRow(row)
return <RenderRow
key={row.getRowProps().key}
row={row}
virtualRow={virtualRow}
/>
})}
</div>
{!finish && (
<LinearProgress
ref={loaderRef}
color='secondary'
style={{ width: '100%', marginTop: 10 }}
/>
)}
</div>
<p style={{ display: 'flex', alignItems: 'center', gap: '1em' }}>
<span>Total loaded: {rows.length}</span>
{isLoading && <CircularProgress size='1em' />}
</p>
<div
style={{
padding: '1em',
display: 'flex',
alignItems: 'center',
gap: '1em',
color: '#4a5568',
backgroundColor: '#e6e8f7',
borderBlock: '0.5px solid #eef2f7'
}}
>
<span>Total loaded: {useTableProps.rows.length}</span>
{isLoading && <CircularProgress size='1em' />}
</div>
</Paper>
</Box>
)
}

View File

@ -0,0 +1,72 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Box } from '@material-ui/core'
import clsx from 'clsx'
const useStyles = makeStyles(theme => ({
root: {
// <-- it's needed to virtualize -->
position: 'absolute',
top: 0,
left: 0,
width: '100%',
fontSize: '1em',
fontWeight: theme.typography.fontWeightMedium,
lineHeight: '1rem',
overflowWrap: 'break-word',
textAlign: 'start',
padding: '1em',
alignItems: 'center',
boxShadow: '0 0 0 0.5px #e6e8f7'
},
virtual: ({ size, start }) => ({
height: size,
transform: `translateY(${start}px)`
})
}))
const Row = ({ virtualRow, useTableProps }) => {
/** @type {import('react-virtual').VirtualItem} */
const { index, measureRef, size, start } = virtualRow
const classes = useStyles({ size, start })
/** @type {import('react-table').UseTableInstanceProps} */
const { rows, prepareRow } = useTableProps
/** @type {import('react-table').UseTableRowProps} */
const row = rows[index]
prepareRow(row)
const renderCell = React.useCallback(cell => (
<Box {...cell.getCellProps()}>
{cell.render('Cell')}
</Box>
), [])
return (
<Box {...row.getRowProps()}
ref={measureRef}
className={clsx(classes.root, classes.virtual)}
>
{row?.cells?.map(renderCell)}
</Box>
)
}
Row.propTypes = {
virtualRow: PropTypes.object,
useTableProps: PropTypes.object
}
Row.defaultProps = {
virtualRow: {},
useTableProps: {}
}
export default Row

View File

@ -0,0 +1,77 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { makeStyles, Toolbar as MToolbar, Button } from '@material-ui/core'
import { Filter as FilterIcon } from 'iconoir-react'
import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
const useToolbarStyles = makeStyles(theme => ({
root: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(1)
},
filterWrapper: {
display: 'flex',
alignItems: 'center',
gap: '1em'
},
filterButton: {
...theme.typography.body1,
fontWeight: theme.typography.fontWeightBold,
textTransform: 'none'
},
filters: {
...theme.typography.body1,
color: theme.palette.grey[700]
}
}))
const Toolbar = ({ useTableProps }) => {
const classes = useToolbarStyles()
/** @type {import('react-table').UseGlobalFiltersInstanceProps} */
const { preGlobalFilteredRows, setGlobalFilter, state } = useTableProps
// const { selectedRowIds, globalFilter } = state
// const numSelected = Object.keys(selectedRowIds).length
return (
<MToolbar className={classes.root}>
<div className={classes.filterWrapper}>
<Button
variant='outlined'
startIcon={<FilterIcon size='1rem' />}
className={classes.filterButton}
>
Filters
</Button>
<span className={classes.filters}>
No filters selected
</span>
</div>
{/* numSelected > 0 && (
<Typography className={classes.title} color='inherit' variant='subtitle1'>
{numSelected} selected
</Typography>
) */}
{/* <GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={globalFilter}
setGlobalFilter={setGlobalFilter}
/> */}
</MToolbar>
)
}
Toolbar.propTypes = {
useTableProps: PropTypes.object
}
Toolbar.defaultProps = {
useTableProps: {}
}
export default Toolbar

View File

@ -1,14 +1,12 @@
import React, { useEffect, useState } from 'react'
import { Container } from '@material-ui/core'
import { useAuth } from 'client/features/Auth'
import { useVm, useVmApi } from 'client/features/One'
import { useFetch } from 'client/hooks'
import { VmTable } from 'client/components/Tables'
const INITIAL_ELEMENT = -1
const INITIAL_ELEMENT = 0
const NUMBER_OF_INTERVAL = -100
function VirtualMachines () {
@ -22,9 +20,7 @@ function VirtualMachines () {
useEffect(() => { fetchRequest({ start, end }) }, [filterPool])
const handleGetMoreData = () => {
console.log('FETCH MORE')
const fetchMore = () => {
setPage(prevState => {
const newStart = prevState.start + NUMBER_OF_INTERVAL
const newEnd = prevState.end - NUMBER_OF_INTERVAL
@ -36,17 +32,14 @@ function VirtualMachines () {
}
const finish = data?.length < NUMBER_OF_INTERVAL
// console.log({ start, end, loading, finish, vms })
return (
<Container disableGutters style={{ height: '100%' }}>
<VmTable
data={vms}
isLoading={(vms.length === 0 && (loading || reloading))}
finish={finish}
getNextData={handleGetMoreData}
/>
</Container>
<VmTable
data={vms}
isLoading={loading || reloading}
finish={finish}
fetchMore={fetchMore}
/>
)
}