mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
M ~#: Add newwstone tab
This commit is contained in:
parent
8a65245d54
commit
daadc63f20
@ -7,8 +7,7 @@ import { Folder as DatastoreIcon } from 'iconoir-react'
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { StatusBadge, StatusChip, LinearProgressWithLabel } from 'client/components/Status'
|
||||
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import Datastore from 'client/constants/datastore'
|
||||
import * as DatastoreModel from 'client/models/Datastore'
|
||||
|
||||
const useStyles = makeStyles(({
|
||||
title: {
|
||||
@ -27,14 +26,15 @@ const DatastoreCard = memo(
|
||||
({ value, isSelected, handleClick, actions }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { ID, NAME, TYPE, STATE, TOTAL_MB, USED_MB } = value
|
||||
const type = Datastore.TYPES[TYPE]
|
||||
const state = Datastore.STATES[STATE]
|
||||
const { ID, NAME } = value
|
||||
|
||||
const percentOfUsed = +USED_MB * 100 / +TOTAL_MB || 0
|
||||
const usedBytes = prettyBytes(+USED_MB, 'MB')
|
||||
const totalBytes = prettyBytes(+TOTAL_MB, 'MB')
|
||||
const percentLabel = `${usedBytes} / ${totalBytes} (${Math.round(percentOfUsed)}%)`
|
||||
const type = DatastoreModel.getType(value)
|
||||
const state = DatastoreModel.getState(value)
|
||||
|
||||
const {
|
||||
percentOfUsed,
|
||||
percentLabel
|
||||
} = DatastoreModel.getCapacityInfo(value)
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
|
@ -7,8 +7,7 @@ import { HardDrive as HostIcon } from 'iconoir-react'
|
||||
import SelectCard, { Action } from 'client/components/Cards/SelectCard'
|
||||
import { StatusBadge, StatusChip, LinearProgressWithLabel } from 'client/components/Status'
|
||||
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import Host from 'client/constants/host'
|
||||
import * as HostModel from 'client/models/Host'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
title: {
|
||||
@ -27,18 +26,17 @@ const HostCard = memo(
|
||||
({ value, isSelected, handleClick, actions }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { ID, NAME, STATE, IM_MAD, VM_MAD, HOST_SHARE } = value
|
||||
const { CPU_USAGE, TOTAL_CPU, MEM_USAGE, TOTAL_MEM } = HOST_SHARE
|
||||
const { ID, NAME, IM_MAD, VM_MAD } = value
|
||||
|
||||
const percentCpuUsed = +CPU_USAGE * 100 / +TOTAL_CPU || 0
|
||||
const percentCpuLabel = `${CPU_USAGE} / ${TOTAL_CPU} (${Math.round(percentCpuUsed)}%)`
|
||||
const {
|
||||
percentCpuUsed,
|
||||
percentCpuLabel,
|
||||
percentMemUsed,
|
||||
percentMemLabel
|
||||
} = HostModel.getAllocatedInfo(value)
|
||||
|
||||
const percentMemUsed = +MEM_USAGE * 100 / +TOTAL_MEM || 0
|
||||
const usedMemBytes = prettyBytes(+MEM_USAGE)
|
||||
const totalMemBytes = prettyBytes(+TOTAL_MEM)
|
||||
const percentMemLabel = `${usedMemBytes} / ${totalMemBytes} (${Math.round(percentMemUsed)}%)`
|
||||
const state = HostModel.getState(value)
|
||||
|
||||
const state = Host.STATES[STATE]
|
||||
const mad = IM_MAD === VM_MAD ? IM_MAD : `${IM_MAD}/${VM_MAD}`
|
||||
|
||||
return (
|
||||
|
@ -28,7 +28,7 @@ const debugLogStyles = makeStyles(theme => ({
|
||||
border: '4px solid transparent',
|
||||
borderRadius: 7,
|
||||
boxShadow: 'inset 0 0 0 10px',
|
||||
color: theme.palette.primary.light
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
@ -47,7 +47,7 @@ export default makeStyles(theme => ({
|
||||
border: '4px solid transparent',
|
||||
borderRadius: 7,
|
||||
boxShadow: 'inset 0 0 0 10px',
|
||||
color: theme.palette.primary.light
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
},
|
||||
/* ROUTES TRANSITIONS */
|
||||
|
@ -2,10 +2,35 @@ import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { useVirtual } from 'react-virtual'
|
||||
import { debounce, Box, LinearProgress } from '@material-ui/core'
|
||||
import { debounce, makeStyles, Box, LinearProgress } from '@material-ui/core'
|
||||
|
||||
import { useNearScreen } from 'client/hooks'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
height: '100%',
|
||||
overflow: 'auto',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: 14
|
||||
},
|
||||
'&::-webkit-scrollbar-thumb': {
|
||||
backgroundClip: 'content-box',
|
||||
border: '4px solid transparent',
|
||||
borderRadius: 7,
|
||||
boxShadow: 'inset 0 0 0 10px',
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
},
|
||||
container: {
|
||||
width: '100%',
|
||||
position: 'relative'
|
||||
},
|
||||
loading: {
|
||||
width: '100%',
|
||||
marginTop: 10
|
||||
}
|
||||
}))
|
||||
|
||||
const ListVirtualized = ({
|
||||
canFetchMore,
|
||||
containerProps,
|
||||
@ -14,6 +39,9 @@ const ListVirtualized = ({
|
||||
fetchMore,
|
||||
children
|
||||
}) => {
|
||||
// STYLES
|
||||
const classes = useStyles()
|
||||
|
||||
// OBSERVER
|
||||
const loaderRef = React.useRef()
|
||||
const { isNearScreen } = useNearScreen({
|
||||
@ -39,11 +67,10 @@ const ListVirtualized = ({
|
||||
}, [isNearScreen, canFetchMore, debounceHandleNextPage])
|
||||
|
||||
return (
|
||||
<Box ref={parentRef} height={1} overflow='auto'>
|
||||
<Box ref={parentRef} className={classes.root}>
|
||||
<Box {...containerProps}
|
||||
height={`${rowVirtualizer.totalSize}px`}
|
||||
width={1}
|
||||
position='relative'
|
||||
className={classes.container}
|
||||
height={rowVirtualizer.totalSize}
|
||||
>
|
||||
{children(rowVirtualizer.virtualItems)}
|
||||
</Box>
|
||||
@ -52,7 +79,7 @@ const ListVirtualized = ({
|
||||
<LinearProgress
|
||||
ref={loaderRef}
|
||||
color='secondary'
|
||||
style={{ width: '100%', marginTop: 10 }}
|
||||
className={classes.loading}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -107,15 +107,8 @@ export default makeStyles(theme => ({
|
||||
backgroundClip: 'content-box',
|
||||
border: '4px solid transparent',
|
||||
borderRadius: 7,
|
||||
boxShadow: 'inset 0 0 0 10px'
|
||||
},
|
||||
'&::-webkit-scrollbar-button': {
|
||||
width: 0,
|
||||
height: 0,
|
||||
display: 'none'
|
||||
},
|
||||
'&::-webkit-scrollbar-corner': {
|
||||
backgroundColor: 'transparent'
|
||||
boxShadow: 'inset 0 0 0 10px',
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
},
|
||||
list: {
|
||||
|
@ -14,7 +14,7 @@ const BorderLinearProgress = withStyles(({ palette }) => ({
|
||||
},
|
||||
bar: {
|
||||
borderRadius: 5,
|
||||
backgroundColor: palette.primary.main
|
||||
backgroundColor: palette.secondary.main
|
||||
}
|
||||
}))(LinearProgress)
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import * as DatastoreModel from 'client/models/Datastore'
|
||||
|
||||
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'
|
||||
},
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'State',
|
||||
id: 'STATE',
|
||||
accessor: row => DatastoreModel.getState(row)?.name
|
||||
},
|
||||
{
|
||||
Header: 'Type',
|
||||
id: 'TYPE',
|
||||
accessor: row => DatastoreModel.getType(row)?.name
|
||||
},
|
||||
{
|
||||
Header: 'Owner/Group',
|
||||
accessor: row => `${row.UNAME}/${row.GNAME}`
|
||||
},
|
||||
{ Header: 'Cluster', accessor: 'CLUSTER' },
|
||||
{
|
||||
Header: 'Allocated CPU',
|
||||
accessor: row => {
|
||||
const { percentOfUsed, percentLabel } = DatastoreModel.getCapacityInfo(row)
|
||||
|
||||
return (
|
||||
<div style={{ paddingRight: '1em' }}>
|
||||
<LinearProgressWithLabel value={percentOfUsed} label={percentLabel} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
@ -0,0 +1,53 @@
|
||||
import React, { useEffect, useState } 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 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)
|
||||
|
||||
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
|
||||
|
||||
return (
|
||||
<VirtualizedTable
|
||||
columns={columns}
|
||||
data={datastores}
|
||||
isLoading={loading || reloading}
|
||||
canFetchMore={canFetchMore}
|
||||
fetchMore={fetchMore}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DatastoresTable
|
66
src/fireedge/src/client/components/Tables/Hosts/columns.js
Normal file
66
src/fireedge/src/client/components/Tables/Hosts/columns.js
Normal file
@ -0,0 +1,66 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { LinearProgressWithLabel } from 'client/components/Status'
|
||||
import * as HostModel from 'client/models/Host'
|
||||
|
||||
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'
|
||||
},
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
Header: 'State',
|
||||
id: 'STATE',
|
||||
accessor: row => HostModel.getState(row)?.name
|
||||
},
|
||||
{
|
||||
Header: 'IM/VM',
|
||||
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: 'Allocated CPU',
|
||||
accessor: row => {
|
||||
const { percentCpuUsed, percentCpuLabel } = HostModel.getAllocatedInfo(row)
|
||||
|
||||
return (
|
||||
<div style={{ paddingRight: '1em' }}>
|
||||
<LinearProgressWithLabel value={percentCpuUsed} label={percentCpuLabel} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
Header: 'Allocated MEM',
|
||||
accessor: row => {
|
||||
const { percentMemUsed, percentMemLabel } = HostModel.getAllocatedInfo(row)
|
||||
|
||||
return (
|
||||
<div style={{ paddingRight: '1em' }}>
|
||||
<LinearProgressWithLabel value={percentMemUsed} label={percentMemLabel} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
53
src/fireedge/src/client/components/Tables/Hosts/index.js
Normal file
53
src/fireedge/src/client/components/Tables/Hosts/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useEffect, useState } 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 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)
|
||||
|
||||
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
|
||||
|
||||
return (
|
||||
<VirtualizedTable
|
||||
columns={columns}
|
||||
data={hosts}
|
||||
isLoading={loading || reloading}
|
||||
canFetchMore={canFetchMore}
|
||||
fetchMore={fetchMore}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default HostsTable
|
@ -15,9 +15,15 @@ const useStyles = makeStyles(theme => ({
|
||||
textAlign: 'start',
|
||||
padding: '1em',
|
||||
|
||||
color: '#4A5568',
|
||||
backgroundColor: '#e6e8f7',
|
||||
borderBlock: '0.5px solid #EDF2F7'
|
||||
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
|
||||
}
|
||||
}))
|
||||
|
122
src/fireedge/src/client/components/Tables/Virtualized/index.js
Normal file
122
src/fireedge/src/client/components/Tables/Virtualized/index.js
Normal file
@ -0,0 +1,122 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/jsx-key */
|
||||
import * as React from 'react'
|
||||
|
||||
import { makeStyles, Box, CircularProgress } from '@material-ui/core'
|
||||
import {
|
||||
useTable,
|
||||
useGlobalFilter,
|
||||
useRowSelect,
|
||||
useFlexLayout
|
||||
} from 'react-table'
|
||||
|
||||
import { ListVirtualized } from 'client/components/List'
|
||||
import Toolbar from 'client/components/Tables/Virtualized/toolbar'
|
||||
import Header from 'client/components/Tables/Virtualized/header'
|
||||
import Row from 'client/components/Tables/Virtualized/row'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
table: {
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
borderTop: 0,
|
||||
border: `1px solid ${theme.palette.action.disabledBackground}`,
|
||||
borderRadius: '0 0 6px 6px',
|
||||
|
||||
'& *[role=row]': {
|
||||
fontSize: '1em',
|
||||
fontWeight: theme.typography.fontWeightMedium,
|
||||
lineHeight: '1rem',
|
||||
|
||||
overflowWrap: 'break-word',
|
||||
textAlign: 'start',
|
||||
padding: '1em',
|
||||
alignItems: 'center',
|
||||
|
||||
color: theme.palette.text.primary,
|
||||
borderTop: `1px solid ${theme.palette.action.disabledBackground}`,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.action.hover
|
||||
},
|
||||
'&:first-of-type': {
|
||||
borderTopColor: 'transparent'
|
||||
}
|
||||
}
|
||||
},
|
||||
header: {
|
||||
...theme.typography.body1,
|
||||
color: theme.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 VirtualizedTable = ({ data, columns, isLoading, canFetchMore, fetchMore }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const defaultColumn = React.useMemo(() => ({
|
||||
// Filter: DefaultFilter,
|
||||
Cell: DefaultCell
|
||||
}), [])
|
||||
|
||||
const useTableProps = useTable(
|
||||
{ columns, data, defaultColumn },
|
||||
useRowSelect,
|
||||
useFlexLayout,
|
||||
useGlobalFilter
|
||||
)
|
||||
|
||||
const { getTableProps, getTableBodyProps, rows } = useTableProps
|
||||
|
||||
return (
|
||||
<Box {...getTableProps()} className={classes.root}>
|
||||
|
||||
<div className={classes.header}>
|
||||
<Toolbar useTableProps={useTableProps} />
|
||||
<div className={classes.total}>
|
||||
{isLoading && <CircularProgress size='1em' color='secondary' />}
|
||||
Total loaded: {useTableProps.rows.length}
|
||||
</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>
|
||||
</div>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default VirtualizedTable
|
@ -4,24 +4,13 @@ import PropTypes from 'prop-types'
|
||||
import { makeStyles, Box } from '@material-ui/core'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
const useStyles = makeStyles(() => ({
|
||||
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'
|
||||
width: '100%'
|
||||
},
|
||||
virtual: ({ size, start }) => ({
|
||||
height: size,
|
@ -1,18 +1,15 @@
|
||||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { makeStyles, Toolbar as MToolbar, Button } from '@material-ui/core'
|
||||
import { makeStyles, Button } from '@material-ui/core'
|
||||
import { Filter as FilterIcon } from 'iconoir-react'
|
||||
|
||||
import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
|
||||
// import GlobalFilter from 'client/components/Table/Filters/GlobalFilter'
|
||||
|
||||
const useToolbarStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
},
|
||||
filterWrapper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: '1em'
|
||||
},
|
||||
@ -20,10 +17,6 @@ const useToolbarStyles = makeStyles(theme => ({
|
||||
...theme.typography.body1,
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
textTransform: 'none'
|
||||
},
|
||||
filters: {
|
||||
...theme.typography.body1,
|
||||
color: theme.palette.grey[700]
|
||||
}
|
||||
}))
|
||||
|
||||
@ -31,39 +24,35 @@ const Toolbar = ({ useTableProps }) => {
|
||||
const classes = useToolbarStyles()
|
||||
|
||||
/** @type {import('react-table').UseGlobalFiltersInstanceProps} */
|
||||
const { preGlobalFilteredRows, setGlobalFilter, state } = useTableProps
|
||||
// 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}
|
||||
>
|
||||
<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>
|
||||
</Button>
|
||||
<span>No filters selected</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
{/* numSelected > 0 && (
|
||||
/* numSelected > 0 && (
|
||||
<Typography className={classes.title} color='inherit' variant='subtitle1'>
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) */}
|
||||
) */
|
||||
|
||||
{/* <GlobalFilter
|
||||
/* <GlobalFilter
|
||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
||||
globalFilter={globalFilter}
|
||||
setGlobalFilter={setGlobalFilter}
|
||||
/> */}
|
||||
</MToolbar>
|
||||
)
|
||||
/> */
|
||||
}
|
||||
|
||||
Toolbar.propTypes = {
|
@ -1,226 +0,0 @@
|
||||
/* 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
|
@ -1,81 +0,0 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable react/jsx-key */
|
||||
import * as React from 'react'
|
||||
|
||||
import { Box, CircularProgress } from '@material-ui/core'
|
||||
import {
|
||||
useTable,
|
||||
useGlobalFilter,
|
||||
useRowSelect,
|
||||
useFlexLayout
|
||||
} from 'react-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'
|
||||
|
||||
const VmTable = ({ data, isLoading, canFetchMore, fetchMore }) => {
|
||||
const columns = React.useMemo(() => Columns, [])
|
||||
|
||||
const defaultColumn = React.useMemo(() => ({
|
||||
// Filter: DefaultFilter,
|
||||
Cell: React.memo(({ value }) => value ?? '--')
|
||||
}), [])
|
||||
|
||||
const useTableProps = useTable(
|
||||
{ columns, data, defaultColumn },
|
||||
useRowSelect,
|
||||
useFlexLayout,
|
||||
useGlobalFilter
|
||||
)
|
||||
|
||||
const { getTableProps, getTableBodyProps, rows } = useTableProps
|
||||
|
||||
return (
|
||||
<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}
|
||||
>
|
||||
{virtualItems => virtualItems?.map(virtualRow => (
|
||||
<Row key={virtualRow.index}
|
||||
virtualRow={virtualRow}
|
||||
useTableProps={useTableProps}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</ListVirtualized>
|
||||
|
||||
<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>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default VmTable
|
@ -1,9 +1,3 @@
|
||||
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 [
|
||||
@ -27,31 +21,33 @@ export default [
|
||||
)
|
||||
}, */
|
||||
{
|
||||
Header: '#',
|
||||
accessor: 'ID',
|
||||
Cell: ({ value }) =>
|
||||
<StatusChip stateColor={Colors.debug.light} text={`#${value}`} />
|
||||
Header: '#', accessor: 'ID'
|
||||
// Cell: ({ value }) =>
|
||||
// <StatusChip stateColor={Colors.debug.light} text={`#${value}`} />
|
||||
},
|
||||
{ Header: 'Name', accessor: 'NAME' },
|
||||
{
|
||||
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)
|
||||
accessor: row => VirtualMachineModel.getState(row)?.name
|
||||
// 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: 'Owner/Group',
|
||||
accessor: row => `${row.UNAME}/${row.GNAME}`
|
||||
},
|
||||
{ Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` },
|
||||
{
|
||||
Header: 'Ips',
|
||||
accessor: row => VirtualMachineModel.getIps(row),
|
||||
Cell: ({ value }) => value.map(nic => (
|
||||
<StatusChip key={nic} stateColor={Colors.debug.light} text={nic} />
|
||||
))
|
||||
accessor: row => VirtualMachineModel.getIps(row).join(',')
|
||||
// Cell: ({ value }) => value.map(nic => (
|
||||
// <StatusChip key={nic} stateColor={Colors.debug.light} text={nic} />
|
||||
// ))
|
||||
}
|
||||
]
|
@ -1,22 +1,28 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { useAuth } from 'client/features/Auth'
|
||||
import { useVm, useVmApi } from 'client/features/One'
|
||||
import { useFetch } from 'client/hooks'
|
||||
import { useVm, useVmApi } from 'client/features/One'
|
||||
|
||||
import { VmTable } from 'client/components/Tables'
|
||||
import { VirtualizedTable } from 'client/components/Tables'
|
||||
import Columns from 'client/components/Tables/Vms/columns'
|
||||
|
||||
const INITIAL_ELEMENT = 0
|
||||
const NUMBER_OF_INTERVAL = -100
|
||||
const NUMBER_OF_INTERVAL = 20
|
||||
|
||||
function VirtualMachines () {
|
||||
const [{ start, end }, setPage] = useState(({ start: INITIAL_ELEMENT, end: -NUMBER_OF_INTERVAL }))
|
||||
const VmsTable = () => {
|
||||
const [{ start, end }, setPage] = useState({
|
||||
start: INITIAL_ELEMENT,
|
||||
end: -NUMBER_OF_INTERVAL
|
||||
})
|
||||
|
||||
const columns = React.useMemo(() => Columns, [])
|
||||
|
||||
const vms = useVm()
|
||||
const { getVms } = useVmApi()
|
||||
const { filterPool } = useAuth()
|
||||
|
||||
const { data, fetchRequest, loading, reloading } = useFetch(getVms)
|
||||
const { data, fetchRequest, loading, reloading, error } = useFetch(getVms)
|
||||
|
||||
useEffect(() => { fetchRequest({ start, end }) }, [filterPool])
|
||||
|
||||
@ -31,16 +37,17 @@ function VirtualMachines () {
|
||||
})
|
||||
}
|
||||
|
||||
const finish = data?.length < NUMBER_OF_INTERVAL
|
||||
const canFetchMore = error || data?.vms?.length < NUMBER_OF_INTERVAL
|
||||
|
||||
return (
|
||||
<VmTable
|
||||
<VirtualizedTable
|
||||
columns={columns}
|
||||
data={vms}
|
||||
isLoading={loading || reloading}
|
||||
finish={finish}
|
||||
canFetchMore={canFetchMore}
|
||||
fetchMore={fetchMore}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default VirtualMachines
|
||||
export default VmsTable
|
@ -1,5 +1,11 @@
|
||||
import VmTable from 'client/components/Tables/VmTable'
|
||||
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 {
|
||||
VmTable
|
||||
VirtualizedTable,
|
||||
VmsTable,
|
||||
DatastoresTable,
|
||||
HostsTable
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as STATES from 'client/constants/states'
|
||||
import COLOR from 'client/constants/color'
|
||||
|
||||
const DATASTORE_TYPES = [
|
||||
export const DATASTORE_TYPES = [
|
||||
{
|
||||
name: 'IMAGE',
|
||||
shortName: 'img'
|
||||
@ -16,7 +16,7 @@ const DATASTORE_TYPES = [
|
||||
}
|
||||
]
|
||||
|
||||
const DATASTORE_STATES = [
|
||||
export const DATASTORE_STATES = [
|
||||
{
|
||||
name: STATES.READY,
|
||||
shortName: 'on',
|
||||
@ -28,8 +28,3 @@ const DATASTORE_STATES = [
|
||||
color: COLOR.error.dark
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
TYPES: DATASTORE_TYPES,
|
||||
STATES: DATASTORE_STATES
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as STATES from 'client/constants/states'
|
||||
import COLOR from 'client/constants/color'
|
||||
|
||||
const HOST_STATES = [
|
||||
export const HOST_STATES = [
|
||||
{
|
||||
name: STATES.INIT,
|
||||
shortName: 'init',
|
||||
@ -48,7 +48,3 @@ const HOST_STATES = [
|
||||
color: COLOR.error.dark
|
||||
}
|
||||
]
|
||||
|
||||
export default {
|
||||
STATES: HOST_STATES
|
||||
}
|
||||
|
@ -82,3 +82,5 @@ export * as STATES from 'client/constants/states'
|
||||
export * from 'client/constants/flow'
|
||||
export * from 'client/constants/provision'
|
||||
export * from 'client/constants/vm'
|
||||
export * from 'client/constants/host'
|
||||
export * from 'client/constants/datastore'
|
||||
|
89
src/fireedge/src/client/containers/Newstone/index.js
Normal file
89
src/fireedge/src/client/containers/Newstone/index.js
Normal file
@ -0,0 +1,89 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { useParams, useHistory } from 'react-router'
|
||||
import { Redirect, Route, Switch, Link } from 'react-router-dom'
|
||||
|
||||
import { withStyles, Container, Tabs, Tab, Box } from '@material-ui/core'
|
||||
|
||||
import {
|
||||
DatastoresTable,
|
||||
HostsTable,
|
||||
VmsTable
|
||||
} from 'client/components/Tables'
|
||||
import { PATH } from 'client/router/dev'
|
||||
|
||||
const TABS = {
|
||||
vms: PATH.NEWSTONE.replace(':resource', 'vms'),
|
||||
datastores: PATH.NEWSTONE.replace(':resource', 'datastores'),
|
||||
hosts: PATH.NEWSTONE.replace(':resource', 'hosts')
|
||||
}
|
||||
|
||||
const AntTabs = withStyles(theme => ({
|
||||
root: {
|
||||
borderBottom: '1px solid #e8e8e8'
|
||||
},
|
||||
indicator: {
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
}
|
||||
}))(Tabs)
|
||||
|
||||
const AntTab = withStyles(theme => ({
|
||||
root: {
|
||||
minWidth: 72,
|
||||
fontWeight: theme.typography.fontWeightRegular,
|
||||
marginRight: theme.spacing(4),
|
||||
'&:hover': {
|
||||
color: theme.palette.secondary.light,
|
||||
opacity: 1
|
||||
},
|
||||
'&$selected': {
|
||||
color: theme.palette.secondary.main,
|
||||
fontWeight: theme.typography.fontWeightMedium
|
||||
},
|
||||
'&:focus': {
|
||||
color: theme.palette.secondary.light
|
||||
}
|
||||
},
|
||||
selected: {}
|
||||
}))(props => <Tab disableRipple {...props} />)
|
||||
|
||||
const Newstone = () => {
|
||||
const history = useHistory()
|
||||
const { resource } = useParams()
|
||||
|
||||
const renderTabs = React.useMemo(() => (
|
||||
<AntTabs
|
||||
value={resource}
|
||||
variant='scrollable'
|
||||
scrollButtons='auto'
|
||||
>
|
||||
{Object.keys(TABS).map(tabName =>
|
||||
<AntTab
|
||||
key={`tab-${tabName}`}
|
||||
label={tabName}
|
||||
value={tabName}
|
||||
component={Link}
|
||||
to={tabName}
|
||||
/>
|
||||
)}
|
||||
</AntTabs>
|
||||
), [resource])
|
||||
|
||||
return (
|
||||
<Container disableGutters>
|
||||
{Object.values(TABS).includes(history.location.pathname) && renderTabs}
|
||||
|
||||
<Box py={2}>
|
||||
<Switch>
|
||||
<Route path={TABS.vms} component={VmsTable} />
|
||||
<Route path={TABS.datastores} component={DatastoresTable} />
|
||||
<Route path={TABS.hosts} component={HostsTable} />
|
||||
|
||||
<Route component={() => <Redirect to={TABS.vms} />} />
|
||||
</Switch>
|
||||
</Box>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default Newstone
|
@ -1,159 +0,0 @@
|
||||
import React, { memo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { List, ListItem, Typography, Grid, Paper, Divider } from '@material-ui/core'
|
||||
import { CheckBox, CheckBoxOutlineBlank, Visibility } from '@material-ui/icons'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { useProviderApi } from 'client/features/One'
|
||||
import { Action } from 'client/components/Cards/SelectCard'
|
||||
import { Tr } from 'client/components/HOC'
|
||||
import { T } from 'client/constants'
|
||||
|
||||
import useStyles from 'client/containers/Providers/Sections/styles'
|
||||
|
||||
const Info = memo(({ data }) => {
|
||||
const classes = useStyles()
|
||||
const { getProviderConnection } = useProviderApi()
|
||||
|
||||
const [showConnection, setShowConnection] = useState(undefined)
|
||||
|
||||
const { ID, NAME, GNAME, UNAME, PERMISSIONS, TEMPLATE } = data
|
||||
const {
|
||||
connection,
|
||||
description,
|
||||
provider: providerName,
|
||||
registration_time: time
|
||||
} = TEMPLATE?.PROVISION_BODY
|
||||
|
||||
const hasConnection = connection && Object.keys(connection).length > 0
|
||||
|
||||
const isChecked = checked =>
|
||||
checked === '1' ? <CheckBox /> : <CheckBoxOutlineBlank />
|
||||
|
||||
const ConnectionButton = () => (
|
||||
<Action
|
||||
icon={<Visibility />}
|
||||
cy='provider-connection'
|
||||
handleClick={() => getProviderConnection(ID).then(setShowConnection)}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper variant="outlined" className={classes.marginBottom}>
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Information)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography>{'ID'}</Typography>
|
||||
<Typography>{ID}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Name)}</Typography>
|
||||
<Typography data-cy="provider-name">{NAME}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Description)}</Typography>
|
||||
<Typography data-cy="provider-description" noWrap>{description}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Provider)}</Typography>
|
||||
<Typography data-cy="provider-type">{providerName}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.RegistrationTime)}</Typography>
|
||||
<Typography>
|
||||
{new Date(time * 1000).toLocaleString()}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
{hasConnection && (
|
||||
<Paper variant="outlined">
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Credentials)}</Typography>
|
||||
<span className={classes.alignToRight}>
|
||||
{!showConnection && <ConnectionButton />}
|
||||
</span>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
{Object.entries(connection)?.map(([key, value]) =>
|
||||
typeof value === 'string' && (
|
||||
<ListItem key={key}>
|
||||
<Typography>{key}</Typography>
|
||||
<Typography data-cy={`provider-${key}`}>
|
||||
{showConnection?.[key] ?? value}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Paper>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Paper variant="outlined" className={classes.marginBottom}>
|
||||
<List className={clsx(classes.list, 'w-25')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Permissions)}</Typography>
|
||||
<Typography>{Tr(T.Use)}</Typography>
|
||||
<Typography>{Tr(T.Manage)}</Typography>
|
||||
<Typography>{Tr(T.Admin)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Owner)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OWNER_U)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OWNER_M)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OWNER_A)}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Group)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.GROUP_U)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.GROUP_M)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.GROUP_A)}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Other)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OTHER_U)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OTHER_M)}</Typography>
|
||||
<Typography>{isChecked(PERMISSIONS.OTHER_A)}</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
<Paper variant="outlined">
|
||||
<List className={clsx(classes.list, 'w-50')}>
|
||||
<ListItem className={classes.title}>
|
||||
<Typography>{Tr(T.Ownership)}</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Owner)}</Typography>
|
||||
<Typography>{UNAME}</Typography>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Typography>{Tr(T.Group)}</Typography>
|
||||
<Typography>{GNAME}</Typography>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
})
|
||||
|
||||
Info.propTypes = {
|
||||
data: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
Info.defaultProps = {
|
||||
data: {}
|
||||
}
|
||||
|
||||
Info.displayName = 'Info'
|
||||
|
||||
export default Info
|
@ -1,32 +0,0 @@
|
||||
import { makeStyles } from '@material-ui/core'
|
||||
|
||||
export default makeStyles(theme => ({
|
||||
marginBottom: {
|
||||
marginBottom: theme.spacing(2)
|
||||
},
|
||||
list: {
|
||||
'& p': {
|
||||
...theme.typography.body2,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis'
|
||||
},
|
||||
'&.w-50 > *': {
|
||||
'& > p, & > span': {
|
||||
width: '50%'
|
||||
}
|
||||
},
|
||||
'&.w-25 > *': {
|
||||
'& > p, & > span': {
|
||||
width: '25%'
|
||||
}
|
||||
}
|
||||
},
|
||||
title: {
|
||||
'& p.bold': {
|
||||
fontWeight: theme.typography.fontWeightBold
|
||||
}
|
||||
},
|
||||
alignToRight: {
|
||||
textAlign: 'right'
|
||||
}
|
||||
}))
|
@ -4,7 +4,7 @@ import { datastoreService } from 'client/features/One/datastore/services'
|
||||
export const getDatastore = createAction('datastore', datastoreService.getDatastore)
|
||||
|
||||
export const getDatastores = createAction(
|
||||
'datastore',
|
||||
'datastore/pool',
|
||||
datastoreService.getDatastores,
|
||||
response => ({ datastores: response })
|
||||
)
|
||||
|
@ -18,6 +18,6 @@ export const useDatastoreApi = () => {
|
||||
|
||||
return {
|
||||
getDatastore: id => unwrapDispatch(actions.getDatastore({ id })),
|
||||
getDatastores: () => unwrapDispatch(actions.getDatastores())
|
||||
getDatastores: options => unwrapDispatch(actions.getDatastores(options))
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,6 @@ export const useHostApi = () => {
|
||||
|
||||
return {
|
||||
getHost: id => unwrapDispatch(actions.getHost({ id })),
|
||||
getHosts: () => unwrapDispatch(actions.getHosts())
|
||||
getHosts: options => unwrapDispatch(actions.getHosts(options))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { debounce } from '@material-ui/core'
|
||||
import { fakeDelay } from 'client/utils'
|
||||
import { console } from 'window-or-global'
|
||||
|
||||
const useRequest = request => {
|
||||
const [data, setData] = useState(undefined)
|
||||
@ -22,7 +23,8 @@ const useRequest = request => {
|
||||
} else setError(true)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(e => {
|
||||
console.log('error', e)
|
||||
if (isMounted.current) {
|
||||
setData(undefined)
|
||||
setError(true)
|
||||
|
15
src/fireedge/src/client/models/Datastore.js
Normal file
15
src/fireedge/src/client/models/Datastore.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { DATASTORE_STATES, DATASTORE_TYPES } from 'client/constants'
|
||||
|
||||
export const getType = ({ TYPE } = {}) => DATASTORE_TYPES[TYPE]
|
||||
|
||||
export const getState = ({ STATE } = {}) => DATASTORE_STATES[STATE]
|
||||
|
||||
export const getCapacityInfo = ({ TOTAL_MB, USED_MB } = {}) => {
|
||||
const percentOfUsed = +USED_MB * 100 / +TOTAL_MB || 0
|
||||
const usedBytes = prettyBytes(+USED_MB, 'MB')
|
||||
const totalBytes = prettyBytes(+TOTAL_MB, 'MB')
|
||||
const percentLabel = `${usedBytes} / ${totalBytes} (${Math.round(percentOfUsed)}%)`
|
||||
|
||||
return { percentOfUsed, percentLabel }
|
||||
}
|
23
src/fireedge/src/client/models/Host.js
Normal file
23
src/fireedge/src/client/models/Host.js
Normal file
@ -0,0 +1,23 @@
|
||||
import { prettyBytes } from 'client/utils'
|
||||
import { HOST_STATES } from 'client/constants'
|
||||
|
||||
export const getState = ({ STATE } = {}) => HOST_STATES[STATE]
|
||||
|
||||
export const getAllocatedInfo = ({ HOST_SHARE } = {}) => {
|
||||
const { CPU_USAGE, TOTAL_CPU, MEM_USAGE, TOTAL_MEM } = HOST_SHARE
|
||||
|
||||
const percentCpuUsed = +CPU_USAGE * 100 / +TOTAL_CPU || 0
|
||||
const percentCpuLabel = `${CPU_USAGE} / ${TOTAL_CPU} (${Math.round(percentCpuUsed)}%)`
|
||||
|
||||
const percentMemUsed = +MEM_USAGE * 100 / +TOTAL_MEM || 0
|
||||
const usedMemBytes = prettyBytes(+MEM_USAGE)
|
||||
const totalMemBytes = prettyBytes(+TOTAL_MEM)
|
||||
const percentMemLabel = `${usedMemBytes} / ${totalMemBytes} (${Math.round(percentMemUsed)}%)`
|
||||
|
||||
return {
|
||||
percentCpuUsed,
|
||||
percentCpuLabel,
|
||||
percentMemUsed,
|
||||
percentMemLabel
|
||||
}
|
||||
}
|
@ -1,27 +1,26 @@
|
||||
import loadable from '@loadable/component'
|
||||
import {
|
||||
Code as DevIcon,
|
||||
ViewGrid as VmIcon
|
||||
ViewGrid as NewstoneIcon
|
||||
} from 'iconoir-react'
|
||||
|
||||
const VirtualMachines = loadable(() => import('client/containers/VirtualMachines'), { ssr: false })
|
||||
const Newstone = loadable(() => import('client/containers/Newstone'), { ssr: false })
|
||||
const TestApi = loadable(() => import('client/containers/TestApi'), { ssr: false })
|
||||
const WebConsole = loadable(() => import('client/containers/WebConsole'), { ssr: false })
|
||||
|
||||
export const PATH = {
|
||||
VIRTUAL_MACHINES: '/vms',
|
||||
NEWSTONE: '/newstone/:resource',
|
||||
TEST_API: '/test-api',
|
||||
WEB_CONSOLE: '/webconsole'
|
||||
}
|
||||
|
||||
export const ENDPOINTS = [
|
||||
{
|
||||
label: 'VMs',
|
||||
path: PATH.VIRTUAL_MACHINES,
|
||||
devMode: true,
|
||||
label: 'Newstone',
|
||||
path: PATH.NEWSTONE,
|
||||
sidebar: true,
|
||||
icon: VmIcon,
|
||||
Component: VirtualMachines
|
||||
icon: NewstoneIcon,
|
||||
Component: Newstone
|
||||
},
|
||||
{
|
||||
label: 'Test API',
|
||||
|
@ -34,11 +34,17 @@ export default {
|
||||
},
|
||||
typography: {
|
||||
fontFamily: [
|
||||
'"Ubuntu"',
|
||||
'Ubuntu',
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'Helvetica',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif'
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"'
|
||||
].join(',')
|
||||
},
|
||||
mixins: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user