diff --git a/src/components/VmsList/TableView.js b/src/components/VmsList/TableView.js index 707aed1f0..1a45de0b7 100644 --- a/src/components/VmsList/TableView.js +++ b/src/components/VmsList/TableView.js @@ -11,19 +11,51 @@ import { Tr, } from '@patternfly/react-table' -import { toJS, translate } from '_/helpers' +import { translate } from '_/helpers' -import { saveEventFilters, setVmSort } from '_/actions' -import { NAME, SortFields } from '_/utils' +import { setVmSort } from '_/actions' +import { + SortFields, + ICON, + POOL_INFO, + ACTIONS, + NAME, + OS, + STATUS, +} from '_/utils' + +import { + TableVm, +} from './Vm' +import { + TablePool, +} from './Pool' const TableView = ({ msg, locale, - children: rows, - columns, + vmsAndPools, sort, setSort, }) => { + const columns = [ + { id: ICON }, + { + ...SortFields[NAME], + sort: true, + }, + { + ...SortFields[STATUS], + sort: true, + }, + { id: POOL_INFO }, + { + ...SortFields[OS], + sort: true, + }, + { id: ACTIONS }, + ] + const activeSortIndex = columns.findIndex(({ id }) => id === sort.id) const activeSortDirection = sort.isAsc ? 'asc' : 'desc' @@ -42,7 +74,7 @@ const TableView = ({ }) return ( <> - { rows?.length > 0 && ( + { vmsAndPools?.length > 0 && ( - { rows} + { vmsAndPools.map(entity => ( + entity.get('isVm') + ? ( + + ) + : ( + + ) + ))} ) } @@ -71,15 +119,9 @@ const TableView = ({ } TableView.propTypes = { - columns: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - messageDescriptor: PropTypes.object, - sort: PropTypes.bool, - })).isRequired, msg: PropTypes.object.isRequired, locale: PropTypes.string.isRequired, - children: PropTypes.array.isRequired, + vmsAndPools: PropTypes.array.isRequired, sort: PropTypes.shape({ id: PropTypes.string.isRequired, isAsc: PropTypes.bool, @@ -88,13 +130,8 @@ TableView.propTypes = { } export default connect( - ({ userMessages }, { vmId }) => ({ - events: toJS(userMessages.getIn(['events', vmId])), - eventFilters: toJS(userMessages.getIn(['eventFilters'], {})), - eventSort: toJS(userMessages.getIn(['eventSort'])), - }), + null, (dispatch) => ({ - clearAllFilters: () => dispatch(saveEventFilters({ filters: {} })), setSort: (sort) => dispatch(setVmSort({ sort })), }) )(withMsg(TableView)) diff --git a/src/components/VmsList/VmCardList.js b/src/components/VmsList/VmCardList.js index 95de65c8d..20b66ab49 100644 --- a/src/components/VmsList/VmCardList.js +++ b/src/components/VmsList/VmCardList.js @@ -1,33 +1,14 @@ -import React, { useContext, useEffect } from 'react' +import React from 'react' import PropTypes from 'prop-types' -import { connect } from 'react-redux' -import { MsgContext } from '_/intl' -import { getByPage } from '_/actions' -import { - filterVms, - sortFunction, - SortFields, - ICON, - POOL_INFO, - ACTIONS, - NAME, - OS, - STATUS, -} from '_/utils' - -import useInfiniteScroll from '@closeio/use-infinite-scroll' import { CardVm, - TableVm, } from './Vm' import { CardPool, - TablePool, } from './Pool' import style from './style.css' import { Gallery, GalleryItem } from '@patternfly/react-core' -import TableView from './TableView' /** * Use Patternfly 'Single Select Card View' pattern to show every VM and Pool @@ -37,138 +18,21 @@ import TableView from './TableView' * before this component is rendered. This will prevent two "initial page" fetches * from running at the same time. The `VmsList` component handles this normally. */ -const VmCardList = ({ vms, alwaysShowPoolCard, fetchMoreVmsAndPools, tableView }) => { - const { msg, locale } = useContext(MsgContext) - const sort = vms.get('sort').toJS() - const filters = vms.get('filters').toJS() - - // Filter the VMs (1. apply the filter bar criteria, 2. only show Pool VMs if the Pool exists) - const filteredVms = vms.get('vms') - .filter(vm => filterVms(vm, filters)) - .filter(vm => vm.getIn(['pool', 'id'], false) ? !!vms.getIn(['pools', vm.getIn(['pool', 'id'])], false) : true) - .toList() - .map(vm => vm.set('isVm', true)) - - // Filter the Pools (only show a Pool card if the user can currently 'Take' a VM from it) - const filteredPools = vms.get('pools') - .filter(pool => - (alwaysShowPoolCard || (pool.get('vmsCount') < pool.get('maxUserVms') && pool.get('size') > 0)) && - filterVms(pool, filters) - ) - .toList() - - // Display the VMs and Pools together, sorted nicely - const vmsAndPools = [...filteredVms, ...filteredPools].sort(sortFunction(sort, locale, msg)) - - // Handle the infinite scroll and pagination - const hasMore = vms.get('vmsExpectMorePages') || vms.get('poolsExpectMorePages') - const [page, sentinelRef, scrollerRef] = useInfiniteScroll({ hasMore, distance: 0 }) - - useEffect(() => { // `VmsList` will not display this component until the first page of data is loaded - if (page > 0) { - fetchMoreVmsAndPools() - } - }, [page, fetchMoreVmsAndPools]) - - useEffect(() => { - if (!scrollerRef.current || !sentinelRef.current) { - return - } - - // - // If a page fetch doesn't pull enough entities to push the sentinel out of view - // underlying IntersectionObserver doesn't fire another event, and the scroller - // gets stuck. Manually check if the sentinel is in view, and if it is, fetch - // more data. The effect is only run when the `vms` part of the redux store is - // updated. - // - const scrollRect = scrollerRef.current.getBoundingClientRect() - const scrollVisibleTop = scrollRect.y - const scrollVisibleBottom = scrollRect.y + scrollRect.height - - const sentinelRect = sentinelRef.current.getBoundingClientRect() - const sentinelTop = sentinelRect.y - const sentinelBottom = sentinelRect.y + sentinelRect.height - - const sentinelStillInView = sentinelBottom >= scrollVisibleTop && sentinelTop <= scrollVisibleBottom - if (sentinelStillInView) { - fetchMoreVmsAndPools() - } - }, [vms, scrollerRef, sentinelRef, fetchMoreVmsAndPools]) - - const columnList = [ - { id: ICON }, - { - ...SortFields[NAME], - sort: true, - }, - { - ...SortFields[STATUS], - sort: true, - }, - { id: POOL_INFO }, - { - ...SortFields[OS], - sort: true, - }, - { id: ACTIONS }, - ] - +const VmCardList = ({ vmsAndPools }) => { return ( -
- { !tableView && ( - - {vmsAndPools.map(entity => ( - { + + {vmsAndPools.map(entity => ( + { entity.get('isVm') ? : } - - ))} - - )} - {tableView && ( - - { vmsAndPools.map(entity => ( - entity.get('isVm') - ? ( - - ) - : ( - - ) - ))} - - )} - {hasMore &&
{msg.loadingTripleDot()}
} -
+ + ))} + ) } VmCardList.propTypes = { - tableView: PropTypes.bool.isRequired, - vms: PropTypes.object.isRequired, - alwaysShowPoolCard: PropTypes.bool, - fetchMoreVmsAndPools: PropTypes.func.isRequired, + vmsAndPools: PropTypes.array.isRequired, } -export default connect( - ({ vms, config, options }) => ({ - vms, - alwaysShowPoolCard: !config.get('filter'), - tableView: options.getIn(['remoteOptions', 'viewForVirtualMachines', 'content']) === 'table', - }), - (dispatch) => ({ - fetchMoreVmsAndPools: () => dispatch(getByPage()), - }) -)(VmCardList) +export default VmCardList diff --git a/src/components/VmsList/index.js b/src/components/VmsList/index.js index ac8e75ba0..dc7e45657 100644 --- a/src/components/VmsList/index.js +++ b/src/components/VmsList/index.js @@ -1,9 +1,17 @@ -import React, { useContext } from 'react' +import React, { useContext, useEffect } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' +import style from './style.css' +import { getByPage } from '_/actions' +import useInfiniteScroll from '@closeio/use-infinite-scroll' +import { + filterVms, + sortFunction, +} from '_/utils' + import { EmptyState, EmptyStateIcon, @@ -11,8 +19,9 @@ import { Title, } from '@patternfly/react-core' import VmCardList from './VmCardList' -import { MsgContext } from '_/intl' +import { MsgContext, withMsg } from '_/intl' import { AddCircleOIcon } from '@patternfly/react-icons/dist/esm/icons' +import TableView from './TableView' /** * Component displayed when VMs or Pools exist but the data is still loading. @@ -38,33 +47,133 @@ const NoVmAvailable = () => { ) } -const VmsList = ({ vms, waitForFirstFetch }) => { +const InfiniteScroller = ({ children, className, fetchMoreVmsAndPools, vms }) => { + const { msg } = useContext(MsgContext) + // Handle the infinite scroll and pagination + const hasMore = vms.get('vmsExpectMorePages') || vms.get('poolsExpectMorePages') + const [page, sentinelRef, scrollerRef] = useInfiniteScroll({ hasMore, distance: 0 }) + + useEffect(() => { // `VmsList` will not display this component until the first page of data is loaded + if (page > 0) { + fetchMoreVmsAndPools() + } + }, [page, fetchMoreVmsAndPools]) + + useEffect(() => { + if (!scrollerRef.current || !sentinelRef.current) { + return + } + + // + // If a page fetch doesn't pull enough entities to push the sentinel out of view + // underlying IntersectionObserver doesn't fire another event, and the scroller + // gets stuck. Manually check if the sentinel is in view, and if it is, fetch + // more data. The effect is only run when the `vms` part of the redux store is + // updated. + // + const scrollRect = scrollerRef.current.getBoundingClientRect() + const scrollVisibleTop = scrollRect.y + const scrollVisibleBottom = scrollRect.y + scrollRect.height + + const sentinelRect = sentinelRef.current.getBoundingClientRect() + const sentinelTop = sentinelRect.y + const sentinelBottom = sentinelRect.y + sentinelRect.height + + const sentinelStillInView = sentinelBottom >= scrollVisibleTop && sentinelTop <= scrollVisibleBottom + if (sentinelStillInView) { + fetchMoreVmsAndPools() + } + }, [vms, scrollerRef, sentinelRef, fetchMoreVmsAndPools]) + + return ( +
+ {children} + {hasMore &&
{msg.loadingTripleDot()}
} +
+ ) +} + +InfiniteScroller.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + fetchMoreVmsAndPools: PropTypes.func.isRequired, + vms: PropTypes.object.isRequired, +} + +const VmsList = ({ + alwaysShowPoolCard, + fetchMoreVmsAndPools, + tableView, + vms, + waitForFirstFetch, + msg, + locale, +}) => { const haveVms = (vms.get('vms') && !vms.get('vms').isEmpty()) const havePools = (vms.get('pools') && !vms.get('pools').isEmpty()) - let el = null - if (waitForFirstFetch) { - el = - } else if (haveVms || havePools) { - el = - } else { - el = + return } - return el + if (!haveVms && !havePools) { + + } + + const sort = vms.get('sort').toJS() + const filters = vms.get('filters').toJS() + + // Filter the VMs (1. apply the filter bar criteria, 2. only show Pool VMs if the Pool exists) + const filteredVms = vms.get('vms') + .filter(vm => filterVms(vm, filters)) + .filter(vm => vm.getIn(['pool', 'id'], false) ? !!vms.getIn(['pools', vm.getIn(['pool', 'id'])], false) : true) + .toList() + .map(vm => vm.set('isVm', true)) + + // Filter the Pools (only show a Pool card if the user can currently 'Take' a VM from it) + const filteredPools = vms.get('pools') + .filter(pool => + (alwaysShowPoolCard || (pool.get('vmsCount') < pool.get('maxUserVms') && pool.get('size') > 0)) && + filterVms(pool, filters) + ) + .toList() + + // Display the VMs and Pools together, sorted nicely + const vmsAndPools = [...filteredVms, ...filteredPools].sort(sortFunction(sort, locale, msg)) + + return ( + + {tableView && } + {!tableView && } + + ) } VmsList.propTypes = { + alwaysShowPoolCard: PropTypes.bool, + fetchMoreVmsAndPools: PropTypes.func.isRequired, + tableView: PropTypes.bool.isRequired, vms: PropTypes.object.isRequired, waitForFirstFetch: PropTypes.bool.isRequired, + + msg: PropTypes.object.isRequired, + locale: PropTypes.string.isRequired, } export default withRouter(connect( - (state) => ({ - vms: state.vms, + ({ vms, config, options }) => ({ + vms, + alwaysShowPoolCard: !config.get('filter'), + tableView: options.getIn(['remoteOptions', 'viewForVirtualMachines', 'content']) === 'table', waitForFirstFetch: ( - state.vms.get('vmsPage') === 0 && !!state.vms.get('vmsExpectMorePages') && - state.vms.get('poolsPage') === 0 && !!state.vms.get('poolsExpectMorePages') + vms.get('vmsPage') === 0 && !!vms.get('vmsExpectMorePages') && + vms.get('poolsPage') === 0 && !!vms.get('poolsExpectMorePages') ), + }), + (dispatch) => ({ + fetchMoreVmsAndPools: () => dispatch(getByPage()), }) -)(VmsList)) +)(withMsg(VmsList))) diff --git a/src/components/VmsList/style.css b/src/components/VmsList/style.css index 0c8afed86..e6078ab53 100644 --- a/src/components/VmsList/style.css +++ b/src/components/VmsList/style.css @@ -51,8 +51,6 @@ td .vm-status { margin-bottom: 0; } - - .vm-detail-link { color: black; } @@ -153,4 +151,4 @@ dl.pool-info dd { :global(#page-router-render-component) .tableView { padding-top: 0; -} \ No newline at end of file +}