summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-20 10:51:39 +0200
committerGitHub <noreply@github.com>2021-07-20 10:51:39 +0200
commit51c759e74b088d405b63fdb3e374822308d21366 (patch)
tree3094cb874872d932d278d98d60f79902bf08b1a0 /opendc-web/opendc-web-ui/src/components
parentdb1d2c2f8c18850dedf34b5d690b6cd6a1d1f6b5 (diff)
parent28d6d13844db28745bc2813e87a367131f862070 (diff)
merge: Address technical dept in topology view (#162)
This pull request aims to address some of the technical debt in the topology view of the OpenDC frontend. * Add support for panning of the datacenter topology * Isolate world coordinate space (world objects do not depend on camera scale or position) * Split transpiled modules into a separate chunk to reduce deduplication * Encode state in topology actions to reduce global state * Restructure components per page * Enable more ESLint rules through `eslint:recommended` ruleset * Move page components in separate files.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components')
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppNavigation.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppPage.js3
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/MapStage.js66
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.js28
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js28
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js25
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js44
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js64
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js26
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js11
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js37
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js16
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarContainer.js32
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarContainer.js32
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js (renamed from opendc-web/opendc-web-ui/src/components/projects/NewScenario.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js)10
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js121
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js (renamed from opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js (renamed from opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js)28
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js (renamed from opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js (renamed from opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js)26
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewProject.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewTopology.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js)26
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js98
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js76
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js77
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js)3
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js83
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js)15
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js)14
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js)8
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js)6
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js)8
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js)12
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js35
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js)22
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js30
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js24
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js27
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js44
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js)28
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js55
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayer.js)20
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayer.js)35
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js (renamed from opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayer.js)50
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js)22
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js)18
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js)14
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js)17
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js)7
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js)6
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js)17
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js)23
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js)21
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js)13
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js)6
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js)6
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js)14
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js)13
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js)11
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js)5
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js)8
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js (renamed from opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js)10
-rw-r--r--opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/util/modals/Modal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/Modal.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js (renamed from opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js)0
97 files changed, 1020 insertions, 692 deletions
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/MapStage.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js
deleted file mode 100644
index 73accf3f..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { useEffect, 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 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'
-
-function MapStage() {
- const store = useStore()
- const dispatch = useDispatch()
-
- 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 (
- <HotKeys handlers={handlers} allowChanges={true} innerRef={ref} className={mapContainer}>
- <Stage ref={stage} width={width} height={height} onMouseMove={updateMousePosition} onWheel={updateScale}>
- <Provider store={store}>
- <MapLayer />
- <RoomHoverLayer mouseX={x} mouseY={y} />
- <ObjectHoverLayer mouseX={x} mouseY={y} />
- </Provider>
- </Stage>
- </HotKeys>
- )
-}
-
-export default MapStage
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 }) => (
- <div className={toolBar}>
- <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}>
- <SearchPlusIcon />
- </Button>
- <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}>
- <SearchMinusIcon />
- </Button>
- <Button variant="tertiary" title="Export Canvas to PNG Image" onClick={() => onExport()} className={control}>
- <FontAwesomeIcon icon={faCamera} />
- </Button>
- </div>
-)
-
-Toolbar.propTypes = {
- onZoom: PropTypes.func,
- onExport: PropTypes.func,
-}
-
-export default Toolbar
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 11bba0e1..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 = ({ pixelX, pixelY, isValid, scale, onClick }) => (
- <Rect
- x={pixelX}
- y={pixelY}
- scaleX={scale}
- scaleY={scale}
- width={TILE_SIZE_IN_PIXELS}
- height={TILE_SIZE_IN_PIXELS}
- fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR}
- onClick={onClick}
- />
-)
-
-HoverTile.propTypes = {
- pixelX: PropTypes.number.isRequired,
- pixelY: PropTypes.number.isRequired,
- isValid: PropTypes.bool.isRequired,
- scale: PropTypes.number.isRequired,
- onClick: PropTypes.func.isRequired,
-}
-
-export default HoverTile
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 }) => (
- <Rect
- x={tile.positionX * TILE_SIZE_IN_PIXELS}
- y={tile.positionY * TILE_SIZE_IN_PIXELS}
- width={TILE_SIZE_IN_PIXELS}
- height={TILE_SIZE_IN_PIXELS}
- fill={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 }) => (
- <Rect
- x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS}
- y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS}
- width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2}
- height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2}
- fill={color}
- stroke={OBJECT_BORDER_COLOR}
- strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS}
- />
-)
-
-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 be3a00a8..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'
-
-const TilePlusIcon = ({ pixelX, pixelY, mapScale }) => {
- 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,
- ],
- [
- 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,
- ],
- ]
- return (
- <Group>
- {linePoints.map((points, index) => (
- <Line
- key={index}
- points={points}
- lineCap="round"
- stroke={TILE_PLUS_COLOR}
- strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * mapScale}
- listening={false}
- />
- ))}
- </Group>
- )
-}
-
-TilePlusIcon.propTypes = {
- pixelX: PropTypes.number,
- pixelY: PropTypes.number,
- mapScale: 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
deleted file mode 100644
index a88a8b34..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import PropTypes from 'prop-types'
-import React, { useEffect, useState } from 'react'
-import { Layer } from 'react-konva'
-import HoverTile from '../elements/HoverTile'
-import { TILE_SIZE_IN_PIXELS } from '../MapConstants'
-
-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)
-
- useEffect(() => {
- if (!isEnabled()) {
- 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))
-
- if (positionX !== x || positionY !== y) {
- setPos([positionX, positionY])
- setValid(isValid(positionX, positionY))
- }
- }, [isEnabled, isValid, x, y, mouseX, mouseY, mapPosition, mapScale])
-
- if (!isEnabled()) {
- return <Layer />
- }
-
- const pixelX = mapScale * x * TILE_SIZE_IN_PIXELS + mapPosition.x
- const pixelY = mapScale * y * TILE_SIZE_IN_PIXELS + mapPosition.y
-
- return (
- <Layer opacity={0.6}>
- <HoverTile
- pixelX={pixelX}
- pixelY={pixelY}
- scale={mapScale}
- isValid={valid}
- onClick={() => (valid ? onClick(x, y) : undefined)}
- />
- {children
- ? React.cloneElement(children, {
- pixelX,
- pixelY,
- scale: mapScale,
- })
- : undefined}
- </Layer>
- )
-}
-
-HoverLayerComponent.propTypes = {
- mouseX: PropTypes.number.isRequired,
- mouseY: PropTypes.number.isRequired,
- mapPosition: PropTypes.object.isRequired,
- mapScale: PropTypes.number.isRequired,
- isEnabled: PropTypes.func.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/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 }) => (
- <Layer>
- <Group x={mapPosition.x} y={mapPosition.y} scaleX={mapScale} scaleY={mapScale}>
- <Backdrop />
- <TopologyContainer />
- <GridGroup />
- </Group>
- </Layer>
-)
-
-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/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) => (
- <HoverLayerComponent {...props}>
- <TilePlusIcon {...props} />
- </HoverLayerComponent>
-)
-
-export default ObjectHoverLayerComponent
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) => <HoverLayerComponent {...props} />
-
-export default RoomHoverLayerComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.js
deleted file mode 100644
index 94d9f2c3..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarContainer.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 MachineSidebarComponent from './MachineSidebarComponent'
-
-const MachineSidebarContainer = (props) => {
- const machineId = useSelector(
- (state) =>
- state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].machines[
- state.interactionLevel.position - 1
- ]
- )
- return <MachineSidebarComponent {...props} machineId={machineId} />
-}
-
-export default MachineSidebarContainer
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 }) => (
- <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm">
- Save this rack to a prefab
- </Button>
-)
-
-AddPrefabComponent.propTypes = {
- onClick: PropTypes.func,
-}
-
-export default AddPrefabComponent
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 <RackSidebarComponent {...props} tileId={tileId} />
-}
-
-export default RackSidebarContainer
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 <RoomSidebarComponent {...props} roomId={roomId} />
-}
-
-export default RoomSidebarContainer
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js
index 6d4f48c1..856282a7 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/NewScenario.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.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 NewScenarioModal from '../modals/custom-components/NewScenarioModal'
import { useMutation } from 'react-query'
+import NewScenarioModal from './NewScenarioModal'
function NewScenario({ portfolioId }) {
const [isVisible, setVisible] = useState(false)
diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js
index 94d0d424..7f620c8c 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModal.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js
@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'
import React, { useRef, useState } from 'react'
-import { Portfolio } from '../../../shapes'
-import Modal from '../Modal'
+import Modal from '../util/modals/Modal'
import {
Checkbox,
Form,
@@ -12,9 +11,9 @@ import {
NumberInput,
TextInput,
} from '@patternfly/react-core'
-import { useSchedulers, useTraces } from '../../../data/experiments'
-import { useProjectTopologies } from '../../../data/topology'
-import { usePortfolio } from '../../../data/project'
+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)
@@ -22,6 +21,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
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)
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 (
+ <Grid hasGutter>
+ <GridItem md={2}>
+ <Card>
+ <CardTitle>Details</CardTitle>
+ <CardBody>
+ <DescriptionList>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Scenarios</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.scenarioIds.length ?? <Skeleton screenreaderText="Loading portfolio" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Metrics</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.targets?.enabledMetrics ? (
+ portfolio.targets.enabledMetrics.length > 0 ? (
+ <ChipGroup>
+ {portfolio.targets.enabledMetrics.map((metric) => (
+ <Chip isReadOnly key={metric}>
+ {METRIC_NAMES[metric]}
+ </Chip>
+ ))}
+ </ChipGroup>
+ ) : (
+ 'No metrics enabled'
+ )
+ ) : (
+ <Skeleton screenreaderText="Loading portfolio" />
+ )}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.targets?.repeatsPerScenario ?? (
+ <Skeleton screenreaderText="Loading portfolio" />
+ )}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem md={6}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <NewScenario portfolioId={portfolioId} />
+ </CardActions>
+ <CardTitle>Scenarios</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <ScenarioTable portfolioId={portfolioId} />
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ )
+}
+
+PortfolioOverview.propTypes = {
+ portfolioId: PropTypes.string,
+}
+
+export default PortfolioOverview
diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js
index 09348e60..dbfa928f 100644
--- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultInfo.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js
@@ -23,7 +23,7 @@
import PropTypes from 'prop-types'
import { Tooltip } from '@patternfly/react-core'
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons'
-import { METRIC_DESCRIPTIONS } from '../../../util/available-metrics'
+import { METRIC_DESCRIPTIONS } from '../../util/available-metrics'
function PortfolioResultInfo({ metric }) {
return (
diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
index 6a96c70c..00023d9e 100644
--- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResults.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
@@ -1,7 +1,29 @@
+/*
+ * 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_NAMES_SHORT, METRIC_UNITS } from '../../../util/available-metrics'
+import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics'
import { mean, std } from 'mathjs'
import approx from 'approximate-number'
import {
@@ -20,9 +42,9 @@ import {
Title,
} from '@patternfly/react-core'
import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons'
-import { usePortfolioScenarios } from '../../../data/project'
-import NewScenario from '../../projects/NewScenario'
+import { usePortfolioScenarios } from '../../data/project'
import PortfolioResultInfo from './PortfolioResultInfo'
+import NewScenario from './NewScenario'
const PortfolioResults = ({ portfolioId }) => {
const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId)
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js
index 285345e7..66691580 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/ScenarioState.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js
@@ -21,7 +21,7 @@
*/
import PropTypes from 'prop-types'
-import { ClockIcon, InfoIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons'
+import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons'
function ScenarioState({ state }) {
switch (state) {
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js
index 9966e3ba..9966e3ba 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/ScenarioTable.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js
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/modals/custom-components/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js
index afe07597..4276d7d4 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModal.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js
@@ -1,6 +1,27 @@
+/*
+ * 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 Modal from '../Modal'
import {
Form,
FormGroup,
@@ -12,7 +33,8 @@ import {
SelectVariant,
TextInput,
} from '@patternfly/react-core'
-import { METRIC_GROUPS, METRIC_NAMES } from '../../../util/available-metrics'
+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)
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/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/modals/custom-components/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js
index 49952aec..a495f73e 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModal.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js
@@ -1,8 +1,30 @@
+/*
+ * 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 Modal from '../Modal'
import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core'
-import { useProjectTopologies } from '../../../data/topology'
+import { useProjectTopologies } from '../../data/topology'
+import Modal from '../util/modals/Modal'
const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => {
const nameInput = useRef(null)
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 (
+ <Grid hasGutter>
+ <GridItem md={2}>
+ <Card>
+ <CardTitle>Details</CardTitle>
+ <CardBody>
+ <DescriptionList>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>
+ {project?.name ?? <Skeleton screenreaderText="Loading project" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem md={5}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <NewTopology projectId={projectId} />
+ </CardActions>
+ <CardTitle>Topologies</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <TopologyTable projectId={projectId} />
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem md={5}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <NewPortfolio projectId={projectId} />
+ </CardActions>
+ <CardTitle>Portfolios</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <PortfolioTable projectId={projectId} />
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ )
+}
+
+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 = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} />
+
+ // Make sure that holding down a key will generate repeated events
+ configure({
+ ignoreRepeatedEventsWhenKeyHeldDown: false,
+ })
+
+ return topologyIsLoading ? (
+ <Bullseye>
+ <EmptyState>
+ <EmptyStateIcon variant="container" component={Spinner} />
+ <Title size="lg" headingLevel="h4">
+ Loading Topology
+ </Title>
+ </EmptyState>
+ </Bullseye>
+ ) : (
+ <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="full-height">
+ <Drawer isExpanded={isExpanded}>
+ <DrawerContent panelContent={panelContent}>
+ <DrawerContentBody>
+ <MapStage />
+ <Collapse onClick={() => setExpanded(true)} />
+ </DrawerContentBody>
+ </DrawerContent>
+ </Drawer>
+ </HotKeys>
+ )
+}
+
+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 (
+ <Grid hasGutter>
+ <GridItem md={2}>
+ <Card>
+ <CardTitle>Details</CardTitle>
+ <CardBody>
+ <DescriptionList>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>
+ {topology?.name ?? <Skeleton screenreaderText="Loading topology" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Last edited</DescriptionListTerm>
+ <DescriptionListDescription>
+ {topology ? (
+ parseAndFormatDateTime(topology.datetimeLastEdited)
+ ) : (
+ <Skeleton screenreaderText="Loading topology" />
+ )}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ )
+}
+
+TopologyOverview.propTypes = {
+ topologyId: PropTypes.string,
+}
+
+export default TopologyOverview
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js
index 4791940f..ccf637e5 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/GrayContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js
@@ -23,9 +23,9 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level'
-import GrayLayer from '../../../components/app/map/elements/GrayLayer'
+import GrayLayer from './elements/GrayLayer'
-const GrayContainer = () => {
+function GrayContainer() {
const dispatch = useDispatch()
const onClick = () => dispatch(goDownOneInteractionLevel())
return <GrayLayer onClick={onClick} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/topologies/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/topologies/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/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 (
+ <HotKeys handlers={handlers} allowChanges={true} innerRef={ref} className={mapContainer}>
+ <Stage
+ ref={stageRef}
+ onWheel={onZoom}
+ onDragEnd={onDragEnd}
+ draggable
+ width={width}
+ height={height}
+ scale={{ x: scale, y: scale }}
+ x={x}
+ y={y}
+ >
+ <Provider store={store}>
+ <MapLayer />
+ <RoomHoverLayer />
+ <ObjectHoverLayer />
+ </Provider>
+ </Stage>
+ <ScaleIndicator scale={scale} />
+ <Toolbar onZoom={onZoomButton} onExport={onExport} />
+ </HotKeys>
+ )
+}
+
+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/topologies/map/MapStage.module.scss
index d879b4c8..d879b4c8 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/MapStage.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.scss
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js
index 3c75d3a7..14449a91 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/RackContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js
@@ -22,10 +22,10 @@
import React from 'react'
import { useSelector } from 'react-redux'
-import RackGroup from '../../../components/app/map/groups/RackGroup'
import { Tile } from '../../../shapes'
+import RackGroup from './groups/RackGroup'
-const RackContainer = ({ tile }) => {
+function RackContainer({ tile }) {
const interactionLevel = useSelector((state) => state.interactionLevel)
return <RackGroup interactionLevel={interactionLevel} tile={tile} />
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js
index 838aea5a..c35cbde7 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/RackEnergyFillContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js
@@ -1,12 +1,12 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
-import RackFillBar from '../../../components/app/map/elements/RackFillBar'
+import RackFillBar from './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 <RackFillBar {...props} {...state} />
+ return <RackFillBar {...props} type="energy" fillFraction={fillFraction} />
}
RackSpaceFillContainer.propTypes = {
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js
index 6791120e..a6766f33 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/RackSpaceFillContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js
@@ -23,17 +23,11 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
-import RackFillBar from '../../../components/app/map/elements/RackFillBar'
+import RackFillBar from './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 <RackFillBar {...props} {...state} />
+function RackSpaceFillContainer({ tileId, ...props }) {
+ const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+ return <RackFillBar {...props} type="space" fillFraction={rack.machines.length / rack.capacity} />
}
RackSpaceFillContainer.propTypes = {
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js
index 26fbcd7a..93ba9c93 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/RoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js
@@ -24,18 +24,18 @@ 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'
+import RoomGroup from './groups/RoomGroup'
-const RoomContainer = (props) => {
+function RoomContainer({ roomId, ...props }) {
const state = useSelector((state) => {
return {
interactionLevel: state.interactionLevel,
currentRoomInConstruction: state.construction.currentRoomInConstruction,
- room: state.objects.room[props.roomId],
+ room: state.objects.room[roomId],
}
})
const dispatch = useDispatch()
- return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(props.roomId))} />
+ return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(roomId))} />
}
RoomContainer.propTypes = {
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
index bfcbf735..149e26a1 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/TileContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
@@ -24,11 +24,11 @@ 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'
+import TileGroup from './groups/TileGroup'
-const TileContainer = (props) => {
+function TileContainer({ tileId, ...props }) {
const interactionLevel = useSelector((state) => state.interactionLevel)
- const tile = useSelector((state) => state.objects.tile[props.tileId])
+ const tile = useSelector((state) => state.objects.tile[tileId])
const dispatch = useDispatch()
const onClick = (tile) => {
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js
index 78e75d0f..eaebabd5 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/TopologyContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js
@@ -22,10 +22,10 @@
import React from 'react'
import { useSelector } from 'react-redux'
-import TopologyGroup from '../../../components/app/map/groups/TopologyGroup'
import { useActiveTopology } from '../../../data/topology'
+import TopologyGroup from './groups/TopologyGroup'
-const TopologyContainer = () => {
+function TopologyContainer() {
const topology = useActiveTopology()
const interactionLevel = useSelector((state) => state.interactionLevel)
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js
index 51dffe4b..77f553dd 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/WallContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js
@@ -23,12 +23,10 @@
import React from 'react'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
-import WallGroup from '../../../components/app/map/groups/WallGroup'
+import WallGroup from './groups/WallGroup'
-const WallContainer = (props) => {
- const tiles = useSelector((state) =>
- state.objects.room[props.roomId].tiles.map((tileId) => state.objects.tile[tileId])
- )
+function WallContainer({ roomId, ...props }) {
+ const tiles = useSelector((state) => state.objects.room[roomId].tiles.map((tileId) => state.objects.tile[tileId]))
return <WallGroup {...props} tiles={tiles} />
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js
index f54b7c84..f54b7c84 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss
index 0c1fac94..0c1fac94 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Collapse.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.scss
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js
index 11c2f2d3..58d2ccc9 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js
@@ -3,11 +3,13 @@ import React from 'react'
import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants'
import { scaleIndicator } from './ScaleIndicator.module.scss'
-const ScaleIndicator = ({ scale }) => (
- <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}>
- {TILE_SIZE_IN_METERS}m
- </div>
-)
+function ScaleIndicator({ scale }) {
+ return (
+ <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}>
+ {TILE_SIZE_IN_METERS}m
+ </div>
+ )
+}
ScaleIndicator.propTypes = {
scale: PropTypes.number.isRequired,
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss
index f19e0ff2..f19e0ff2 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicator.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.scss
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 (
+ <div className={toolBar}>
+ <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}>
+ <SearchPlusIcon />
+ </Button>
+ <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}>
+ <SearchMinusIcon />
+ </Button>
+ <Button
+ variant="tertiary"
+ title="Export Canvas to PNG Image"
+ onClick={() => onExport()}
+ className={control}
+ >
+ <FontAwesomeIcon icon={faCamera} />
+ </Button>
+ </div>
+ )
+}
+
+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/topologies/map/controls/Toolbar.module.scss
index 0d505acc..0d505acc 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/Toolbar.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.scss
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js
index 8ccfe584..93037b51 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js
@@ -3,6 +3,8 @@ import { Rect } from 'react-konva'
import { BACKDROP_COLOR } from '../../../../util/colors'
import { MAP_SIZE_IN_PIXELS } from '../MapConstants'
-const Backdrop = () => <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} />
+function Backdrop() {
+ return <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} />
+}
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/topologies/map/elements/GrayLayer.js
index 35af4d96..08c687f6 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js
@@ -4,16 +4,18 @@ import { Rect } from 'react-konva'
import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors'
import { MAP_SIZE_IN_PIXELS } from '../MapConstants'
-const GrayLayer = ({ onClick }) => (
- <Rect
- x={0}
- y={0}
- width={MAP_SIZE_IN_PIXELS}
- height={MAP_SIZE_IN_PIXELS}
- fill={GRAYED_OUT_AREA_COLOR}
- onClick={onClick}
- />
-)
+function GrayLayer({ onClick }) {
+ return (
+ <Rect
+ x={0}
+ y={0}
+ width={MAP_SIZE_IN_PIXELS}
+ height={MAP_SIZE_IN_PIXELS}
+ fill={GRAYED_OUT_AREA_COLOR}
+ onClick={onClick}
+ />
+ )
+}
GrayLayer.propTypes = {
onClick: PropTypes.func,
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 (
+ <Rect
+ x={x}
+ y={y}
+ scaleX={scale}
+ scaleY={scale}
+ width={TILE_SIZE_IN_PIXELS}
+ height={TILE_SIZE_IN_PIXELS}
+ fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR}
+ onClick={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/topologies/map/elements/ImageComponent.js
index 7d304b6b..7d304b6b 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/topologies/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/topologies/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/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 (
+ <Rect
+ x={tile.positionX * TILE_SIZE_IN_PIXELS}
+ y={tile.positionY * TILE_SIZE_IN_PIXELS}
+ width={TILE_SIZE_IN_PIXELS}
+ height={TILE_SIZE_IN_PIXELS}
+ fill={color}
+ />
+ )
+}
+
+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 (
+ <Rect
+ x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS}
+ y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS}
+ width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2}
+ height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2}
+ fill={color}
+ stroke={OBJECT_BORDER_COLOR}
+ strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS}
+ />
+ )
+}
+
+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 (
+ <Group>
+ {linePoints.map((points, index) => (
+ <Line
+ key={index}
+ points={points}
+ lineCap="round"
+ stroke={TILE_PLUS_COLOR}
+ strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * scale}
+ listening={false}
+ />
+ ))}
+ </Group>
+ )
+}
+
+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/topologies/map/elements/WallSegment.js
index ad6412c3..4f18813e 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js
@@ -4,7 +4,7 @@ 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 }) => {
+function WallSegment({ wallSegment }) {
let points
if (wallSegment.isHorizontal) {
points = [
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js
index ebc00244..d66a18de 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js
@@ -17,18 +17,20 @@ const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [
MAP_SIZE_IN_PIXELS,
])
-const GridGroup = () => (
- <Group>
- {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => (
- <Line
- key={index}
- points={points}
- stroke={GRID_COLOR}
- strokeWidth={GRID_LINE_WIDTH_IN_PIXELS}
- listening={false}
- />
- ))}
- </Group>
-)
+function GridGroup() {
+ return (
+ <Group>
+ {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => (
+ <Line
+ key={index}
+ points={points}
+ stroke={GRID_COLOR}
+ strokeWidth={GRID_LINE_WIDTH_IN_PIXELS}
+ listening={false}
+ />
+ ))}
+ </Group>
+ )
+}
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/topologies/map/groups/RackGroup.js
index 9c4abc4a..46030135 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js
@@ -6,7 +6,7 @@ import TileObject from '../elements/TileObject'
import RackSpaceFillContainer from '../RackSpaceFillContainer'
import RackEnergyFillContainer from '../RackEnergyFillContainer'
-const RackGroup = ({ tile }) => {
+function RackGroup({ tile }) {
return (
<Group>
<TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js
index a14f3676..a42e7bb7 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js
@@ -6,7 +6,7 @@ import GrayContainer from '../GrayContainer'
import TileContainer from '../TileContainer'
import WallContainer from '../WallContainer'
-const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => {
+function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) {
if (currentRoomInConstruction === room._id) {
return (
<Group onClick={onClick}>
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js
index cd36c7e5..f2084017 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js
@@ -6,7 +6,7 @@ import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util
import RoomTile from '../elements/RoomTile'
import RackContainer from '../RackContainer'
-const TileGroup = ({ tile, newTile, onClick }) => {
+function TileGroup({ tile, newTile, onClick }) {
let tileObject
if (tile.rack) {
tileObject = <RackContainer tile={tile} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js
index d3bcb279..011dcf34 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js
@@ -4,7 +4,7 @@ import { InteractionLevel, Topology } from '../../../../shapes'
import RoomContainer from '../RoomContainer'
import GrayContainer from '../GrayContainer'
-const TopologyGroup = ({ topology, interactionLevel }) => {
+function TopologyGroup({ topology, interactionLevel }) {
if (!topology) {
return <Group />
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js
index c73a95a7..6cbd1cd0 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js
@@ -5,7 +5,7 @@ import { Tile } from '../../../../shapes'
import { deriveWallLocations } from '../../../../util/tile-calculations'
import WallSegment from '../elements/WallSegment'
-const WallGroup = ({ tiles }) => {
+function WallGroup({ tiles }) {
return (
<Group>
{deriveWallLocations(tiles).map((wallSegment, index) => (
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 <Layer />
+ }
+
+ const x = gridX * TILE_SIZE_IN_PIXELS
+ const y = gridY * TILE_SIZE_IN_PIXELS
+
+ return (
+ <Layer opacity={0.6} ref={layerRef}>
+ <HoverTile x={x} y={y} isValid={valid} onClick={() => (valid ? onClick(gridX, gridY) : undefined)} />
+ {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined}
+ </Layer>
+ )
+}
+
+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/topologies/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/topologies/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 <MapLayerComponent {...props} mapPosition={position} mapScale={scale} />
+function MapLayer() {
+ return (
+ <Layer>
+ <Group>
+ <Backdrop />
+ <TopologyContainer />
+ <GridGroup />
+ </Group>
+ </Layer>
+ )
}
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/topologies/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/topologies/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 <ObjectHoverLayerComponent {...props} {...state} onClick={onClick} />
+ return (
+ <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid}>
+ <TilePlusIcon />
+ </HoverLayerComponent>
+ )
}
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/topologies/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/topologies/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 <RoomHoverLayerComponent onClick={onClick} {...props} {...state} />
+
+ return <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid} />
}
export default RoomHoverLayer
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js
index ececd07b..ececd07b 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js
index c4a880b1..5d9340b2 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/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 { InteractionLevel } from '../../../shapes'
+import BuildingSidebar from './building/BuildingSidebar'
import {
Button,
DrawerActions,
@@ -14,11 +14,11 @@ import {
} 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 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'
+import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level'
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 = <BuildingSidebarComponent />
+ sidebarContent = <BuildingSidebar />
break
case 'ROOM':
- sidebarContent = <RoomSidebarContainer />
+ sidebarContent = <RoomSidebar roomId={interactionLevel.roomId} />
break
case 'RACK':
- sidebarContent = <RackSidebarContainer />
+ sidebarContent = <RackSidebar tileId={interactionLevel.tileId} />
break
case 'MACHINE':
- sidebarContent = <MachineSidebarContainer />
+ sidebarContent = <MachineSidebar tileId={interactionLevel.tileId} position={interactionLevel.position} />
break
default:
sidebarContent = 'Missing Content'
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss
index 45dc98da..45dc98da 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebar.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.scss
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js
index 6c2556d3..5fcd46be 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js
@@ -1,8 +1,8 @@
import React from 'react'
import NewRoomConstructionContainer from './NewRoomConstructionContainer'
-const BuildingSidebarComponent = () => {
+function BuildingSidebar() {
return <NewRoomConstructionContainer />
}
-export default BuildingSidebarComponent
+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/topologies/sidebar/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/topologies/sidebar/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 (
<Button isBlock icon={<PlusIcon />} onClick={onStart}>
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js
index 0836263c..c149b224 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js
@@ -26,20 +26,20 @@ import {
cancelNewRoomConstruction,
finishNewRoomConstruction,
startNewRoomConstruction,
-} from '../../../../../redux/actions/topology/building'
+} from '../../../../redux/actions/topology/building'
import NewRoomConstructionComponent from './NewRoomConstructionComponent'
-const NewRoomConstructionButton = (props) => {
+function NewRoomConstructionButton() {
const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction)
-
const dispatch = useDispatch()
- const actions = {
- onStart: () => dispatch(startNewRoomConstruction()),
- onFinish: () => dispatch(finishNewRoomConstruction()),
- onCancel: () => dispatch(cancelNewRoomConstruction()),
- }
+
return (
- <NewRoomConstructionComponent {...props} {...actions} currentRoomInConstruction={currentRoomInConstruction} />
+ <NewRoomConstructionComponent
+ onStart={() => dispatch(startNewRoomConstruction())}
+ onFinish={() => dispatch(finishNewRoomConstruction())}
+ onCancel={() => dispatch(cancelNewRoomConstruction())}
+ currentRoomInConstruction={currentRoomInConstruction}
+ />
)
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js
index a7bf3719..00ce4603 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachine.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js
@@ -21,18 +21,20 @@
*/
import React, { useState } from 'react'
-import { useDispatch } from 'react-redux'
-import { deleteMachine } from '../../../../../redux/actions/topology/machine'
+import { useDispatch, useSelector } from 'react-redux'
import { Button } from '@patternfly/react-core'
-import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon'
-import ConfirmationModal from '../../../../modals/ConfirmationModal'
+import { TrashIcon } from '@patternfly/react-icons'
+import ConfirmationModal from '../../../util/modals/ConfirmationModal'
+import { deleteMachine } from '../../../../redux/actions/topology/machine'
-const DeleteMachine = () => {
+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())
+ dispatch(deleteMachine(rackId, position))
}
setVisible(false)
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
index d3d4a8cf..0c3dea98 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
@@ -12,8 +12,12 @@ import {
} from '@patternfly/react-core'
import { useSelector } from 'react-redux'
-const MachineSidebarComponent = ({ machineId }) => {
- const machine = useSelector((state) => state.objects.machine[machineId])
+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 (
<div>
<TextContent>
@@ -31,14 +35,15 @@ const MachineSidebarComponent = ({ machineId }) => {
<Title headingLevel="h2">Units</Title>
</TextContent>
<div className="pf-u-h-100">
- <UnitTabsComponent />
+ <UnitTabsComponent machineId={machineId} />
</div>
</div>
)
}
-MachineSidebarComponent.propTypes = {
- machineId: PropTypes.string,
+MachineSidebar.propTypes = {
+ tileId: PropTypes.string.isRequired,
+ position: PropTypes.number.isRequired,
}
-export default MachineSidebarComponent
+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/topologies/sidebar/machine/UnitAddComponent.js
index 88591208..88591208 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js
index 8a6680e6..fc805b95 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js
@@ -23,19 +23,20 @@
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'
+import { addUnit } from '../../../../redux/actions/topology/machine'
-const UnitAddContainer = ({ unitType }) => {
+function UnitAddContainer({ machineId, unitType }) {
const units = useSelector((state) => Object.values(state.objects[unitType]))
const dispatch = useDispatch()
- const onAdd = (id) => dispatch(addUnit(unitType, id))
+ const onAdd = (id) => dispatch(addUnit(machineId, unitType, id))
return <UnitAddComponent onAdd={onAdd} units={units} />
}
UnitAddContainer.propTypes = {
+ machineId: PropTypes.string.isRequired,
unitType: PropTypes.string.isRequired,
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js
index 9c3c08fd..daa3e7a7 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js
@@ -1,6 +1,5 @@
import PropTypes from 'prop-types'
import React from 'react'
-import { ProcessingUnit, StorageUnit } from '../../../../../shapes'
import {
Button,
DataList,
@@ -20,8 +19,9 @@ import {
Title,
} from '@patternfly/react-core'
import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons'
+import { ProcessingUnit, StorageUnit } from '../../../../shapes'
-const UnitInfo = ({ unit, unitType }) => {
+function UnitInfo({ unit, unitType }) {
if (unitType === 'cpu' || unitType === 'gpu') {
return (
<DescriptionList>
@@ -64,7 +64,7 @@ UnitInfo.propTypes = {
unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired,
}
-const UnitListComponent = ({ unitType, units, onDelete }) => {
+function UnitListComponent({ unitType, units, onDelete }) {
if (units.length === 0) {
return (
<EmptyState>
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
index 2d994f97..901fa45b 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
@@ -24,7 +24,7 @@ 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'
+import { deleteUnit } from '../../../../redux/actions/topology/machine'
const unitMapping = {
cpu: 'cpus',
@@ -33,23 +33,20 @@ const unitMapping = {
storage: 'storages',
}
-const UnitListContainer = ({ unitType, ...props }) => {
+function UnitListContainer({ machineId, unitType }) {
const dispatch = useDispatch()
const units = useSelector((state) => {
- const machine =
- state.objects.machine[
- state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].machines[
- state.interactionLevel.position - 1
- ]
- ]
+ const machine = state.objects.machine[machineId]
return machine[unitMapping[unitType]].map((id) => state.objects[unitType][id])
})
- const onDelete = (unit, unitType) => dispatch(deleteUnit(unitType, unit._id))
- return <UnitListComponent {...props} units={units} unitType={unitType} onDelete={onDelete} />
+ const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id))
+
+ return <UnitListComponent units={units} unitType={unitType} onDelete={onDelete} />
}
UnitListContainer.propTypes = {
+ machineId: PropTypes.string.isRequired,
unitType: PropTypes.string.isRequired,
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js
index 723ed2e2..6d10d2df 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js
@@ -1,31 +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'
-const UnitTabsComponent = () => {
+function UnitTabsComponent({ machineId }) {
const [activeTab, setActiveTab] = useState('cpu-units')
return (
<Tabs activeKey={activeTab} onSelect={(_, tab) => setActiveTab(tab)}>
<Tab eventKey="cpu-units" title={<TabTitleText>CPU</TabTitleText>}>
- <UnitAddContainer unitType="cpu" />
- <UnitListContainer unitType="cpu" />
+ <UnitAddContainer machineId={machineId} unitType="cpu" />
+ <UnitListContainer machineId={machineId} unitType="cpu" />
</Tab>
<Tab eventKey="gpu-units" title={<TabTitleText>GPU</TabTitleText>}>
- <UnitAddContainer unitType="gpu" />
- <UnitListContainer unitType="gpu" />
+ <UnitAddContainer machineId={machineId} unitType="gpu" />
+ <UnitListContainer machineId={machineId} unitType="gpu" />
</Tab>
<Tab eventKey="memory-units" title={<TabTitleText>Memory</TabTitleText>}>
- <UnitAddContainer unitType="memory" />
- <UnitListContainer unitType="memory" />
+ <UnitAddContainer machineId={machineId} unitType="memory" />
+ <UnitListContainer machineId={machineId} unitType="memory" />
</Tab>
<Tab eventKey="storage-units" title={<TabTitleText>Storage</TabTitleText>}>
- <UnitAddContainer unitType="storage" />
- <UnitListContainer unitType="storage" />
+ <UnitAddContainer machineId={machineId} unitType="storage" />
+ <UnitListContainer machineId={machineId} unitType="storage" />
</Tab>
</Tabs>
)
}
+UnitTabsComponent.propTypes = {
+ machineId: PropTypes.string.isRequired,
+}
+
export default UnitTabsComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
index d3d9aaf5..e944c2e8 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
@@ -20,14 +20,25 @@
* SOFTWARE.
*/
+import PropTypes from 'prop-types'
import React from 'react'
import { useDispatch } from 'react-redux'
-import { addPrefab } from '../../../../../redux/actions/prefabs'
-import AddPrefabComponent from './AddPrefabComponent'
+import { Button } from '@patternfly/react-core'
+import { SaveIcon } from '@patternfly/react-icons'
+import { addPrefab } from '../../../../api/prefabs'
-const AddPrefabContainer = (props) => {
+function AddPrefab({ tileId }) {
const dispatch = useDispatch()
- return <AddPrefabComponent {...props} onClick={() => dispatch(addPrefab('name'))} />
+ const onClick = () => dispatch(addPrefab('name', tileId))
+ return (
+ <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm">
+ Save this rack to a prefab
+ </Button>
+ )
}
-export default AddPrefabContainer
+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/topologies/sidebar/rack/DeleteRackContainer.js
index 47959f03..80c6349a 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js
@@ -20,19 +20,20 @@
* 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'
+import ConfirmationModal from '../../../util/modals/ConfirmationModal'
+import { deleteRack } from '../../../../redux/actions/topology/rack'
-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/topologies/sidebar/rack/MachineComponent.js
index 1617b3bf..921622d6 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js
@@ -1,8 +1,8 @@
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'
+import { Machine } from '../../../../shapes'
const UnitIcon = ({ id, type }) => (
<Image
@@ -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/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js
index 27834cf4..de7a2140 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js
@@ -1,7 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import MachineComponent from './MachineComponent'
-import { Machine } from '../../../../../shapes'
import {
Badge,
Button,
@@ -13,6 +12,7 @@ import {
DataListItemRow,
} from '@patternfly/react-core'
import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons'
+import { Machine } from '../../../../shapes'
function MachineListComponent({ machines = [], onSelect, onAdd }) {
return (
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
index 54e6db0a..6fbff949 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
@@ -24,8 +24,8 @@ 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'
+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])
@@ -43,7 +43,7 @@ function MachineListContainer({ tileId, ...props }) {
<MachineListComponent
{...props}
machines={machinesNull}
- onAdd={(index) => 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/topologies/sidebar/rack/RackNameContainer.js
index 11529b55..09d73af7 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
@@ -2,14 +2,14 @@ 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'
+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 <NameComponent name={rackName} onEdit={callback} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
index dd5117f7..3c9f152a 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
@@ -1,8 +1,8 @@
import PropTypes from 'prop-types'
import React from 'react'
-import { machineListContainer, sidebarContainer } from './RackSidebarComponent.module.scss'
+import { machineListContainer, sidebarContainer } from './RackSidebar.module.scss'
import RackNameContainer from './RackNameContainer'
-import AddPrefabContainer from './AddPrefabContainer'
+import AddPrefab from './AddPrefab'
import DeleteRackContainer from './DeleteRackContainer'
import MachineListContainer from './MachineListContainer'
import {
@@ -16,7 +16,7 @@ import {
} from '@patternfly/react-core'
import { useSelector } from 'react-redux'
-function RackSidebarComponent({ tileId }) {
+function RackSidebar({ tileId }) {
const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
return (
@@ -39,8 +39,8 @@ function RackSidebarComponent({ tileId }) {
</TextListItem>
</TextList>
<Title headingLevel="h2">Actions</Title>
- <AddPrefabContainer />
- <DeleteRackContainer />
+ <AddPrefab tileId={tileId} />
+ <DeleteRackContainer tileId={tileId} />
<Title headingLevel="h2">Slots</Title>
</TextContent>
@@ -51,8 +51,8 @@ function RackSidebarComponent({ tileId }) {
)
}
-RackSidebarComponent.propTypes = {
+RackSidebar.propTypes = {
tileId: PropTypes.string.isRequired,
}
-export default RackSidebarComponent
+export default RackSidebar
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/topologies/sidebar/rack/RackSidebar.module.scss
index 6f258aec..6f258aec 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.scss
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js
index 284c4d53..29b8f78a 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js
@@ -20,19 +20,20 @@
* 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 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'
-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/topologies/sidebar/room/EditRoomContainer.js
index 6db2bfb6..7a278cd6 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js
@@ -20,19 +20,20 @@
* 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 { 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'
-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/topologies/sidebar/room/RackConstructionComponent.js
index 8aebe969..a384d5d5 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js
@@ -1,13 +1,12 @@
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'
+import { PlusIcon, TimesIcon } from '@patternfly/react-icons'
const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => {
if (inRackConstructionMode) {
return (
- <Button isBlock={true} icon={<TimesIcon />} onClick={onStop}>
+ <Button isBlock={true} icon={<TimesIcon />} onClick={onStop} className="pf-u-mb-sm">
Stop rack construction
</Button>
)
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js
index 38af447a..e04287a5 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js
@@ -22,10 +22,10 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { startRackConstruction, stopRackConstruction } from '../../../../../redux/actions/topology/room'
+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/topologies/sidebar/room/RoomName.js
index d7b006a6..e8d8b33c 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomName.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
@@ -23,15 +23,15 @@
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'
+import NameComponent from '../NameComponent'
+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 <NameComponent name={roomName} onEdit={callback} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js
index fac58c51..6ad489e0 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js
@@ -13,7 +13,7 @@ import {
Title,
} from '@patternfly/react-core'
-const RoomSidebarComponent = ({ roomId }) => {
+const RoomSidebar = ({ roomId }) => {
return (
<TextContent>
<Title headingLevel="h2">Details</Title>
@@ -30,14 +30,14 @@ const RoomSidebarComponent = ({ roomId }) => {
</TextList>
<Title headingLevel="h2">Construction</Title>
<RackConstructionContainer />
- <EditRoomContainer />
- <DeleteRoomContainer />
+ <EditRoomContainer roomId={roomId} />
+ <DeleteRoomContainer roomId={roomId} />
</TextContent>
)
}
-RoomSidebarComponent.propTypes = {
+RoomSidebar.propTypes = {
roomId: PropTypes.string.isRequired,
}
-export default RoomSidebarComponent
+export default RoomSidebar
diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js
index f6e1c98b..f6e1c98b 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js
+++ b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js
diff --git a/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js
index d4577062..d4577062 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/Modal.js
+++ b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js
diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js
index 392a729e..392a729e 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js
+++ b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js