diff options
Diffstat (limited to 'opendc-web/opendc-web-ui/src')
14 files changed, 145 insertions, 117 deletions
diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js index 75751658..3411b96e 100644 --- a/opendc-web/opendc-web-ui/src/api/index.js +++ b/opendc-web/opendc-web-ui/src/api/index.js @@ -49,7 +49,7 @@ export async function request(auth, path, method = 'GET', body) { const json = await response.json() if (!response.ok) { - throw response.message + throw json.message } return json 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 62deace0..1c2c4f04 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js +++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js @@ -20,18 +20,22 @@ * SOFTWARE. */ -import { Bullseye } from '@patternfly/react-core' +import { Bullseye, AlertGroup, Alert, AlertVariant, AlertActionCloseButton } from '@patternfly/react-core' import PropTypes from 'prop-types' import Link from 'next/link' import { Tr, Th, Thead, Td, ActionsColumn, Tbody, TableComposable } from '@patternfly/react-table' -import React from 'react' +import React, { useState } from 'react' import TableEmptyState from '../util/TableEmptyState' import { parseAndFormatDateTime } from '../../util/date-time' import { useTopologies, useDeleteTopology } from '../../data/topology' function TopologyTable({ projectId }) { + const [error, setError] = useState('') + const { status, data: topologies = [] } = useTopologies(projectId) - const { mutate: deleteTopology } = useDeleteTopology() + const { mutate: deleteTopology } = useDeleteTopology({ + onError: (error) => setError(error), + }) const actions = ({ number }) => [ { @@ -42,45 +46,65 @@ function TopologyTable({ projectId }) { ] return ( - <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 && ( + <> + <AlertGroup isToast> + {error && ( + <Alert + isLiveRegion + variant={AlertVariant.danger} + title={error} + actionClose={ + <AlertActionCloseButton + title={error} + variantLabel="danger alert" + onClose={() => setError(null)} + /> + } + /> + )} + </AlertGroup> + <TableComposable aria-label="Topology List" variant="compact"> + <Thead> <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> + <Th>Name</Th> + <Th>Rooms</Th> + <Th>Last Edited</Th> </Tr> - )} - </Tbody> - </TableComposable> + </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/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js index 1b9fff72..e2b626ec 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js @@ -1,9 +1,8 @@ -import React, { useRef, useState, useContext } from 'react' +import React, { useRef, useState } from 'react' import PropTypes from 'prop-types' import { useHotkeys } from 'react-hotkeys-hook' import { Stage } from 'react-konva' import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' -import { ReactReduxContext } from 'react-redux' import useResizeObserver from 'use-resize-observer' import { mapContainer } from './MapStage.module.css' import MapLayer from './layers/MapLayer' @@ -13,7 +12,6 @@ import ScaleIndicator from './controls/ScaleIndicator' import Toolbar from './controls/Toolbar' function MapStage({ hotkeysRef }) { - const reduxContext = useContext(ReactReduxContext) const stageRef = useRef(null) const { width = 500, height = 500 } = useResizeObserver({ ref: stageRef.current?.attrs?.container }) const [[x, y], setPos] = useState([0, 0]) @@ -68,11 +66,9 @@ function MapStage({ hotkeysRef }) { x={x} y={y} > - <ReactReduxContext.Provider value={reduxContext}> - <MapLayer /> - <RoomHoverLayer /> - <ObjectHoverLayer /> - </ReactReduxContext.Provider> + <MapLayer /> + <RoomHoverLayer /> + <ObjectHoverLayer /> </Stage> <ScaleIndicator scale={scale} /> <Toolbar onZoom={onZoomButton} onExport={onExport} /> diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js index 2b1060c0..d7e0c56a 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js @@ -15,11 +15,11 @@ function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { const stage = layer.getStage() - // Transform used to convert mouse coordinates to world coordinates - const transform = stage.getAbsoluteTransform().copy() - transform.invert() - stage.on('mousemove.hover', () => { + // Transform used to convert mouse coordinates to world coordinates + const transform = stage.getAbsoluteTransform().copy() + transform.invert() + const { x, y } = transform.point(stage.getPointerPosition()) setPos([x, y]) }) @@ -38,7 +38,7 @@ function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { const y = gridY * TILE_SIZE_IN_PIXELS return ( - <Layer opacity={0.6} ref={layerRef}> + <Layer opacity={0.2} ref={layerRef}> <HoverTile x={x} y={y} isValid={valid} onClick={() => (valid ? onClick(gridX, gridY) : undefined)} /> {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} </Layer> diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js index 1f00de36..5e741a3b 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js @@ -27,7 +27,7 @@ import { findTileWithPosition } from '../../../../util/tile-calculations' import HoverLayerComponent from './HoverLayerComponent' import TilePlusIcon from '../elements/TilePlusIcon' -function ObjectHoverLayer() { +export default function ObjectHoverLayer() { const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) const isValid = useSelector((state) => (x, y) => { if (state.interactionLevel.mode !== 'ROOM') { @@ -49,5 +49,3 @@ function ObjectHoverLayer() { </HoverLayerComponent> ) } - -export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js index 727f4e25..b9cfcaf4 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js @@ -30,7 +30,7 @@ import { } from '../../../../util/tile-calculations' import HoverLayerComponent from './HoverLayerComponent' -function RoomHoverLayer() { +export default function RoomHoverLayer() { const dispatch = useDispatch() const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') @@ -57,5 +57,3 @@ function RoomHoverLayer() { return <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid} /> } - -export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js index 6f89e10b..8a4c33dc 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js @@ -15,7 +15,13 @@ import { useSelector } from 'react-redux' function MachineSidebar({ tileId, position }) { const machine = useSelector(({ topology }) => { const rack = topology.racks[topology.tiles[tileId].rack] - return topology.machines[rack.machines[position - 1]] + + for (const machineId of rack.machines) { + const machine = topology.machines[machineId] + if (machine.position === position) { + return machine + } + } }) const machineId = machine.id return ( diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js index 4507b409..18cba23a 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js @@ -27,7 +27,7 @@ function UnitAddComponent({ units, onAdd }) { </SelectOption> ))} </Select> - <Button icon={<PlusIcon />} variant="control" onClick={() => onAdd(selected)}> + <Button icon={<PlusIcon />} variant="control" onClick={() => onAdd(selected)} isDisabled={!selected}> Add </Button> </InputGroup> diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js index de7a2140..02c97730 100644 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js @@ -17,49 +17,56 @@ import { Machine } from '../../../../shapes' function MachineListComponent({ machines = [], onSelect, onAdd }) { return ( <DataList aria-label="Rack Units"> - {machines.map((machine, index) => - machine ? ( - <DataListItem key={index} onClick={() => onSelect(index + 1)}> - <DataListItemRow> - <DataListItemCells - dataListCells={[ - <DataListCell isIcon key="icon"> - <Badge isRead>{machines.length - index}U</Badge> - </DataListCell>, - <DataListCell key="primary content"> - <MachineComponent onClick={() => onSelect(index + 1)} machine={machine} /> - </DataListCell>, - ]} - /> - <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> - <Button isSmall variant="plain" className="pf-u-p-0"> - <AngleRightIcon /> - </Button> - </DataListAction> - </DataListItemRow> - </DataListItem> - ) : ( - <DataListItem key={index}> - <DataListItemRow> - <DataListItemCells - dataListCells={[ - <DataListCell isIcon key="icon"> - <Badge isRead>{machines.length - index}U</Badge> - </DataListCell>, - <DataListCell key="add" className="text-secondary"> - Empty Slot - </DataListCell>, - ]} - /> - <DataListAction id="add" aria-label="Add Machine" aria-labelledby="add"> - <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onAdd(index + 1)}> - <PlusIcon /> - </Button> - </DataListAction> - </DataListItemRow> - </DataListItem> + {machines + .map((machine, index) => + machine ? ( + <DataListItem key={index} onClick={() => onSelect(index + 1)}> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell isIcon key="icon"> + <Badge isRead>{index + 1}U</Badge> + </DataListCell>, + <DataListCell key="primary content"> + <MachineComponent onClick={() => onSelect(index + 1)} machine={machine} /> + </DataListCell>, + ]} + /> + <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> + <Button isSmall variant="plain" className="pf-u-p-0"> + <AngleRightIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ) : ( + <DataListItem key={index}> + <DataListItemRow> + <DataListItemCells + dataListCells={[ + <DataListCell isIcon key="icon"> + <Badge isRead>{index + 1}U</Badge> + </DataListCell>, + <DataListCell key="add" className="text-secondary"> + Empty Slot + </DataListCell>, + ]} + /> + <DataListAction id="add" aria-label="Add Machine" aria-labelledby="add"> + <Button + isSmall + variant="plain" + className="pf-u-p-0" + onClick={() => onAdd(index + 1)} + > + <PlusIcon /> + </Button> + </DataListAction> + </DataListItemRow> + </DataListItem> + ) ) - )} + .reverse()} </DataList> ) } diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js index ac6cabe5..d5e624d5 100644 --- a/opendc-web/opendc-web-ui/src/data/topology.js +++ b/opendc-web/opendc-web-ui/src/data/topology.js @@ -83,6 +83,6 @@ export function useNewTopology() { /** * Create a mutation for deleting a topology. */ -export function useDeleteTopology() { - return useMutation('deleteTopology') +export function useDeleteTopology(options = {}) { + return useMutation('deleteTopology', options) } diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js index 350c1d92..14cc126c 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js @@ -53,10 +53,10 @@ export function addRackToTile(positionX, positionY) { if (tile !== null) { dispatch({ type: ADD_RACK_TO_TILE, + tileId: tile.id, rack: { id: uuid(), name: 'Rack', - tileId: tile.id, capacity: DEFAULT_RACK_SLOT_CAPACITY, powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, machines: [], diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js index b1c7d29e..2c849387 100644 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js @@ -35,7 +35,7 @@ function objects(state = {}, action) { storages: STORAGE_UNITS, machines: machine(state.machines, action, state), racks: rack(state.racks, action, state), - tiles: tile(state.tiles, action, state), + tiles: tile(state.tiles, action), rooms: room(state.rooms, action, state), root: topology(state.root, action, state), } diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js index 8e5ecd6e..24c0e20c 100644 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js @@ -26,7 +26,7 @@ import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' import { DELETE_RACK } from '../../actions/topology/rack' import { ADD_RACK_TO_TILE } from '../../actions/topology/room' -function tile(state = {}, action, { racks }) { +function tile(state = {}, action) { switch (action.type) { case STORE_TOPOLOGY: return action.entities.tiles || {} @@ -42,14 +42,13 @@ function tile(state = {}, action, { racks }) { }) case ADD_RACK_TO_TILE: return produce(state, (draft) => { - const { rack } = action - draft[rack.tileId].rack = rack.id + const { rack, tileId } = action + draft[tileId].rack = rack.id }) case DELETE_RACK: return produce(state, (draft) => { - const { rackId } = action - const rack = racks[rackId] - draft[rack.tileId].rack = undefined + const { tileId } = action + draft[tileId].rack = undefined }) default: return state diff --git a/opendc-web/opendc-web-ui/src/util/effect-ref.js b/opendc-web/opendc-web-ui/src/util/effect-ref.js index cda0324b..78528585 100644 --- a/opendc-web/opendc-web-ui/src/util/effect-ref.js +++ b/opendc-web/opendc-web-ui/src/util/effect-ref.js @@ -26,9 +26,9 @@ const noop = () => {} /** * A hook that will invoke the specified callback when the reference returned by this function is initialized. - * The callback can return an optional clean up function. + * The callback can return an optional clean-up function. */ -export function useEffectRef(callback) { +export function useEffectRef(callback, deps = []) { const disposeRef = useRef(noop) return useCallback((element) => { disposeRef.current() @@ -37,5 +37,5 @@ export function useEffectRef(callback) { if (element) { disposeRef.current = callback(element) || noop } - }, []) // eslint-disable-line react-hooks/exhaustive-deps + }, deps) // eslint-disable-line react-hooks/exhaustive-deps } |
