diff options
4 files changed, 209 insertions, 192 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js index 68647957..8dc52f7a 100644 --- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js @@ -20,8 +20,9 @@ * SOFTWARE. */ +import { Bullseye } from '@patternfly/react-core' import Link from 'next/link' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { TableComposable, Thead, Tr, Th, Tbody, Td, ActionsColumn } from '@patternfly/react-table' import React from 'react' import { Portfolio, Status } from '../../shapes' import TableEmptyState from '../util/TableEmptyState' @@ -33,65 +34,65 @@ function ScenarioTable({ portfolio, status }) { const projectId = portfolio?.project?.id const scenarios = portfolio?.scenarios ?? [] - const columns = ['Name', 'Topology', 'Trace', 'State'] - const rows = - scenarios.length > 0 - ? scenarios.map((scenario) => { - const topology = scenario.topology - - return [ - scenario.name, - { - title: topology ? ( - <Link href={`/projects/${projectId}/topologies/${topology.number}`}> - <a>{topology.name}</a> - </Link> - ) : ( - 'Unknown Topology' - ), - }, - `${scenario.workload.trace.name} (${scenario.workload.samplingFraction * 100}%)`, - { title: <ScenarioState state={scenario.job.state} /> }, - ] - }) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 4 }, - title: ( - <TableEmptyState - status={status} - loadingTitle="Loading Scenarios" - emptyTitle="No scenarios" - emptyText="You have not created any scenario for this portfolio yet. Click the New Scenario button to create one." - /> - ), - }, - ], - }, - ] - - const actionResolver = (_, { rowIndex }) => [ + const actions = ({ number }) => [ { title: 'Delete Scenario', - onClick: (_, rowId) => deleteScenario({ projectId: projectId, number: scenarios[rowId].number }), - isDisabled: rowIndex === 0, + onClick: () => deleteScenario({ projectId: projectId, number }), + isDisabled: number === 0, }, ] return ( - <Table - aria-label="Scenario List" - variant="compact" - cells={columns} - rows={rows} - actionResolver={scenarios.length > 0 ? actionResolver : undefined} - > - <TableHeader /> - <TableBody /> - </Table> + <TableComposable aria-label="Scenario List" variant="compact"> + <Thead> + <Tr> + <Th>Name</Th> + <Th>Topology</Th> + <Th>Trace</Th> + <Th>State</Th> + </Tr> + </Thead> + <Tbody> + {scenarios.map((scenario) => ( + <Tr key={scenario.id}> + <Td dataLabel="Name">{scenario.name}</Td> + <Td dataLabel="Topology"> + {scenario.topology ? ( + <Link href={`/projects/${projectId}/topologies/${scenario.topology.number}`}> + <a>{scenario.topology.name}</a> + </Link> + ) : ( + 'Unknown Topology' + )} + , + </Td> + <Td dataLabel="Workload">{`${scenario.workload.trace.name} (${ + scenario.workload.samplingFraction * 100 + }%)`}</Td> + <Td dataLabel="State"> + <ScenarioState state={scenario.job.state} /> + </Td> + <Td isActionCell> + <ActionsColumn items={actions(scenario)} /> + </Td> + </Tr> + ))} + {scenarios.length === 0 && ( + <Tr> + <Td colSpan={4}> + <Bullseye> + <TableEmptyState + status={status} + loadingTitle="Loading Scenarios" + emptyTitle="No scenarios" + emptyText="You have not created any scenario for this portfolio yet. Click the New Scenario button to create one." + /> + </Bullseye> + </Td> + </Tr> + )} + </Tbody> + </TableComposable> ) } diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js index aa679843..0afeaeaf 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js @@ -20,64 +20,75 @@ * SOFTWARE. */ +import { Bullseye } from '@patternfly/react-core' import PropTypes from 'prop-types' import Link from 'next/link' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { TableComposable, Thead, Tbody, Tr, Th, Td, ActionsColumn } from '@patternfly/react-table' import React from 'react' import TableEmptyState from '../util/TableEmptyState' import { usePortfolios, useDeletePortfolio } from '../../data/project' -const PortfolioTable = ({ projectId }) => { +function PortfolioTable({ projectId }) { const { status, data: portfolios = [] } = usePortfolios(projectId) const { mutate: deletePortfolio } = useDeletePortfolio() - const columns = ['Name', 'Scenarios', 'Metrics', 'Repeats'] - const rows = - portfolios.length > 0 - ? portfolios.map((portfolio) => [ - { - title: ( - <Link href={`/projects/${projectId}/portfolios/${portfolio.number}`}>{portfolio.name}</Link> - ), - }, - portfolio.scenarios.length === 1 ? '1 scenario' : `${portfolio.scenarios.length} scenarios`, - portfolio.targets.metrics.length === 1 ? '1 metric' : `${portfolio.targets.metrics.length} metrics`, - portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`, - ]) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 4 }, - title: ( - <TableEmptyState - status={status} - loadingTitle="Loading portfolios" - emptyTitle="No portfolios" - emptyText="You have not created any portfolio for this project yet. Click the New Portfolio button to create one." - /> - ), - }, - ], - }, - ] - - const actions = - portfolios.length > 0 - ? [ - { - title: 'Delete Portfolio', - onClick: (_, rowId) => deletePortfolio({ projectId, number: portfolios[rowId].number }), - }, - ] - : [] + const actions = (portfolio) => [ + { + title: 'Delete Portfolio', + onClick: () => deletePortfolio({ projectId, number: portfolio.number }), + }, + ] return ( - <Table aria-label="Portfolio List" variant="compact" cells={columns} rows={rows} actions={actions}> - <TableHeader /> - <TableBody /> - </Table> + <TableComposable aria-label="Portfolio List" variant="compact"> + <Thead> + <Tr> + <Th>Name</Th> + <Th>Scenarios</Th> + <Th>Metrics</Th> + <Th>Repeats</Th> + </Tr> + </Thead> + <Tbody> + {portfolios.map((portfolio) => ( + <Tr key={portfolio.id}> + <Td dataLabel="Name"> + <Link href={`/projects/${projectId}/portfolios/${portfolio.number}`}>{portfolio.name}</Link> + </Td> + <Td dataLabel="Scenarios"> + {portfolio.scenarios.length === 1 + ? '1 scenario' + : `${portfolio.scenarios.length} scenarios`} + </Td> + <Td dataLabel="Metrics"> + {portfolio.targets.metrics.length === 1 + ? '1 metric' + : `${portfolio.targets.metrics.length} metrics`} + </Td> + <Td dataLabel="Repeats"> + {portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`} + </Td> + <Td isActionCell> + <ActionsColumn items={actions(portfolio)} /> + </Td> + </Tr> + ))} + {portfolios.length === 0 && ( + <Tr> + <Td colSpan={4}> + <Bullseye> + <TableEmptyState + status={status} + loadingTitle="Loading portfolios" + emptyTitle="No portfolios" + emptyText="You have not created any portfolio for this project yet. Click the New Portfolio button to create one." + /> + </Bullseye> + </Td> + </Tr> + )} + </Tbody> + </TableComposable> ) } diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js index ced5304a..62deace0 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js @@ -20,66 +20,67 @@ * SOFTWARE. */ +import { Bullseye } from '@patternfly/react-core' import PropTypes from 'prop-types' import Link from 'next/link' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { Tr, Th, Thead, Td, ActionsColumn, Tbody, TableComposable } from '@patternfly/react-table' import React from 'react' import TableEmptyState from '../util/TableEmptyState' import { parseAndFormatDateTime } from '../../util/date-time' import { useTopologies, useDeleteTopology } from '../../data/topology' -const TopologyTable = ({ projectId }) => { +function TopologyTable({ projectId }) { const { status, data: topologies = [] } = useTopologies(projectId) const { mutate: deleteTopology } = useDeleteTopology() - const columns = ['Name', 'Rooms', 'Last Edited'] - const rows = - topologies.length > 0 - ? topologies.map((topology) => [ - { - title: <Link href={`/projects/${projectId}/topologies/${topology.number}`}>{topology.name}</Link>, - }, - topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`, - parseAndFormatDateTime(topology.updatedAt), - ]) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 3 }, - title: ( - <TableEmptyState - status={status} - loadingTitle="Loading topologies" - emptyTitle="No topologies" - emptyText="You have not created any topology for this project yet. Click the New Topology button to create one." - /> - ), - }, - ], - }, - ] - - const actionResolver = (_, { rowIndex }) => [ + const actions = ({ number }) => [ { title: 'Delete Topology', - onClick: (_, rowId) => deleteTopology({ projectId, number: topologies[rowId].number }), - isDisabled: rowIndex === 0, + onClick: () => deleteTopology({ projectId, number }), + isDisabled: number === 0, }, ] return ( - <Table - aria-label="Topology List" - variant="compact" - cells={columns} - rows={rows} - actionResolver={topologies.length > 0 ? actionResolver : () => []} - > - <TableHeader /> - <TableBody /> - </Table> + <TableComposable aria-label="Topology List" variant="compact"> + <Thead> + <Tr> + <Th>Name</Th> + <Th>Rooms</Th> + <Th>Last Edited</Th> + </Tr> + </Thead> + <Tbody> + {topologies.map((topology) => ( + <Tr key={topology.id}> + <Td dataLabel="Name"> + <Link href={`/projects/${projectId}/topologies/${topology.number}`}>{topology.name}</Link> + </Td> + <Td dataLabel="Rooms"> + {topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`} + </Td> + <Td dataLabel="Last Edited">{parseAndFormatDateTime(topology.updatedAt)}</Td> + <Td isActionCell> + <ActionsColumn items={actions(topology)} /> + </Td> + </Tr> + ))} + {topologies.length === 0 && ( + <Tr> + <Td colSpan={3}> + <Bullseye> + <TableEmptyState + status={status} + loadingTitle="Loading topologies" + emptyTitle="No topologies" + emptyText="You have not created any topology for this project yet. Click the New Topology button to create one." + /> + </Bullseye> + </Td> + </Tr> + )} + </Tbody> + </TableComposable> ) } diff --git a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js index 49e5f095..7f7b4171 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js @@ -1,63 +1,67 @@ -import { Button } from '@patternfly/react-core' +import { Button, Bullseye } from '@patternfly/react-core' import PropTypes from 'prop-types' import React from 'react' import { useDispatch } from 'react-redux' import { useTopology } from '../../data/topology' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import { Tr, Th, Thead, TableComposable, Td, ActionsColumn, Tbody } from '@patternfly/react-table' import { deleteRoom } from '../../redux/actions/topology/room' import TableEmptyState from '../util/TableEmptyState' function RoomTable({ projectId, topologyId, onSelect }) { const dispatch = useDispatch() const { status, data: topology } = useTopology(projectId, topologyId) - const onDelete = (room) => dispatch(deleteRoom(room.id)) - - const columns = ['Name', 'Tiles', 'Racks'] - const rows = - topology?.rooms.length > 0 - ? topology.rooms.map((room) => { - const tileCount = room.tiles.length - const rackCount = room.tiles.filter((tile) => tile.rack).length - return [ - { - title: ( - <Button variant="link" isInline onClick={() => onSelect(room)}> - {room.name} - </Button> - ), - }, - tileCount === 1 ? '1 tile' : `${tileCount} tiles`, - rackCount === 1 ? '1 rack' : `${rackCount} racks`, - ] - }) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 3 }, - title: <TableEmptyState status={status} loadingTitle="Loading Rooms" />, - }, - ], - }, - ] - - const actions = - topology?.rooms.length > 0 - ? [ - { - title: 'Delete room', - onClick: (_, rowId) => onDelete(topology.rooms[rowId]), - }, - ] - : [] + const actions = (room) => [ + { + title: 'Delete room', + onClick: () => onDelete(room), + }, + ] return ( - <Table aria-label="Room list" variant="compact" cells={columns} rows={rows} actions={actions}> - <TableHeader /> - <TableBody /> - </Table> + <TableComposable aria-label="Room list" variant="compact"> + <Thead> + <Tr> + <Th>Name</Th> + <Th>Tiles</Th> + <Th>Racks</Th> + </Tr> + </Thead> + <Tbody> + {topology?.rooms.map((room) => { + const tileCount = room.tiles.length + const rackCount = room.tiles.filter((tile) => tile.rack).length + return ( + <Tr key={room.id}> + <Td dataLabel="Name"> + <Button variant="link" isInline onClick={() => onSelect(room)}> + {room.name} + </Button> + </Td> + <Td dataLabel="Tiles">{tileCount === 1 ? '1 tile' : `${tileCount} tiles`}</Td> + <Td dataLabel="Racks">{rackCount === 1 ? '1 rack' : `${rackCount} racks`}</Td> + <Td isActionCell> + <ActionsColumn items={actions(room)} /> + </Td> + </Tr> + ) + })} + {topology?.rooms.length === 0 && ( + <Tr> + <Td colSpan={4}> + <Bullseye> + <TableEmptyState + status={status} + loadingTitle="Loading Rooms" + emptyTitle="No rooms" + emptyText="There are currently no rooms in this topology. Open the Floor Plan to create a room" + /> + </Bullseye> + </Td> + </Tr> + )} + </Tbody> + </TableComposable> ) } |
