mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
F OpenNebula/one#5422: Add virtual list component
This commit is contained in:
parent
ebdc1dc422
commit
694280f5f6
@ -5,9 +5,9 @@
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"dev": "rimraf dist/ && cross-env NODE_ENV=development babel-node ./src/server/index.js",
|
||||
"build-front": "rimraf dist/client && webpack --config ./webpack.config.prod.client.js",
|
||||
"build-client": "rimraf dist/client && webpack --config ./webpack.config.prod.client.js",
|
||||
"build-server": "rimraf dist/index.js && webpack --config ./webpack.config.prod.server.js",
|
||||
"build": "npm run build-front && npm run build-server",
|
||||
"build": "npm run build-client && npm run build-server",
|
||||
"start": "cross-env NODE_ENV=production babel-node ./dist/index.js",
|
||||
"pot": "node potfile.js",
|
||||
"po2json": "node po2json.js",
|
||||
@ -108,7 +108,9 @@
|
||||
"react-redux": "7.2.1",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.2.0",
|
||||
"react-table": "7.7.0",
|
||||
"react-transition-group": "4.4.1",
|
||||
"react-virtual": "2.7.1",
|
||||
"redux": "4.1.0",
|
||||
"redux-thunk": "2.3.0",
|
||||
"rimraf": "3.0.2",
|
||||
@ -129,4 +131,4 @@
|
||||
"yup": "0.32.9",
|
||||
"zeromq": "5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
66
src/fireedge/src/client/components/List/ListVirtualized.js
Normal file
66
src/fireedge/src/client/components/List/ListVirtualized.js
Normal file
@ -0,0 +1,66 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useVirtual } from 'react-virtual'
|
||||
import { Box } from '@material-ui/core'
|
||||
|
||||
const ListVirtualized = ({ list = [] }) => {
|
||||
const parentRef = React.useRef()
|
||||
|
||||
const rowVirtualizer = useVirtual({
|
||||
size: list.length,
|
||||
parentRef,
|
||||
overscan: 20,
|
||||
estimateSize: React.useCallback(() => 35, []),
|
||||
keyExtractor: index => list[index]?.ID
|
||||
})
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<div
|
||||
ref={parentRef}
|
||||
style={{
|
||||
height: '150px',
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
ListVirtualized.propTypes = {
|
||||
list: PropTypes.arrayOf(PropTypes.any)
|
||||
}
|
||||
|
||||
ListVirtualized.defaultProps = {
|
||||
list: []
|
||||
}
|
||||
|
||||
export default ListVirtualized
|
@ -1,9 +1,11 @@
|
||||
import ListHeader from 'client/components/List/ListHeader'
|
||||
import ListCards from 'client/components/List/ListCards'
|
||||
import ListHeader from 'client/components/List/ListHeader'
|
||||
import ListInfiniteScroll from 'client/components/List/ListInfiniteScroll'
|
||||
import ListVirtualized from 'client/components/List/ListVirtualized'
|
||||
|
||||
export {
|
||||
ListHeader,
|
||||
ListCards,
|
||||
ListInfiniteScroll
|
||||
ListHeader,
|
||||
ListInfiniteScroll,
|
||||
ListVirtualized
|
||||
}
|
||||
|
162
src/fireedge/src/client/components/Table/EnhancedTable.js
Normal file
162
src/fireedge/src/client/components/Table/EnhancedTable.js
Normal file
@ -0,0 +1,162 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
Table as MTable,
|
||||
TableFooter,
|
||||
TableContainer,
|
||||
TablePagination,
|
||||
TableSortLabel,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow
|
||||
} from '@material-ui/core'
|
||||
|
||||
import {
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
useSortBy,
|
||||
useTable
|
||||
} from 'react-table'
|
||||
|
||||
import TablePaginationActions from 'client/components/Table/TablePaginationActions'
|
||||
import TableToolbar from 'client/components/Table/TableToolbar'
|
||||
import * as TableFilters from 'client/components/Table/Filters'
|
||||
|
||||
const EnhancedTable = ({
|
||||
title,
|
||||
columns,
|
||||
data,
|
||||
actions,
|
||||
skipPageReset,
|
||||
filterTypes
|
||||
}) => {
|
||||
const defaultColumn = React.useMemo(() => ({
|
||||
Filter: TableFilters.DefaultFilter
|
||||
}), [])
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
prepareRow,
|
||||
page,
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
preGlobalFilteredRows,
|
||||
setGlobalFilter,
|
||||
state: { pageIndex, pageSize, selectedRowIds, globalFilter }
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
filterTypes,
|
||||
autoResetPage: !skipPageReset
|
||||
|
||||
},
|
||||
useFilters,
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
usePagination,
|
||||
useRowSelect
|
||||
)
|
||||
|
||||
const handleChangePage = (_, newPage) => {
|
||||
gotoPage(newPage)
|
||||
}
|
||||
|
||||
const handleChangeRowsPerPage = event => {
|
||||
setPageSize(parseInt(event.target.value, 10))
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableToolbar
|
||||
title={title}
|
||||
numSelected={Object.keys(selectedRowIds).length}
|
||||
actions={actions}
|
||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
||||
setGlobalFilter={setGlobalFilter}
|
||||
globalFilter={globalFilter}
|
||||
/>
|
||||
<MTable size='small' {...getTableProps()}>
|
||||
<TableHead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<TableRow {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<TableCell
|
||||
{...(column.id === 'selection'
|
||||
? column.getHeaderProps()
|
||||
: column.getHeaderProps(column.getSortByToggleProps()))}
|
||||
>
|
||||
{column.render('Header')}
|
||||
{column.id !== 'selection' ? (
|
||||
<TableSortLabel
|
||||
active={column.isSorted}
|
||||
// react-table has a unsorted state which is not treated here
|
||||
direction={column.isSortedDesc ? 'desc' : 'asc'}
|
||||
/>
|
||||
) : null}
|
||||
{/* Render the columns filter UI */}
|
||||
<div onClick={event => event.stopPropagation()}>
|
||||
{column.canFilter ? column.render('Filter') : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{page.map((row, i) => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<TableRow {...row.getRowProps()}>
|
||||
{row.cells.map(cell => (
|
||||
<TableCell {...cell.getCellProps()}>
|
||||
{cell.render('Cell')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25, { label: 'All', value: -1 }]}
|
||||
count={data.length}
|
||||
rowsPerPage={pageSize}
|
||||
page={pageIndex}
|
||||
SelectProps={{
|
||||
inputProps: { 'aria-label': 'rows per page' },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</MTable>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
EnhancedTable.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
columns: PropTypes.array.isRequired,
|
||||
data: PropTypes.array.isRequired,
|
||||
actions: PropTypes.array.isRequired,
|
||||
skipPageReset: PropTypes.bool,
|
||||
filterTypes: PropTypes.array
|
||||
}
|
||||
|
||||
EnhancedTable.defaultProps = {
|
||||
skipPageReset: false
|
||||
}
|
||||
|
||||
export default EnhancedTable
|
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { debounce } from '@material-ui/core'
|
||||
|
||||
const DefaultFilter = ({ column }) => {
|
||||
/** @type {import('react-table').UseFiltersInstanceProps} */
|
||||
const { filterValue, preFilteredRows, setFilter } = column
|
||||
const count = preFilteredRows?.length
|
||||
|
||||
const [value, setValue] = React.useState(filterValue)
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
// Set undefined to remove the filter entirely
|
||||
debounce(value => { setFilter(value || undefined) }, 200)
|
||||
)
|
||||
|
||||
return (
|
||||
<input
|
||||
value={value || ''}
|
||||
onChange={event => {
|
||||
setValue(event.target.value)
|
||||
handleChange(event.target.value)
|
||||
}}
|
||||
placeholder={`Search ${count} records...`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
DefaultFilter.propTypes = {
|
||||
column: PropTypes.shape({
|
||||
filterValue: PropTypes.any,
|
||||
preFilteredRows: PropTypes.array,
|
||||
setFilter: PropTypes.func.isRequired
|
||||
})
|
||||
}
|
||||
|
||||
export default DefaultFilter
|
@ -0,0 +1,90 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, fade, debounce, InputBase } from '@material-ui/core'
|
||||
import { Search as SearchIcon } from 'iconoir-react'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
search: {
|
||||
position: 'relative',
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
backgroundColor: fade(theme.palette.common.white, 0.15),
|
||||
'&:hover': {
|
||||
backgroundColor: fade(theme.palette.common.white, 0.25)
|
||||
},
|
||||
marginRight: theme.spacing(2),
|
||||
marginLeft: 0,
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
marginLeft: theme.spacing(3),
|
||||
width: 'auto'
|
||||
}
|
||||
},
|
||||
searchIcon: {
|
||||
width: theme.spacing(7),
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
inputRoot: {
|
||||
color: 'inherit'
|
||||
},
|
||||
inputInput: {
|
||||
padding: theme.spacing(1, 1, 1, 7),
|
||||
transition: theme.transitions.create('width'),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: 200
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
const GlobalFilter = props => {
|
||||
const { preGlobalFilteredRows, globalFilter, setGlobalFilter } = props
|
||||
|
||||
const classes = useStyles()
|
||||
const count = preGlobalFilteredRows.length
|
||||
|
||||
const [value, setValue] = React.useState(globalFilter)
|
||||
|
||||
const handleChange = React.useCallback(
|
||||
// Set undefined to remove the filter entirely
|
||||
debounce(value => { setGlobalFilter(value || undefined) }, 200)
|
||||
)
|
||||
|
||||
// Global filter only works with pagination from the first page.
|
||||
// This may not be a problem for server side pagination when
|
||||
// only the current page is downloaded.
|
||||
|
||||
return (
|
||||
<div className={classes.search}>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<InputBase
|
||||
value={value ?? ''}
|
||||
onChange={event => {
|
||||
setValue(event.target.value)
|
||||
handleChange(event.target.value)
|
||||
}}
|
||||
placeholder={`${count} records...`}
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput
|
||||
}}
|
||||
inputProps={{ 'aria-label': 'search' }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
GlobalFilter.propTypes = {
|
||||
preGlobalFilteredRows: PropTypes.array.isRequired,
|
||||
globalFilter: PropTypes.string,
|
||||
setGlobalFilter: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default GlobalFilter
|
@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { T } from 'client/constants'
|
||||
|
||||
const SelectFilter = ({ column, accessorOption }) => {
|
||||
/** @type {import('react-table').UseFiltersInstanceProps} */
|
||||
const { filterValue, setFilter, preFilteredRows, id } = column
|
||||
|
||||
// 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, preFilteredRows])
|
||||
|
||||
return (
|
||||
<select
|
||||
value={filterValue}
|
||||
onChange={event => {
|
||||
setFilter(event.target.value || undefined)
|
||||
}}
|
||||
>
|
||||
<option value=''>{T.All}</option>
|
||||
{options.map((option, i) => {
|
||||
const value = option[accessorOption] ?? option
|
||||
|
||||
return (
|
||||
<option key={i} value={value}>
|
||||
{value}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
||||
SelectFilter.propTypes = {
|
||||
column: PropTypes.shape({
|
||||
filterValue: PropTypes.any,
|
||||
preFilteredRows: PropTypes.array,
|
||||
setFilter: PropTypes.func.isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
}),
|
||||
accessorOption: PropTypes.string
|
||||
}
|
||||
|
||||
export default SelectFilter
|
@ -0,0 +1,9 @@
|
||||
import DefaultFilter from 'client/components/Table/Filters/DefaultFilter'
|
||||
import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
|
||||
import SelectFilter from 'client/components/Table/Filters/SelectFilter'
|
||||
|
||||
export {
|
||||
DefaultFilter,
|
||||
GlobalFilter,
|
||||
SelectFilter
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, useTheme, 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: {
|
||||
flexShrink: 0,
|
||||
marginLeft: theme.spacing(2.5)
|
||||
}
|
||||
}))
|
||||
|
||||
const TablePaginationActions = ({ count, page, rowsPerPage, onChangePage }) => {
|
||||
const classes = useStyles()
|
||||
const theme = useTheme()
|
||||
|
||||
const handleFirstPageButtonClick = event => {
|
||||
onChangePage(event, 0)
|
||||
}
|
||||
|
||||
const handleBackButtonClick = event => {
|
||||
onChangePage(event, page - 1)
|
||||
}
|
||||
|
||||
const handleNextButtonClick = event => {
|
||||
onChangePage(event, page + 1)
|
||||
}
|
||||
|
||||
const handleLastPageButtonClick = event => {
|
||||
onChangePage(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<IconButton
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="first page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? <LastPageIcon /> : <FirstPageIcon />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="previous page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<NextPageIcon />
|
||||
) : (
|
||||
<PreviousPageIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="next page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? (
|
||||
<PreviousPageIcon />
|
||||
) : (
|
||||
<NextPageIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="last page"
|
||||
>
|
||||
{theme.direction === 'rtl' ? <FirstPageIcon /> : <LastPageIcon />}
|
||||
</IconButton>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
count: PropTypes.number.isRequired,
|
||||
onChangePage: PropTypes.func.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired
|
||||
}
|
||||
|
||||
export default TablePaginationActions
|
92
src/fireedge/src/client/components/Table/TableToolbar.js
Normal file
92
src/fireedge/src/client/components/Table/TableToolbar.js
Normal file
@ -0,0 +1,92 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import {
|
||||
makeStyles,
|
||||
lighten,
|
||||
Toolbar,
|
||||
Typography,
|
||||
Tooltip,
|
||||
IconButton
|
||||
} from '@material-ui/core'
|
||||
|
||||
import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
|
||||
|
||||
const useToolbarStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
},
|
||||
highlight:
|
||||
theme.palette.type === 'light'
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85)
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark
|
||||
},
|
||||
title: {
|
||||
flex: '1 1 100%'
|
||||
}
|
||||
}))
|
||||
|
||||
const TableToolbar = props => {
|
||||
const classes = useToolbarStyles()
|
||||
|
||||
const {
|
||||
title,
|
||||
numSelected,
|
||||
actions,
|
||||
preGlobalFilteredRows,
|
||||
setGlobalFilter,
|
||||
globalFilter
|
||||
} = props
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
className={clsx(classes.root, {
|
||||
[classes.highlight]: numSelected > 0
|
||||
})}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
<Typography className={classes.title} color='inherit' variant='subtitle1'>
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography className={classes.title} variant='h6' id='tableTitle'>
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{numSelected > 0 ? (
|
||||
actions?.map(({ title, icon: Icon, handleClick }) => (
|
||||
<Tooltip key={title} title={title}>
|
||||
<IconButton aria-label={title} onClick={handleClick}>
|
||||
<Icon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
))
|
||||
) : (
|
||||
<GlobalFilter
|
||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
||||
globalFilter={globalFilter}
|
||||
setGlobalFilter={setGlobalFilter}
|
||||
/>
|
||||
)}
|
||||
</Toolbar>
|
||||
)
|
||||
}
|
||||
|
||||
TableToolbar.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
numSelected: PropTypes.number.isRequired,
|
||||
actions: PropTypes.array,
|
||||
setGlobalFilter: PropTypes.func.isRequired,
|
||||
preGlobalFilteredRows: PropTypes.array.isRequired,
|
||||
globalFilter: PropTypes.string
|
||||
}
|
||||
|
||||
export default TableToolbar
|
11
src/fireedge/src/client/components/Table/index.js
Normal file
11
src/fireedge/src/client/components/Table/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
import EnhancedTable from 'client/components/Table/EnhancedTable'
|
||||
import TablePaginationActions from 'client/components/Table/TablePaginationActions'
|
||||
import TableToolbar from 'client/components/Table/TableToolbar'
|
||||
|
||||
export * from 'client/components/Table/Filters'
|
||||
|
||||
export {
|
||||
EnhancedTable,
|
||||
TablePaginationActions,
|
||||
TableToolbar
|
||||
}
|
10
src/fireedge/src/client/components/Tables/VmTable/actions.js
Normal file
10
src/fireedge/src/client/components/Tables/VmTable/actions.js
Normal file
@ -0,0 +1,10 @@
|
||||
import * as Icons from 'iconoir-react'
|
||||
|
||||
export default [
|
||||
{ title: 'Delete', icon: Icons.Trash, handleClick: () => undefined },
|
||||
{ title: 'Resume', icon: Icons.PlayOutline, handleClick: () => undefined },
|
||||
{ title: 'Power Off', icon: Icons.OffRounded, handleClick: () => undefined },
|
||||
{ title: 'Reboot', icon: Icons.Refresh, handleClick: () => undefined },
|
||||
{ title: 'Lock', icon: Icons.Lock, handleClick: () => undefined },
|
||||
{ title: 'Unlock', icon: Icons.NoLock, handleClick: () => undefined }
|
||||
]
|
226
src/fireedge/src/client/components/Tables/VmTable/body.js
Normal file
226
src/fireedge/src/client/components/Tables/VmTable/body.js
Normal file
@ -0,0 +1,226 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/jsx-key */
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
makeStyles,
|
||||
fade,
|
||||
Table as MTable,
|
||||
TableFooter,
|
||||
TableContainer,
|
||||
TablePagination,
|
||||
TableSortLabel,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow
|
||||
} from '@material-ui/core'
|
||||
|
||||
import { TableToolbar, TablePaginationActions } from 'client/components/Table'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
table: {
|
||||
// borderCollapse: 'collapse',
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
borderCollapse: 'separate',
|
||||
borderSpacing: theme.spacing(0, 4)
|
||||
}
|
||||
},
|
||||
head: {
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
headRow: {
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
display: 'block',
|
||||
marginBottom: 5
|
||||
}
|
||||
},
|
||||
bodyRow: {
|
||||
'&:nth-of-type(odd)': {
|
||||
// backgroundColor: theme.palette.action.hover
|
||||
},
|
||||
'&$selected, &$selected:hover': {
|
||||
backgroundColor: theme.palette.action.hover
|
||||
// backgroundColor: fade(theme.palette.secondary.main, theme.palette.action.selectedOpacity)
|
||||
}
|
||||
},
|
||||
cell: {
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
'&:first-of-type': {
|
||||
width: 45
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
'&:first-of-type': {
|
||||
display: 'none'
|
||||
},
|
||||
'&:last-of-type': {
|
||||
borderBottom: `1px solid ${theme.palette.primary.contrastText}`
|
||||
},
|
||||
'&:nth-of-type(2)': {
|
||||
borderTop: `1px solid ${theme.palette.primary.contrastText}`
|
||||
},
|
||||
borderInline: `1px solid ${theme.palette.primary.contrastText}`,
|
||||
display: 'block',
|
||||
position: 'relative',
|
||||
textAlign: 'left',
|
||||
borderBottom: 'none'
|
||||
// paddingLeft: 130,
|
||||
// '&::before': {
|
||||
// content: 'attr(data-heading)',
|
||||
// position: 'absolute',
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// width: 120,
|
||||
// height: '100%',
|
||||
// display: 'flex',
|
||||
// alignItems: 'center',
|
||||
// backgroundColor: theme.palette.primary.main,
|
||||
// color: theme.palette.primary.contrastText,
|
||||
// fontSize: '0.75rem',
|
||||
// padding: theme.spacing(0, 1),
|
||||
// justifyContent: 'center'
|
||||
// }
|
||||
}
|
||||
},
|
||||
selected: {}
|
||||
}))
|
||||
|
||||
const TableBod = ({
|
||||
getTableProps,
|
||||
headerGroups,
|
||||
prepareRow,
|
||||
page,
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
preGlobalFilteredRows,
|
||||
setGlobalFilter,
|
||||
state: { pageIndex, pageSize, selectedRowIds, globalFilter },
|
||||
data
|
||||
}) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const handleChangePage = (_, newPage) => {
|
||||
gotoPage(newPage)
|
||||
}
|
||||
|
||||
const handleChangeRowsPerPage = event => {
|
||||
setPageSize(parseInt(event.target.value, 10))
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer>
|
||||
<TableToolbar
|
||||
numSelected={Object.keys(selectedRowIds).length}
|
||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
||||
setGlobalFilter={setGlobalFilter}
|
||||
globalFilter={globalFilter}
|
||||
/>
|
||||
<MTable size='small' stickyHeader {...getTableProps()} className={classes.table}>
|
||||
<TableHead className={classes.head}>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<TableRow {...headerGroup.getHeaderGroupProps()} className={classes.headRow}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<TableCell
|
||||
{...(column.id === 'selection'
|
||||
? column.getHeaderProps()
|
||||
: column.getHeaderProps(column.getSortByToggleProps()))}
|
||||
>
|
||||
{column.render('Header')}
|
||||
{column.id !== 'selection' ? (
|
||||
<TableSortLabel
|
||||
active={column.isSorted}
|
||||
// react-table has a unsorted state which is not treated here
|
||||
direction={column.isSortedDesc ? 'desc' : 'asc'}
|
||||
/>
|
||||
) : null}
|
||||
{/* Render the columns filter UI */}
|
||||
<div onClick={event => event.stopPropagation()}>
|
||||
{column.canFilter ? column.render('Filter') : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{page.map((row, i) => {
|
||||
prepareRow(row)
|
||||
const { onChange, checked } = row.getToggleRowSelectedProps()
|
||||
|
||||
return (
|
||||
<TableRow {...row.getRowProps()} hover onClick={onChange} selected={checked} className={classes.bodyRow}>
|
||||
{row.cells.map(cell => {
|
||||
console.log({ cell })
|
||||
return (
|
||||
<TableCell {...cell.getCellProps()} className={classes.cell} data-heading={cell.column.Header}>
|
||||
{cell.render('Cell')}
|
||||
</TableCell>
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
count={data.length}
|
||||
rowsPerPage={pageSize}
|
||||
page={pageIndex}
|
||||
SelectProps={{
|
||||
inputProps: { 'aria-label': 'rows per page' },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</MTable>
|
||||
</TableContainer>
|
||||
)
|
||||
|
||||
/* return (
|
||||
<Box
|
||||
{...getTableBodyProps()}
|
||||
height={`${rowVirtualizer.totalSize}px`}
|
||||
position='relative'
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'column',
|
||||
gap: '1em',
|
||||
margin: '0 0 3em 0',
|
||||
padding: 0,
|
||||
boxShadow: '0 0 40px rgba(0,0,0,0.2)'
|
||||
}}
|
||||
>
|
||||
{rowVirtualizer.virtualItems.map(virtualRow => {
|
||||
const row = rows[virtualRow.index]
|
||||
prepareRow(row)
|
||||
|
||||
return <TableRow
|
||||
key={row.getRowProps().key}
|
||||
row={row}
|
||||
virtualRow={virtualRow}
|
||||
/>
|
||||
})}
|
||||
</Box>
|
||||
) */
|
||||
}
|
||||
|
||||
TableBod.propTypes = {
|
||||
}
|
||||
|
||||
TableBod.defaultProps = {
|
||||
}
|
||||
|
||||
export default TableBod
|
57
src/fireedge/src/client/components/Tables/VmTable/columns.js
Normal file
57
src/fireedge/src/client/components/Tables/VmTable/columns.js
Normal file
@ -0,0 +1,57 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { SelectFilter } from 'client/components/Table'
|
||||
import { StatusChip } from 'client/components/Status'
|
||||
|
||||
import Colors from 'client/constants/color'
|
||||
import * as VirtualMachineModel from 'client/models/VirtualMachine'
|
||||
|
||||
export default [
|
||||
/* {
|
||||
id: 'selection',
|
||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||
// to render a checkbox.
|
||||
// Pagination is a problem since this will select all rows even though
|
||||
// not all rows are on the current page.
|
||||
// The solution should be server side pagination.
|
||||
// For one, the clients should not download all rows in most cases.
|
||||
// The client should only download data for the current page.
|
||||
// In that case, getToggleAllRowsSelectedProps works fine.
|
||||
Header: ({ getToggleAllRowsSelectedProps }) => (
|
||||
<CheckboxCell {...getToggleAllRowsSelectedProps()} />
|
||||
),
|
||||
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||
// to the render a checkbox
|
||||
Cell: ({ row }) => (
|
||||
<CheckboxCell {...row.getToggleRowSelectedProps()} />
|
||||
)
|
||||
}, */
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'ID',
|
||||
Cell: ({ value }) =>
|
||||
<StatusChip stateColor={Colors.debug.light} text={`#${value}`} />
|
||||
},
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{ Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` },
|
||||
{
|
||||
Header: 'State',
|
||||
id: 'STATE',
|
||||
accessor: row => VirtualMachineModel.getState(row),
|
||||
Cell: ({ value: { name, color } = {} }) => name && (
|
||||
<StatusChip stateColor={color} text={name} />
|
||||
),
|
||||
Filter: ({ column }) => (
|
||||
<SelectFilter column={column} accessorOption='name' />
|
||||
),
|
||||
filter: (rows, id, filterValue) =>
|
||||
rows.filter(row => row.values[id]?.name === filterValue)
|
||||
},
|
||||
{
|
||||
Header: 'Ips',
|
||||
accessor: row => VirtualMachineModel.getIps(row),
|
||||
Cell: ({ value }) => value.map(nic => (
|
||||
<StatusChip key={nic} stateColor={Colors.debug.light} text={nic} />
|
||||
))
|
||||
}
|
||||
]
|
157
src/fireedge/src/client/components/Tables/VmTable/index.js
Normal file
157
src/fireedge/src/client/components/Tables/VmTable/index.js
Normal file
@ -0,0 +1,157 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* 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 {
|
||||
useTable,
|
||||
useGlobalFilter,
|
||||
useSortBy,
|
||||
useRowSelect,
|
||||
useFilters,
|
||||
usePagination,
|
||||
useFlexLayout
|
||||
} from 'react-table'
|
||||
|
||||
import { useNearScreen } from 'client/hooks'
|
||||
import { EnhancedTable, DefaultFilter } from 'client/components/Table'
|
||||
|
||||
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 columns = React.useMemo(() => Columns, [])
|
||||
|
||||
const defaultColumn = React.useMemo(() => ({
|
||||
Filter: DefaultFilter
|
||||
}), [])
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
totalColumnsWidth,
|
||||
prepareRow
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn
|
||||
},
|
||||
useRowSelect,
|
||||
useFlexLayout
|
||||
)
|
||||
// <----------- 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])
|
||||
|
||||
return (
|
||||
<Paper style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<div
|
||||
{...getTableProps()}
|
||||
style={{ height: '100%', display: 'flex', flexFlow: 'column' }}
|
||||
>
|
||||
<div>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<div {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<div {...column.getHeaderProps()}>
|
||||
{column.render('Header')}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
export default VmTable
|
5
src/fireedge/src/client/components/Tables/index.js
Normal file
5
src/fireedge/src/client/components/Tables/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import VmTable from 'client/components/Tables/VmTable'
|
||||
|
||||
export {
|
||||
VmTable
|
||||
}
|
@ -1,76 +1,51 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { Container, Box } from '@material-ui/core'
|
||||
import { Trash as DeleteIcon } from 'iconoir-react'
|
||||
import { Container } from '@material-ui/core'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useVm, useVmApi } from 'client/features/One'
|
||||
import { useGeneralApi } from 'client/features/General'
|
||||
import { useFetch, useSearch } from 'client/hooks'
|
||||
import { useFetch } from 'client/hooks'
|
||||
|
||||
import { ListHeader, ListCards } from 'client/components/List'
|
||||
import { VirtualMachineCard } from 'client/components/Cards'
|
||||
// import { DialogRequest } from 'client/components/Dialogs'
|
||||
// import Information from 'client/containers/VirtualMachines/Sections/info'
|
||||
import { T } from 'client/constants'
|
||||
import { filterDoneVms } from 'client/models/VirtualMachine'
|
||||
import { VmTable } from 'client/components/Tables'
|
||||
|
||||
const INITIAL_ELEMENT = -1
|
||||
const NUMBER_OF_INTERVAL = -100
|
||||
|
||||
function VirtualMachines () {
|
||||
// const [showDialog, setShowDialog] = useState(false)
|
||||
const [{ start, end }, setPage] = useState(({ start: INITIAL_ELEMENT, end: -NUMBER_OF_INTERVAL }))
|
||||
|
||||
const vms = useVm()
|
||||
const { getVm, getVms, terminateVm } = useVmApi()
|
||||
const { getVms } = useVmApi()
|
||||
const { filterPool } = useAuth()
|
||||
|
||||
const { enqueueSuccess } = useGeneralApi()
|
||||
const { data, fetchRequest, loading, reloading } = useFetch(getVms)
|
||||
|
||||
const { fetchRequest, loading, reloading } = useFetch(getVms)
|
||||
const { result, handleChange } = useSearch({
|
||||
list: vms,
|
||||
listOptions: { shouldSort: true, keys: ['ID', 'NAME'] }
|
||||
})
|
||||
useEffect(() => { fetchRequest({ start, end }) }, [filterPool])
|
||||
|
||||
useEffect(() => { fetchRequest() }, [filterPool])
|
||||
const handleGetMoreData = () => {
|
||||
console.log('FETCH MORE')
|
||||
|
||||
// const handleCancel = () => setShowDialog(false)
|
||||
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 finish = data?.length < NUMBER_OF_INTERVAL
|
||||
// console.log({ start, end, loading, finish, vms })
|
||||
|
||||
return (
|
||||
<Container disableGutters>
|
||||
<ListHeader
|
||||
title={T.VMs}
|
||||
reloadButtonProps={{
|
||||
onClick: () => fetchRequest(undefined, { reload: true }),
|
||||
isSubmitting: Boolean(loading || reloading)
|
||||
}}
|
||||
searchProps={{ handleChange }}
|
||||
<Container disableGutters style={{ height: '100%' }}>
|
||||
<VmTable
|
||||
data={vms}
|
||||
isLoading={(vms.length === 0 && (loading || reloading))}
|
||||
finish={finish}
|
||||
getNextData={handleGetMoreData}
|
||||
/>
|
||||
<Box p={3}>
|
||||
<ListCards
|
||||
list={result ?? filterDoneVms(vms)}
|
||||
isLoading={vms.length === 0 && loading}
|
||||
gridProps={{ 'data-cy': 'vms' }}
|
||||
CardComponent={VirtualMachineCard}
|
||||
cardsProps={({ value: { ID, NAME } }) => ({
|
||||
actions: [
|
||||
{
|
||||
handleClick: () => terminateVm(ID)
|
||||
.then(() => enqueueSuccess(`VM terminate - ID: ${ID}`))
|
||||
.then(() => fetchRequest(undefined, { reload: true })),
|
||||
icon: <DeleteIcon color='error' />,
|
||||
cy: 'vm-delete'
|
||||
}
|
||||
]
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
{/* {showDialog !== false && (
|
||||
<DialogRequest
|
||||
request={() => getVm(showDialog.id)}
|
||||
dialogProps={{ handleCancel, ...showDialog }}
|
||||
>
|
||||
{({ data }) => <Information data={data} />}
|
||||
</DialogRequest>
|
||||
)} */}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const useVmApi = () => {
|
||||
|
||||
return {
|
||||
getVm: id => unwrapDispatch(actions.getVm({ id })),
|
||||
getVms: () => unwrapDispatch(actions.getVms()),
|
||||
getVms: options => unwrapDispatch(actions.getVms(options)),
|
||||
terminateVm: id => unwrapDispatch(actions.terminateVm({ id }))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,25 @@
|
||||
import { STATES, VM_STATES, VM_LCM_STATES } from 'client/constants'
|
||||
|
||||
const EXTERNAL_IP_ATTRS = [
|
||||
'GUEST_IP',
|
||||
'GUEST_IP_ADDRESSES',
|
||||
'AWS_IP_ADDRESS',
|
||||
'AWS_PUBLIC_IP_ADDRESS',
|
||||
'AWS_PRIVATE_IP_ADDRESS',
|
||||
'AZ_IPADDRESS',
|
||||
'SL_PRIMARYIPADDRESS'
|
||||
]
|
||||
|
||||
const NIC_ALIAS_IP_ATTRS = [
|
||||
'IP',
|
||||
'IP6',
|
||||
'IP6_GLOBAL',
|
||||
'IP6_ULA',
|
||||
'VROUTER_IP',
|
||||
'VROUTER_IP6_GLOBAL',
|
||||
'VROUTER_IP6_ULA'
|
||||
]
|
||||
|
||||
export const filterDoneVms = (vms = []) =>
|
||||
vms.filter(({ STATE }) => VM_STATES[STATE]?.name !== STATES.DONE)
|
||||
|
||||
@ -8,3 +28,37 @@ export const getState = ({ STATE, LCM_STATE } = {}) => {
|
||||
|
||||
return state?.name === STATES.ACTIVE ? VM_LCM_STATES[+LCM_STATE] : state
|
||||
}
|
||||
|
||||
export const getIps = ({ TEMPLATE = {} } = {}) => {
|
||||
const { NIC = [], PCI = [] } = TEMPLATE
|
||||
// TODO: add monitoring ips
|
||||
|
||||
const nics = [NIC, PCI].flat()
|
||||
|
||||
return nics
|
||||
.map(nic => NIC_ALIAS_IP_ATTRS.map(attr => nic[attr]).filter(Boolean))
|
||||
.flat()
|
||||
}
|
||||
|
||||
const getNicsFromMonitoring = ({ ID }) => {
|
||||
const monitoringPool = {} // _getMonitoringPool()
|
||||
const monitoringVM = monitoringPool[ID]
|
||||
|
||||
if (!monitoringPool || Object.keys(monitoringPool).length === 0 || !monitoringVM) return []
|
||||
|
||||
return EXTERNAL_IP_ATTRS.reduce(function (externalNics, attr) {
|
||||
const monitoringValues = monitoringVM[attr]
|
||||
|
||||
if (monitoringValues) {
|
||||
monitoringValues.split(',').forEach((_, ip) => {
|
||||
const exists = externalNics.some(nic => nic.IP === ip)
|
||||
|
||||
if (!exists) {
|
||||
externalNics.push({ NIC_ID: '_', IP: ip })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return externalNics
|
||||
}, [])
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user