From e5caf6c6122684e441d1d73e2e0507fdd36c67e0 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 16 Jul 2021 14:20:34 +0200 Subject: feat(ui): Support panning of the datacenter topology This change enables support for panning the visualized datacenter topology by using the mouse or holding shortcuts. Previously, this could only be done through repeated key presses. --- opendc-web/opendc-web-ui/src/components/app/map/MapStage.js | 2 +- .../src/pages/projects/[project]/topologies/[topology].js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'opendc-web/opendc-web-ui/src') diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js index 73accf3f..684ddf28 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js @@ -52,7 +52,7 @@ function MapStage() { return ( - + diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js index 5873ed11..786bed07 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -24,7 +24,7 @@ import { useRouter } from 'next/router' import { useProject } from '../../../../data/project' import { useDispatch, useSelector } from 'react-redux' import React, { useEffect, useState } from 'react' -import { HotKeys } from 'react-hotkeys' +import {configure, HotKeys} from 'react-hotkeys' import { KeymapConfiguration } from '../../../../hotkeys' import Head from 'next/head' import MapStage from '../../../../components/app/map/MapStage' @@ -70,6 +70,11 @@ function Topology() { const [isExpanded, setExpanded] = useState(true) const panelContent = setExpanded(false)} /> + // Make sure that holding down a key will generate repeated events + configure({ + ignoreRepeatedEventsWhenKeyHeldDown: false + }) + return ( -- cgit v1.2.3 From f2aeecccc096728d3df955b71e711c8d9c429427 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 16 Jul 2021 17:37:01 +0200 Subject: refactor(ui): Isolate world coordinate space This change updates the topology view in the OpenDC frontend to isolate the world coordinate space. This means that zooming and panning should not affect the coordinates in world space (but only in camera space). In turn, this allows us to remove the dependency on Redux for the camera controls. --- .../src/components/app/map/MapConstants.js | 3 - .../src/components/app/map/MapStage.js | 91 +++++++++++++--------- .../components/app/map/RackEnergyFillContainer.js | 13 ++-- .../components/app/map/RackSpaceFillContainer.js | 12 +-- .../src/components/app/map/elements/HoverTile.js | 12 +-- .../src/components/app/map/elements/RackFillBar.js | 2 +- .../components/app/map/elements/TilePlusIcon.js | 26 +++---- .../app/map/layers/HoverLayerComponent.js | 67 +++++++--------- .../src/components/app/map/layers/MapLayer.js | 20 +++-- .../components/app/map/layers/MapLayerComponent.js | 26 ------- .../components/app/map/layers/ObjectHoverLayer.js | 35 ++++----- .../app/map/layers/ObjectHoverLayerComponent.js | 11 --- .../components/app/map/layers/RoomHoverLayer.js | 50 ++++++------ .../app/map/layers/RoomHoverLayerComponent.js | 6 -- opendc-web/opendc-web-ui/src/data/map.js | 37 --------- .../projects/[project]/topologies/[topology].js | 12 +-- opendc-web/opendc-web-ui/src/redux/actions/map.js | 83 -------------------- opendc-web/opendc-web-ui/src/redux/index.js | 3 +- .../src/redux/middleware/viewport-adjustment.js | 73 ----------------- .../opendc-web-ui/src/redux/reducers/index.js | 2 - opendc-web/opendc-web-ui/src/redux/reducers/map.js | 35 --------- opendc-web/opendc-web-ui/src/util/effect-ref.js | 41 ++++++++++ 22 files changed, 208 insertions(+), 452 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/data/map.js delete mode 100644 opendc-web/opendc-web-ui/src/redux/actions/map.js delete mode 100644 opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js delete mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/map.js create mode 100644 opendc-web/opendc-web-ui/src/util/effect-ref.js (limited to 'opendc-web/opendc-web-ui/src') diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js index 45799f70..4c3b2757 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js @@ -12,9 +12,6 @@ export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 -export const SIDEBAR_WIDTH = 350 -export const VIEWPORT_PADDING = 50 - export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 export const RACK_FILL_ICON_OPACITY = 0.8 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js index 684ddf28..5d19b3ad 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js @@ -1,64 +1,81 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, { useRef, useState } from 'react' import { HotKeys } from 'react-hotkeys' import { Stage } from 'react-konva' -import { MAP_MOVE_PIXELS_PER_EVENT } from './MapConstants' -import { Provider, useDispatch, useStore } from 'react-redux' +import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' +import { Provider, useStore } from 'react-redux' import useResizeObserver from 'use-resize-observer' import { mapContainer } from './MapStage.module.scss' -import { useMapPosition } from '../../../data/map' -import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../redux/actions/map' import MapLayer from './layers/MapLayer' import RoomHoverLayer from './layers/RoomHoverLayer' import ObjectHoverLayer from './layers/ObjectHoverLayer' +import ScaleIndicator from './controls/ScaleIndicator' +import Toolbar from './controls/Toolbar' function MapStage() { const store = useStore() - const dispatch = useDispatch() + const { ref, width = 100, height = 100 } = useResizeObserver() + const stageRef = useRef(null) + const [[x, y], setPos] = useState([0, 0]) + const [scale, setScale] = useState(1) + + const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) + const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) + + const onZoom = (e) => { + e.evt.preventDefault() + + const stage = stageRef.current.getStage() + const oldScale = scale + + const pointer = stage.getPointerPosition() + const mousePointTo = { + x: (pointer.x - x) / oldScale, + y: (pointer.y - y) / oldScale, + } + + const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) + + setScale(newScale) + setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) + } + const onZoomButton = (zoomIn) => + setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) + const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) + const onExport = () => { + const download = document.createElement('a') + download.href = stageRef.current.getStage().toDataURL() + download.download = 'opendc-canvas-export-' + Date.now() + '.png' + download.click() + } - const stage = useRef(null) - const [pos, setPos] = useState([0, 0]) - const [x, y] = pos const handlers = { MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), } - const mapPosition = useMapPosition() - const { ref, width = 100, height = 100 } = useResizeObserver() - - const moveWithDelta = (deltaX, deltaY) => - dispatch(setMapPositionWithBoundsCheck(mapPosition.x + deltaX, mapPosition.y + deltaY)) - const updateMousePosition = () => { - if (!stage.current) { - return - } - - const mousePos = stage.current.getStage().getPointerPosition() - setPos([mousePos.x, mousePos.y]) - } - const updateScale = ({ evt }) => dispatch(zoomInOnPosition(evt.deltaY < 0, x, y)) - - useEffect(() => { - window['exportCanvasToImage'] = () => { - const download = document.createElement('a') - download.href = stage.current.getStage().toDataURL() - download.download = 'opendc-canvas-export-' + Date.now() + '.png' - download.click() - } - }, [stage]) - - useEffect(() => dispatch(setMapDimensions(width, height)), [width, height]) // eslint-disable-line react-hooks/exhaustive-deps return ( - + - - + + + + ) } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js index 838aea5a..dbc26f14 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js @@ -3,10 +3,10 @@ import PropTypes from 'prop-types' import { useSelector } from 'react-redux' import RackFillBar from '../../../components/app/map/elements/RackFillBar' -const RackSpaceFillContainer = (props) => { - const state = useSelector((state) => { +function RackSpaceFillContainer({ tileId, ...props }) { + const fillFraction = useSelector((state) => { let energyConsumptionTotal = 0 - const rack = state.objects.rack[state.objects.tile[props.tileId].rack] + const rack = state.objects.rack[state.objects.tile[tileId].rack] const machineIds = rack.machines machineIds.forEach((machineId) => { if (machineId !== null) { @@ -22,12 +22,9 @@ const RackSpaceFillContainer = (props) => { } }) - return { - type: 'energy', - fillFraction: Math.min(1, energyConsumptionTotal / rack.powerCapacityW), - } + return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) }) - return + return } RackSpaceFillContainer.propTypes = { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js index 6791120e..7ca5c930 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js @@ -25,15 +25,9 @@ import PropTypes from 'prop-types' import { useSelector } from 'react-redux' import RackFillBar from '../../../components/app/map/elements/RackFillBar' -const RackSpaceFillContainer = (props) => { - const state = useSelector((state) => { - const machineIds = state.objects.rack[state.objects.tile[props.tileId].rack].machines - return { - type: 'space', - fillFraction: machineIds.filter((id) => id !== null).length / machineIds.length, - } - }) - return +function RackSpaceFillContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + return } RackSpaceFillContainer.propTypes = { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js index 11bba0e1..0369bb79 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js @@ -4,10 +4,10 @@ import { Rect } from 'react-konva' import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' import { TILE_SIZE_IN_PIXELS } from '../MapConstants' -const HoverTile = ({ pixelX, pixelY, isValid, scale, onClick }) => ( +const HoverTile = ({ x, y, isValid, scale = 1, onClick }) => ( ( ) HoverTile.propTypes = { - pixelX: PropTypes.number.isRequired, - pixelY: PropTypes.number.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, isValid: PropTypes.bool.isRequired, - scale: PropTypes.number.isRequired, + scale: PropTypes.number, onClick: PropTypes.func.isRequired, } diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js index 8c573a6f..aa284944 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js @@ -16,7 +16,7 @@ import { } from '../MapConstants' import ImageComponent from './ImageComponent' -const RackFillBar = ({ positionX, positionY, type, fillFraction }) => { +function RackFillBar({ positionX, positionY, type, fillFraction }) { const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 const x = positionX * TILE_SIZE_IN_PIXELS + diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js index be3a00a8..186c2b3a 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js @@ -4,19 +4,19 @@ import { Group, Line } from 'react-konva' import { TILE_PLUS_COLOR } from '../../../../util/colors' import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' -const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => { +function TilePlusIcon({ x, y, scale = 1 }) { const linePoints = [ [ - pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelY + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelX + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelY + TILE_SIZE_IN_PIXELS * mapScale - TILE_PLUS_MARGIN_IN_PIXELS * mapScale, + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_PLUS_MARGIN_IN_PIXELS * scale, + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, ], [ - pixelX + TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, - pixelX + TILE_SIZE_IN_PIXELS * mapScale - TILE_PLUS_MARGIN_IN_PIXELS * mapScale, - pixelY + 0.5 * TILE_SIZE_IN_PIXELS * mapScale, + x + TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, ], ] return ( @@ -27,7 +27,7 @@ const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => { points={points} lineCap="round" stroke={TILE_PLUS_COLOR} - strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * mapScale} + strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * scale} listening={false} /> ))} @@ -36,9 +36,9 @@ const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => { } TilePlusIcon.propTypes = { - pixelX: PropTypes.number, - pixelY: PropTypes.number, - mapScale: PropTypes.number, + x: PropTypes.number, + y: PropTypes.number, + scale: PropTypes.number, } export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js index a88a8b34..2b1060c0 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js @@ -1,61 +1,52 @@ import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import { Layer } from 'react-konva' +import React, { useMemo, useState } from 'react' +import { Layer } from 'react-konva/lib/ReactKonva' import HoverTile from '../elements/HoverTile' import { TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { useEffectRef } from '../../../../util/effect-ref' -function HoverLayerComponent({ mouseX, mouseY, mapPosition, mapScale, isEnabled, isValid, onClick, children }) { - const [pos, setPos] = useState([-1, -1]) - const [x, y] = pos - const [valid, setValid] = useState(false) +function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { + const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) - useEffect(() => { - if (!isEnabled()) { + const layerRef = useEffectRef((layer) => { + if (!layer) { return } - const positionX = Math.floor((mouseX - mapPosition.x) / (mapScale * TILE_SIZE_IN_PIXELS)) - const positionY = Math.floor((mouseY - mapPosition.y) / (mapScale * TILE_SIZE_IN_PIXELS)) + const stage = layer.getStage() - if (positionX !== x || positionY !== y) { - setPos([positionX, positionY]) - setValid(isValid(positionX, positionY)) - } - }, [isEnabled, isValid, x, y, mouseX, mouseY, mapPosition, mapScale]) + // Transform used to convert mouse coordinates to world coordinates + const transform = stage.getAbsoluteTransform().copy() + transform.invert() + + stage.on('mousemove.hover', () => { + const { x, y } = transform.point(stage.getPointerPosition()) + setPos([x, y]) + }) + return () => stage.off('mousemove.hover') + }) + + const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) + const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) + const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) - if (!isEnabled()) { + if (!isEnabled) { return } - const pixelX = mapScale * x * TILE_SIZE_IN_PIXELS + mapPosition.x - const pixelY = mapScale * y * TILE_SIZE_IN_PIXELS + mapPosition.y + const x = gridX * TILE_SIZE_IN_PIXELS + const y = gridY * TILE_SIZE_IN_PIXELS return ( - - (valid ? onClick(x, y) : undefined)} - /> - {children - ? React.cloneElement(children, { - pixelX, - pixelY, - scale: mapScale, - }) - : undefined} + + (valid ? onClick(gridX, gridY) : undefined)} /> + {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} ) } HoverLayerComponent.propTypes = { - mouseX: PropTypes.number.isRequired, - mouseY: PropTypes.number.isRequired, - mapPosition: PropTypes.object.isRequired, - mapScale: PropTypes.number.isRequired, - isEnabled: PropTypes.func.isRequired, + isEnabled: PropTypes.bool.isRequired, isValid: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired, children: PropTypes.node, diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js index badb9f68..c902532b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js @@ -21,13 +21,21 @@ */ import React from 'react' -import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent' -import { useMapPosition, useMapScale } from '../../../../data/map' +import { Group, Layer } from 'react-konva' +import Backdrop from '../elements/Backdrop' +import TopologyContainer from '../TopologyContainer' +import GridGroup from '../groups/GridGroup' -const MapLayer = (props) => { - const position = useMapPosition() - const scale = useMapScale() - return +function MapLayer() { + return ( + + + + + + + + ) } export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js deleted file mode 100644 index efe5b4e5..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Layer } from 'react-konva' -import Backdrop from '../elements/Backdrop' -import GridGroup from '../groups/GridGroup' -import TopologyContainer from '../TopologyContainer' - -const MapLayerComponent = ({ mapPosition, mapScale }) => ( - - - - - - - -) - -MapLayerComponent.propTypes = { - mapPosition: PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, - }), - mapScale: PropTypes.number, -} - -export default MapLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js index 9a087bd5..47d9c992 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js @@ -23,32 +23,31 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { addRackToTile } from '../../../../redux/actions/topology/room' -import ObjectHoverLayerComponent from '../../../../components/app/map/layers/ObjectHoverLayerComponent' import { findTileWithPosition } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' +import TilePlusIcon from '../elements/TilePlusIcon' -const ObjectHoverLayer = (props) => { - const state = useSelector((state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - isEnabled: () => state.construction.inRackConstructionMode, - isValid: (x, y) => { - if (state.interactionLevel.mode !== 'ROOM') { - return false - } +function ObjectHoverLayer() { + const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) + const isValid = useSelector((state) => (x, y) => { + if (state.interactionLevel.mode !== 'ROOM') { + return false + } - const currentRoom = state.objects.room[state.interactionLevel.roomId] - const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId]) - const tile = findTileWithPosition(tiles, x, y) + const currentRoom = state.objects.room[state.interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId]) + const tile = findTileWithPosition(tiles, x, y) - return !(tile === null || tile.rack) - }, - } + return !(tile === null || tile.rack) }) const dispatch = useDispatch() const onClick = (x, y) => dispatch(addRackToTile(x, y)) - return + return ( + + + + ) } export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js deleted file mode 100644 index 661fc255..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react' -import TilePlusIcon from '../elements/TilePlusIcon' -import HoverLayerComponent from './HoverLayerComponent' - -const ObjectHoverLayerComponent = (props) => ( - - - -) - -export default ObjectHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js index 87240813..59f83b2b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js @@ -23,45 +23,39 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' -import RoomHoverLayerComponent from '../../../../components/app/map/layers/RoomHoverLayerComponent' import { deriveValidNextTilePositions, findPositionInPositions, findPositionInRooms, } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' -const RoomHoverLayer = (props) => { +function RoomHoverLayer() { const dispatch = useDispatch() const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) + const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isValid = useSelector((state) => (x, y) => { + const newRoom = { ...state.objects.room[state.construction.currentRoomInConstruction] } + const oldRooms = Object.keys(state.objects.room) + .map((id) => ({ ...state.objects.room[id] })) + .filter( + (room) => + state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 && + room._id !== state.construction.currentRoomInConstruction + ) - const state = useSelector((state) => { - return { - mapPosition: state.map.position, - mapScale: state.map.scale, - isEnabled: () => state.construction.currentRoomInConstruction !== '-1', - isValid: (x, y) => { - const newRoom = Object.assign({}, state.objects.room[state.construction.currentRoomInConstruction]) - const oldRooms = Object.keys(state.objects.room) - .map((id) => Object.assign({}, state.objects.room[id])) - .filter( - (room) => - state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 && - room._id !== state.construction.currentRoomInConstruction - ) - - ;[...oldRooms, newRoom].forEach((room) => { - room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]) - }) - if (newRoom.tiles.length === 0) { - return findPositionInRooms(oldRooms, x, y) === -1 - } - - const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) - return findPositionInPositions(validNextPositions, x, y) !== -1 - }, + ;[...oldRooms, newRoom].forEach((room) => { + room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]) + }) + if (newRoom.tiles.length === 0) { + return findPositionInRooms(oldRooms, x, y) === -1 } + + const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) + return findPositionInPositions(validNextPositions, x, y) !== -1 }) - return + + return } export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js deleted file mode 100644 index 887e2891..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react' -import HoverLayerComponent from './HoverLayerComponent' - -const RoomHoverLayerComponent = (props) => - -export default RoomHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/data/map.js b/opendc-web/opendc-web-ui/src/data/map.js deleted file mode 100644 index 348a6664..00000000 --- a/opendc-web/opendc-web-ui/src/data/map.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useSelector } from 'react-redux' - -/** - * Return the map scale. - */ -export function useMapScale() { - return useSelector((state) => state.map.scale) -} - -/** - * Return the map position. - */ -export function useMapPosition() { - return useSelector((state) => state.map.position) -} diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js index 786bed07..d79e8e7a 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -24,7 +24,7 @@ import { useRouter } from 'next/router' import { useProject } from '../../../../data/project' import { useDispatch, useSelector } from 'react-redux' import React, { useEffect, useState } from 'react' -import {configure, HotKeys} from 'react-hotkeys' +import { configure, HotKeys } from 'react-hotkeys' import { KeymapConfiguration } from '../../../../hotkeys' import Head from 'next/head' import MapStage from '../../../../components/app/map/MapStage' @@ -40,9 +40,7 @@ import { Spinner, Title, } from '@patternfly/react-core' -import { zoomInOnCenter } from '../../../../redux/actions/map' import Toolbar from '../../../../components/app/map/controls/Toolbar' -import { useMapScale } from '../../../../data/map' import ScaleIndicator from '../../../../components/app/map/controls/ScaleIndicator' import TopologySidebar from '../../../../components/app/sidebars/topology/TopologySidebar' import Collapse from '../../../../components/app/map/controls/Collapse' @@ -64,7 +62,6 @@ function Topology() { }, [projectId, topologyId, dispatch]) const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1') - const scale = useMapScale() const interactionLevel = useSelector((state) => state.interactionLevel) const [isExpanded, setExpanded] = useState(true) @@ -72,7 +69,7 @@ function Topology() { // Make sure that holding down a key will generate repeated events configure({ - ignoreRepeatedEventsWhenKeyHeldDown: false + ignoreRepeatedEventsWhenKeyHeldDown: false, }) return ( @@ -95,11 +92,6 @@ function Topology() { - - dispatch(zoomInOnCenter(zoomIn))} - onExport={() => window['exportCanvasToImage']()} - /> setExpanded(true)} /> diff --git a/opendc-web/opendc-web-ui/src/redux/actions/map.js b/opendc-web/opendc-web-ui/src/redux/actions/map.js deleted file mode 100644 index aa14cacd..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/map.js +++ /dev/null @@ -1,83 +0,0 @@ -import { - MAP_MAX_SCALE, - MAP_MIN_SCALE, - MAP_SCALE_PER_EVENT, - MAP_SIZE_IN_PIXELS, -} from '../../components/app/map/MapConstants' - -export const SET_MAP_POSITION = 'SET_MAP_POSITION' -export const SET_MAP_DIMENSIONS = 'SET_MAP_DIMENSIONS' -export const SET_MAP_SCALE = 'SET_MAP_SCALE' - -export function setMapPosition(x, y) { - return { - type: SET_MAP_POSITION, - x, - y, - } -} - -export function setMapDimensions(width, height) { - return { - type: SET_MAP_DIMENSIONS, - width, - height, - } -} - -export function setMapScale(scale) { - return { - type: SET_MAP_SCALE, - scale, - } -} - -export function zoomInOnCenter(zoomIn) { - return (dispatch, getState) => { - const state = getState() - - dispatch(zoomInOnPosition(zoomIn, state.map.dimensions.width / 2, state.map.dimensions.height / 2)) - } -} - -export function zoomInOnPosition(zoomIn, x, y) { - return (dispatch, getState) => { - const state = getState() - - const centerPoint = { - x: x / state.map.scale - state.map.position.x / state.map.scale, - y: y / state.map.scale - state.map.position.y / state.map.scale, - } - const newScale = zoomIn ? state.map.scale * MAP_SCALE_PER_EVENT : state.map.scale / MAP_SCALE_PER_EVENT - const boundedScale = Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) - - const newX = -(centerPoint.x - x / boundedScale) * boundedScale - const newY = -(centerPoint.y - y / boundedScale) * boundedScale - - dispatch(setMapPositionWithBoundsCheck(newX, newY)) - dispatch(setMapScale(boundedScale)) - } -} - -export function setMapPositionWithBoundsCheck(x, y) { - return (dispatch, getState) => { - const state = getState() - - const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale - - const updatedX = - x > 0 - ? 0 - : x < -scaledMapSize + state.map.dimensions.width - ? -scaledMapSize + state.map.dimensions.width - : x - const updatedY = - y > 0 - ? 0 - : y < -scaledMapSize + state.map.dimensions.height - ? -scaledMapSize + state.map.dimensions.height - : y - - dispatch(setMapPosition(updatedX, updatedY)) - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js index 3c7ad55f..fa0c9d23 100644 --- a/opendc-web/opendc-web-ui/src/redux/index.js +++ b/opendc-web/opendc-web-ui/src/redux/index.js @@ -5,7 +5,6 @@ import createSagaMiddleware from 'redux-saga' import thunk from 'redux-thunk' import rootReducer from './reducers' import rootSaga from './sagas' -import { viewportAdjustmentMiddleware } from './middleware/viewport-adjustment' import { createReduxEnhancer } from '@sentry/react' let store @@ -13,7 +12,7 @@ let store function initStore(initialState, ctx) { const sagaMiddleware = createSagaMiddleware({ context: ctx }) - const middlewares = [thunk, sagaMiddleware, viewportAdjustmentMiddleware] + const middlewares = [thunk, sagaMiddleware] if (process.env.NODE_ENV !== 'production') { middlewares.push(createLogger()) diff --git a/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js deleted file mode 100644 index c2fc5004..00000000 --- a/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js +++ /dev/null @@ -1,73 +0,0 @@ -import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../actions/map' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { - MAP_MAX_SCALE, - MAP_MIN_SCALE, - SIDEBAR_WIDTH, - TILE_SIZE_IN_PIXELS, - VIEWPORT_PADDING, -} from '../../components/app/map/MapConstants' -import { calculateRoomListBounds } from '../../util/tile-calculations' - -export const viewportAdjustmentMiddleware = (store) => (next) => (action) => { - const state = store.getState() - - let topologyId = '-1' - let mapDimensions = {} - if (action.type === SET_CURRENT_TOPOLOGY && action.topologyId !== '-1') { - topologyId = action.topologyId - mapDimensions = state.map.dimensions - } else if (action.type === SET_MAP_DIMENSIONS && state.currentTopologyId !== '-1') { - topologyId = state.currentTopologyId - mapDimensions = { width: action.width, height: action.height } - } - - if (topologyId && topologyId !== '-1') { - const roomIds = state.objects.topology[topologyId].rooms - const rooms = roomIds.map((id) => Object.assign({}, state.objects.room[id])) - rooms.forEach((room) => (room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]))) - - let hasNoTiles = true - for (let i in rooms) { - if (rooms[i].tiles.length > 0) { - hasNoTiles = false - break - } - } - - if (!hasNoTiles) { - const viewportParams = calculateParametersToZoomInOnRooms(rooms, mapDimensions.width, mapDimensions.height) - store.dispatch(setMapPosition(viewportParams.newX, viewportParams.newY)) - store.dispatch(setMapScale(viewportParams.newScale)) - } - } - - next(action) -} - -function calculateParametersToZoomInOnRooms(rooms, mapWidth, mapHeight) { - const bounds = calculateRoomListBounds(rooms) - const newScale = calculateNewScale(bounds, mapWidth, mapHeight) - - // Coordinates of the center of the room, relative to the global origin of the map - const roomCenterCoordinates = { - x: bounds.center.x * TILE_SIZE_IN_PIXELS * newScale, - y: bounds.center.y * TILE_SIZE_IN_PIXELS * newScale, - } - - const newX = -roomCenterCoordinates.x + mapWidth / 2 - const newY = -roomCenterCoordinates.y + mapHeight / 2 - - return { newScale, newX, newY } -} - -function calculateNewScale(bounds, mapWidth, mapHeight) { - const width = bounds.max.x - bounds.min.x - const height = bounds.max.y - bounds.min.y - - const scaleX = (mapWidth - 2 * SIDEBAR_WIDTH) / (width * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) - const scaleY = mapHeight / (height * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) - const newScale = Math.min(scaleX, scaleY) - - return Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) -} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js index 1b17a206..2f1359d6 100644 --- a/opendc-web/opendc-web-ui/src/redux/reducers/index.js +++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js @@ -2,13 +2,11 @@ import { combineReducers } from 'redux' import { construction } from './construction-mode' import { currentTopologyId } from './current-ids' import { interactionLevel } from './interaction-level' -import { map } from './map' import { objects } from './objects' const rootReducer = combineReducers({ objects, construction, - map, currentTopologyId, interactionLevel, }) diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/map.js b/opendc-web/opendc-web-ui/src/redux/reducers/map.js deleted file mode 100644 index de712c15..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/map.js +++ /dev/null @@ -1,35 +0,0 @@ -import { combineReducers } from 'redux' -import { SET_MAP_DIMENSIONS, SET_MAP_POSITION, SET_MAP_SCALE } from '../actions/map' - -export function position(state = { x: 0, y: 0 }, action) { - switch (action.type) { - case SET_MAP_POSITION: - return { x: action.x, y: action.y } - default: - return state - } -} - -export function dimensions(state = { width: 600, height: 400 }, action) { - switch (action.type) { - case SET_MAP_DIMENSIONS: - return { width: action.width, height: action.height } - default: - return state - } -} - -export function scale(state = 1, action) { - switch (action.type) { - case SET_MAP_SCALE: - return action.scale - default: - return state - } -} - -export const map = combineReducers({ - position, - dimensions, - scale, -}) diff --git a/opendc-web/opendc-web-ui/src/util/effect-ref.js b/opendc-web/opendc-web-ui/src/util/effect-ref.js new file mode 100644 index 00000000..cda0324b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/util/effect-ref.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useCallback, useRef } from 'react' + +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. + */ +export function useEffectRef(callback) { + const disposeRef = useRef(noop) + return useCallback((element) => { + disposeRef.current() + disposeRef.current = noop + + if (element) { + disposeRef.current = callback(element) || noop + } + }, []) // eslint-disable-line react-hooks/exhaustive-deps +} -- cgit v1.2.3 From 54d07120191eb81de91a49cdebf619cfecce2666 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 19 Jul 2021 14:45:25 +0200 Subject: refactor(ui): Encode state in topology actions This change updates the OpenDC frontend to reduce its reliance of global state during the execution of actions. Actions that modify the topology now require parameters to be passed via the action constructor instead of relying on the global interactionLevel state. --- .../app/sidebars/topology/TopologySidebar.js | 18 +++--- .../sidebars/topology/building/BuildingSidebar.js | 8 +++ .../topology/building/BuildingSidebarComponent.js | 8 --- .../building/NewRoomConstructionComponent.js | 2 +- .../building/NewRoomConstructionContainer.js | 2 +- .../app/sidebars/topology/machine/DeleteMachine.js | 8 ++- .../sidebars/topology/machine/MachineSidebar.js | 49 ++++++++++++++++ .../topology/machine/MachineSidebarComponent.js | 44 -------------- .../topology/machine/MachineSidebarContainer.js | 37 ------------ .../sidebars/topology/machine/UnitAddContainer.js | 5 +- .../sidebars/topology/machine/UnitListComponent.js | 4 +- .../sidebars/topology/machine/UnitListContainer.js | 15 ++--- .../sidebars/topology/machine/UnitTabsComponent.js | 23 +++++--- .../app/sidebars/topology/rack/AddPrefab.js | 44 ++++++++++++++ .../sidebars/topology/rack/AddPrefabComponent.js | 16 ------ .../sidebars/topology/rack/AddPrefabContainer.js | 33 ----------- .../sidebars/topology/rack/DeleteRackContainer.js | 9 ++- .../app/sidebars/topology/rack/MachineComponent.js | 2 +- .../sidebars/topology/rack/MachineListContainer.js | 2 +- .../sidebars/topology/rack/RackNameContainer.js | 4 +- .../app/sidebars/topology/rack/RackSidebar.js | 58 +++++++++++++++++++ .../sidebars/topology/rack/RackSidebar.module.scss | 12 ++++ .../sidebars/topology/rack/RackSidebarComponent.js | 58 ------------------- .../topology/rack/RackSidebarComponent.module.scss | 12 ---- .../sidebars/topology/rack/RackSidebarContainer.js | 32 ----------- .../sidebars/topology/room/DeleteRoomContainer.js | 9 ++- .../sidebars/topology/room/EditRoomContainer.js | 9 ++- .../topology/room/RackConstructionComponent.js | 2 +- .../topology/room/RackConstructionContainer.js | 2 +- .../app/sidebars/topology/room/RoomName.js | 4 +- .../app/sidebars/topology/room/RoomSidebar.js | 43 ++++++++++++++ .../sidebars/topology/room/RoomSidebarComponent.js | 43 -------------- .../sidebars/topology/room/RoomSidebarContainer.js | 32 ----------- .../projects/[project]/topologies/[topology].js | 2 - .../opendc-web-ui/src/redux/actions/prefabs.js | 3 +- .../src/redux/actions/topology/building.js | 11 ++-- .../src/redux/actions/topology/machine.js | 10 +++- .../src/redux/actions/topology/rack.js | 9 ++- .../src/redux/actions/topology/room.js | 6 +- .../opendc-web-ui/src/redux/sagas/prefabs.js | 7 +-- .../opendc-web-ui/src/redux/sagas/topology.js | 67 ++++++++-------------- opendc-web/opendc-web-ui/src/util/sidebar-space.js | 2 - opendc-web/opendc-web-ui/src/util/state-utils.js | 6 -- .../opendc-web-ui/src/util/tile-calculations.js | 4 +- 44 files changed, 337 insertions(+), 439 deletions(-) create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefab.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/util/sidebar-space.js delete mode 100644 opendc-web/opendc-web-ui/src/util/state-utils.js (limited to 'opendc-web/opendc-web-ui/src') diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js index c4a880b1..564f4030 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types' import React from 'react' import { InteractionLevel } from '../../../../shapes' -import BuildingSidebarComponent from './building/BuildingSidebarComponent' +import BuildingSidebar from './building/BuildingSidebar' import { Button, DrawerActions, @@ -16,9 +16,9 @@ import { AngleLeftIcon } from '@patternfly/react-icons' import { useDispatch } from 'react-redux' import { goDownOneInteractionLevel } from '../../../../redux/actions/interaction-level' import { backButton } from './TopologySidebar.module.scss' -import RoomSidebarContainer from './room/RoomSidebarContainer' -import RackSidebarContainer from './rack/RackSidebarContainer' -import MachineSidebarContainer from './machine/MachineSidebarContainer' +import RoomSidebar from './room/RoomSidebar' +import RackSidebar from './rack/RackSidebar' +import MachineSidebar from './machine/MachineSidebar' const name = { BUILDING: 'Building', @@ -27,21 +27,21 @@ const name = { MACHINE: 'Machine', } -const TopologySidebar = ({ interactionLevel, onClose }) => { +function TopologySidebar({ interactionLevel, onClose }) { let sidebarContent switch (interactionLevel.mode) { case 'BUILDING': - sidebarContent = + sidebarContent = break case 'ROOM': - sidebarContent = + sidebarContent = break case 'RACK': - sidebarContent = + sidebarContent = break case 'MACHINE': - sidebarContent = + sidebarContent = break default: sidebarContent = 'Missing Content' diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js new file mode 100644 index 00000000..5fcd46be --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js @@ -0,0 +1,8 @@ +import React from 'react' +import NewRoomConstructionContainer from './NewRoomConstructionContainer' + +function BuildingSidebar() { + return +} + +export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js deleted file mode 100644 index 6c2556d3..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import NewRoomConstructionContainer from './NewRoomConstructionContainer' - -const BuildingSidebarComponent = () => { - return -} - -export default BuildingSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js index 656b2515..9fc85d0c 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js @@ -4,7 +4,7 @@ import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@pat import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' -const NewRoomConstructionComponent = ({ onStart, onFinish, onCancel, currentRoomInConstruction }) => { +function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { if (currentRoomInConstruction === '-1') { return ( + ) +} + +AddPrefab.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js deleted file mode 100644 index c8543134..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { SaveIcon } from '@patternfly/react-icons' -import { Button } from '@patternfly/react-core' - -const AddPrefabComponent = ({ onClick }) => ( - -) - -AddPrefabComponent.propTypes = { - onClick: PropTypes.func, -} - -export default AddPrefabComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js deleted file mode 100644 index d3d9aaf5..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch } from 'react-redux' -import { addPrefab } from '../../../../../redux/actions/prefabs' -import AddPrefabComponent from './AddPrefabComponent' - -const AddPrefabContainer = (props) => { - const dispatch = useDispatch() - return dispatch(addPrefab('name'))} /> -} - -export default AddPrefabContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js index 47959f03..f0dc7b6b 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js @@ -20,6 +20,7 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React, { useState } from 'react' import { useDispatch } from 'react-redux' import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' @@ -27,12 +28,12 @@ import { deleteRack } from '../../../../../redux/actions/topology/rack' import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' import { Button } from '@patternfly/react-core' -const DeleteRackContainer = () => { +function DeleteRackContainer({ tileId }) { const dispatch = useDispatch() const [isVisible, setVisible] = useState(false) const callback = (isConfirmed) => { if (isConfirmed) { - dispatch(deleteRack()) + dispatch(deleteRack(tileId)) } setVisible(false) } @@ -51,4 +52,8 @@ const DeleteRackContainer = () => { ) } +DeleteRackContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js index 1617b3bf..97141711 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js @@ -19,7 +19,7 @@ UnitIcon.propTypes = { type: PropTypes.string, } -const MachineComponent = ({ machine, onClick }) => { +function MachineComponent({ machine, onClick }) { const hasNoUnits = machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js index 54e6db0a..3ed0ffd0 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js @@ -43,7 +43,7 @@ function MachineListContainer({ tileId, ...props }) { dispatch(addMachine(index))} + onAdd={(index) => dispatch(addMachine(rack._id, index))} onSelect={(index) => dispatch(goFromRackToMachine(index))} /> ) diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js index 11529b55..11db6420 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js @@ -5,11 +5,11 @@ import NameComponent from '../NameComponent' import { editRackName } from '../../../../../redux/actions/topology/rack' const RackNameContainer = ({ tileId }) => { - const rackName = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack].name) + const { name: rackName, _id } = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) const dispatch = useDispatch() const callback = (name) => { if (name) { - dispatch(editRackName(name)) + dispatch(editRackName(_id, name)) } } return diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js new file mode 100644 index 00000000..3c9f152a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js @@ -0,0 +1,58 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss' +import RackNameContainer from './RackNameContainer' +import AddPrefab from './AddPrefab' +import DeleteRackContainer from './DeleteRackContainer' +import MachineListContainer from './MachineListContainer' +import { + Skeleton, + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function RackSidebar({ tileId }) { + const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + + return ( +
+ + Details + + + Name + + + + + Capacity + + {rack?.capacity ?? } + + + Actions + + + + Slots + +
+ +
+
+ ) +} + +RackSidebar.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss new file mode 100644 index 00000000..6f258aec --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss @@ -0,0 +1,12 @@ +.sidebarContainer { + display: flex; + height: 100%; + max-height: 100%; + flex-direction: column; +} + +.machineListContainer { + flex: 1; + overflow-y: scroll; + margin-top: 10px; +} diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js deleted file mode 100644 index dd5117f7..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js +++ /dev/null @@ -1,58 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { machineListContainer, sidebarContainer } from './RackSidebarComponent.module.scss' -import RackNameContainer from './RackNameContainer' -import AddPrefabContainer from './AddPrefabContainer' -import DeleteRackContainer from './DeleteRackContainer' -import MachineListContainer from './MachineListContainer' -import { - Skeleton, - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' -import { useSelector } from 'react-redux' - -function RackSidebarComponent({ tileId }) { - const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) - - return ( -
- - Details - - - Name - - - - - Capacity - - {rack?.capacity ?? } - - - Actions - - - - Slots - -
- -
-
- ) -} - -RackSidebarComponent.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss deleted file mode 100644 index 6f258aec..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.sidebarContainer { - display: flex; - height: 100%; - max-height: 100%; - flex-direction: column; -} - -.machineListContainer { - flex: 1; - overflow-y: scroll; - margin-top: 10px; -} diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js deleted file mode 100644 index 2b31413d..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useSelector } from 'react-redux' -import RackSidebarComponent from './RackSidebarComponent' - -const RackSidebarContainer = (props) => { - const tileId = useSelector((state) => state.interactionLevel.tileId) - return -} - -export default RackSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js index 284c4d53..19a782a6 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js @@ -20,6 +20,7 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React, { useState } from 'react' import { useDispatch } from 'react-redux' import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' @@ -27,12 +28,12 @@ import { deleteRoom } from '../../../../../redux/actions/topology/room' import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' import { Button } from '@patternfly/react-core' -const DeleteRoomContainer = () => { +function DeleteRoomContainer({ roomId }) { const dispatch = useDispatch() const [isVisible, setVisible] = useState(false) const callback = (isConfirmed) => { if (isConfirmed) { - dispatch(deleteRoom()) + dispatch(deleteRoom(roomId)) } setVisible(false) } @@ -51,4 +52,8 @@ const DeleteRoomContainer = () => { ) } +DeleteRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js index 6db2bfb6..96c077cb 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js @@ -20,6 +20,7 @@ * SOFTWARE. */ +import PropTypes from 'prop-types' import React from 'react' import { useDispatch, useSelector } from 'react-redux' import { finishRoomEdit, startRoomEdit } from '../../../../../redux/actions/topology/building' @@ -27,12 +28,12 @@ import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' import { Button } from '@patternfly/react-core' -const EditRoomContainer = () => { +function EditRoomContainer({ roomId }) { const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) const dispatch = useDispatch() - const onEdit = () => dispatch(startRoomEdit()) + const onEdit = () => dispatch(startRoomEdit(roomId)) const onFinish = () => dispatch(finishRoomEdit()) return isEditing ? ( @@ -53,4 +54,8 @@ const EditRoomContainer = () => { ) } +EditRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js index 8aebe969..13432689 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js @@ -7,7 +7,7 @@ import TimesIcon from '@patternfly/react-icons/dist/js/icons/times-icon' const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { if (inRackConstructionMode) { return ( - ) diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js index 38af447a..b9ab6610 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js @@ -25,7 +25,7 @@ import { useDispatch, useSelector } from 'react-redux' import { startRackConstruction, stopRackConstruction } from '../../../../../redux/actions/topology/room' import RackConstructionComponent from './RackConstructionComponent' -const RackConstructionContainer = (props) => { +function RackConstructionContainer(props) { const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js index d7b006a6..11909189 100644 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js @@ -27,11 +27,11 @@ import NameComponent from '../../../../../components/app/sidebars/topology/NameC import { editRoomName } from '../../../../../redux/actions/topology/room' function RoomName({ roomId }) { - const roomName = useSelector((state) => state.objects.room[roomId].name) + const { name: roomName, _id } = useSelector((state) => state.objects.room[roomId]) const dispatch = useDispatch() const callback = (name) => { if (name) { - dispatch(editRoomName(name)) + dispatch(editRoomName(_id, name)) } } return diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js new file mode 100644 index 00000000..6ad489e0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types' +import React from 'react' +import RoomName from './RoomName' +import RackConstructionContainer from './RackConstructionContainer' +import EditRoomContainer from './EditRoomContainer' +import DeleteRoomContainer from './DeleteRoomContainer' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' + +const RoomSidebar = ({ roomId }) => { + return ( + + Details + + + Name + + + + + + Construction + + + + + ) +} + +RoomSidebar.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js deleted file mode 100644 index fac58c51..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import RoomName from './RoomName' -import RackConstructionContainer from './RackConstructionContainer' -import EditRoomContainer from './EditRoomContainer' -import DeleteRoomContainer from './DeleteRoomContainer' -import { - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' - -const RoomSidebarComponent = ({ roomId }) => { - return ( - - Details - - - Name - - - - - - Construction - - - - - ) -} - -RoomSidebarComponent.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default RoomSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js deleted file mode 100644 index 2076b00e..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useSelector } from 'react-redux' -import RoomSidebarComponent from './RoomSidebarComponent' - -const RoomSidebarContainer = (props) => { - const roomId = useSelector((state) => state.interactionLevel.roomId) - return -} - -export default RoomSidebarContainer diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js index d79e8e7a..139c2979 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -40,8 +40,6 @@ import { Spinner, Title, } from '@patternfly/react-core' -import Toolbar from '../../../../components/app/map/controls/Toolbar' -import ScaleIndicator from '../../../../components/app/map/controls/ScaleIndicator' import TopologySidebar from '../../../../components/app/sidebars/topology/TopologySidebar' import Collapse from '../../../../components/app/map/controls/Collapse' diff --git a/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js index c112feed..0ef7795f 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js @@ -3,10 +3,11 @@ export const DELETE_PREFAB = 'DELETE_PREFAB' export const DELETE_PREFAB_SUCCEEDED = 'DELETE_PREFAB_SUCCEEDED' export const OPEN_PREFAB_SUCCEEDED = 'OPEN_PREFAB_SUCCEEDED' -export function addPrefab(name) { +export function addPrefab(name, tileId) { return { type: ADD_PREFAB, name, + tileId, } } diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js index f1a7d569..49425318 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js @@ -55,13 +55,10 @@ export function cancelNewRoomConstructionSucceeded() { } } -export function startRoomEdit() { - return (dispatch, getState) => { - const { interactionLevel } = getState() - dispatch({ - type: START_ROOM_EDIT, - roomId: interactionLevel.roomId, - }) +export function startRoomEdit(roomId) { + return { + type: START_ROOM_EDIT, + roomId: roomId, } } diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js index 17ccce5d..170b7648 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js @@ -2,23 +2,27 @@ export const DELETE_MACHINE = 'DELETE_MACHINE' export const ADD_UNIT = 'ADD_UNIT' export const DELETE_UNIT = 'DELETE_UNIT' -export function deleteMachine() { +export function deleteMachine(rackId, position) { return { type: DELETE_MACHINE, + rackId, + position, } } -export function addUnit(unitType, id) { +export function addUnit(machineId, unitType, id) { return { type: ADD_UNIT, + machineId, unitType, id, } } -export function deleteUnit(unitType, index) { +export function deleteUnit(machineId, unitType, index) { return { type: DELETE_UNIT, + machineId, unitType, index, } diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js index b117402e..228e3ae9 100644 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js @@ -2,22 +2,25 @@ export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' export const DELETE_RACK = 'DELETE_RACK' export const ADD_MACHINE = 'ADD_MACHINE' -export function editRackName(name) { +export function editRackName(rackId, name) { return { type: EDIT_RACK_NAME, name, + rackId, } } -export function deleteRack() { +export function deleteRack(tileId) { return { type: DELETE_RACK, + tileId, } } -export function addMachine(position) { +export function addMachine(rackId, position) { return { type: ADD_MACHINE, position, + rackId, } } 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 80ef7c5e..e584af89 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 @@ -6,10 +6,11 @@ export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION' export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION' export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE' -export function editRoomName(name) { +export function editRoomName(roomId, name) { return { type: EDIT_ROOM_NAME, name, + roomId, } } @@ -41,8 +42,9 @@ export function addRackToTile(positionX, positionY) { } } -export function deleteRoom() { +export function deleteRoom(roomId) { return { type: DELETE_ROOM, + roomId, } } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js index 91b03bf6..f717d878 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js @@ -4,14 +4,13 @@ import { addPrefab } from '../../api/prefabs' import { Rack } from '../../util/topology-schema' import { denormalize } from 'normalizr' -export function* onAddPrefab(action) { +export function* onAddPrefab({ name, tileId }) { try { - const interactionLevel = yield select((state) => state.interactionLevel) const objects = yield select((state) => state.objects) - const rack = objects.rack[objects.tile[interactionLevel.tileId].rack] + const rack = objects.rack[objects.tile[tileId].rack] const prefabRack = denormalize(rack, Rack, objects) const auth = yield getContext('auth') - const prefab = yield call(() => addPrefab(auth, { name: action.name, rack: prefabRack })) + const prefab = yield call(() => addPrefab(auth, { name, rack: prefabRack })) yield put(addToStore('prefab', prefab)) } catch (error) { console.error(error) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index 333c1485..f3742b78 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -37,10 +37,8 @@ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = fa } } -export function* onAddTopology(action) { +export function* onAddTopology({ projectId, duplicateId, name }) { try { - const { projectId, duplicateId, name } = action - let topologyToBeCreated if (duplicateId) { topologyToBeCreated = yield denormalizeTopology(duplicateId) @@ -119,21 +117,19 @@ export function* onDeleteTile(action) { } } -export function* onEditRoomName(action) { +export function* onEditRoomName({ roomId, name }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - yield put(addPropToStoreObject('room', roomId, { name: action.name })) + yield put(addPropToStoreObject('room', roomId, { name })) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) } } -export function* onDeleteRoom() { +export function* onDeleteRoom({ roomId }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) yield put(goDownOneInteractionLevel()) yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId)) yield updateTopologyOnServer(topologyId) @@ -142,21 +138,19 @@ export function* onDeleteRoom() { } } -export function* onEditRackName(action) { +export function* onEditRackName({ rackId, name }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rack) - yield put(addPropToStoreObject('rack', rackId, { name: action.name })) + yield put(addPropToStoreObject('rack', rackId, { name })) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) } } -export function* onDeleteRack() { +export function* onDeleteRack({ tileId }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) yield put(goDownOneInteractionLevel()) yield put(addPropToStoreObject('tile', tileId, { rack: undefined })) yield updateTopologyOnServer(topologyId) @@ -165,35 +159,34 @@ export function* onDeleteRack() { } } -export function* onAddRackToTile(action) { +export function* onAddRackToTile({ tileId }) { try { const topologyId = yield select((state) => state.currentTopologyId) const rack = { _id: uuid(), name: 'Rack', - tileId: action.tileId, + tileId, capacity: DEFAULT_RACK_SLOT_CAPACITY, powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, machines: [], } yield put(addToStore('rack', rack)) - yield put(addPropToStoreObject('tile', action.tileId, { rack: rack._id })) + yield put(addPropToStoreObject('tile', tileId, { rack: rack._id })) yield updateTopologyOnServer(topologyId) } catch (error) { console.error(error) } } -export function* onAddMachine(action) { +export function* onAddMachine({ rackId, position }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rack) const rack = yield select((state) => state.objects.rack[rackId]) const machine = { _id: uuid(), rackId, - position: action.position, + position, cpus: [], gpus: [], memories: [], @@ -209,15 +202,13 @@ export function* onAddMachine(action) { } } -export function* onDeleteMachine() { +export function* onDeleteMachine({ rackId, position }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rack]) + const rack = yield select((state) => state.objects.rack[rackId]) yield put(goDownOneInteractionLevel()) yield put( - addPropToStoreObject('rack', rack._id, { machines: rack.machines.filter((_, idx) => idx !== position - 1) }) + addPropToStoreObject('rack', rackId, { machines: rack.machines.filter((_, idx) => idx !== position - 1) }) ) yield updateTopologyOnServer(topologyId) } catch (error) { @@ -232,23 +223,19 @@ const unitMapping = { storage: 'storages', } -export function* onAddUnit(action) { +export function* onAddUnit({ machineId, unitType, id }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const machine = yield select( - (state) => state.objects.machine[state.objects.rack[state.objects.tile[tileId].rack].machines[position - 1]] - ) + const machine = yield select((state) => state.objects.machine[machineId]) - if (machine[unitMapping[action.unitType]].length >= MAX_NUM_UNITS_PER_MACHINE) { + if (machine[unitMapping[unitType]].length >= MAX_NUM_UNITS_PER_MACHINE) { return } - const units = [...machine[unitMapping[action.unitType]], action.id] + const units = [...machine[unitMapping[unitType]], id] yield put( addPropToStoreObject('machine', machine._id, { - [unitMapping[action.unitType]]: units, + [unitMapping[unitType]]: units, }) ) yield updateTopologyOnServer(topologyId) @@ -257,20 +244,16 @@ export function* onAddUnit(action) { } } -export function* onDeleteUnit(action) { +export function* onDeleteUnit({ machineId, unitType, index }) { try { const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const machine = yield select( - (state) => state.objects.machine[state.objects.rack[state.objects.tile[tileId].rack].machines[position - 1]] - ) - const unitIds = machine[unitMapping[action.unitType]].slice() - unitIds.splice(action.index, 1) + const machine = yield select((state) => state.objects.machine[machineId]) + const unitIds = machine[unitMapping[unitType]].slice() + unitIds.splice(index, 1) yield put( addPropToStoreObject('machine', machine._id, { - [unitMapping[action.unitType]]: unitIds, + [unitMapping[unitType]]: unitIds, }) ) yield updateTopologyOnServer(topologyId) diff --git a/opendc-web/opendc-web-ui/src/util/sidebar-space.js b/opendc-web/opendc-web-ui/src/util/sidebar-space.js deleted file mode 100644 index 005c40f3..00000000 --- a/opendc-web/opendc-web-ui/src/util/sidebar-space.js +++ /dev/null @@ -1,2 +0,0 @@ -export const isCollapsible = (router) => - router.asPath.indexOf('portfolios') === -1 && router.asPath.indexOf('scenarios') === -1 diff --git a/opendc-web/opendc-web-ui/src/util/state-utils.js b/opendc-web/opendc-web-ui/src/util/state-utils.js deleted file mode 100644 index e5b695c3..00000000 --- a/opendc-web/opendc-web-ui/src/util/state-utils.js +++ /dev/null @@ -1,6 +0,0 @@ -export const getState = (dispatch) => - new Promise((resolve) => { - dispatch((dispatch, getState) => { - resolve(getState()) - }) - }) diff --git a/opendc-web/opendc-web-ui/src/util/tile-calculations.js b/opendc-web/opendc-web-ui/src/util/tile-calculations.js index 764ae6ac..374ca48c 100644 --- a/opendc-web/opendc-web-ui/src/util/tile-calculations.js +++ b/opendc-web/opendc-web-ui/src/util/tile-calculations.js @@ -18,8 +18,8 @@ function getWallSegments(tiles) { } let doInsert = true - for (let tileIndex in tiles) { - if (tiles[tileIndex].positionX === x + dX && tiles[tileIndex].positionY === y + dY) { + for (const tile of tiles) { + if (tile.positionX === x + dX && tile.positionY === y + dY) { doInsert = false break } -- cgit v1.2.3 From 5e5ab63b280eb446db4090733cd3ad2e97d02018 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 19 Jul 2021 15:47:23 +0200 Subject: refactor(ui): Restructure components per page This change updates the source structure of the OpenDC frontend to follow the page structure. --- opendc-web/opendc-web-ui/src/api/topologies.js | 1 + .../opendc-web-ui/src/components/AppNavigation.js | 2 +- opendc-web/opendc-web-ui/src/components/AppPage.js | 3 +- .../src/components/app/map/GrayContainer.js | 34 ----- .../src/components/app/map/MapConstants.js | 25 ---- .../src/components/app/map/MapStage.js | 83 ----------- .../src/components/app/map/MapStage.module.scss | 31 ---- .../src/components/app/map/RackContainer.js | 37 ----- .../components/app/map/RackEnergyFillContainer.js | 34 ----- .../components/app/map/RackSpaceFillContainer.js | 37 ----- .../src/components/app/map/RoomContainer.js | 45 ------ .../src/components/app/map/TileContainer.js | 46 ------ .../src/components/app/map/TopologyContainer.js | 35 ----- .../src/components/app/map/WallContainer.js | 39 ----- .../src/components/app/map/controls/Collapse.js | 42 ------ .../app/map/controls/Collapse.module.scss | 55 ------- .../components/app/map/controls/ScaleIndicator.js | 16 -- .../app/map/controls/ScaleIndicator.module.scss | 10 -- .../src/components/app/map/controls/Toolbar.js | 28 ---- .../app/map/controls/Toolbar.module.scss | 29 ---- .../src/components/app/map/elements/Backdrop.js | 8 - .../src/components/app/map/elements/GrayLayer.js | 22 --- .../src/components/app/map/elements/HoverTile.js | 28 ---- .../components/app/map/elements/ImageComponent.js | 36 ----- .../src/components/app/map/elements/RackFillBar.js | 68 --------- .../src/components/app/map/elements/RoomTile.js | 22 --- .../src/components/app/map/elements/TileObject.js | 25 ---- .../components/app/map/elements/TilePlusIcon.js | 44 ------ .../src/components/app/map/elements/WallSegment.js | 32 ---- .../src/components/app/map/groups/GridGroup.js | 34 ----- .../src/components/app/map/groups/RackGroup.js | 25 ---- .../src/components/app/map/groups/RoomGroup.js | 52 ------- .../src/components/app/map/groups/TileGroup.js | 36 ----- .../src/components/app/map/groups/TopologyGroup.js | 44 ------ .../src/components/app/map/groups/WallGroup.js | 22 --- .../app/map/layers/HoverLayerComponent.js | 55 ------- .../src/components/app/map/layers/MapLayer.js | 41 ------ .../components/app/map/layers/ObjectHoverLayer.js | 53 ------- .../components/app/map/layers/RoomHoverLayer.js | 61 -------- .../components/app/results/PortfolioResultInfo.js | 40 ----- .../src/components/app/results/PortfolioResults.js | 134 ----------------- .../app/sidebars/topology/NameComponent.js | 69 --------- .../app/sidebars/topology/TopologySidebar.js | 83 ----------- .../sidebars/topology/TopologySidebar.module.scss | 37 ----- .../sidebars/topology/building/BuildingSidebar.js | 8 - .../building/NewRoomConstructionComponent.js | 46 ------ .../building/NewRoomConstructionContainer.js | 46 ------ .../app/sidebars/topology/machine/DeleteMachine.js | 56 ------- .../sidebars/topology/machine/MachineSidebar.js | 49 ------- .../sidebars/topology/machine/UnitAddComponent.js | 42 ------ .../sidebars/topology/machine/UnitAddContainer.js | 43 ------ .../sidebars/topology/machine/UnitListComponent.js | 112 -------------- .../sidebars/topology/machine/UnitListContainer.js | 53 ------- .../sidebars/topology/machine/UnitTabsComponent.js | 36 ----- .../app/sidebars/topology/rack/AddPrefab.js | 44 ------ .../sidebars/topology/rack/DeleteRackContainer.js | 59 -------- .../app/sidebars/topology/rack/MachineComponent.js | 46 ------ .../sidebars/topology/rack/MachineListComponent.js | 73 ---------- .../sidebars/topology/rack/MachineListContainer.js | 56 ------- .../sidebars/topology/rack/RackNameContainer.js | 22 --- .../app/sidebars/topology/rack/RackSidebar.js | 58 -------- .../sidebars/topology/rack/RackSidebar.module.scss | 12 -- .../sidebars/topology/room/DeleteRoomContainer.js | 59 -------- .../sidebars/topology/room/EditRoomContainer.js | 61 -------- .../topology/room/RackConstructionComponent.js | 36 ----- .../topology/room/RackConstructionContainer.js | 46 ------ .../app/sidebars/topology/room/RoomName.js | 44 ------ .../app/sidebars/topology/room/RoomSidebar.js | 43 ------ .../src/components/modals/ConfirmationModal.js | 27 ---- .../opendc-web-ui/src/components/modals/Modal.js | 38 ----- .../src/components/modals/TextInputModal.js | 70 --------- .../modals/custom-components/NewPortfolioModal.js | 139 ------------------ .../modals/custom-components/NewScenarioModal.js | 159 -------------------- .../modals/custom-components/NewTopologyModal.js | 81 ----------- .../src/components/portfolios/NewScenario.js | 64 ++++++++ .../src/components/portfolios/NewScenarioModal.js | 159 ++++++++++++++++++++ .../src/components/portfolios/ScenarioState.js | 62 ++++++++ .../src/components/portfolios/ScenarioTable.js | 108 ++++++++++++++ .../portfolios/results/PortfolioResultInfo.js | 40 +++++ .../portfolios/results/PortfolioResults.js | 134 +++++++++++++++++ .../src/components/projects/NewPortfolio.js | 2 +- .../src/components/projects/NewPortfolioModal.js | 161 +++++++++++++++++++++ .../src/components/projects/NewProject.js | 2 +- .../src/components/projects/NewScenario.js | 64 -------- .../src/components/projects/NewTopology.js | 2 +- .../src/components/projects/NewTopologyModal.js | 103 +++++++++++++ .../src/components/projects/ScenarioState.js | 62 -------- .../src/components/projects/ScenarioTable.js | 108 -------------- .../src/components/topologies/map/GrayContainer.js | 34 +++++ .../src/components/topologies/map/MapConstants.js | 25 ++++ .../src/components/topologies/map/MapStage.js | 83 +++++++++++ .../components/topologies/map/MapStage.module.scss | 31 ++++ .../src/components/topologies/map/RackContainer.js | 37 +++++ .../topologies/map/RackEnergyFillContainer.js | 34 +++++ .../topologies/map/RackSpaceFillContainer.js | 37 +++++ .../src/components/topologies/map/RoomContainer.js | 45 ++++++ .../src/components/topologies/map/TileContainer.js | 46 ++++++ .../components/topologies/map/TopologyContainer.js | 35 +++++ .../src/components/topologies/map/WallContainer.js | 37 +++++ .../components/topologies/map/controls/Collapse.js | 42 ++++++ .../topologies/map/controls/Collapse.module.scss | 55 +++++++ .../topologies/map/controls/ScaleIndicator.js | 18 +++ .../map/controls/ScaleIndicator.module.scss | 10 ++ .../components/topologies/map/controls/Toolbar.js | 35 +++++ .../topologies/map/controls/Toolbar.module.scss | 29 ++++ .../components/topologies/map/elements/Backdrop.js | 10 ++ .../topologies/map/elements/GrayLayer.js | 24 +++ .../topologies/map/elements/HoverTile.js | 30 ++++ .../topologies/map/elements/ImageComponent.js | 36 +++++ .../topologies/map/elements/RackFillBar.js | 68 +++++++++ .../components/topologies/map/elements/RoomTile.js | 24 +++ .../topologies/map/elements/TileObject.js | 27 ++++ .../topologies/map/elements/TilePlusIcon.js | 44 ++++++ .../topologies/map/elements/WallSegment.js | 32 ++++ .../components/topologies/map/groups/GridGroup.js | 36 +++++ .../components/topologies/map/groups/RackGroup.js | 25 ++++ .../components/topologies/map/groups/RoomGroup.js | 52 +++++++ .../components/topologies/map/groups/TileGroup.js | 36 +++++ .../topologies/map/groups/TopologyGroup.js | 44 ++++++ .../components/topologies/map/groups/WallGroup.js | 22 +++ .../topologies/map/layers/HoverLayerComponent.js | 55 +++++++ .../components/topologies/map/layers/MapLayer.js | 41 ++++++ .../topologies/map/layers/ObjectHoverLayer.js | 53 +++++++ .../topologies/map/layers/RoomHoverLayer.js | 61 ++++++++ .../components/topologies/sidebar/NameComponent.js | 69 +++++++++ .../topologies/sidebar/TopologySidebar.js | 83 +++++++++++ .../topologies/sidebar/TopologySidebar.module.scss | 37 +++++ .../topologies/sidebar/building/BuildingSidebar.js | 8 + .../building/NewRoomConstructionComponent.js | 46 ++++++ .../building/NewRoomConstructionContainer.js | 46 ++++++ .../topologies/sidebar/machine/DeleteMachine.js | 56 +++++++ .../topologies/sidebar/machine/MachineSidebar.js | 49 +++++++ .../topologies/sidebar/machine/UnitAddComponent.js | 42 ++++++ .../topologies/sidebar/machine/UnitAddContainer.js | 43 ++++++ .../sidebar/machine/UnitListComponent.js | 112 ++++++++++++++ .../sidebar/machine/UnitListContainer.js | 53 +++++++ .../sidebar/machine/UnitTabsComponent.js | 36 +++++ .../topologies/sidebar/rack/AddPrefab.js | 44 ++++++ .../topologies/sidebar/rack/DeleteRackContainer.js | 59 ++++++++ .../topologies/sidebar/rack/MachineComponent.js | 46 ++++++ .../sidebar/rack/MachineListComponent.js | 73 ++++++++++ .../sidebar/rack/MachineListContainer.js | 56 +++++++ .../topologies/sidebar/rack/RackNameContainer.js | 22 +++ .../topologies/sidebar/rack/RackSidebar.js | 58 ++++++++ .../sidebar/rack/RackSidebar.module.scss | 12 ++ .../topologies/sidebar/room/DeleteRoomContainer.js | 59 ++++++++ .../topologies/sidebar/room/EditRoomContainer.js | 61 ++++++++ .../sidebar/room/RackConstructionComponent.js | 35 +++++ .../sidebar/room/RackConstructionContainer.js | 46 ++++++ .../components/topologies/sidebar/room/RoomName.js | 44 ++++++ .../topologies/sidebar/room/RoomSidebar.js | 43 ++++++ .../components/util/modals/ConfirmationModal.js | 27 ++++ .../src/components/util/modals/Modal.js | 38 +++++ .../src/components/util/modals/TextInputModal.js | 70 +++++++++ .../src/pages/projects/[project]/index.js | 1 - .../projects/[project]/portfolios/[portfolio].js | 6 +- .../projects/[project]/topologies/[topology].js | 123 ++++++++++++---- .../opendc-web-ui/src/redux/sagas/topology.js | 4 +- 158 files changed, 3765 insertions(+), 3633 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/MapStage.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefab.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js delete mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/Modal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/projects/NewScenario.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js delete mode 100644 opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js delete mode 100644 opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js create mode 100644 opendc-web/opendc-web-ui/src/components/util/modals/Modal.js create mode 100644 opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js (limited to 'opendc-web/opendc-web-ui/src') diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js index 0b8864e0..bd4e3bc4 100644 --- a/opendc-web/opendc-web-ui/src/api/topologies.js +++ b/opendc-web/opendc-web-ui/src/api/topologies.js @@ -35,6 +35,7 @@ export function addTopology(auth, topology) { } export function updateTopology(auth, topology) { + // eslint-disable-next-line no-unused-vars const { _id, ...data } = topology return request(auth, `topologies/${topology._id}`, 'PUT', { topology: data }) } diff --git a/opendc-web/opendc-web-ui/src/components/AppNavigation.js b/opendc-web/opendc-web-ui/src/components/AppNavigation.js index b3f11f34..178c3ec0 100644 --- a/opendc-web/opendc-web-ui/src/components/AppNavigation.js +++ b/opendc-web/opendc-web-ui/src/components/AppNavigation.js @@ -20,7 +20,7 @@ * SOFTWARE. */ -import { Dropdown, DropdownItem, DropdownToggle, Nav, NavItem, NavList } from '@patternfly/react-core' +import { Nav, NavItem, NavList } from '@patternfly/react-core' import { useRouter } from 'next/router' import NavItemLink from './util/NavItemLink' import { useProject } from '../data/project' diff --git a/opendc-web/opendc-web-ui/src/components/AppPage.js b/opendc-web/opendc-web-ui/src/components/AppPage.js index 7cf9cc15..2e0ea4cc 100644 --- a/opendc-web/opendc-web-ui/src/components/AppPage.js +++ b/opendc-web/opendc-web-ui/src/components/AppPage.js @@ -22,8 +22,7 @@ import PropTypes from 'prop-types' import { AppHeader } from './AppHeader' -import { AppNavigation } from './AppNavigation' -import React, { useState } from 'react' +import React from 'react' import { Page } from '@patternfly/react-core' export function AppPage({ children, breadcrumb, tertiaryNav }) { diff --git a/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js deleted file mode 100644 index 4791940f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' -import GrayLayer from '../../../components/app/map/elements/GrayLayer' - -const GrayContainer = () => { - const dispatch = useDispatch() - const onClick = () => dispatch(goDownOneInteractionLevel()) - return -} - -export default GrayContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js deleted file mode 100644 index 4c3b2757..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js +++ /dev/null @@ -1,25 +0,0 @@ -export const MAP_SIZE = 50 -export const TILE_SIZE_IN_PIXELS = 100 -export const TILE_SIZE_IN_METERS = 0.5 -export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS - -export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5 -export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3 -export const OBJECT_SIZE_IN_PIXELS = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - -export const GRID_LINE_WIDTH_IN_PIXELS = 2 -export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 -export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 -export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 - -export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 -export const RACK_FILL_ICON_OPACITY = 0.8 - -export const MAP_MOVE_PIXELS_PER_EVENT = 20 -export const MAP_SCALE_PER_EVENT = 1.1 -export const MAP_MIN_SCALE = 0.5 -export const MAP_MAX_SCALE = 1.5 - -export const MAX_NUM_UNITS_PER_MACHINE = 6 -export const DEFAULT_RACK_SLOT_CAPACITY = 42 -export const DEFAULT_RACK_POWER_CAPACITY = 10000 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js deleted file mode 100644 index 5d19b3ad..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useRef, useState } from 'react' -import { HotKeys } from 'react-hotkeys' -import { Stage } from 'react-konva' -import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' -import { Provider, useStore } from 'react-redux' -import useResizeObserver from 'use-resize-observer' -import { mapContainer } from './MapStage.module.scss' -import MapLayer from './layers/MapLayer' -import RoomHoverLayer from './layers/RoomHoverLayer' -import ObjectHoverLayer from './layers/ObjectHoverLayer' -import ScaleIndicator from './controls/ScaleIndicator' -import Toolbar from './controls/Toolbar' - -function MapStage() { - const store = useStore() - const { ref, width = 100, height = 100 } = useResizeObserver() - const stageRef = useRef(null) - const [[x, y], setPos] = useState([0, 0]) - const [scale, setScale] = useState(1) - - const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) - const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) - - const onZoom = (e) => { - e.evt.preventDefault() - - const stage = stageRef.current.getStage() - const oldScale = scale - - const pointer = stage.getPointerPosition() - const mousePointTo = { - x: (pointer.x - x) / oldScale, - y: (pointer.y - y) / oldScale, - } - - const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) - - setScale(newScale) - setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) - } - const onZoomButton = (zoomIn) => - setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) - const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) - const onExport = () => { - const download = document.createElement('a') - download.href = stageRef.current.getStage().toDataURL() - download.download = 'opendc-canvas-export-' + Date.now() + '.png' - download.click() - } - - const handlers = { - MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), - MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), - MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), - MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), - } - - return ( - - - - - - - - - - - - ) -} - -export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss deleted file mode 100644 index d879b4c8..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss +++ /dev/null @@ -1,31 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.mapContainer { - background-color: var(--pf-global--Color--light-200); - position: relative; - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; -} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js deleted file mode 100644 index 3c75d3a7..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useSelector } from 'react-redux' -import RackGroup from '../../../components/app/map/groups/RackGroup' -import { Tile } from '../../../shapes' - -const RackContainer = ({ tile }) => { - const interactionLevel = useSelector((state) => state.interactionLevel) - return -} - -RackContainer.propTypes = { - tile: Tile, -} - -export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js deleted file mode 100644 index dbc26f14..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import RackFillBar from '../../../components/app/map/elements/RackFillBar' - -function RackSpaceFillContainer({ tileId, ...props }) { - const fillFraction = useSelector((state) => { - let energyConsumptionTotal = 0 - const rack = state.objects.rack[state.objects.tile[tileId].rack] - const machineIds = rack.machines - machineIds.forEach((machineId) => { - if (machineId !== null) { - const machine = state.objects.machine[machineId] - machine.cpus.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW)) - machine.gpus.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW)) - machine.memories.forEach( - (id) => (energyConsumptionTotal += state.objects.memory[id].energyConsumptionW) - ) - machine.storages.forEach( - (id) => (energyConsumptionTotal += state.objects.storage[id].energyConsumptionW) - ) - } - }) - - return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) - }) - return -} - -RackSpaceFillContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js deleted file mode 100644 index 7ca5c930..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import RackFillBar from '../../../components/app/map/elements/RackFillBar' - -function RackSpaceFillContainer({ tileId, ...props }) { - const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) - return -} - -RackSpaceFillContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js deleted file mode 100644 index 26fbcd7a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level' -import RoomGroup from '../../../components/app/map/groups/RoomGroup' - -const RoomContainer = (props) => { - const state = useSelector((state) => { - return { - interactionLevel: state.interactionLevel, - currentRoomInConstruction: state.construction.currentRoomInConstruction, - room: state.objects.room[props.roomId], - } - }) - const dispatch = useDispatch() - return dispatch(goFromBuildingToRoom(props.roomId))} /> -} - -RoomContainer.propTypes = { - roomId: PropTypes.string, -} - -export default RoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js deleted file mode 100644 index bfcbf735..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import { useDispatch, useSelector } from 'react-redux' -import { goFromRoomToRack } from '../../../redux/actions/interaction-level' -import TileGroup from '../../../components/app/map/groups/TileGroup' - -const TileContainer = (props) => { - const interactionLevel = useSelector((state) => state.interactionLevel) - const tile = useSelector((state) => state.objects.tile[props.tileId]) - - const dispatch = useDispatch() - const onClick = (tile) => { - if (tile.rack) { - dispatch(goFromRoomToRack(tile._id)) - } - } - return -} - -TileContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js deleted file mode 100644 index 78e75d0f..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useSelector } from 'react-redux' -import TopologyGroup from '../../../components/app/map/groups/TopologyGroup' -import { useActiveTopology } from '../../../data/topology' - -const TopologyContainer = () => { - const topology = useActiveTopology() - const interactionLevel = useSelector((state) => state.interactionLevel) - - return -} - -export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js deleted file mode 100644 index 51dffe4b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import WallGroup from '../../../components/app/map/groups/WallGroup' - -const WallContainer = (props) => { - const tiles = useSelector((state) => - state.objects.room[props.roomId].tiles.map((tileId) => state.objects.tile[tileId]) - ) - return -} - -WallContainer.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default WallContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js deleted file mode 100644 index f54b7c84..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { ChevronLeftIcon } from '@patternfly/react-icons' -import { collapseContainer } from './Collapse.module.scss' -import { Button } from '@patternfly/react-core' - -function Collapse({ onClick }) { - return ( -
- -
- ) -} - -Collapse.propTypes = { - onClick: PropTypes.func, -} - -export default Collapse diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss b/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss deleted file mode 100644 index 0c1fac94..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.collapseContainer { - position: absolute; - right: var(--pf-global--spacer--xs); - top: 0; - bottom: 10%; - margin: auto 0; - height: 50px; - - button:global(.pf-m-tertiary) { - height: 100%; - padding: 2px; - - margin-right: var(--pf-global--spacer--xs); - margin-top: var(--pf-global--spacer--xs); - background-color: var(--pf-global--BackgroundColor--100); - border: none; - border-radius: var(--pf-global--BorderRadius--sm); - box-shadow: var(--pf-global--BoxShadow--sm); - - &:not(:global(.pf-m-disabled)) { - background-color: var(--pf-global--BackgroundColor--100); - } - - &:after { - display: none; - } - - &:hover { - border: none; - box-shadow: var(--pf-global--BoxShadow--md); - } - } -} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js deleted file mode 100644 index 11c2f2d3..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' -import { scaleIndicator } from './ScaleIndicator.module.scss' - -const ScaleIndicator = ({ scale }) => ( -
- {TILE_SIZE_IN_METERS}m -
-) - -ScaleIndicator.propTypes = { - scale: PropTypes.number.isRequired, -} - -export default ScaleIndicator diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss deleted file mode 100644 index f19e0ff2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss +++ /dev/null @@ -1,10 +0,0 @@ -.scaleIndicator { - position: absolute; - right: 10px; - bottom: 10px; - z-index: 50; - - border: solid 2px #212529; - border-top: none; - border-left: none; -} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js deleted file mode 100644 index 4c60bfb2..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { control, toolBar } from './Toolbar.module.scss' -import { Button } from '@patternfly/react-core' -import { SearchPlusIcon, SearchMinusIcon } from '@patternfly/react-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCamera } from '@fortawesome/free-solid-svg-icons' - -const Toolbar = ({ onZoom, onExport }) => ( -
- - - -
-) - -Toolbar.propTypes = { - onZoom: PropTypes.func, - onExport: PropTypes.func, -} - -export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss b/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss deleted file mode 100644 index 0d505acc..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss +++ /dev/null @@ -1,29 +0,0 @@ -.toolBar { - position: absolute; - bottom: var(--pf-global--spacer--md); - left: var(--pf-global--spacer--xl); -} - -.control { - &:global(.pf-m-tertiary) { - margin-right: var(--pf-global--spacer--xs); - margin-top: var(--pf-global--spacer--xs); - background-color: var(--pf-global--BackgroundColor--100); - border: none; - border-radius: var(--pf-global--BorderRadius--sm); - box-shadow: var(--pf-global--BoxShadow--sm); - - &:not(:global(.pf-m-disabled)) { - background-color: var(--pf-global--BackgroundColor--100); - } - - &:after { - display: none; - } - - &:hover { - border: none; - box-shadow: var(--pf-global--BoxShadow--md); - } - } -} diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js deleted file mode 100644 index 8ccfe584..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import { Rect } from 'react-konva' -import { BACKDROP_COLOR } from '../../../../util/colors' -import { MAP_SIZE_IN_PIXELS } from '../MapConstants' - -const Backdrop = () => - -export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js deleted file mode 100644 index 35af4d96..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' -import { MAP_SIZE_IN_PIXELS } from '../MapConstants' - -const GrayLayer = ({ onClick }) => ( - -) - -GrayLayer.propTypes = { - onClick: PropTypes.func, -} - -export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js deleted file mode 100644 index 0369bb79..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const HoverTile = ({ x, y, isValid, scale = 1, onClick }) => ( - -) - -HoverTile.propTypes = { - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - isValid: PropTypes.bool.isRequired, - scale: PropTypes.number, - onClick: PropTypes.func.isRequired, -} - -export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js deleted file mode 100644 index 7d304b6b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import { Image } from 'react-konva' - -const imageCaches = {} - -function ImageComponent({ src, x, y, width, height, opacity }) { - const [image, setImage] = useState(null) - - useEffect(() => { - if (imageCaches[src]) { - setImage(imageCaches[src]) - return - } - - const image = new window.Image() - image.src = src - image.onload = () => { - setImage(image) - imageCaches[src] = image - } - }, [src]) - - return -} - -ImageComponent.propTypes = { - src: PropTypes.string.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, -} - -export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js deleted file mode 100644 index aa284944..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js +++ /dev/null @@ -1,68 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Rect } from 'react-konva' -import { - RACK_ENERGY_BAR_BACKGROUND_COLOR, - RACK_ENERGY_BAR_FILL_COLOR, - RACK_SPACE_BAR_BACKGROUND_COLOR, - RACK_SPACE_BAR_FILL_COLOR, -} from '../../../../util/colors' -import { - OBJECT_BORDER_WIDTH_IN_PIXELS, - OBJECT_MARGIN_IN_PIXELS, - RACK_FILL_ICON_OPACITY, - RACK_FILL_ICON_WIDTH, - TILE_SIZE_IN_PIXELS, -} from '../MapConstants' -import ImageComponent from './ImageComponent' - -function RackFillBar({ positionX, positionY, type, fillFraction }) { - const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 - const x = - positionX * TILE_SIZE_IN_PIXELS + - OBJECT_MARGIN_IN_PIXELS + - (type === 'space' ? halfOfObjectBorderWidth : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)) - const startY = positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS + halfOfObjectBorderWidth - const width = 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - halfOfObjectBorderWidth - const fullHeight = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - OBJECT_BORDER_WIDTH_IN_PIXELS - - const fractionHeight = fillFraction * fullHeight - const fractionY = - (positionY + 1) * TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS - halfOfObjectBorderWidth - fractionHeight - - return ( - - - - - - ) -} - -RackFillBar.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - type: PropTypes.string.isRequired, - fillFraction: PropTypes.number.isRequired, -} - -export default RackFillBar diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js deleted file mode 100644 index ed718601..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { Tile } from '../../../../shapes' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const RoomTile = ({ tile, color }) => ( - -) - -RoomTile.propTypes = { - tile: Tile, - color: PropTypes.string, -} - -export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js deleted file mode 100644 index 9e87cc82..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { OBJECT_BORDER_COLOR } from '../../../../util/colors' -import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const TileObject = ({ positionX, positionY, color }) => ( - -) - -TileObject.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, -} - -export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js deleted file mode 100644 index 186c2b3a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js +++ /dev/null @@ -1,44 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Line } from 'react-konva' -import { TILE_PLUS_COLOR } from '../../../../util/colors' -import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -function TilePlusIcon({ x, y, scale = 1 }) { - const linePoints = [ - [ - x + 0.5 * TILE_SIZE_IN_PIXELS * scale, - y + TILE_PLUS_MARGIN_IN_PIXELS * scale, - x + 0.5 * TILE_SIZE_IN_PIXELS * scale, - y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, - ], - [ - x + TILE_PLUS_MARGIN_IN_PIXELS * scale, - y + 0.5 * TILE_SIZE_IN_PIXELS * scale, - x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, - y + 0.5 * TILE_SIZE_IN_PIXELS * scale, - ], - ] - return ( - - {linePoints.map((points, index) => ( - - ))} - - ) -} - -TilePlusIcon.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - scale: PropTypes.number, -} - -export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js deleted file mode 100644 index ad6412c3..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { Line } from 'react-konva' -import { WallSegment as WallSegmentShape } from '../../../../shapes' -import { WALL_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' - -const WallSegment = ({ wallSegment }) => { - let points - if (wallSegment.isHorizontal) { - points = [ - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - ] - } else { - points = [ - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS, - ] - } - - return -} - -WallSegment.propTypes = { - wallSegment: WallSegmentShape, -} - -export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js deleted file mode 100644 index ebc00244..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react' -import { Group, Line } from 'react-konva' -import { GRID_COLOR } from '../../../../util/colors' -import { GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i) -const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ - 0, - index * TILE_SIZE_IN_PIXELS, - MAP_SIZE_IN_PIXELS, - index * TILE_SIZE_IN_PIXELS, -]) -const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ - index * TILE_SIZE_IN_PIXELS, - 0, - index * TILE_SIZE_IN_PIXELS, - MAP_SIZE_IN_PIXELS, -]) - -const GridGroup = () => ( - - {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( - - ))} - -) - -export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js deleted file mode 100644 index 9c4abc4a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import { Tile } from '../../../../shapes' -import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' -import TileObject from '../elements/TileObject' -import RackSpaceFillContainer from '../RackSpaceFillContainer' -import RackEnergyFillContainer from '../RackEnergyFillContainer' - -const RackGroup = ({ tile }) => { - return ( - - - - - - - - ) -} - -RackGroup.propTypes = { - tile: Tile, -} - -export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js deleted file mode 100644 index a14f3676..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import { InteractionLevel, Room } from '../../../../shapes' -import GrayContainer from '../GrayContainer' -import TileContainer from '../TileContainer' -import WallContainer from '../WallContainer' - -const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => { - if (currentRoomInConstruction === room._id) { - return ( - - {room.tiles.map((tileId) => ( - - ))} - - ) - } - - return ( - - {(() => { - if ( - (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && - interactionLevel.roomId === room._id - ) { - return [ - room.tiles - .filter((tileId) => tileId !== interactionLevel.tileId) - .map((tileId) => ), - , - room.tiles - .filter((tileId) => tileId === interactionLevel.tileId) - .map((tileId) => ), - ] - } else { - return room.tiles.map((tileId) => ) - } - })()} - - - ) -} - -RoomGroup.propTypes = { - room: Room, - interactionLevel: InteractionLevel, - currentRoomInConstruction: PropTypes.string, - onClick: PropTypes.func, -} - -export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js deleted file mode 100644 index cd36c7e5..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import { Tile } from '../../../../shapes' -import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' -import RoomTile from '../elements/RoomTile' -import RackContainer from '../RackContainer' - -const TileGroup = ({ tile, newTile, onClick }) => { - let tileObject - if (tile.rack) { - tileObject = - } else { - tileObject = null - } - - let color = ROOM_DEFAULT_COLOR - if (newTile) { - color = ROOM_IN_CONSTRUCTION_COLOR - } - - return ( - onClick(tile)}> - - {tileObject} - - ) -} - -TileGroup.propTypes = { - tile: Tile, - newTile: PropTypes.bool, - onClick: PropTypes.func, -} - -export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js deleted file mode 100644 index d3bcb279..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import { InteractionLevel, Topology } from '../../../../shapes' -import RoomContainer from '../RoomContainer' -import GrayContainer from '../GrayContainer' - -const TopologyGroup = ({ topology, interactionLevel }) => { - if (!topology) { - return - } - - if (interactionLevel.mode === 'BUILDING') { - return ( - - {topology.rooms.map((roomId) => ( - - ))} - - ) - } - - return ( - - {topology.rooms - .filter((roomId) => roomId !== interactionLevel.roomId) - .map((roomId) => ( - - ))} - {interactionLevel.mode === 'ROOM' ? : null} - {topology.rooms - .filter((roomId) => roomId === interactionLevel.roomId) - .map((roomId) => ( - - ))} - - ) -} - -TopologyGroup.propTypes = { - topology: Topology, - interactionLevel: InteractionLevel, -} - -export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js deleted file mode 100644 index c73a95a7..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import { Tile } from '../../../../shapes' -import { deriveWallLocations } from '../../../../util/tile-calculations' -import WallSegment from '../elements/WallSegment' - -const WallGroup = ({ tiles }) => { - return ( - - {deriveWallLocations(tiles).map((wallSegment, index) => ( - - ))} - - ) -} - -WallGroup.propTypes = { - tiles: PropTypes.arrayOf(Tile).isRequired, -} - -export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js deleted file mode 100644 index 2b1060c0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js +++ /dev/null @@ -1,55 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useMemo, useState } from 'react' -import { Layer } from 'react-konva/lib/ReactKonva' -import HoverTile from '../elements/HoverTile' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' -import { useEffectRef } from '../../../../util/effect-ref' - -function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { - const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) - - const layerRef = useEffectRef((layer) => { - if (!layer) { - return - } - - const stage = layer.getStage() - - // Transform used to convert mouse coordinates to world coordinates - const transform = stage.getAbsoluteTransform().copy() - transform.invert() - - stage.on('mousemove.hover', () => { - const { x, y } = transform.point(stage.getPointerPosition()) - setPos([x, y]) - }) - return () => stage.off('mousemove.hover') - }) - - const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) - const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) - const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) - - if (!isEnabled) { - return - } - - const x = gridX * TILE_SIZE_IN_PIXELS - const y = gridY * TILE_SIZE_IN_PIXELS - - return ( - - (valid ? onClick(gridX, gridY) : undefined)} /> - {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} - - ) -} - -HoverLayerComponent.propTypes = { - isEnabled: PropTypes.bool.isRequired, - isValid: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, - children: PropTypes.node, -} - -export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js deleted file mode 100644 index c902532b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { Group, Layer } from 'react-konva' -import Backdrop from '../elements/Backdrop' -import TopologyContainer from '../TopologyContainer' -import GridGroup from '../groups/GridGroup' - -function MapLayer() { - return ( - - - - - - - - ) -} - -export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js deleted file mode 100644 index 47d9c992..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { addRackToTile } from '../../../../redux/actions/topology/room' -import { findTileWithPosition } from '../../../../util/tile-calculations' -import HoverLayerComponent from './HoverLayerComponent' -import TilePlusIcon from '../elements/TilePlusIcon' - -function ObjectHoverLayer() { - const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) - const isValid = useSelector((state) => (x, y) => { - if (state.interactionLevel.mode !== 'ROOM') { - return false - } - - const currentRoom = state.objects.room[state.interactionLevel.roomId] - const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId]) - const tile = findTileWithPosition(tiles, x, y) - - return !(tile === null || tile.rack) - }) - - const dispatch = useDispatch() - const onClick = (x, y) => dispatch(addRackToTile(x, y)) - return ( - - - - ) -} - -export default ObjectHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js deleted file mode 100644 index 59f83b2b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' -import { - deriveValidNextTilePositions, - findPositionInPositions, - findPositionInRooms, -} from '../../../../util/tile-calculations' -import HoverLayerComponent from './HoverLayerComponent' - -function RoomHoverLayer() { - const dispatch = useDispatch() - const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) - const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') - const isValid = useSelector((state) => (x, y) => { - const newRoom = { ...state.objects.room[state.construction.currentRoomInConstruction] } - const oldRooms = Object.keys(state.objects.room) - .map((id) => ({ ...state.objects.room[id] })) - .filter( - (room) => - state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 && - room._id !== state.construction.currentRoomInConstruction - ) - - ;[...oldRooms, newRoom].forEach((room) => { - room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]) - }) - if (newRoom.tiles.length === 0) { - return findPositionInRooms(oldRooms, x, y) === -1 - } - - const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) - return findPositionInPositions(validNextPositions, x, y) !== -1 - }) - - return -} - -export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js deleted file mode 100644 index 09348e60..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { Tooltip } from '@patternfly/react-core' -import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' -import { METRIC_DESCRIPTIONS } from '../../../util/available-metrics' - -function PortfolioResultInfo({ metric }) { - return ( - {METRIC_DESCRIPTIONS[metric]}}> - - - ) -} - -PortfolioResultInfo.propTypes = { - metric: PropTypes.string.isRequired, -} - -export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js deleted file mode 100644 index 6a96c70c..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' -import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics' -import { mean, std } from 'mathjs' -import approx from 'approximate-number' -import { - Bullseye, - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Grid, - GridItem, - Spinner, - Title, -} from '@patternfly/react-core' -import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' -import { usePortfolioScenarios } from '../../../data/project' -import NewScenario from '../../projects/NewScenario' -import PortfolioResultInfo from './PortfolioResultInfo' - -const PortfolioResults = ({ portfolioId }) => { - const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) - - if (status === 'loading') { - return ( - - - - - Loading Results - - - - ) - } else if (status === 'error') { - return ( - - - - - Unable to connect - - - There was an error retrieving data. Check your connection and try again. - - - - ) - } else if (scenarios.length === 0) { - return ( - - - - - No results - - - No results are currently available for this portfolio. Run a scenario to obtain simulation - results. - - - - - ) - } - - const dataPerMetric = {} - - AVAILABLE_METRICS.forEach((metric) => { - dataPerMetric[metric] = scenarios - .filter((scenario) => scenario.results) - .map((scenario) => ({ - name: scenario.name, - value: mean(scenario.results[metric]), - errorX: std(scenario.results[metric]), - })) - }) - - return ( - - {AVAILABLE_METRICS.map((metric) => ( - - - - - - - {METRIC_NAMES[metric]} - - - - - - approx(tick)} - label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} - type="number" - /> - - - - - - - - - - - ))} - - ) -} - -PortfolioResults.propTypes = { - portfolioId: PropTypes.string, -} - -export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js deleted file mode 100644 index ececd07b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js +++ /dev/null @@ -1,69 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import { Button, TextInput } from '@patternfly/react-core' -import { PencilAltIcon, CheckIcon, TimesIcon } from '@patternfly/react-icons' - -function NameComponent({ name, onEdit }) { - const [isEditing, setEditing] = useState(false) - const nameInput = useRef(null) - - const onCancel = () => { - nameInput.current.value = name - setEditing(false) - } - - const onSubmit = (event) => { - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - if (name) { - onEdit(name) - } - - setEditing(false) - } - - return ( -
-
-
- {name} -
-
- -
-
-
-
- -
-
-
- -
-
- -
-
-
-
- ) -} - -NameComponent.propTypes = { - name: PropTypes.string, - onEdit: PropTypes.func, -} - -export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js deleted file mode 100644 index 564f4030..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js +++ /dev/null @@ -1,83 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { InteractionLevel } from '../../../../shapes' -import BuildingSidebar from './building/BuildingSidebar' -import { - Button, - DrawerActions, - DrawerCloseButton, - DrawerHead, - DrawerPanelBody, - DrawerPanelContent, - Flex, - Title, -} from '@patternfly/react-core' -import { AngleLeftIcon } from '@patternfly/react-icons' -import { useDispatch } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../../redux/actions/interaction-level' -import { backButton } from './TopologySidebar.module.scss' -import RoomSidebar from './room/RoomSidebar' -import RackSidebar from './rack/RackSidebar' -import MachineSidebar from './machine/MachineSidebar' - -const name = { - BUILDING: 'Building', - ROOM: 'Room', - RACK: 'Rack', - MACHINE: 'Machine', -} - -function TopologySidebar({ interactionLevel, onClose }) { - let sidebarContent - - switch (interactionLevel.mode) { - case 'BUILDING': - sidebarContent = - break - case 'ROOM': - sidebarContent = - break - case 'RACK': - sidebarContent = - break - case 'MACHINE': - sidebarContent = - break - default: - sidebarContent = 'Missing Content' - } - - const dispatch = useDispatch() - const onClick = () => dispatch(goDownOneInteractionLevel()) - - return ( - - - - - - {name[interactionLevel.mode]} - - - - - - - {sidebarContent} - - ) -} - -TopologySidebar.propTypes = { - interactionLevel: InteractionLevel, - onClose: PropTypes.func, -} - -export default TopologySidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss deleted file mode 100644 index 45dc98da..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.backButton { - &:global(.pf-c-button) { - align-self: center; - --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--light-100); - color: var(--pf-global--Color--400); - - --pf-c-button--PaddingRight: var(--pf-global--spacer--sm); - --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm); - - &:hover, - &:focus { - --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--100); - } - } -} diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js deleted file mode 100644 index 5fcd46be..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebar.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import NewRoomConstructionContainer from './NewRoomConstructionContainer' - -function BuildingSidebar() { - return -} - -export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js deleted file mode 100644 index 9fc85d0c..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core' -import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' -import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' - -function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { - if (currentRoomInConstruction === '-1') { - return ( - - ) - } - return ( - - - - - - - - - - - - - ) -} - -NewRoomConstructionComponent.propTypes = { - onStart: PropTypes.func, - onFinish: PropTypes.func, - onCancel: PropTypes.func, - currentRoomInConstruction: PropTypes.string, -} - -export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js deleted file mode 100644 index 5b031a6b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { - cancelNewRoomConstruction, - finishNewRoomConstruction, - startNewRoomConstruction, -} from '../../../../../redux/actions/topology/building' -import NewRoomConstructionComponent from './NewRoomConstructionComponent' - -function NewRoomConstructionButton(props) { - const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) - - const dispatch = useDispatch() - const actions = { - onStart: () => dispatch(startNewRoomConstruction()), - onFinish: () => dispatch(finishNewRoomConstruction()), - onCancel: () => dispatch(cancelNewRoomConstruction()), - } - return ( - - ) -} - -export default NewRoomConstructionButton diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js deleted file mode 100644 index 75d458b6..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React, { useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { deleteMachine } from '../../../../../redux/actions/topology/machine' -import { Button } from '@patternfly/react-core' -import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' -import ConfirmationModal from '../../../../modals/ConfirmationModal' - -function DeleteMachine() { - const dispatch = useDispatch() - const [isVisible, setVisible] = useState(false) - const rackId = useSelector((state) => state.objects.tile[state.interactionLevel.tileId].rack) - const position = useSelector((state) => state.interactionLevel.position) - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteMachine(rackId, position)) - } - setVisible(false) - } - return ( - <> - - - - ) -} - -export default DeleteMachine diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebar.js deleted file mode 100644 index 0c3dea98..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebar.js +++ /dev/null @@ -1,49 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import UnitTabsComponent from './UnitTabsComponent' -import DeleteMachine from './DeleteMachine' -import { - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' -import { useSelector } from 'react-redux' - -function MachineSidebar({ tileId, position }) { - const machine = useSelector(({ objects }) => { - const rack = objects.rack[objects.tile[tileId].rack] - return objects.machine[rack.machines[position - 1]] - }) - const machineId = machine._id - return ( -
- - Details - - Name - - Machine at position {machine.position} - - - - Actions - - - Units - -
- -
-
- ) -} - -MachineSidebar.propTypes = { - tileId: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, -} - -export default MachineSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js deleted file mode 100644 index 88591208..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { Button, InputGroup, Select, SelectOption, SelectVariant } from '@patternfly/react-core' -import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' - -function UnitAddComponent({ units, onAdd }) { - const [isOpen, setOpen] = useState(false) - const [selected, setSelected] = useState(null) - - return ( - - - - - ) -} - -UnitAddComponent.propTypes = { - units: PropTypes.array.isRequired, - onAdd: PropTypes.func.isRequired, -} - -export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js deleted file mode 100644 index cc226d46..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { addUnit } from '../../../../../redux/actions/topology/machine' -import UnitAddComponent from './UnitAddComponent' - -function UnitAddContainer({ machineId, unitType }) { - const units = useSelector((state) => Object.values(state.objects[unitType])) - const dispatch = useDispatch() - - const onAdd = (id) => dispatch(addUnit(machineId, unitType, id)) - - return -} - -UnitAddContainer.propTypes = { - machineId: PropTypes.string.isRequired, - unitType: PropTypes.string.isRequired, -} - -export default UnitAddContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js deleted file mode 100644 index 16ebd708..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js +++ /dev/null @@ -1,112 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { ProcessingUnit, StorageUnit } from '../../../../../shapes' -import { - Button, - DataList, - DataListAction, - DataListCell, - DataListItem, - DataListItemCells, - DataListItemRow, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Popover, - Title, -} from '@patternfly/react-core' -import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons' - -function UnitInfo({ unit, unitType }) { - if (unitType === 'cpu' || unitType === 'gpu') { - return ( - - - Clock Frequency - {unit.clockRateMhz} MHz - - - Number of Cores - {unit.numberOfCores} - - - Energy Consumption - {unit.energyConsumptionW} W - - - ) - } - - return ( - - - Speed - {unit.speedMbPerS} Mb/s - - - Capacity - {unit.sizeMb} MB - - - Energy Consumption - {unit.energyConsumptionW} W - - - ) -} - -UnitInfo.propTypes = { - unitType: PropTypes.string.isRequired, - unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired, -} - -function UnitListComponent({ unitType, units, onDelete }) { - if (units.length === 0) { - return ( - - - - No units found - - You have not configured any units yet. Add some with the menu above! - - ) - } - - return ( - - {units.map((unit, index) => ( - - - {unit.name}]} /> - - } - > - - - - - - - ))} - - ) -} - -UnitListComponent.propTypes = { - unitType: PropTypes.string.isRequired, - units: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, StorageUnit])).isRequired, - onDelete: PropTypes.func, -} - -export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js deleted file mode 100644 index f76684a5..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import UnitListComponent from './UnitListComponent' -import { deleteUnit } from '../../../../../redux/actions/topology/machine' - -const unitMapping = { - cpu: 'cpus', - gpu: 'gpus', - memory: 'memories', - storage: 'storages', -} - -function UnitListContainer({ machineId, unitType }) { - const dispatch = useDispatch() - const units = useSelector((state) => { - const machine = state.objects.machine[machineId] - return machine[unitMapping[unitType]].map((id) => state.objects[unitType][id]) - }) - - const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id)) - - return -} - -UnitListContainer.propTypes = { - machineId: PropTypes.string.isRequired, - unitType: PropTypes.string.isRequired, -} - -export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js deleted file mode 100644 index 6d10d2df..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { Tab, Tabs, TabTitleText } from '@patternfly/react-core' -import UnitAddContainer from './UnitAddContainer' -import UnitListContainer from './UnitListContainer' - -function UnitTabsComponent({ machineId }) { - const [activeTab, setActiveTab] = useState('cpu-units') - - return ( - setActiveTab(tab)}> - CPU}> - - - - GPU}> - - - - Memory}> - - - - Storage}> - - - - - ) -} - -UnitTabsComponent.propTypes = { - machineId: PropTypes.string.isRequired, -} - -export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefab.js deleted file mode 100644 index 3af46861..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefab.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch } from 'react-redux' -import { addPrefab } from '../../../../../redux/actions/prefabs' -import { Button } from '@patternfly/react-core' -import { SaveIcon } from '@patternfly/react-icons' - -function AddPrefab({ tileId }) { - const dispatch = useDispatch() - const onClick = () => dispatch(addPrefab('name', tileId)) - return ( - - ) -} - -AddPrefab.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js deleted file mode 100644 index f0dc7b6b..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { useDispatch } from 'react-redux' -import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteRack } from '../../../../../redux/actions/topology/rack' -import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' -import { Button } from '@patternfly/react-core' - -function DeleteRackContainer({ tileId }) { - const dispatch = useDispatch() - const [isVisible, setVisible] = useState(false) - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteRack(tileId)) - } - setVisible(false) - } - return ( - <> - - - - ) -} - -DeleteRackContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js deleted file mode 100644 index 97141711..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Image from 'next/image' -import { Machine } from '../../../../../shapes' -import { Flex, Label } from '@patternfly/react-core' - -const UnitIcon = ({ id, type }) => ( - {'Machine -) - -UnitIcon.propTypes = { - id: PropTypes.string, - type: PropTypes.string, -} - -function MachineComponent({ machine, onClick }) { - const hasNoUnits = - machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 - - return ( - onClick()}> - {machine.cpus.length > 0 ? : undefined} - {machine.gpus.length > 0 ? : undefined} - {machine.memories.length > 0 ? : undefined} - {machine.storages.length > 0 ? : undefined} - {hasNoUnits ? ( - - ) : undefined} - - ) -} - -MachineComponent.propTypes = { - machine: Machine.isRequired, - onClick: PropTypes.func, -} - -export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js deleted file mode 100644 index 27834cf4..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js +++ /dev/null @@ -1,73 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import MachineComponent from './MachineComponent' -import { Machine } from '../../../../../shapes' -import { - Badge, - Button, - DataList, - DataListAction, - DataListCell, - DataListItem, - DataListItemCells, - DataListItemRow, -} from '@patternfly/react-core' -import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons' - -function MachineListComponent({ machines = [], onSelect, onAdd }) { - return ( - - {machines.map((machine, index) => - machine ? ( - onSelect(index + 1)}> - - - {machines.length - index}U - , - - onSelect(index + 1)} machine={machine} /> - , - ]} - /> - - - - - - ) : ( - - - - {machines.length - index}U - , - - Empty Slot - , - ]} - /> - - - - - - ) - )} - - ) -} - -MachineListComponent.propTypes = { - machines: PropTypes.arrayOf(Machine), - onSelect: PropTypes.func.isRequired, - onAdd: PropTypes.func.isRequired, -} - -export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js deleted file mode 100644 index 3ed0ffd0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useMemo } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import MachineListComponent from './MachineListComponent' -import { goFromRackToMachine } from '../../../../../redux/actions/interaction-level' -import { addMachine } from '../../../../../redux/actions/topology/rack' - -function MachineListContainer({ tileId, ...props }) { - const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) - const machines = useSelector((state) => rack.machines.map((id) => state.objects.machine[id])) - const machinesNull = useMemo(() => { - const res = Array(rack.capacity).fill(null) - for (const machine of machines) { - res[machine.position - 1] = machine - } - return res - }, [rack, machines]) - const dispatch = useDispatch() - - return ( - dispatch(addMachine(rack._id, index))} - onSelect={(index) => dispatch(goFromRackToMachine(index))} - /> - ) -} - -MachineListContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js deleted file mode 100644 index 11db6420..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import NameComponent from '../NameComponent' -import { editRackName } from '../../../../../redux/actions/topology/rack' - -const RackNameContainer = ({ tileId }) => { - const { name: rackName, _id } = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) - const dispatch = useDispatch() - const callback = (name) => { - if (name) { - dispatch(editRackName(_id, name)) - } - } - return -} - -RackNameContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js deleted file mode 100644 index 3c9f152a..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.js +++ /dev/null @@ -1,58 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss' -import RackNameContainer from './RackNameContainer' -import AddPrefab from './AddPrefab' -import DeleteRackContainer from './DeleteRackContainer' -import MachineListContainer from './MachineListContainer' -import { - Skeleton, - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' -import { useSelector } from 'react-redux' - -function RackSidebar({ tileId }) { - const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) - - return ( -
- - Details - - - Name - - - - - Capacity - - {rack?.capacity ?? } - - - Actions - - - - Slots - -
- -
-
- ) -} - -RackSidebar.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss deleted file mode 100644 index 6f258aec..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebar.module.scss +++ /dev/null @@ -1,12 +0,0 @@ -.sidebarContainer { - display: flex; - height: 100%; - max-height: 100%; - flex-direction: column; -} - -.machineListContainer { - flex: 1; - overflow-y: scroll; - margin-top: 10px; -} diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js deleted file mode 100644 index 19a782a6..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { useDispatch } from 'react-redux' -import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteRoom } from '../../../../../redux/actions/topology/room' -import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' -import { Button } from '@patternfly/react-core' - -function DeleteRoomContainer({ roomId }) { - const dispatch = useDispatch() - const [isVisible, setVisible] = useState(false) - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteRoom(roomId)) - } - setVisible(false) - } - return ( - <> - - - - ) -} - -DeleteRoomContainer.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js deleted file mode 100644 index 96c077cb..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { finishRoomEdit, startRoomEdit } from '../../../../../redux/actions/topology/building' -import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' -import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' -import { Button } from '@patternfly/react-core' - -function EditRoomContainer({ roomId }) { - const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') - const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) - - const dispatch = useDispatch() - const onEdit = () => dispatch(startRoomEdit(roomId)) - const onFinish = () => dispatch(finishRoomEdit()) - - return isEditing ? ( - - ) : ( - - ) -} - -EditRoomContainer.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js deleted file mode 100644 index 13432689..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Button } from '@patternfly/react-core' -import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' -import TimesIcon from '@patternfly/react-icons/dist/js/icons/times-icon' - -const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { - if (inRackConstructionMode) { - return ( - - ) - } - - return ( - - ) -} - -RackConstructionComponent.propTypes = { - onStart: PropTypes.func, - onStop: PropTypes.func, - inRackConstructionMode: PropTypes.bool, - isEditingRoom: PropTypes.bool, -} - -export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js deleted file mode 100644 index b9ab6610..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { startRackConstruction, stopRackConstruction } from '../../../../../redux/actions/topology/room' -import RackConstructionComponent from './RackConstructionComponent' - -function RackConstructionContainer(props) { - const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) - const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') - - const dispatch = useDispatch() - const onStart = () => dispatch(startRackConstruction()) - const onStop = () => dispatch(stopRackConstruction()) - return ( - - ) -} - -export default RackConstructionContainer diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js deleted file mode 100644 index 11909189..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import NameComponent from '../../../../../components/app/sidebars/topology/NameComponent' -import { editRoomName } from '../../../../../redux/actions/topology/room' - -function RoomName({ roomId }) { - const { name: roomName, _id } = useSelector((state) => state.objects.room[roomId]) - const dispatch = useDispatch() - const callback = (name) => { - if (name) { - dispatch(editRoomName(_id, name)) - } - } - return -} - -RoomName.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default RoomName diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js deleted file mode 100644 index 6ad489e0..00000000 --- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebar.js +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import RoomName from './RoomName' -import RackConstructionContainer from './RackConstructionContainer' -import EditRoomContainer from './EditRoomContainer' -import DeleteRoomContainer from './DeleteRoomContainer' -import { - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' - -const RoomSidebar = ({ roomId }) => { - return ( - - Details - - - Name - - - - - - Construction - - - - - ) -} - -RoomSidebar.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js deleted file mode 100644 index f6e1c98b..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Modal from './Modal' - -function ConfirmationModal({ title, message, isOpen, callback }) { - return ( - callback(true)} - onCancel={() => callback(false)} - submitButtonType="danger" - submitButtonText="Confirm" - > - {message} - - ) -} - -ConfirmationModal.propTypes = { - title: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - isOpen: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, -} - -export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/modals/Modal.js deleted file mode 100644 index d4577062..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/Modal.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Button, Modal as PModal, ModalVariant } from '@patternfly/react-core' - -function Modal({ children, title, isOpen, onSubmit, onCancel, submitButtonType, submitButtonText }) { - const actions = [ - , - , - ] - - return ( - - {children} - - ) -} - -Modal.propTypes = { - title: PropTypes.string.isRequired, - isOpen: PropTypes.bool, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - submitButtonType: PropTypes.string, - submitButtonText: PropTypes.string, - children: PropTypes.node, -} - -Modal.defaultProps = { - submitButtonType: 'primary', - submitButtonText: 'Save', - isOpen: false, -} - -export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js deleted file mode 100644 index 392a729e..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js +++ /dev/null @@ -1,70 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import Modal from './Modal' -import { Form, FormGroup, TextInput } from '@patternfly/react-core' - -function TextInputModal({ title, label, isOpen, callback, initialValue }) { - const textInput = useRef(null) - const [isSubmitted, setSubmitted] = useState(false) - const [isValid, setValid] = useState(true) - - const resetState = () => { - textInput.current.value = '' - setSubmitted(false) - setValid(false) - } - const onSubmit = (event) => { - const value = textInput.current.value - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - if (!value) { - setValid(false) - return false - } - - callback(value) - resetState() - return true - } - const onCancel = () => { - callback(undefined) - resetState() - } - - return ( - -
- - - -
-
- ) -} - -TextInputModal.propTypes = { - title: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - isOpen: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - initialValue: PropTypes.string, -} - -export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js deleted file mode 100644 index afe07597..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js +++ /dev/null @@ -1,139 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import Modal from '../Modal' -import { - Form, - FormGroup, - FormSection, - NumberInput, - Select, - SelectGroup, - SelectOption, - SelectVariant, - TextInput, -} from '@patternfly/react-core' -import { METRIC_GROUPS, METRIC_NAMES } from '../../../util/available-metrics' - -const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { - const nameInput = useRef(null) - const [repeats, setRepeats] = useState(1) - const [isSelectOpen, setSelectOpen] = useState(false) - const [selectedMetrics, setSelectedMetrics] = useState([]) - - const [isSubmitted, setSubmitted] = useState(false) - const [errors, setErrors] = useState({}) - - const clearState = () => { - setSubmitted(false) - setErrors({}) - nameInput.current.value = '' - setRepeats(1) - setSelectOpen(false) - setSelectedMetrics([]) - } - - const onSubmit = (event) => { - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - - if (!name) { - setErrors({ name: true }) - return false - } else { - onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) - } - - clearState() - return false - } - const onCancel = () => { - onUpstreamCancel() - clearState() - } - - const onSelect = (event, selection) => { - if (selectedMetrics.includes(selection)) { - setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) - } else { - setSelectedMetrics((metrics) => [...metrics, selection]) - } - } - - return ( - -
- - - - - - - - - - - setRepeats(Number(e.target.value))} - onPlus={() => setRepeats((r) => r + 1)} - onMinus={() => setRepeats((r) => r - 1)} - min={1} - /> - - -
-
- ) -} - -NewPortfolioModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js deleted file mode 100644 index 94d0d424..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js +++ /dev/null @@ -1,159 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import { Portfolio } from '../../../shapes' -import Modal from '../Modal' -import { - Checkbox, - Form, - FormGroup, - FormSection, - FormSelect, - FormSelectOption, - NumberInput, - TextInput, -} from '@patternfly/react-core' -import { useSchedulers, useTraces } from '../../../data/experiments' -import { useProjectTopologies } from '../../../data/topology' -import { usePortfolio } from '../../../data/project' - -const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { - const { data: portfolio } = usePortfolio(portfolioId) - const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) - const { data: traces = [] } = useTraces() - const { data: schedulers = [] } = useSchedulers() - - const [isSubmitted, setSubmitted] = useState(false) - const [traceLoad, setTraceLoad] = useState(100) - const [trace, setTrace] = useState(undefined) - const [topology, setTopology] = useState(undefined) - const [scheduler, setScheduler] = useState(undefined) - const [failuresEnabled, setFailuresEnabled] = useState(false) - const [opPhenEnabled, setOpPhenEnabled] = useState(false) - const nameInput = useRef(null) - - const resetState = () => { - setSubmitted(false) - setTraceLoad(100) - setTrace(undefined) - setTopology(undefined) - setScheduler(undefined) - setFailuresEnabled(false) - setOpPhenEnabled(false) - nameInput.current.value = '' - } - - const onSubmit = (event) => { - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - - onSubmitUpstream( - name, - portfolio._id, - { - traceId: trace || traces[0]._id, - loadSamplingFraction: traceLoad / 100, - }, - { - topologyId: topology || topologies[0]._id, - }, - { - failuresEnabled, - performanceInterferenceEnabled: opPhenEnabled, - schedulerName: scheduler || schedulers[0].name, - } - ) - - resetState() - return true - } - const onCancel = () => { - onCancelUpstream() - resetState() - } - - return ( - -
- - - - - - - {traces.map((trace) => ( - - ))} - - - - setTraceLoad((load) => load - 1)} - onPlus={() => setTraceLoad((load) => load + 1)} - onChange={(e) => setTraceLoad(Number(e.target.value))} - unit="%" - /> - - - - - - {topologies.map((topology) => ( - - ))} - - - - - - {schedulers.map((scheduler) => ( - - ))} - - - - - setFailuresEnabled((e) => !e)} - /> - setOpPhenEnabled((e) => !e)} - /> - -
-
- ) -} - -NewScenarioModal.propTypes = { - portfolioId: PropTypes.string, - isOpen: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js deleted file mode 100644 index 49952aec..00000000 --- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js +++ /dev/null @@ -1,81 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import Modal from '../Modal' -import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' -import { useProjectTopologies } from '../../../data/topology' - -const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { - const nameInput = useRef(null) - const [isSubmitted, setSubmitted] = useState(false) - const [originTopology, setOriginTopology] = useState(-1) - const [errors, setErrors] = useState({}) - - const { data: topologies = [] } = useProjectTopologies(projectId) - - const clearState = () => { - nameInput.current.value = '' - setSubmitted(false) - setOriginTopology(-1) - setErrors({}) - } - - const onSubmit = (event) => { - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - - if (!name) { - setErrors({ name: true }) - return false - } else if (originTopology === -1) { - onSubmitUpstream(name) - } else { - onSubmitUpstream(name, originTopology) - } - - clearState() - return true - } - - const onCancel = () => { - onCancelUpstream() - clearState() - } - - return ( - -
- - - - - - - {topologies.map((topology) => ( - - ))} - - -
-
- ) -} - -NewTopologyModal.propTypes = { - projectId: PropTypes.string, - isOpen: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js new file mode 100644 index 00000000..856282a7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { PlusIcon } from '@patternfly/react-icons' +import { Button } from '@patternfly/react-core' +import { useState } from 'react' +import { useMutation } from 'react-query' +import NewScenarioModal from './NewScenarioModal' + +function NewScenario({ portfolioId }) { + const [isVisible, setVisible] = useState(false) + const { mutate: addScenario } = useMutation('addScenario') + + const onSubmit = (name, portfolioId, trace, topology, operational) => { + addScenario({ + portfolioId, + name, + trace, + topology, + operational, + }) + setVisible(false) + } + + return ( + <> + + setVisible(false)} + /> + + ) +} + +NewScenario.propTypes = { + portfolioId: PropTypes.string, +} + +export default NewScenario diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js new file mode 100644 index 00000000..7f620c8c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js @@ -0,0 +1,159 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from '../util/modals/Modal' +import { + Checkbox, + Form, + FormGroup, + FormSection, + FormSelect, + FormSelectOption, + NumberInput, + TextInput, +} from '@patternfly/react-core' +import { useSchedulers, useTraces } from '../../data/experiments' +import { useProjectTopologies } from '../../data/topology' +import { usePortfolio } from '../../data/project' + +const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId) + const { data: traces = [] } = useTraces() + const { data: schedulers = [] } = useSchedulers() + + // eslint-disable-next-line no-unused-vars + const [isSubmitted, setSubmitted] = useState(false) + const [traceLoad, setTraceLoad] = useState(100) + const [trace, setTrace] = useState(undefined) + const [topology, setTopology] = useState(undefined) + const [scheduler, setScheduler] = useState(undefined) + const [failuresEnabled, setFailuresEnabled] = useState(false) + const [opPhenEnabled, setOpPhenEnabled] = useState(false) + const nameInput = useRef(null) + + const resetState = () => { + setSubmitted(false) + setTraceLoad(100) + setTrace(undefined) + setTopology(undefined) + setScheduler(undefined) + setFailuresEnabled(false) + setOpPhenEnabled(false) + nameInput.current.value = '' + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + onSubmitUpstream( + name, + portfolio._id, + { + traceId: trace || traces[0]._id, + loadSamplingFraction: traceLoad / 100, + }, + { + topologyId: topology || topologies[0]._id, + }, + { + failuresEnabled, + performanceInterferenceEnabled: opPhenEnabled, + schedulerName: scheduler || schedulers[0].name, + } + ) + + resetState() + return true + } + const onCancel = () => { + onCancelUpstream() + resetState() + } + + return ( + +
+ + + + + + + {traces.map((trace) => ( + + ))} + + + + setTraceLoad((load) => load - 1)} + onPlus={() => setTraceLoad((load) => load + 1)} + onChange={(e) => setTraceLoad(Number(e.target.value))} + unit="%" + /> + + + + + + {topologies.map((topology) => ( + + ))} + + + + + + {schedulers.map((scheduler) => ( + + ))} + + + + + setFailuresEnabled((e) => !e)} + /> + setOpPhenEnabled((e) => !e)} + /> + +
+
+ ) +} + +NewScenarioModal.propTypes = { + portfolioId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js new file mode 100644 index 00000000..66691580 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' + +function ScenarioState({ state }) { + switch (state) { + case 'CLAIMED': + case 'QUEUED': + return ( + + Queued + + ) + case 'RUNNING': + return ( + + Running + + ) + case 'FINISHED': + return ( + + Finished + + ) + case 'FAILED': + return ( + + Failed + + ) + } + + return 'Unknown' +} + +ScenarioState.propTypes = { + state: PropTypes.oneOf(['QUEUED', 'CLAIMED', 'RUNNING', 'FINISHED', 'FAILED']), +} + +export default ScenarioState diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js new file mode 100644 index 00000000..9966e3ba --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import Link from 'next/link' +import { Table, TableBody, TableHeader } from '@patternfly/react-table' +import React from 'react' +import TableEmptyState from '../util/TableEmptyState' +import ScenarioState from './ScenarioState' +import { usePortfolio, usePortfolioScenarios } from '../../data/project' +import { useProjectTopologies } from '../../data/topology' +import { useMutation } from 'react-query' + +const ScenarioTable = ({ portfolioId }) => { + const { data: portfolio } = usePortfolio(portfolioId) + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + const { data: topologies } = useProjectTopologies(portfolio?.projectId, { + select: (topologies) => new Map(topologies.map((topology) => [topology._id, topology])), + }) + + const { mutate: deleteScenario } = useMutation('deleteScenario') + + const columns = ['Name', 'Topology', 'Trace', 'State'] + const rows = + scenarios.length > 0 + ? scenarios.map((scenario) => { + const topology = topologies?.get(scenario.topology.topologyId) + + return [ + scenario.name, + { + title: topology ? ( + + {topology.name} + + ) : ( + 'Unknown Topology' + ), + }, + scenario.trace.traceId, + { title: }, + ] + }) + : [ + { + heightAuto: true, + cells: [ + { + props: { colSpan: 4 }, + title: ( + + ), + }, + ], + }, + ] + + const actionResolver = (_, { rowIndex }) => [ + { + title: 'Delete Scenario', + onClick: (_, rowId) => deleteScenario(scenarios[rowId]._id), + isDisabled: rowIndex === 0, + }, + ] + + return ( + 0 ? actionResolver : undefined} + > + + +
+ ) +} + +ScenarioTable.propTypes = { + portfolioId: PropTypes.string, +} + +export default ScenarioTable diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js new file mode 100644 index 00000000..09348e60 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { Tooltip } from '@patternfly/react-core' +import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' +import { METRIC_DESCRIPTIONS } from '../../../util/available-metrics' + +function PortfolioResultInfo({ metric }) { + return ( + {METRIC_DESCRIPTIONS[metric]}}> + + + ) +} + +PortfolioResultInfo.propTypes = { + metric: PropTypes.string.isRequired, +} + +export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js new file mode 100644 index 00000000..f1883e68 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js @@ -0,0 +1,134 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' +import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../../util/available-metrics' +import { mean, std } from 'mathjs' +import approx from 'approximate-number' +import { + Bullseye, + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Grid, + GridItem, + Spinner, + Title, +} from '@patternfly/react-core' +import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' +import { usePortfolioScenarios } from '../../../data/project' +import PortfolioResultInfo from './PortfolioResultInfo' +import NewScenario from '../NewScenario' + +const PortfolioResults = ({ portfolioId }) => { + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + + if (status === 'loading') { + return ( + + + + + Loading Results + + + + ) + } else if (status === 'error') { + return ( + + + + + Unable to connect + + + There was an error retrieving data. Check your connection and try again. + + + + ) + } else if (scenarios.length === 0) { + return ( + + + + + No results + + + No results are currently available for this portfolio. Run a scenario to obtain simulation + results. + + + + + ) + } + + const dataPerMetric = {} + + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios + .filter((scenario) => scenario.results) + .map((scenario) => ({ + name: scenario.name, + value: mean(scenario.results[metric]), + errorX: std(scenario.results[metric]), + })) + }) + + return ( + + {AVAILABLE_METRICS.map((metric) => ( + + + + + + + {METRIC_NAMES[metric]} + + + + + + approx(tick)} + label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} + type="number" + /> + + + + + + + + + + + ))} + + ) +} + +PortfolioResults.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js index ae4cb9cd..87ea059d 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js @@ -24,8 +24,8 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import NewPortfolioModal from '../modals/custom-components/NewPortfolioModal' import { useMutation } from 'react-query' +import NewPortfolioModal from './NewPortfolioModal' function NewPortfolio({ projectId }) { const [isVisible, setVisible] = useState(false) diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js new file mode 100644 index 00000000..4276d7d4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { + Form, + FormGroup, + FormSection, + NumberInput, + Select, + SelectGroup, + SelectOption, + SelectVariant, + TextInput, +} from '@patternfly/react-core' +import Modal from '../util/modals/Modal' +import { METRIC_GROUPS, METRIC_NAMES } from '../../util/available-metrics' + +const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { + const nameInput = useRef(null) + const [repeats, setRepeats] = useState(1) + const [isSelectOpen, setSelectOpen] = useState(false) + const [selectedMetrics, setSelectedMetrics] = useState([]) + + const [isSubmitted, setSubmitted] = useState(false) + const [errors, setErrors] = useState({}) + + const clearState = () => { + setSubmitted(false) + setErrors({}) + nameInput.current.value = '' + setRepeats(1) + setSelectOpen(false) + setSelectedMetrics([]) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else { + onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats }) + } + + clearState() + return false + } + const onCancel = () => { + onUpstreamCancel() + clearState() + } + + const onSelect = (event, selection) => { + if (selectedMetrics.includes(selection)) { + setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) + } else { + setSelectedMetrics((metrics) => [...metrics, selection]) + } + } + + return ( + +
+ + + + + + + + + + + setRepeats(Number(e.target.value))} + onPlus={() => setRepeats((r) => r + 1)} + onMinus={() => setRepeats((r) => r - 1)} + min={1} + /> + + +
+
+ ) +} + +NewPortfolioModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js index 4f5d79cf..984264dc 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js @@ -1,9 +1,9 @@ import React, { useState } from 'react' -import TextInputModal from '../../components/modals/TextInputModal' import { Button } from '@patternfly/react-core' import { useMutation } from 'react-query' import { PlusIcon } from '@patternfly/react-icons' import { buttonContainer } from './NewProject.module.scss' +import TextInputModal from '../util/modals/TextInputModal' /** * A container for creating a new project. diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js b/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js deleted file mode 100644 index 6d4f48c1..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { PlusIcon } from '@patternfly/react-icons' -import { Button } from '@patternfly/react-core' -import { useState } from 'react' -import NewScenarioModal from '../modals/custom-components/NewScenarioModal' -import { useMutation } from 'react-query' - -function NewScenario({ portfolioId }) { - const [isVisible, setVisible] = useState(false) - const { mutate: addScenario } = useMutation('addScenario') - - const onSubmit = (name, portfolioId, trace, topology, operational) => { - addScenario({ - portfolioId, - name, - trace, - topology, - operational, - }) - setVisible(false) - } - - return ( - <> - - setVisible(false)} - /> - - ) -} - -NewScenario.propTypes = { - portfolioId: PropTypes.string, -} - -export default NewScenario diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js index c6c4171b..bf59e020 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js @@ -24,9 +24,9 @@ import PropTypes from 'prop-types' import { PlusIcon } from '@patternfly/react-icons' import { Button } from '@patternfly/react-core' import { useState } from 'react' -import NewTopologyModal from '../modals/custom-components/NewTopologyModal' import { useDispatch } from 'react-redux' import { addTopology } from '../../redux/actions/topologies' +import NewTopologyModal from './NewTopologyModal' function NewTopology({ projectId }) { const [isVisible, setVisible] = useState(false) diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js new file mode 100644 index 00000000..a495f73e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' +import { useProjectTopologies } from '../../data/topology' +import Modal from '../util/modals/Modal' + +const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { + const nameInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [originTopology, setOriginTopology] = useState(-1) + const [errors, setErrors] = useState({}) + + const { data: topologies = [] } = useProjectTopologies(projectId) + + const clearState = () => { + nameInput.current.value = '' + setSubmitted(false) + setOriginTopology(-1) + setErrors({}) + } + + const onSubmit = (event) => { + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + + if (!name) { + setErrors({ name: true }) + return false + } else if (originTopology === -1) { + onSubmitUpstream(name) + } else { + onSubmitUpstream(name, originTopology) + } + + clearState() + return true + } + + const onCancel = () => { + onCancelUpstream() + clearState() + } + + return ( + +
+ + + + + + + {topologies.map((topology) => ( + + ))} + + +
+
+ ) +} + +NewTopologyModal.propTypes = { + projectId: PropTypes.string, + isOpen: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js deleted file mode 100644 index 285345e7..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { ClockIcon, InfoIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' - -function ScenarioState({ state }) { - switch (state) { - case 'CLAIMED': - case 'QUEUED': - return ( - - Queued - - ) - case 'RUNNING': - return ( - - Running - - ) - case 'FINISHED': - return ( - - Finished - - ) - case 'FAILED': - return ( - - Failed - - ) - } - - return 'Unknown' -} - -ScenarioState.propTypes = { - state: PropTypes.oneOf(['QUEUED', 'CLAIMED', 'RUNNING', 'FINISHED', 'FAILED']), -} - -export default ScenarioState diff --git a/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js deleted file mode 100644 index 9966e3ba..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import Link from 'next/link' -import { Table, TableBody, TableHeader } from '@patternfly/react-table' -import React from 'react' -import TableEmptyState from '../util/TableEmptyState' -import ScenarioState from './ScenarioState' -import { usePortfolio, usePortfolioScenarios } from '../../data/project' -import { useProjectTopologies } from '../../data/topology' -import { useMutation } from 'react-query' - -const ScenarioTable = ({ portfolioId }) => { - const { data: portfolio } = usePortfolio(portfolioId) - const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) - const { data: topologies } = useProjectTopologies(portfolio?.projectId, { - select: (topologies) => new Map(topologies.map((topology) => [topology._id, topology])), - }) - - const { mutate: deleteScenario } = useMutation('deleteScenario') - - const columns = ['Name', 'Topology', 'Trace', 'State'] - const rows = - scenarios.length > 0 - ? scenarios.map((scenario) => { - const topology = topologies?.get(scenario.topology.topologyId) - - return [ - scenario.name, - { - title: topology ? ( - - {topology.name} - - ) : ( - 'Unknown Topology' - ), - }, - scenario.trace.traceId, - { title: }, - ] - }) - : [ - { - heightAuto: true, - cells: [ - { - props: { colSpan: 4 }, - title: ( - - ), - }, - ], - }, - ] - - const actionResolver = (_, { rowIndex }) => [ - { - title: 'Delete Scenario', - onClick: (_, rowId) => deleteScenario(scenarios[rowId]._id), - isDisabled: rowIndex === 0, - }, - ] - - return ( - 0 ? actionResolver : undefined} - > - - -
- ) -} - -ScenarioTable.propTypes = { - portfolioId: PropTypes.string, -} - -export default ScenarioTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js new file mode 100644 index 00000000..ccf637e5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch } from 'react-redux' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' +import GrayLayer from './elements/GrayLayer' + +function GrayContainer() { + const dispatch = useDispatch() + const onClick = () => dispatch(goDownOneInteractionLevel()) + return +} + +export default GrayContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js new file mode 100644 index 00000000..4c3b2757 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js @@ -0,0 +1,25 @@ +export const MAP_SIZE = 50 +export const TILE_SIZE_IN_PIXELS = 100 +export const TILE_SIZE_IN_METERS = 0.5 +export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS + +export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5 +export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3 +export const OBJECT_SIZE_IN_PIXELS = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 + +export const GRID_LINE_WIDTH_IN_PIXELS = 2 +export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 +export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 +export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 + +export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 +export const RACK_FILL_ICON_OPACITY = 0.8 + +export const MAP_MOVE_PIXELS_PER_EVENT = 20 +export const MAP_SCALE_PER_EVENT = 1.1 +export const MAP_MIN_SCALE = 0.5 +export const MAP_MAX_SCALE = 1.5 + +export const MAX_NUM_UNITS_PER_MACHINE = 6 +export const DEFAULT_RACK_SLOT_CAPACITY = 42 +export const DEFAULT_RACK_POWER_CAPACITY = 10000 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 new file mode 100644 index 00000000..5d19b3ad --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js @@ -0,0 +1,83 @@ +import React, { useRef, useState } from 'react' +import { HotKeys } from 'react-hotkeys' +import { Stage } from 'react-konva' +import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' +import { Provider, useStore } from 'react-redux' +import useResizeObserver from 'use-resize-observer' +import { mapContainer } from './MapStage.module.scss' +import MapLayer from './layers/MapLayer' +import RoomHoverLayer from './layers/RoomHoverLayer' +import ObjectHoverLayer from './layers/ObjectHoverLayer' +import ScaleIndicator from './controls/ScaleIndicator' +import Toolbar from './controls/Toolbar' + +function MapStage() { + const store = useStore() + const { ref, width = 100, height = 100 } = useResizeObserver() + const stageRef = useRef(null) + const [[x, y], setPos] = useState([0, 0]) + const [scale, setScale] = useState(1) + + const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) + const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) + + const onZoom = (e) => { + e.evt.preventDefault() + + const stage = stageRef.current.getStage() + const oldScale = scale + + const pointer = stage.getPointerPosition() + const mousePointTo = { + x: (pointer.x - x) / oldScale, + y: (pointer.y - y) / oldScale, + } + + const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) + + setScale(newScale) + setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) + } + const onZoomButton = (zoomIn) => + setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) + const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) + const onExport = () => { + const download = document.createElement('a') + download.href = stageRef.current.getStage().toDataURL() + download.download = 'opendc-canvas-export-' + Date.now() + '.png' + download.click() + } + + const handlers = { + MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), + MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), + MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), + } + + return ( + + + + + + + + + + + + ) +} + +export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss new file mode 100644 index 00000000..d879b4c8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss @@ -0,0 +1,31 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.mapContainer { + background-color: var(--pf-global--Color--light-200); + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js new file mode 100644 index 00000000..14449a91 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useSelector } from 'react-redux' +import { Tile } from '../../../shapes' +import RackGroup from './groups/RackGroup' + +function RackContainer({ tile }) { + const interactionLevel = useSelector((state) => state.interactionLevel) + return +} + +RackContainer.propTypes = { + tile: Tile, +} + +export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js new file mode 100644 index 00000000..c35cbde7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js @@ -0,0 +1,34 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import RackFillBar from './elements/RackFillBar' + +function RackSpaceFillContainer({ tileId, ...props }) { + const fillFraction = useSelector((state) => { + let energyConsumptionTotal = 0 + const rack = state.objects.rack[state.objects.tile[tileId].rack] + const machineIds = rack.machines + machineIds.forEach((machineId) => { + if (machineId !== null) { + const machine = state.objects.machine[machineId] + machine.cpus.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW)) + machine.gpus.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW)) + machine.memories.forEach( + (id) => (energyConsumptionTotal += state.objects.memory[id].energyConsumptionW) + ) + machine.storages.forEach( + (id) => (energyConsumptionTotal += state.objects.storage[id].energyConsumptionW) + ) + } + }) + + return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) + }) + return +} + +RackSpaceFillContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js new file mode 100644 index 00000000..a6766f33 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import RackFillBar from './elements/RackFillBar' + +function RackSpaceFillContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + return +} + +RackSpaceFillContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js new file mode 100644 index 00000000..93ba9c93 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level' +import RoomGroup from './groups/RoomGroup' + +function RoomContainer({ roomId, ...props }) { + const state = useSelector((state) => { + return { + interactionLevel: state.interactionLevel, + currentRoomInConstruction: state.construction.currentRoomInConstruction, + room: state.objects.room[roomId], + } + }) + const dispatch = useDispatch() + return dispatch(goFromBuildingToRoom(roomId))} /> +} + +RoomContainer.propTypes = { + roomId: PropTypes.string, +} + +export default RoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js new file mode 100644 index 00000000..149e26a1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useDispatch, useSelector } from 'react-redux' +import { goFromRoomToRack } from '../../../redux/actions/interaction-level' +import TileGroup from './groups/TileGroup' + +function TileContainer({ tileId, ...props }) { + const interactionLevel = useSelector((state) => state.interactionLevel) + const tile = useSelector((state) => state.objects.tile[tileId]) + + const dispatch = useDispatch() + const onClick = (tile) => { + if (tile.rack) { + dispatch(goFromRoomToRack(tile._id)) + } + } + return +} + +TileContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js new file mode 100644 index 00000000..eaebabd5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useSelector } from 'react-redux' +import { useActiveTopology } from '../../../data/topology' +import TopologyGroup from './groups/TopologyGroup' + +function TopologyContainer() { + const topology = useActiveTopology() + const interactionLevel = useSelector((state) => state.interactionLevel) + + return +} + +export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js new file mode 100644 index 00000000..77f553dd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { useSelector } from 'react-redux' +import WallGroup from './groups/WallGroup' + +function WallContainer({ roomId, ...props }) { + const tiles = useSelector((state) => state.objects.room[roomId].tiles.map((tileId) => state.objects.tile[tileId])) + return +} + +WallContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default WallContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js new file mode 100644 index 00000000..f54b7c84 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { ChevronLeftIcon } from '@patternfly/react-icons' +import { collapseContainer } from './Collapse.module.scss' +import { Button } from '@patternfly/react-core' + +function Collapse({ onClick }) { + return ( +
+ +
+ ) +} + +Collapse.propTypes = { + onClick: PropTypes.func, +} + +export default Collapse diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss new file mode 100644 index 00000000..0c1fac94 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss @@ -0,0 +1,55 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.collapseContainer { + position: absolute; + right: var(--pf-global--spacer--xs); + top: 0; + bottom: 10%; + margin: auto 0; + height: 50px; + + button:global(.pf-m-tertiary) { + height: 100%; + padding: 2px; + + margin-right: var(--pf-global--spacer--xs); + margin-top: var(--pf-global--spacer--xs); + background-color: var(--pf-global--BackgroundColor--100); + border: none; + border-radius: var(--pf-global--BorderRadius--sm); + box-shadow: var(--pf-global--BoxShadow--sm); + + &:not(:global(.pf-m-disabled)) { + background-color: var(--pf-global--BackgroundColor--100); + } + + &:after { + display: none; + } + + &:hover { + border: none; + box-shadow: var(--pf-global--BoxShadow--md); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js new file mode 100644 index 00000000..58d2ccc9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js @@ -0,0 +1,18 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { scaleIndicator } from './ScaleIndicator.module.scss' + +function ScaleIndicator({ scale }) { + return ( +
+ {TILE_SIZE_IN_METERS}m +
+ ) +} + +ScaleIndicator.propTypes = { + scale: PropTypes.number.isRequired, +} + +export default ScaleIndicator diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss new file mode 100644 index 00000000..f19e0ff2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss @@ -0,0 +1,10 @@ +.scaleIndicator { + position: absolute; + right: 10px; + bottom: 10px; + z-index: 50; + + border: solid 2px #212529; + border-top: none; + border-left: none; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js new file mode 100644 index 00000000..469fd515 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { control, toolBar } from './Toolbar.module.scss' +import { Button } from '@patternfly/react-core' +import { SearchPlusIcon, SearchMinusIcon } from '@patternfly/react-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCamera } from '@fortawesome/free-solid-svg-icons' + +function Toolbar({ onZoom, onExport }) { + return ( +
+ + + +
+ ) +} + +Toolbar.propTypes = { + onZoom: PropTypes.func, + onExport: PropTypes.func, +} + +export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss new file mode 100644 index 00000000..0d505acc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss @@ -0,0 +1,29 @@ +.toolBar { + position: absolute; + bottom: var(--pf-global--spacer--md); + left: var(--pf-global--spacer--xl); +} + +.control { + &:global(.pf-m-tertiary) { + margin-right: var(--pf-global--spacer--xs); + margin-top: var(--pf-global--spacer--xs); + background-color: var(--pf-global--BackgroundColor--100); + border: none; + border-radius: var(--pf-global--BorderRadius--sm); + box-shadow: var(--pf-global--BoxShadow--sm); + + &:not(:global(.pf-m-disabled)) { + background-color: var(--pf-global--BackgroundColor--100); + } + + &:after { + display: none; + } + + &:hover { + border: none; + box-shadow: var(--pf-global--BoxShadow--md); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js new file mode 100644 index 00000000..93037b51 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js @@ -0,0 +1,10 @@ +import React from 'react' +import { Rect } from 'react-konva' +import { BACKDROP_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +function Backdrop() { + return +} + +export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js new file mode 100644 index 00000000..08c687f6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +function GrayLayer({ onClick }) { + return ( + + ) +} + +GrayLayer.propTypes = { + onClick: PropTypes.func, +} + +export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js new file mode 100644 index 00000000..20c2c6d1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js @@ -0,0 +1,30 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function HoverTile({ x, y, isValid, scale = 1, onClick }) { + return ( + + ) +} + +HoverTile.propTypes = { + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + isValid: PropTypes.bool.isRequired, + scale: PropTypes.number, + onClick: PropTypes.func.isRequired, +} + +export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js new file mode 100644 index 00000000..7d304b6b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { useEffect, useState } from 'react' +import { Image } from 'react-konva' + +const imageCaches = {} + +function ImageComponent({ src, x, y, width, height, opacity }) { + const [image, setImage] = useState(null) + + useEffect(() => { + if (imageCaches[src]) { + setImage(imageCaches[src]) + return + } + + const image = new window.Image() + image.src = src + image.onload = () => { + setImage(image) + imageCaches[src] = image + } + }, [src]) + + return +} + +ImageComponent.propTypes = { + src: PropTypes.string.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + opacity: PropTypes.number.isRequired, +} + +export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js new file mode 100644 index 00000000..aa284944 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js @@ -0,0 +1,68 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group, Rect } from 'react-konva' +import { + RACK_ENERGY_BAR_BACKGROUND_COLOR, + RACK_ENERGY_BAR_FILL_COLOR, + RACK_SPACE_BAR_BACKGROUND_COLOR, + RACK_SPACE_BAR_FILL_COLOR, +} from '../../../../util/colors' +import { + OBJECT_BORDER_WIDTH_IN_PIXELS, + OBJECT_MARGIN_IN_PIXELS, + RACK_FILL_ICON_OPACITY, + RACK_FILL_ICON_WIDTH, + TILE_SIZE_IN_PIXELS, +} from '../MapConstants' +import ImageComponent from './ImageComponent' + +function RackFillBar({ positionX, positionY, type, fillFraction }) { + const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 + const x = + positionX * TILE_SIZE_IN_PIXELS + + OBJECT_MARGIN_IN_PIXELS + + (type === 'space' ? halfOfObjectBorderWidth : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)) + const startY = positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS + halfOfObjectBorderWidth + const width = 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - halfOfObjectBorderWidth + const fullHeight = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - OBJECT_BORDER_WIDTH_IN_PIXELS + + const fractionHeight = fillFraction * fullHeight + const fractionY = + (positionY + 1) * TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS - halfOfObjectBorderWidth - fractionHeight + + return ( + + + + + + ) +} + +RackFillBar.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + type: PropTypes.string.isRequired, + fillFraction: PropTypes.number.isRequired, +} + +export default RackFillBar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js new file mode 100644 index 00000000..e7329dc0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { Tile } from '../../../../shapes' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function RoomTile({ tile, color }) { + return ( + + ) +} + +RoomTile.propTypes = { + tile: Tile, + color: PropTypes.string, +} + +export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js new file mode 100644 index 00000000..3211f187 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { OBJECT_BORDER_COLOR } from '../../../../util/colors' +import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function TileObject({ positionX, positionY, color }) { + return ( + + ) +} + +TileObject.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, +} + +export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js new file mode 100644 index 00000000..186c2b3a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js @@ -0,0 +1,44 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group, Line } from 'react-konva' +import { TILE_PLUS_COLOR } from '../../../../util/colors' +import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +function TilePlusIcon({ x, y, scale = 1 }) { + const linePoints = [ + [ + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_PLUS_MARGIN_IN_PIXELS * scale, + x + 0.5 * TILE_SIZE_IN_PIXELS * scale, + y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + ], + [ + x + TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, + y + 0.5 * TILE_SIZE_IN_PIXELS * scale, + ], + ] + return ( + + {linePoints.map((points, index) => ( + + ))} + + ) +} + +TilePlusIcon.propTypes = { + x: PropTypes.number, + y: PropTypes.number, + scale: PropTypes.number, +} + +export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js new file mode 100644 index 00000000..4f18813e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Line } from 'react-konva' +import { WallSegment as WallSegmentShape } from '../../../../shapes' +import { WALL_COLOR } from '../../../../util/colors' +import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' + +function WallSegment({ wallSegment }) { + let points + if (wallSegment.isHorizontal) { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + ] + } else { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS, + ] + } + + return +} + +WallSegment.propTypes = { + wallSegment: WallSegmentShape, +} + +export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js new file mode 100644 index 00000000..d66a18de --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js @@ -0,0 +1,36 @@ +import React from 'react' +import { Group, Line } from 'react-konva' +import { GRID_COLOR } from '../../../../util/colors' +import { GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i) +const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, + index * TILE_SIZE_IN_PIXELS, +]) +const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ + index * TILE_SIZE_IN_PIXELS, + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, +]) + +function GridGroup() { + return ( + + {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( + + ))} + + ) +} + +export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js new file mode 100644 index 00000000..46030135 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js @@ -0,0 +1,25 @@ +import React from 'react' +import { Group } from 'react-konva' +import { Tile } from '../../../../shapes' +import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' +import TileObject from '../elements/TileObject' +import RackSpaceFillContainer from '../RackSpaceFillContainer' +import RackEnergyFillContainer from '../RackEnergyFillContainer' + +function RackGroup({ tile }) { + return ( + + + + + + + + ) +} + +RackGroup.propTypes = { + tile: Tile, +} + +export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js new file mode 100644 index 00000000..a42e7bb7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js @@ -0,0 +1,52 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import { InteractionLevel, Room } from '../../../../shapes' +import GrayContainer from '../GrayContainer' +import TileContainer from '../TileContainer' +import WallContainer from '../WallContainer' + +function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) { + if (currentRoomInConstruction === room._id) { + return ( + + {room.tiles.map((tileId) => ( + + ))} + + ) + } + + return ( + + {(() => { + if ( + (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && + interactionLevel.roomId === room._id + ) { + return [ + room.tiles + .filter((tileId) => tileId !== interactionLevel.tileId) + .map((tileId) => ), + , + room.tiles + .filter((tileId) => tileId === interactionLevel.tileId) + .map((tileId) => ), + ] + } else { + return room.tiles.map((tileId) => ) + } + })()} + + + ) +} + +RoomGroup.propTypes = { + room: Room, + interactionLevel: InteractionLevel, + currentRoomInConstruction: PropTypes.string, + onClick: PropTypes.func, +} + +export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js new file mode 100644 index 00000000..f2084017 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import { Tile } from '../../../../shapes' +import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' +import RoomTile from '../elements/RoomTile' +import RackContainer from '../RackContainer' + +function TileGroup({ tile, newTile, onClick }) { + let tileObject + if (tile.rack) { + tileObject = + } else { + tileObject = null + } + + let color = ROOM_DEFAULT_COLOR + if (newTile) { + color = ROOM_IN_CONSTRUCTION_COLOR + } + + return ( + onClick(tile)}> + + {tileObject} + + ) +} + +TileGroup.propTypes = { + tile: Tile, + newTile: PropTypes.bool, + onClick: PropTypes.func, +} + +export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js new file mode 100644 index 00000000..011dcf34 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js @@ -0,0 +1,44 @@ +import React from 'react' +import { Group } from 'react-konva' +import { InteractionLevel, Topology } from '../../../../shapes' +import RoomContainer from '../RoomContainer' +import GrayContainer from '../GrayContainer' + +function TopologyGroup({ topology, interactionLevel }) { + if (!topology) { + return + } + + if (interactionLevel.mode === 'BUILDING') { + return ( + + {topology.rooms.map((roomId) => ( + + ))} + + ) + } + + return ( + + {topology.rooms + .filter((roomId) => roomId !== interactionLevel.roomId) + .map((roomId) => ( + + ))} + {interactionLevel.mode === 'ROOM' ? : null} + {topology.rooms + .filter((roomId) => roomId === interactionLevel.roomId) + .map((roomId) => ( + + ))} + + ) +} + +TopologyGroup.propTypes = { + topology: Topology, + interactionLevel: InteractionLevel, +} + +export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js new file mode 100644 index 00000000..6cbd1cd0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import { Tile } from '../../../../shapes' +import { deriveWallLocations } from '../../../../util/tile-calculations' +import WallSegment from '../elements/WallSegment' + +function WallGroup({ tiles }) { + return ( + + {deriveWallLocations(tiles).map((wallSegment, index) => ( + + ))} + + ) +} + +WallGroup.propTypes = { + tiles: PropTypes.arrayOf(Tile).isRequired, +} + +export default WallGroup 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 new file mode 100644 index 00000000..2b1060c0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js @@ -0,0 +1,55 @@ +import PropTypes from 'prop-types' +import React, { useMemo, useState } from 'react' +import { Layer } from 'react-konva/lib/ReactKonva' +import HoverTile from '../elements/HoverTile' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' +import { useEffectRef } from '../../../../util/effect-ref' + +function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { + const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) + + const layerRef = useEffectRef((layer) => { + if (!layer) { + return + } + + const stage = layer.getStage() + + // Transform used to convert mouse coordinates to world coordinates + const transform = stage.getAbsoluteTransform().copy() + transform.invert() + + stage.on('mousemove.hover', () => { + const { x, y } = transform.point(stage.getPointerPosition()) + setPos([x, y]) + }) + return () => stage.off('mousemove.hover') + }) + + const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) + const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) + const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) + + if (!isEnabled) { + return + } + + const x = gridX * TILE_SIZE_IN_PIXELS + const y = gridY * TILE_SIZE_IN_PIXELS + + return ( + + (valid ? onClick(gridX, gridY) : undefined)} /> + {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} + + ) +} + +HoverLayerComponent.propTypes = { + isEnabled: PropTypes.bool.isRequired, + isValid: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + children: PropTypes.node, +} + +export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js new file mode 100644 index 00000000..c902532b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { Group, Layer } from 'react-konva' +import Backdrop from '../elements/Backdrop' +import TopologyContainer from '../TopologyContainer' +import GridGroup from '../groups/GridGroup' + +function MapLayer() { + return ( + + + + + + + + ) +} + +export default MapLayer 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 new file mode 100644 index 00000000..47d9c992 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { addRackToTile } from '../../../../redux/actions/topology/room' +import { findTileWithPosition } from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' +import TilePlusIcon from '../elements/TilePlusIcon' + +function ObjectHoverLayer() { + const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) + const isValid = useSelector((state) => (x, y) => { + if (state.interactionLevel.mode !== 'ROOM') { + return false + } + + const currentRoom = state.objects.room[state.interactionLevel.roomId] + const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId]) + const tile = findTileWithPosition(tiles, x, y) + + return !(tile === null || tile.rack) + }) + + const dispatch = useDispatch() + const onClick = (x, y) => dispatch(addRackToTile(x, y)) + return ( + + + + ) +} + +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 new file mode 100644 index 00000000..59f83b2b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' +import { + deriveValidNextTilePositions, + findPositionInPositions, + findPositionInRooms, +} from '../../../../util/tile-calculations' +import HoverLayerComponent from './HoverLayerComponent' + +function RoomHoverLayer() { + const dispatch = useDispatch() + const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) + const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isValid = useSelector((state) => (x, y) => { + const newRoom = { ...state.objects.room[state.construction.currentRoomInConstruction] } + const oldRooms = Object.keys(state.objects.room) + .map((id) => ({ ...state.objects.room[id] })) + .filter( + (room) => + state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 && + room._id !== state.construction.currentRoomInConstruction + ) + + ;[...oldRooms, newRoom].forEach((room) => { + room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId]) + }) + if (newRoom.tiles.length === 0) { + return findPositionInRooms(oldRooms, x, y) === -1 + } + + const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) + return findPositionInPositions(validNextPositions, x, y) !== -1 + }) + + return +} + +export default RoomHoverLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js new file mode 100644 index 00000000..ececd07b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js @@ -0,0 +1,69 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import { Button, TextInput } from '@patternfly/react-core' +import { PencilAltIcon, CheckIcon, TimesIcon } from '@patternfly/react-icons' + +function NameComponent({ name, onEdit }) { + const [isEditing, setEditing] = useState(false) + const nameInput = useRef(null) + + const onCancel = () => { + nameInput.current.value = name + setEditing(false) + } + + const onSubmit = (event) => { + if (event) { + event.preventDefault() + } + + const name = nameInput.current.value + if (name) { + onEdit(name) + } + + setEditing(false) + } + + return ( +
+
+
+ {name} +
+
+ +
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+ ) +} + +NameComponent.propTypes = { + name: PropTypes.string, + onEdit: PropTypes.func, +} + +export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js new file mode 100644 index 00000000..5d9340b2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js @@ -0,0 +1,83 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { InteractionLevel } from '../../../shapes' +import BuildingSidebar from './building/BuildingSidebar' +import { + Button, + DrawerActions, + DrawerCloseButton, + DrawerHead, + DrawerPanelBody, + DrawerPanelContent, + Flex, + Title, +} from '@patternfly/react-core' +import { AngleLeftIcon } from '@patternfly/react-icons' +import { useDispatch } from 'react-redux' +import { backButton } from './TopologySidebar.module.scss' +import RoomSidebar from './room/RoomSidebar' +import RackSidebar from './rack/RackSidebar' +import MachineSidebar from './machine/MachineSidebar' +import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' + +const name = { + BUILDING: 'Building', + ROOM: 'Room', + RACK: 'Rack', + MACHINE: 'Machine', +} + +function TopologySidebar({ interactionLevel, onClose }) { + let sidebarContent + + switch (interactionLevel.mode) { + case 'BUILDING': + sidebarContent = + break + case 'ROOM': + sidebarContent = + break + case 'RACK': + sidebarContent = + break + case 'MACHINE': + sidebarContent = + break + default: + sidebarContent = 'Missing Content' + } + + const dispatch = useDispatch() + const onClick = () => dispatch(goDownOneInteractionLevel()) + + return ( + + + + + + {name[interactionLevel.mode]} + + + + + + + {sidebarContent} + + ) +} + +TopologySidebar.propTypes = { + interactionLevel: InteractionLevel, + onClose: PropTypes.func, +} + +export default TopologySidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss new file mode 100644 index 00000000..45dc98da --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss @@ -0,0 +1,37 @@ +/*! + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +.backButton { + &:global(.pf-c-button) { + align-self: center; + --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--light-100); + color: var(--pf-global--Color--400); + + --pf-c-button--PaddingRight: var(--pf-global--spacer--sm); + --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm); + + &:hover, + &:focus { + --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--100); + } + } +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js new file mode 100644 index 00000000..5fcd46be --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js @@ -0,0 +1,8 @@ +import React from 'react' +import NewRoomConstructionContainer from './NewRoomConstructionContainer' + +function BuildingSidebar() { + return +} + +export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js new file mode 100644 index 00000000..9fc85d0c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core' +import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' +import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' + +function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { + if (currentRoomInConstruction === '-1') { + return ( + + ) + } + return ( + + + + + + + + + + + + + ) +} + +NewRoomConstructionComponent.propTypes = { + onStart: PropTypes.func, + onFinish: PropTypes.func, + onCancel: PropTypes.func, + currentRoomInConstruction: PropTypes.string, +} + +export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js new file mode 100644 index 00000000..c149b224 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { + cancelNewRoomConstruction, + finishNewRoomConstruction, + startNewRoomConstruction, +} from '../../../../redux/actions/topology/building' +import NewRoomConstructionComponent from './NewRoomConstructionComponent' + +function NewRoomConstructionButton() { + const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) + const dispatch = useDispatch() + + return ( + dispatch(startNewRoomConstruction())} + onFinish={() => dispatch(finishNewRoomConstruction())} + onCancel={() => dispatch(cancelNewRoomConstruction())} + currentRoomInConstruction={currentRoomInConstruction} + /> + ) +} + +export default NewRoomConstructionButton diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js new file mode 100644 index 00000000..00ce4603 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { Button } from '@patternfly/react-core' +import { TrashIcon } from '@patternfly/react-icons' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteMachine } from '../../../../redux/actions/topology/machine' + +function DeleteMachine() { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const rackId = useSelector((state) => state.objects.tile[state.interactionLevel.tileId].rack) + const position = useSelector((state) => state.interactionLevel.position) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteMachine(rackId, position)) + } + setVisible(false) + } + return ( + <> + + + + ) +} + +export default DeleteMachine 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 new file mode 100644 index 00000000..0c3dea98 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js @@ -0,0 +1,49 @@ +import PropTypes from 'prop-types' +import React from 'react' +import UnitTabsComponent from './UnitTabsComponent' +import DeleteMachine from './DeleteMachine' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function MachineSidebar({ tileId, position }) { + const machine = useSelector(({ objects }) => { + const rack = objects.rack[objects.tile[tileId].rack] + return objects.machine[rack.machines[position - 1]] + }) + const machineId = machine._id + return ( +
+ + Details + + Name + + Machine at position {machine.position} + + + + Actions + + + Units + +
+ +
+
+ ) +} + +MachineSidebar.propTypes = { + tileId: PropTypes.string.isRequired, + position: PropTypes.number.isRequired, +} + +export default MachineSidebar 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 new file mode 100644 index 00000000..88591208 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js @@ -0,0 +1,42 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { Button, InputGroup, Select, SelectOption, SelectVariant } from '@patternfly/react-core' +import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' + +function UnitAddComponent({ units, onAdd }) { + const [isOpen, setOpen] = useState(false) + const [selected, setSelected] = useState(null) + + return ( + + + + + ) +} + +UnitAddComponent.propTypes = { + units: PropTypes.array.isRequired, + onAdd: PropTypes.func.isRequired, +} + +export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js new file mode 100644 index 00000000..fc805b95 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import UnitAddComponent from './UnitAddComponent' +import { addUnit } from '../../../../redux/actions/topology/machine' + +function UnitAddContainer({ machineId, unitType }) { + const units = useSelector((state) => Object.values(state.objects[unitType])) + const dispatch = useDispatch() + + const onAdd = (id) => dispatch(addUnit(machineId, unitType, id)) + + return +} + +UnitAddContainer.propTypes = { + machineId: PropTypes.string.isRequired, + unitType: PropTypes.string.isRequired, +} + +export default UnitAddContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js new file mode 100644 index 00000000..daa3e7a7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js @@ -0,0 +1,112 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { + Button, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Popover, + Title, +} from '@patternfly/react-core' +import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons' +import { ProcessingUnit, StorageUnit } from '../../../../shapes' + +function UnitInfo({ unit, unitType }) { + if (unitType === 'cpu' || unitType === 'gpu') { + return ( + + + Clock Frequency + {unit.clockRateMhz} MHz + + + Number of Cores + {unit.numberOfCores} + + + Energy Consumption + {unit.energyConsumptionW} W + + + ) + } + + return ( + + + Speed + {unit.speedMbPerS} Mb/s + + + Capacity + {unit.sizeMb} MB + + + Energy Consumption + {unit.energyConsumptionW} W + + + ) +} + +UnitInfo.propTypes = { + unitType: PropTypes.string.isRequired, + unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired, +} + +function UnitListComponent({ unitType, units, onDelete }) { + if (units.length === 0) { + return ( + + + + No units found + + You have not configured any units yet. Add some with the menu above! + + ) + } + + return ( + + {units.map((unit, index) => ( + + + {unit.name}]} /> + + } + > + + + + + + + ))} + + ) +} + +UnitListComponent.propTypes = { + unitType: PropTypes.string.isRequired, + units: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, StorageUnit])).isRequired, + onDelete: PropTypes.func, +} + +export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js new file mode 100644 index 00000000..901fa45b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import UnitListComponent from './UnitListComponent' +import { deleteUnit } from '../../../../redux/actions/topology/machine' + +const unitMapping = { + cpu: 'cpus', + gpu: 'gpus', + memory: 'memories', + storage: 'storages', +} + +function UnitListContainer({ machineId, unitType }) { + const dispatch = useDispatch() + const units = useSelector((state) => { + const machine = state.objects.machine[machineId] + return machine[unitMapping[unitType]].map((id) => state.objects[unitType][id]) + }) + + const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id)) + + return +} + +UnitListContainer.propTypes = { + machineId: PropTypes.string.isRequired, + unitType: PropTypes.string.isRequired, +} + +export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js new file mode 100644 index 00000000..6d10d2df --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { Tab, Tabs, TabTitleText } from '@patternfly/react-core' +import UnitAddContainer from './UnitAddContainer' +import UnitListContainer from './UnitListContainer' + +function UnitTabsComponent({ machineId }) { + const [activeTab, setActiveTab] = useState('cpu-units') + + return ( + setActiveTab(tab)}> + CPU}> + + + + GPU}> + + + + Memory}> + + + + Storage}> + + + + + ) +} + +UnitTabsComponent.propTypes = { + machineId: PropTypes.string.isRequired, +} + +export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js new file mode 100644 index 00000000..e944c2e8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch } from 'react-redux' +import { Button } from '@patternfly/react-core' +import { SaveIcon } from '@patternfly/react-icons' +import { addPrefab } from '../../../../api/prefabs' + +function AddPrefab({ tileId }) { + const dispatch = useDispatch() + const onClick = () => dispatch(addPrefab('name', tileId)) + return ( + + ) +} + +AddPrefab.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js new file mode 100644 index 00000000..80c6349a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' +import { Button } from '@patternfly/react-core' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRack } from '../../../../redux/actions/topology/rack' + +function DeleteRackContainer({ tileId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteRack(tileId)) + } + setVisible(false) + } + return ( + <> + + + + ) +} + +DeleteRackContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js new file mode 100644 index 00000000..921622d6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js @@ -0,0 +1,46 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Image from 'next/image' +import { Flex, Label } from '@patternfly/react-core' +import { Machine } from '../../../../shapes' + +const UnitIcon = ({ id, type }) => ( + {'Machine +) + +UnitIcon.propTypes = { + id: PropTypes.string, + type: PropTypes.string, +} + +function MachineComponent({ machine, onClick }) { + const hasNoUnits = + machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 + + return ( + onClick()}> + {machine.cpus.length > 0 ? : undefined} + {machine.gpus.length > 0 ? : undefined} + {machine.memories.length > 0 ? : undefined} + {machine.storages.length > 0 ? : undefined} + {hasNoUnits ? ( + + ) : undefined} + + ) +} + +MachineComponent.propTypes = { + machine: Machine.isRequired, + onClick: PropTypes.func, +} + +export default MachineComponent 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 new file mode 100644 index 00000000..de7a2140 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js @@ -0,0 +1,73 @@ +import PropTypes from 'prop-types' +import React from 'react' +import MachineComponent from './MachineComponent' +import { + Badge, + Button, + DataList, + DataListAction, + DataListCell, + DataListItem, + DataListItemCells, + DataListItemRow, +} from '@patternfly/react-core' +import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons' +import { Machine } from '../../../../shapes' + +function MachineListComponent({ machines = [], onSelect, onAdd }) { + return ( + + {machines.map((machine, index) => + machine ? ( + onSelect(index + 1)}> + + + {machines.length - index}U + , + + onSelect(index + 1)} machine={machine} /> + , + ]} + /> + + + + + + ) : ( + + + + {machines.length - index}U + , + + Empty Slot + , + ]} + /> + + + + + + ) + )} + + ) +} + +MachineListComponent.propTypes = { + machines: PropTypes.arrayOf(Machine), + onSelect: PropTypes.func.isRequired, + onAdd: PropTypes.func.isRequired, +} + +export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js new file mode 100644 index 00000000..6fbff949 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useMemo } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import MachineListComponent from './MachineListComponent' +import { goFromRackToMachine } from '../../../../redux/actions/interaction-level' +import { addMachine } from '../../../../redux/actions/topology/rack' + +function MachineListContainer({ tileId, ...props }) { + const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + const machines = useSelector((state) => rack.machines.map((id) => state.objects.machine[id])) + const machinesNull = useMemo(() => { + const res = Array(rack.capacity).fill(null) + for (const machine of machines) { + res[machine.position - 1] = machine + } + return res + }, [rack, machines]) + const dispatch = useDispatch() + + return ( + dispatch(addMachine(rack._id, index))} + onSelect={(index) => dispatch(goFromRackToMachine(index))} + /> + ) +} + +MachineListContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js new file mode 100644 index 00000000..09d73af7 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import NameComponent from '../NameComponent' +import { editRackName } from '../../../../redux/actions/topology/rack' + +const RackNameContainer = ({ tileId }) => { + const { name: rackName, _id } = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + const dispatch = useDispatch() + const callback = (name) => { + if (name) { + dispatch(editRackName(_id, name)) + } + } + return +} + +RackNameContainer.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js new file mode 100644 index 00000000..3c9f152a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js @@ -0,0 +1,58 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss' +import RackNameContainer from './RackNameContainer' +import AddPrefab from './AddPrefab' +import DeleteRackContainer from './DeleteRackContainer' +import MachineListContainer from './MachineListContainer' +import { + Skeleton, + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' +import { useSelector } from 'react-redux' + +function RackSidebar({ tileId }) { + const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack]) + + return ( +
+ + Details + + + Name + + + + + Capacity + + {rack?.capacity ?? } + + + Actions + + + + Slots + +
+ +
+
+ ) +} + +RackSidebar.propTypes = { + tileId: PropTypes.string.isRequired, +} + +export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss new file mode 100644 index 00000000..6f258aec --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss @@ -0,0 +1,12 @@ +.sidebarContainer { + display: flex; + height: 100%; + max-height: 100%; + flex-direction: column; +} + +.machineListContainer { + flex: 1; + overflow-y: scroll; + margin-top: 10px; +} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js new file mode 100644 index 00000000..29b8f78a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import { useDispatch } from 'react-redux' +import ConfirmationModal from '../../../util/modals/ConfirmationModal' +import { deleteRoom } from '../../../../redux/actions/topology/room' +import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' +import { Button } from '@patternfly/react-core' + +function DeleteRoomContainer({ roomId }) { + const dispatch = useDispatch() + const [isVisible, setVisible] = useState(false) + const callback = (isConfirmed) => { + if (isConfirmed) { + dispatch(deleteRoom(roomId)) + } + setVisible(false) + } + return ( + <> + + + + ) +} + +DeleteRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js new file mode 100644 index 00000000..7a278cd6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { finishRoomEdit, startRoomEdit } from '../../../../redux/actions/topology/building' +import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' +import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' +import { Button } from '@patternfly/react-core' + +function EditRoomContainer({ roomId }) { + const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) + + const dispatch = useDispatch() + const onEdit = () => dispatch(startRoomEdit(roomId)) + const onFinish = () => dispatch(finishRoomEdit()) + + return isEditing ? ( + + ) : ( + + ) +} + +EditRoomContainer.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js new file mode 100644 index 00000000..a384d5d5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Button } from '@patternfly/react-core' +import { PlusIcon, TimesIcon } from '@patternfly/react-icons' + +const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { + if (inRackConstructionMode) { + return ( + + ) + } + + return ( + + ) +} + +RackConstructionComponent.propTypes = { + onStart: PropTypes.func, + onStop: PropTypes.func, + inRackConstructionMode: PropTypes.bool, + isEditingRoom: PropTypes.bool, +} + +export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js new file mode 100644 index 00000000..e04287a5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { startRackConstruction, stopRackConstruction } from '../../../../redux/actions/topology/room' +import RackConstructionComponent from './RackConstructionComponent' + +function RackConstructionContainer(props) { + const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) + const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') + + const dispatch = useDispatch() + const onStart = () => dispatch(startRackConstruction()) + const onStop = () => dispatch(stopRackConstruction()) + return ( + + ) +} + +export default RackConstructionContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js new file mode 100644 index 00000000..e8d8b33c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import React from 'react' +import { useDispatch, useSelector } from 'react-redux' +import NameComponent from '../NameComponent' +import { editRoomName } from '../../../../redux/actions/topology/room' + +function RoomName({ roomId }) { + const { name: roomName, _id } = useSelector((state) => state.objects.room[roomId]) + const dispatch = useDispatch() + const callback = (name) => { + if (name) { + dispatch(editRoomName(_id, name)) + } + } + return +} + +RoomName.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomName diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js new file mode 100644 index 00000000..6ad489e0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js @@ -0,0 +1,43 @@ +import PropTypes from 'prop-types' +import React from 'react' +import RoomName from './RoomName' +import RackConstructionContainer from './RackConstructionContainer' +import EditRoomContainer from './EditRoomContainer' +import DeleteRoomContainer from './DeleteRoomContainer' +import { + TextContent, + TextList, + TextListItem, + TextListItemVariants, + TextListVariants, + Title, +} from '@patternfly/react-core' + +const RoomSidebar = ({ roomId }) => { + return ( + + Details + + + Name + + + + + + Construction + + + + + ) +} + +RoomSidebar.propTypes = { + roomId: PropTypes.string.isRequired, +} + +export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js new file mode 100644 index 00000000..f6e1c98b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Modal from './Modal' + +function ConfirmationModal({ title, message, isOpen, callback }) { + return ( + callback(true)} + onCancel={() => callback(false)} + submitButtonType="danger" + submitButtonText="Confirm" + > + {message} + + ) +} + +ConfirmationModal.propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, +} + +export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js new file mode 100644 index 00000000..d4577062 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js @@ -0,0 +1,38 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Button, Modal as PModal, ModalVariant } from '@patternfly/react-core' + +function Modal({ children, title, isOpen, onSubmit, onCancel, submitButtonType, submitButtonText }) { + const actions = [ + , + , + ] + + return ( + + {children} + + ) +} + +Modal.propTypes = { + title: PropTypes.string.isRequired, + isOpen: PropTypes.bool, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + submitButtonType: PropTypes.string, + submitButtonText: PropTypes.string, + children: PropTypes.node, +} + +Modal.defaultProps = { + submitButtonType: 'primary', + submitButtonText: 'Save', + isOpen: false, +} + +export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js new file mode 100644 index 00000000..392a729e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js @@ -0,0 +1,70 @@ +import PropTypes from 'prop-types' +import React, { useRef, useState } from 'react' +import Modal from './Modal' +import { Form, FormGroup, TextInput } from '@patternfly/react-core' + +function TextInputModal({ title, label, isOpen, callback, initialValue }) { + const textInput = useRef(null) + const [isSubmitted, setSubmitted] = useState(false) + const [isValid, setValid] = useState(true) + + const resetState = () => { + textInput.current.value = '' + setSubmitted(false) + setValid(false) + } + const onSubmit = (event) => { + const value = textInput.current.value + setSubmitted(true) + + if (event) { + event.preventDefault() + } + + if (!value) { + setValid(false) + return false + } + + callback(value) + resetState() + return true + } + const onCancel = () => { + callback(undefined) + resetState() + } + + return ( + +
+ + + +
+
+ ) +} + +TextInputModal.propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + isOpen: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string, +} + +export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js index c6ded12b..8b0ed8e0 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js @@ -27,7 +27,6 @@ import Head from 'next/head' import { Breadcrumb, BreadcrumbItem, - Button, Card, CardActions, CardBody, diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js index 55bee445..53cc9c73 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js @@ -53,10 +53,10 @@ import { } from '@patternfly/react-core' import { AppPage } from '../../../../components/AppPage' import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' -import ScenarioTable from '../../../../components/projects/ScenarioTable' -import NewScenario from '../../../../components/projects/NewScenario' import { METRIC_NAMES } from '../../../../util/available-metrics' -import PortfolioResults from '../../../../components/app/results/PortfolioResults' +import NewScenario from '../../../../components/portfolios/NewScenario' +import ScenarioTable from '../../../../components/portfolios/ScenarioTable' +import PortfolioResults from '../../../../components/portfolios/results/PortfolioResults' /** * Page that displays the results in a portfolio. diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js index 139c2979..a1d6ac7e 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -23,25 +23,37 @@ import { useRouter } from 'next/router' import { useProject } from '../../../../data/project' import { useDispatch, useSelector } from 'react-redux' -import React, { useEffect, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { configure, HotKeys } from 'react-hotkeys' import { KeymapConfiguration } from '../../../../hotkeys' import Head from 'next/head' -import MapStage from '../../../../components/app/map/MapStage' import { openProjectSucceeded } from '../../../../redux/actions/projects' import { AppPage } from '../../../../components/AppPage' import { + Breadcrumb, + BreadcrumbItem, Bullseye, + Divider, Drawer, DrawerContent, DrawerContentBody, EmptyState, EmptyStateIcon, + PageSection, + PageSectionVariants, Spinner, + Tab, + TabContent, + Tabs, + TabTitleText, + Text, + TextContent, Title, } from '@patternfly/react-core' -import TopologySidebar from '../../../../components/app/sidebars/topology/TopologySidebar' -import Collapse from '../../../../components/app/map/controls/Collapse' +import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' +import MapStage from '../../../../components/topologies/map/MapStage' +import Collapse from '../../../../components/topologies/map/controls/Collapse' +import TopologySidebar from '../../../../components/topologies/sidebar/TopologySidebar' /** * Page that displays a datacenter topology. @@ -59,10 +71,29 @@ function Topology() { } }, [projectId, topologyId, dispatch]) + const [activeTab, setActiveTab] = useState('overview') + const overviewRef = useRef(null) + const floorPlanRef = useRef(null) + const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1') const interactionLevel = useSelector((state) => state.interactionLevel) const [isExpanded, setExpanded] = useState(true) + + const breadcrumb = ( + + + Projects + + + Project details + + + Topology + + + ) + const panelContent = setExpanded(false)} /> // Make sure that holding down a key will generate repeated events @@ -71,31 +102,71 @@ function Topology() { }) return ( - + {project?.name ?? 'Topologies'} - OpenDC - {topologyIsLoading ? ( - - - - - Loading Topology - - - - ) : ( - - - - - - setExpanded(true)} /> - - - - - )} + + + Topology + + + + + setActiveTab(tabIndex)} + className="pf-m-page-insets" + > + Overview} + tabContentId="overview" + tabContentRef={overviewRef} + /> + Floor Plan} + tabContentId="floor-plan" + tabContentRef={floorPlanRef} + /> + + + + + Test + + + ) } diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js index f3742b78..a5a3be32 100644 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -15,7 +15,7 @@ import { DEFAULT_RACK_POWER_CAPACITY, DEFAULT_RACK_SLOT_CAPACITY, MAX_NUM_UNITS_PER_MACHINE, -} from '../../components/app/map/MapConstants' +} from '../../components/topologies/map/MapConstants' import { fetchAndStoreTopology, denormalizeTopology, updateTopologyOnServer } from './objects' import { uuid } from 'uuidv4' import { addTopology } from '../../api/topologies' @@ -45,7 +45,7 @@ export function* onAddTopology({ projectId, duplicateId, name }) { topologyToBeCreated = { ...topologyToBeCreated, name } delete topologyToBeCreated._id } else { - topologyToBeCreated = { name: action.name, rooms: [] } + topologyToBeCreated = { name, rooms: [] } } const auth = yield getContext('auth') -- cgit v1.2.3 From 28d6d13844db28745bc2813e87a367131f862070 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Mon, 19 Jul 2021 20:59:11 +0200 Subject: refactor(ui): Move page components in separate files --- .../src/components/portfolios/PortfolioOverview.js | 121 ++++++++++++++++ .../components/portfolios/PortfolioResultInfo.js | 40 ++++++ .../src/components/portfolios/PortfolioResults.js | 156 +++++++++++++++++++++ .../portfolios/results/PortfolioResultInfo.js | 40 ------ .../portfolios/results/PortfolioResults.js | 134 ------------------ .../src/components/projects/ProjectOverview.js | 98 +++++++++++++ .../src/components/topologies/TopologyMap.js | 76 ++++++++++ .../src/components/topologies/TopologyOverview.js | 77 ++++++++++ opendc-web/opendc-web-ui/src/data/topology.js | 7 + opendc-web/opendc-web-ui/src/pages/_app.js | 3 +- .../src/pages/projects/[project]/index.js | 60 +------- .../projects/[project]/portfolios/[portfolio].js | 93 +----------- .../projects/[project]/topologies/[topology].js | 53 +------ .../opendc-web-ui/src/pages/projects/index.js | 3 +- 14 files changed, 589 insertions(+), 372 deletions(-) create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js create mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js delete mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js delete mode 100644 opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js create mode 100644 opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js (limited to 'opendc-web/opendc-web-ui/src') diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js new file mode 100644 index 00000000..580b0a29 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + Chip, + ChipGroup, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import React from 'react' +import { usePortfolio } from '../../data/project' +import { METRIC_NAMES } from '../../util/available-metrics' +import NewScenario from './NewScenario' +import ScenarioTable from './ScenarioTable' + +function PortfolioOverview({ portfolioId }) { + const { data: portfolio } = usePortfolio(portfolioId) + + return ( + + + + Details + + + + Name + + {portfolio?.name ?? } + + + + Scenarios + + {portfolio?.scenarioIds.length ?? } + + + + Metrics + + {portfolio?.targets?.enabledMetrics ? ( + portfolio.targets.enabledMetrics.length > 0 ? ( + + {portfolio.targets.enabledMetrics.map((metric) => ( + + {METRIC_NAMES[metric]} + + ))} + + ) : ( + 'No metrics enabled' + ) + ) : ( + + )} + + + + Repeats per Scenario + + {portfolio?.targets?.repeatsPerScenario ?? ( + + )} + + + + + + + + + + + + + Scenarios + + + + + + + + ) +} + +PortfolioOverview.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioOverview diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js new file mode 100644 index 00000000..dbfa928f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { Tooltip } from '@patternfly/react-core' +import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' +import { METRIC_DESCRIPTIONS } from '../../util/available-metrics' + +function PortfolioResultInfo({ metric }) { + return ( + {METRIC_DESCRIPTIONS[metric]}}> + + + ) +} + +PortfolioResultInfo.propTypes = { + metric: PropTypes.string.isRequired, +} + +export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js new file mode 100644 index 00000000..00023d9e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' +import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics' +import { mean, std } from 'mathjs' +import approx from 'approximate-number' +import { + Bullseye, + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + EmptyState, + EmptyStateBody, + EmptyStateIcon, + Grid, + GridItem, + Spinner, + Title, +} from '@patternfly/react-core' +import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' +import { usePortfolioScenarios } from '../../data/project' +import PortfolioResultInfo from './PortfolioResultInfo' +import NewScenario from './NewScenario' + +const PortfolioResults = ({ portfolioId }) => { + const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) + + if (status === 'loading') { + return ( + + + + + Loading Results + + + + ) + } else if (status === 'error') { + return ( + + + + + Unable to connect + + + There was an error retrieving data. Check your connection and try again. + + + + ) + } else if (scenarios.length === 0) { + return ( + + + + + No results + + + No results are currently available for this portfolio. Run a scenario to obtain simulation + results. + + + + + ) + } + + const dataPerMetric = {} + + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios + .filter((scenario) => scenario.results) + .map((scenario) => ({ + name: scenario.name, + value: mean(scenario.results[metric]), + errorX: std(scenario.results[metric]), + })) + }) + + return ( + + {AVAILABLE_METRICS.map((metric) => ( + + + + + + + {METRIC_NAMES[metric]} + + + + + + approx(tick)} + label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} + type="number" + /> + + + + + + + + + + + ))} + + ) +} + +PortfolioResults.propTypes = { + portfolioId: PropTypes.string, +} + +export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js deleted file mode 100644 index 09348e60..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { Tooltip } from '@patternfly/react-core' -import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' -import { METRIC_DESCRIPTIONS } from '../../../util/available-metrics' - -function PortfolioResultInfo({ metric }) { - return ( - {METRIC_DESCRIPTIONS[metric]}}> - - - ) -} - -PortfolioResultInfo.propTypes = { - metric: PropTypes.string.isRequired, -} - -export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js deleted file mode 100644 index f1883e68..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts' -import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../../util/available-metrics' -import { mean, std } from 'mathjs' -import approx from 'approximate-number' -import { - Bullseye, - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Grid, - GridItem, - Spinner, - Title, -} from '@patternfly/react-core' -import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' -import { usePortfolioScenarios } from '../../../data/project' -import PortfolioResultInfo from './PortfolioResultInfo' -import NewScenario from '../NewScenario' - -const PortfolioResults = ({ portfolioId }) => { - const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId) - - if (status === 'loading') { - return ( - - - - - Loading Results - - - - ) - } else if (status === 'error') { - return ( - - - - - Unable to connect - - - There was an error retrieving data. Check your connection and try again. - - - - ) - } else if (scenarios.length === 0) { - return ( - - - - - No results - - - No results are currently available for this portfolio. Run a scenario to obtain simulation - results. - - - - - ) - } - - const dataPerMetric = {} - - AVAILABLE_METRICS.forEach((metric) => { - dataPerMetric[metric] = scenarios - .filter((scenario) => scenario.results) - .map((scenario) => ({ - name: scenario.name, - value: mean(scenario.results[metric]), - errorX: std(scenario.results[metric]), - })) - }) - - return ( - - {AVAILABLE_METRICS.map((metric) => ( - - - - - - - {METRIC_NAMES[metric]} - - - - - - approx(tick)} - label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} - type="number" - /> - - - - - - - - - - - ))} - - ) -} - -PortfolioResults.propTypes = { - portfolioId: PropTypes.string, -} - -export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js new file mode 100644 index 00000000..65b8f5a0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardActions, + CardBody, + CardHeader, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import NewTopology from './NewTopology' +import TopologyTable from './TopologyTable' +import NewPortfolio from './NewPortfolio' +import PortfolioTable from './PortfolioTable' +import { useProject } from '../../data/project' + +function ProjectOverview({ projectId }) { + const { data: project } = useProject(projectId) + + return ( + + + + Details + + + + Name + + {project?.name ?? } + + + + + + + + + + + + + Topologies + + + + + + + + + + + + + Portfolios + + + + + + + + ) +} + +ProjectOverview.propTypes = { + projectId: PropTypes.string, +} + +export default ProjectOverview diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js new file mode 100644 index 00000000..c16f554c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { useState } from 'react' +import { + Bullseye, + Drawer, + DrawerContent, + DrawerContentBody, + EmptyState, + EmptyStateIcon, + Spinner, + Title, +} from '@patternfly/react-core' +import { configure, HotKeys } from 'react-hotkeys' +import { KeymapConfiguration } from '../../hotkeys' +import MapStage from './map/MapStage' +import Collapse from './map/controls/Collapse' +import { useSelector } from 'react-redux' +import TopologySidebar from './sidebar/TopologySidebar' + +function TopologyMap() { + const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1') + const interactionLevel = useSelector((state) => state.interactionLevel) + + const [isExpanded, setExpanded] = useState(true) + const panelContent = setExpanded(false)} /> + + // Make sure that holding down a key will generate repeated events + configure({ + ignoreRepeatedEventsWhenKeyHeldDown: false, + }) + + return topologyIsLoading ? ( + + + + + Loading Topology + + + + ) : ( + + + + + + setExpanded(true)} /> + + + + + ) +} + +export default TopologyMap diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js new file mode 100644 index 00000000..f773dcd1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import PropTypes from 'prop-types' +import { + Card, + CardBody, + CardTitle, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + Grid, + GridItem, + Skeleton, +} from '@patternfly/react-core' +import { useTopology } from '../../data/topology' +import { parseAndFormatDateTime } from '../../util/date-time' + +function TopologyOverview({ topologyId }) { + const { data: topology } = useTopology(topologyId) + + return ( + + + + Details + + + + Name + + {topology?.name ?? } + + + + Last edited + + {topology ? ( + parseAndFormatDateTime(topology.datetimeLastEdited) + ) : ( + + )} + + + + + + + + ) +} + +TopologyOverview.propTypes = { + topologyId: PropTypes.string, +} + +export default TopologyOverview diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js index 14bd7562..bd4d1e4d 100644 --- a/opendc-web/opendc-web-ui/src/data/topology.js +++ b/opendc-web/opendc-web-ui/src/data/topology.js @@ -70,6 +70,13 @@ export function useActiveTopology() { return useSelector((state) => state.currentTopologyId !== '-1' && state.objects.topology[state.currentTopologyId]) } +/** + * Return the current active topology. + */ +export function useTopology(topologyId, options = {}) { + return useQuery(['topologies', topologyId], { enabled: !!topologyId, ...options }) +} + /** * Return the topologies of the specified project. */ diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js index 028dab10..d5f3b329 100644 --- a/opendc-web/opendc-web-ui/src/pages/_app.js +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -24,7 +24,7 @@ import PropTypes from 'prop-types' import Head from 'next/head' import { Provider } from 'react-redux' import { useStore } from '../redux' -import { AuthProvider, useAuth } from '../auth' +import { AuthProvider, useAuth, useRequireAuth } from '../auth' import * as Sentry from '@sentry/react' import { Integrations } from '@sentry/tracing' import { QueryClient, QueryClientProvider } from 'react-query' @@ -48,6 +48,7 @@ import '../style/index.scss' // This setup is necessary to forward the Auth0 context to the Redux context const Inner = ({ Component, pageProps }) => { + useRequireAuth() const auth = useAuth() const queryClient = useMemo(() => { diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js index 8b0ed8e0..5c17303f 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js @@ -21,23 +21,13 @@ */ import { useRouter } from 'next/router' +import ProjectOverview from '../../../components/projects/ProjectOverview' import { useProject } from '../../../data/project' import { AppPage } from '../../../components/AppPage' import Head from 'next/head' import { Breadcrumb, BreadcrumbItem, - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Grid, - GridItem, PageSection, PageSectionVariants, Skeleton, @@ -45,10 +35,6 @@ import { TextContent, } from '@patternfly/react-core' import BreadcrumbLink from '../../../components/util/BreadcrumbLink' -import PortfolioTable from '../../../components/projects/PortfolioTable' -import TopologyTable from '../../../components/projects/TopologyTable' -import NewTopology from '../../../components/projects/NewTopology' -import NewPortfolio from '../../../components/projects/NewPortfolio' function Project() { const router = useRouter() @@ -80,49 +66,7 @@ function Project() { - - - - Details - - - - Name - - {project?.name ?? } - - - - - - - - - - - - - Topologies - - - - - - - - - - - - - Portfolios - - - - - - - + ) diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js index 53cc9c73..28b03c37 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js @@ -23,27 +23,12 @@ import { useRouter } from 'next/router' import Head from 'next/head' import React, { useRef } from 'react' -import { usePortfolio, useProject } from '../../../../data/project' import { Breadcrumb, BreadcrumbItem, - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - Chip, - ChipGroup, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, Divider, - Grid, - GridItem, PageSection, PageSectionVariants, - Skeleton, Tab, TabContent, Tabs, @@ -53,10 +38,8 @@ import { } from '@patternfly/react-core' import { AppPage } from '../../../../components/AppPage' import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' -import { METRIC_NAMES } from '../../../../util/available-metrics' -import NewScenario from '../../../../components/portfolios/NewScenario' -import ScenarioTable from '../../../../components/portfolios/ScenarioTable' -import PortfolioResults from '../../../../components/portfolios/results/PortfolioResults' +import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview' +import PortfolioResults from '../../../../components/portfolios/PortfolioResults' /** * Page that displays the results in a portfolio. @@ -65,9 +48,6 @@ function Portfolio() { const router = useRouter() const { project: projectId, portfolio: portfolioId } = router.query - const { data: project } = useProject(projectId) - const { data: portfolio } = usePortfolio(portfolioId) - const overviewRef = useRef(null) const resultsRef = useRef(null) @@ -88,7 +68,7 @@ function Portfolio() { return ( - {project?.name ?? 'Portfolios'} - OpenDC + Portfolio - OpenDC @@ -114,72 +94,7 @@ function Portfolio() { - - - - Details - - - - Name - - {portfolio?.name ?? } - - - - Scenarios - - {portfolio?.scenarioIds.length ?? ( - - )} - - - - Metrics - - {portfolio?.targets?.enabledMetrics ? ( - portfolio.targets.enabledMetrics.length > 0 ? ( - - {portfolio.targets.enabledMetrics.map((metric) => ( - - {METRIC_NAMES[metric]} - - ))} - - ) : ( - 'No metrics enabled' - ) - ) : ( - - )} - - - - Repeats per Scenario - - {portfolio?.targets?.repeatsPerScenario ?? ( - - )} - - - - - - - - - - - - - Scenarios - - - - - - - + - Test + diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js index 9dcc9aea..eb77701e 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -23,7 +23,7 @@ import React, { useMemo, useState } from 'react' import Head from 'next/head' import ProjectFilterPanel from '../../components/projects/FilterPanel' -import { useAuth, useRequireAuth } from '../../auth' +import { useAuth } from '../../auth' import { AppPage } from '../../components/AppPage' import { PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core' import { useProjects } from '../../data/project' @@ -49,7 +49,6 @@ const getVisibleProjects = (projects, filter, userId) => { } function Projects() { - useRequireAuth() const { user } = useAuth() const { status, data: projects } = useProjects() const [filter, setFilter] = useState('SHOW_ALL') -- cgit v1.2.3