From cd0b45627f0d8da8c8dc4edde223f3c36e9bcfbf Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sun, 25 Apr 2021 16:01:14 +0200 Subject: build: Migrate to flat project structure This change updates the project structure to become flattened. Previously, the simulator, frontend and API each lived into their own directory. With this change, all modules of the project live in the top-level directory of the repository. This should improve discoverability of modules of the project. --- .../src/components/app/map/LoadingScreen.js | 11 ++ .../src/components/app/map/MapConstants.js | 28 ++++ .../src/components/app/map/MapStageComponent.js | 103 +++++++++++++++ .../app/map/controls/ExportCanvasComponent.js | 13 ++ .../app/map/controls/ScaleIndicatorComponent.js | 11 ++ .../app/map/controls/ScaleIndicatorComponent.sass | 9 ++ .../app/map/controls/ToolPanelComponent.js | 13 ++ .../app/map/controls/ToolPanelComponent.sass | 5 + .../app/map/controls/ZoomControlComponent.js | 24 ++++ .../src/components/app/map/elements/Backdrop.js | 8 ++ .../src/components/app/map/elements/GrayLayer.js | 17 +++ .../src/components/app/map/elements/HoverTile.js | 27 ++++ .../components/app/map/elements/ImageComponent.js | 48 +++++++ .../src/components/app/map/elements/RackFillBar.js | 68 ++++++++++ .../src/components/app/map/elements/RoomTile.js | 20 +++ .../src/components/app/map/elements/TileObject.js | 25 ++++ .../components/app/map/elements/TilePlusIcon.js | 44 +++++++ .../src/components/app/map/elements/WallSegment.js | 32 +++++ .../src/components/app/map/groups/GridGroup.js | 34 +++++ .../src/components/app/map/groups/RackGroup.js | 25 ++++ .../src/components/app/map/groups/RoomGroup.js | 48 +++++++ .../src/components/app/map/groups/TileGroup.js | 35 +++++ .../src/components/app/map/groups/TopologyGroup.js | 44 +++++++ .../src/components/app/map/groups/WallGroup.js | 22 ++++ .../app/map/layers/HoverLayerComponent.js | 75 +++++++++++ .../components/app/map/layers/MapLayerComponent.js | 17 +++ .../app/map/layers/ObjectHoverLayerComponent.js | 11 ++ .../app/map/layers/RoomHoverLayerComponent.js | 6 + .../app/results/PortfolioResultsComponent.js | 93 +++++++++++++ .../src/components/app/sidebars/Sidebar.js | 53 ++++++++ .../src/components/app/sidebars/Sidebar.sass | 50 +++++++ .../app/sidebars/project/PortfolioListComponent.js | 66 ++++++++++ .../sidebars/project/ProjectSidebarComponent.js | 15 +++ .../app/sidebars/project/ScenarioListComponent.js | 62 +++++++++ .../app/sidebars/project/TopologyListComponent.js | 60 +++++++++ .../app/sidebars/topology/NameComponent.js | 13 ++ .../sidebars/topology/TopologySidebarComponent.js | 31 +++++ .../topology/building/BuildingSidebarComponent.js | 13 ++ .../building/NewRoomConstructionComponent.js | 26 ++++ .../topology/machine/BackToRackComponent.js | 10 ++ .../topology/machine/DeleteMachineComponent.js | 10 ++ .../topology/machine/MachineNameComponent.js | 5 + .../topology/machine/MachineSidebarComponent.js | 18 +++ .../sidebars/topology/machine/UnitAddComponent.js | 35 +++++ .../app/sidebars/topology/machine/UnitComponent.js | 52 ++++++++ .../sidebars/topology/machine/UnitListComponent.js | 20 +++ .../sidebars/topology/machine/UnitTabsComponent.js | 78 +++++++++++ .../sidebars/topology/rack/AddPrefabComponent.js | 10 ++ .../sidebars/topology/rack/BackToRoomComponent.js | 10 ++ .../sidebars/topology/rack/DeleteRackComponent.js | 10 ++ .../sidebars/topology/rack/EmptySlotComponent.js | 13 ++ .../app/sidebars/topology/rack/MachineComponent.js | 43 ++++++ .../sidebars/topology/rack/MachineListComponent.js | 20 +++ .../topology/rack/MachineListComponent.sass | 2 + .../sidebars/topology/rack/RackNameComponent.js | 6 + .../sidebars/topology/rack/RackSidebarComponent.js | 25 ++++ .../topology/rack/RackSidebarComponent.sass | 11 ++ .../topology/room/BackToBuildingComponent.js | 10 ++ .../sidebars/topology/room/DeleteRoomComponent.js | 10 ++ .../sidebars/topology/room/EditRoomComponent.js | 22 ++++ .../topology/room/RackConstructionComponent.js | 27 ++++ .../sidebars/topology/room/RoomNameComponent.js | 6 + .../sidebars/topology/room/RoomSidebarComponent.js | 20 +++ .../src/components/home/ContactSection.js | 54 ++++++++ .../src/components/home/ContactSection.sass | 15 +++ .../src/components/home/ContentSection.js | 19 +++ .../src/components/home/ContentSection.sass | 9 ++ .../src/components/home/IntroSection.js | 40 ++++++ .../src/components/home/JumbotronHeader.js | 18 +++ .../src/components/home/JumbotronHeader.sass | 24 ++++ .../src/components/home/ModelingSection.js | 22 ++++ .../src/components/home/ScreenshotSection.js | 24 ++++ .../src/components/home/ScreenshotSection.sass | 5 + .../src/components/home/SimulationSection.js | 22 ++++ .../src/components/home/StakeholderSection.js | 30 +++++ .../src/components/home/TeamSection.js | 53 ++++++++ .../src/components/home/TechnologiesSection.js | 40 ++++++ .../src/components/modals/ConfirmationModal.js | 37 ++++++ .../opendc-web-ui/src/components/modals/Modal.js | 53 ++++++++ .../src/components/modals/TextInputModal.js | 54 ++++++++ .../NewPortfolioModalComponent.js | 78 +++++++++++ .../custom-components/NewScenarioModalComponent.js | 144 +++++++++++++++++++++ .../custom-components/NewTopologyModalComponent.js | 71 ++++++++++ .../components/navigation/AppNavbarComponent.js | 26 ++++ .../src/components/navigation/HomeNavbar.js | 23 ++++ .../src/components/navigation/LogoutButton.js | 17 +++ .../src/components/navigation/Navbar.js | 92 +++++++++++++ .../src/components/navigation/Navbar.sass | 30 +++++ .../src/components/not-found/BlinkingCursor.js | 6 + .../src/components/not-found/BlinkingCursor.sass | 35 +++++ .../src/components/not-found/CodeBlock.js | 28 ++++ .../src/components/not-found/CodeBlock.sass | 3 + .../src/components/not-found/TerminalWindow.js | 33 +++++ .../src/components/not-found/TerminalWindow.sass | 70 ++++++++++ .../src/components/projects/FilterButton.js | 24 ++++ .../src/components/projects/FilterPanel.js | 13 ++ .../src/components/projects/FilterPanel.sass | 5 + .../projects/NewProjectButtonComponent.js | 17 +++ .../components/projects/ProjectActionButtons.js | 29 +++++ .../src/components/projects/ProjectAuthList.js | 39 ++++++ .../src/components/projects/ProjectAuthRow.js | 24 ++++ 101 files changed, 3104 insertions(+) create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/ContactSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/ContactSection.sass create mode 100644 opendc-web/opendc-web-ui/src/components/home/ContentSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/ContentSection.sass create mode 100644 opendc-web/opendc-web-ui/src/components/home/IntroSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass create mode 100644 opendc-web/opendc-web-ui/src/components/home/ModelingSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass create mode 100644 opendc-web/opendc-web-ui/src/components/home/SimulationSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/TeamSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/Modal.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js create mode 100644 opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js create mode 100644 opendc-web/opendc-web-ui/src/components/navigation/Navbar.js create mode 100644 opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass create mode 100644 opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js create mode 100644 opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass create mode 100644 opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js create mode 100644 opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass create mode 100644 opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js create mode 100644 opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass create mode 100644 opendc-web/opendc-web-ui/src/components/projects/FilterButton.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass create mode 100644 opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js create mode 100644 opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js (limited to 'opendc-web/opendc-web-ui/src/components') diff --git a/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js b/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js new file mode 100644 index 00000000..7efea9b0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js @@ -0,0 +1,11 @@ +import React from 'react' +import FontAwesome from 'react-fontawesome' + +const LoadingScreen = () => ( +
+ + Loading your project... +
+) + +export default LoadingScreen diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js new file mode 100644 index 00000000..d6ea1f84 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapConstants.js @@ -0,0 +1,28 @@ +export const MAP_SIZE = 50 +export const TILE_SIZE_IN_PIXELS = 100 +export const TILE_SIZE_IN_METERS = 0.5 +export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS + +export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5 +export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3 +export const OBJECT_SIZE_IN_PIXELS = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 + +export const GRID_LINE_WIDTH_IN_PIXELS = 2 +export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 8 +export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 12 +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 + +export const MAP_MOVE_PIXELS_PER_EVENT = 20 +export const MAP_SCALE_PER_EVENT = 1.1 +export const MAP_MIN_SCALE = 0.5 +export const MAP_MAX_SCALE = 1.5 + +export const MAX_NUM_UNITS_PER_MACHINE = 6 +export const DEFAULT_RACK_SLOT_CAPACITY = 42 +export const DEFAULT_RACK_POWER_CAPACITY = 10000 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js new file mode 100644 index 00000000..2cd0ed6e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js @@ -0,0 +1,103 @@ +import React from 'react' +import { Stage } from 'react-konva' +import { Shortcuts } from 'react-shortcuts' +import MapLayer from '../../../containers/app/map/layers/MapLayer' +import ObjectHoverLayer from '../../../containers/app/map/layers/ObjectHoverLayer' +import RoomHoverLayer from '../../../containers/app/map/layers/RoomHoverLayer' +import { NAVBAR_HEIGHT } from '../../navigation/Navbar' +import { MAP_MOVE_PIXELS_PER_EVENT } from './MapConstants' +import { Provider } from 'react-redux' +import { store } from '../../../store/configure-store' + +class MapStageComponent extends React.Component { + state = { + mouseX: 0, + mouseY: 0, + } + + constructor(props) { + super(props) + + this.updateDimensions = this.updateDimensions.bind(this) + this.updateScale = this.updateScale.bind(this) + } + + componentDidMount() { + this.updateDimensions() + + window.addEventListener('resize', this.updateDimensions) + window.addEventListener('wheel', this.updateScale) + + window['exportCanvasToImage'] = () => { + const download = document.createElement('a') + download.href = this.stage.getStage().toDataURL() + download.download = 'opendc-canvas-export-' + Date.now() + '.png' + download.click() + } + } + + componentWillUnmount() { + window.removeEventListener('resize', this.updateDimensions) + window.removeEventListener('wheel', this.updateScale) + } + + updateDimensions() { + this.props.setMapDimensions(window.innerWidth, window.innerHeight - NAVBAR_HEIGHT) + } + + updateScale(e) { + e.preventDefault() + this.props.zoomInOnPosition(e.deltaY < 0, this.state.mouseX, this.state.mouseY) + } + + updateMousePosition() { + const mousePos = this.stage.getStage().getPointerPosition() + this.setState({ mouseX: mousePos.x, mouseY: mousePos.y }) + } + + handleShortcuts(action) { + switch (action) { + case 'MOVE_LEFT': + this.moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0) + break + case 'MOVE_RIGHT': + this.moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0) + break + case 'MOVE_UP': + this.moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT) + break + case 'MOVE_DOWN': + this.moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT) + break + default: + break + } + } + + moveWithDelta(deltaX, deltaY) { + this.props.setMapPositionWithBoundsCheck(this.props.mapPosition.x + deltaX, this.props.mapPosition.y + deltaY) + } + + render() { + return ( + + { + this.stage = stage + }} + width={this.props.mapDimensions.width} + height={this.props.mapDimensions.height} + onMouseMove={this.updateMousePosition.bind(this)} + > + + + + + + + + ) + } +} + +export default MapStageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js new file mode 100644 index 00000000..8487f47b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js @@ -0,0 +1,13 @@ +import React from 'react' + +const ExportCanvasComponent = () => ( + +) + +export default ExportCanvasComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js new file mode 100644 index 00000000..7cbb45c0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js @@ -0,0 +1,11 @@ +import React from 'react' +import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' +import './ScaleIndicatorComponent.sass' + +const ScaleIndicatorComponent = ({ scale }) => ( +
+ {TILE_SIZE_IN_METERS}m +
+) + +export default ScaleIndicatorComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass new file mode 100644 index 00000000..03a72c99 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass @@ -0,0 +1,9 @@ +.scale-indicator + position: absolute + right: 10px + bottom: 10px + z-index: 50 + + border: solid 2px #212529 + border-top: none + border-left: none diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js new file mode 100644 index 00000000..f372734d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js @@ -0,0 +1,13 @@ +import React from 'react' +import ZoomControlContainer from '../../../../containers/app/map/controls/ZoomControlContainer' +import ExportCanvasComponent from './ExportCanvasComponent' +import './ToolPanelComponent.sass' + +const ToolPanelComponent = () => ( +
+ + +
+) + +export default ToolPanelComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass new file mode 100644 index 00000000..8b27d24a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass @@ -0,0 +1,5 @@ +.tool-panel + position: absolute + left: 10px + bottom: 10px + z-index: 50 diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js new file mode 100644 index 00000000..65944bea --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js @@ -0,0 +1,24 @@ +import React from 'react' + +const ZoomControlComponent = ({ zoomInOnCenter }) => { + return ( + + + + + ) +} + +export default ZoomControlComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js new file mode 100644 index 00000000..8ccfe584 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/Backdrop.js @@ -0,0 +1,8 @@ +import React from 'react' +import { Rect } from 'react-konva' +import { BACKDROP_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +const Backdrop = () => + +export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js new file mode 100644 index 00000000..c54a34ad --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/GrayLayer.js @@ -0,0 +1,17 @@ +import React from 'react' +import { Rect } from 'react-konva' +import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' +import { MAP_SIZE_IN_PIXELS } from '../MapConstants' + +const GrayLayer = ({ onClick }) => ( + +) + +export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js new file mode 100644 index 00000000..912229c4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/HoverTile.js @@ -0,0 +1,27 @@ +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 }) => ( + +) + +HoverTile.propTypes = { + pixelX: PropTypes.number.isRequired, + pixelY: PropTypes.number.isRequired, + isValid: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, +} + +export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js new file mode 100644 index 00000000..2b5c569f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js @@ -0,0 +1,48 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Image } from 'react-konva' + +class ImageComponent extends React.Component { + static imageCaches = {} + static propTypes = { + src: PropTypes.string.isRequired, + x: PropTypes.number.isRequired, + y: PropTypes.number.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + opacity: PropTypes.number.isRequired, + } + + state = { + image: null, + } + + componentDidMount() { + if (ImageComponent.imageCaches[this.props.src]) { + this.setState({ image: ImageComponent.imageCaches[this.props.src] }) + return + } + + const image = new window.Image() + image.src = this.props.src + image.onload = () => { + this.setState({ image }) + ImageComponent.imageCaches[this.props.src] = image + } + } + + render() { + return ( + + ) + } +} + +export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js new file mode 100644 index 00000000..8c573a6f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/RackFillBar.js @@ -0,0 +1,68 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group, Rect } from 'react-konva' +import { + RACK_ENERGY_BAR_BACKGROUND_COLOR, + RACK_ENERGY_BAR_FILL_COLOR, + RACK_SPACE_BAR_BACKGROUND_COLOR, + RACK_SPACE_BAR_FILL_COLOR, +} from '../../../../util/colors' +import { + OBJECT_BORDER_WIDTH_IN_PIXELS, + OBJECT_MARGIN_IN_PIXELS, + RACK_FILL_ICON_OPACITY, + RACK_FILL_ICON_WIDTH, + TILE_SIZE_IN_PIXELS, +} from '../MapConstants' +import ImageComponent from './ImageComponent' + +const RackFillBar = ({ positionX, positionY, type, fillFraction }) => { + const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 + const x = + positionX * TILE_SIZE_IN_PIXELS + + OBJECT_MARGIN_IN_PIXELS + + (type === 'space' ? halfOfObjectBorderWidth : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)) + const startY = positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS + halfOfObjectBorderWidth + const width = 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - halfOfObjectBorderWidth + const fullHeight = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - OBJECT_BORDER_WIDTH_IN_PIXELS + + const fractionHeight = fillFraction * fullHeight + const fractionY = + (positionY + 1) * TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS - halfOfObjectBorderWidth - fractionHeight + + return ( + + + + + + ) +} + +RackFillBar.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + type: PropTypes.string.isRequired, + fillFraction: PropTypes.number.isRequired, +} + +export default RackFillBar diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js new file mode 100644 index 00000000..43bf918e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js @@ -0,0 +1,20 @@ +import React from 'react' +import { Rect } from 'react-konva' +import Shapes from '../../../../shapes/index' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +const RoomTile = ({ tile, color }) => ( + +) + +RoomTile.propTypes = { + tile: Shapes.Tile, +} + +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 new file mode 100644 index 00000000..9e87cc82 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/TileObject.js @@ -0,0 +1,25 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Rect } from 'react-konva' +import { OBJECT_BORDER_COLOR } from '../../../../util/colors' +import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +const TileObject = ({ positionX, positionY, color }) => ( + +) + +TileObject.propTypes = { + positionX: PropTypes.number.isRequired, + positionY: PropTypes.number.isRequired, + color: PropTypes.string.isRequired, +} + +export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/TilePlusIcon.js new file mode 100644 index 00000000..be3a00a8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/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' + +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 ( + + {linePoints.map((points, index) => ( + + ))} + + ) +} + +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/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js new file mode 100644 index 00000000..8aa2aebf --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js @@ -0,0 +1,32 @@ +import React from 'react' +import { Line } from 'react-konva' +import Shapes from '../../../../shapes/index' +import { WALL_COLOR } from '../../../../util/colors' +import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' + +const WallSegment = ({ wallSegment }) => { + let points + if (wallSegment.isHorizontal) { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + ] + } else { + points = [ + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + wallSegment.startPosY * TILE_SIZE_IN_PIXELS, + wallSegment.startPosX * TILE_SIZE_IN_PIXELS, + (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS, + ] + } + + return +} + +WallSegment.propTypes = { + wallSegment: Shapes.WallSegment, +} + +export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js new file mode 100644 index 00000000..ebc00244 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/GridGroup.js @@ -0,0 +1,34 @@ +import React from 'react' +import { Group, Line } from 'react-konva' +import { GRID_COLOR } from '../../../../util/colors' +import { GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' + +const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i) +const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, + index * TILE_SIZE_IN_PIXELS, +]) +const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ + index * TILE_SIZE_IN_PIXELS, + 0, + index * TILE_SIZE_IN_PIXELS, + MAP_SIZE_IN_PIXELS, +]) + +const GridGroup = () => ( + + {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( + + ))} + +) + +export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js new file mode 100644 index 00000000..eb6dc24a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js @@ -0,0 +1,25 @@ +import React from 'react' +import { Group } from 'react-konva' +import RackEnergyFillContainer from '../../../../containers/app/map/RackEnergyFillContainer' +import RackSpaceFillContainer from '../../../../containers/app/map/RackSpaceFillContainer' +import Shapes from '../../../../shapes/index' +import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' +import TileObject from '../elements/TileObject' + +const RackGroup = ({ tile }) => { + return ( + + + + + + + + ) +} + +RackGroup.propTypes = { + tile: Shapes.Tile, +} + +export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js new file mode 100644 index 00000000..1fd54687 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js @@ -0,0 +1,48 @@ +import React from 'react' +import { Group } from 'react-konva' +import GrayContainer from '../../../../containers/app/map/GrayContainer' +import TileContainer from '../../../../containers/app/map/TileContainer' +import WallContainer from '../../../../containers/app/map/WallContainer' +import Shapes from '../../../../shapes/index' + +const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => { + if (currentRoomInConstruction === room._id) { + return ( + + {room.tileIds.map((tileId) => ( + + ))} + + ) + } + + return ( + + {(() => { + if ( + (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && + interactionLevel.roomId === room._id + ) { + return [ + room.tileIds + .filter((tileId) => tileId !== interactionLevel.tileId) + .map((tileId) => ), + , + room.tileIds + .filter((tileId) => tileId === interactionLevel.tileId) + .map((tileId) => ), + ] + } else { + return room.tileIds.map((tileId) => ) + } + })()} + + + ) +} + +RoomGroup.propTypes = { + room: Shapes.Room, +} + +export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js new file mode 100644 index 00000000..1e106823 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import RackContainer from '../../../../containers/app/map/RackContainer' +import Shapes from '../../../../shapes/index' +import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' +import RoomTile from '../elements/RoomTile' + +const TileGroup = ({ tile, newTile, roomLoad, onClick }) => { + let tileObject + if (tile.rackId) { + tileObject = + } else { + tileObject = null + } + + let color = ROOM_DEFAULT_COLOR + if (newTile) { + color = ROOM_IN_CONSTRUCTION_COLOR + } + + return ( + onClick(tile)}> + + {tileObject} + + ) +} + +TileGroup.propTypes = { + tile: Shapes.Tile, + newTile: PropTypes.bool, +} + +export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js new file mode 100644 index 00000000..6096fc8b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js @@ -0,0 +1,44 @@ +import React from 'react' +import { Group } from 'react-konva' +import GrayContainer from '../../../../containers/app/map/GrayContainer' +import RoomContainer from '../../../../containers/app/map/RoomContainer' +import Shapes from '../../../../shapes/index' + +const TopologyGroup = ({ topology, interactionLevel }) => { + if (!topology) { + return + } + + if (interactionLevel.mode === 'BUILDING') { + return ( + + {topology.roomIds.map((roomId) => ( + + ))} + + ) + } + + return ( + + {topology.roomIds + .filter((roomId) => roomId !== interactionLevel.roomId) + .map((roomId) => ( + + ))} + {interactionLevel.mode === 'ROOM' ? : null} + {topology.roomIds + .filter((roomId) => roomId === interactionLevel.roomId) + .map((roomId) => ( + + ))} + + ) +} + +TopologyGroup.propTypes = { + topology: Shapes.Topology, + interactionLevel: Shapes.InteractionLevel, +} + +export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js new file mode 100644 index 00000000..7b0f5ca0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js @@ -0,0 +1,22 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Group } from 'react-konva' +import Shapes from '../../../../shapes/index' +import { deriveWallLocations } from '../../../../util/tile-calculations' +import WallSegment from '../elements/WallSegment' + +const WallGroup = ({ tiles }) => { + return ( + + {deriveWallLocations(tiles).map((wallSegment, index) => ( + + ))} + + ) +} + +WallGroup.propTypes = { + tiles: PropTypes.arrayOf(Shapes.Tile).isRequired, +} + +export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js new file mode 100644 index 00000000..bead87de --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js @@ -0,0 +1,75 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Layer } from 'react-konva' +import HoverTile from '../elements/HoverTile' +import { TILE_SIZE_IN_PIXELS } from '../MapConstants' + +class HoverLayerComponent extends React.Component { + static propTypes = { + mouseX: PropTypes.number.isRequired, + mouseY: PropTypes.number.isRequired, + mapPosition: PropTypes.object.isRequired, + mapScale: PropTypes.number.isRequired, + isEnabled: PropTypes.func.isRequired, + onClick: PropTypes.func.isRequired, + } + + state = { + positionX: -1, + positionY: -1, + validity: false, + } + + componentDidUpdate() { + if (!this.props.isEnabled()) { + return + } + + const positionX = Math.floor( + (this.props.mouseX - this.props.mapPosition.x) / (this.props.mapScale * TILE_SIZE_IN_PIXELS) + ) + const positionY = Math.floor( + (this.props.mouseY - this.props.mapPosition.y) / (this.props.mapScale * TILE_SIZE_IN_PIXELS) + ) + + if (positionX !== this.state.positionX || positionY !== this.state.positionY) { + this.setState({ + positionX, + positionY, + validity: this.props.isValid(positionX, positionY), + }) + } + } + + render() { + if (!this.props.isEnabled()) { + return + } + + const pixelX = this.props.mapScale * this.state.positionX * TILE_SIZE_IN_PIXELS + this.props.mapPosition.x + const pixelY = this.props.mapScale * this.state.positionY * TILE_SIZE_IN_PIXELS + this.props.mapPosition.y + + return ( + + + this.state.validity ? this.props.onClick(this.state.positionX, this.state.positionY) : undefined + } + /> + {this.props.children + ? React.cloneElement(this.props.children, { + pixelX, + pixelY, + scale: this.props.mapScale, + }) + : undefined} + + ) + } +} + +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 new file mode 100644 index 00000000..8ee14c9c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/MapLayerComponent.js @@ -0,0 +1,17 @@ +import React from 'react' +import { Group, Layer } from 'react-konva' +import TopologyContainer from '../../../../containers/app/map/TopologyContainer' +import Backdrop from '../elements/Backdrop' +import GridGroup from '../groups/GridGroup' + +const MapLayerComponent = ({ mapPosition, mapScale }) => ( + + + + + + + +) + +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 new file mode 100644 index 00000000..661fc255 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/ObjectHoverLayerComponent.js @@ -0,0 +1,11 @@ +import React from 'react' +import TilePlusIcon from '../elements/TilePlusIcon' +import HoverLayerComponent from './HoverLayerComponent' + +const ObjectHoverLayerComponent = (props) => ( + + + +) + +export default ObjectHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js new file mode 100644 index 00000000..887e2891 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/RoomHoverLayerComponent.js @@ -0,0 +1,6 @@ +import React from 'react' +import HoverLayerComponent from './HoverLayerComponent' + +const RoomHoverLayerComponent = (props) => + +export default RoomHoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js new file mode 100644 index 00000000..759acd57 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js @@ -0,0 +1,93 @@ +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_SHORT, METRIC_UNITS } from '../../../util/available-metrics' +import { mean, std } from 'mathjs' +import Shapes from '../../../shapes/index' +import approx from 'approximate-number' + +const PortfolioResultsComponent = ({ portfolio, scenarios }) => { + if (!portfolio) { + return
Loading...
+ } + + const nonFinishedScenarios = scenarios.filter((s) => s.simulation.state !== 'FINISHED') + + if (nonFinishedScenarios.length > 0) { + if (nonFinishedScenarios.every((s) => s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING')) { + return ( +
+

Simulation running...

+

{nonFinishedScenarios.length} of the scenarios are still being simulated

+
+ ) + } + if (nonFinishedScenarios.some((s) => s.simulation.state === 'FAILED')) { + return ( +
+

Simulation failed.

+

+ Try again by creating a new scenario. Please contact the OpenDC team for support, if issues + persist. +

+
+ ) + } + } + + const dataPerMetric = {} + + AVAILABLE_METRICS.forEach((metric) => { + dataPerMetric[metric] = scenarios.map((scenario) => ({ + name: scenario.name, + value: mean(scenario.results[metric]), + errorX: std(scenario.results[metric]), + })) + }) + + return ( +
+

Portfolio: {portfolio.name}

+

Repeats per Scenario: {portfolio.targets.repeatsPerScenario}

+
+ {AVAILABLE_METRICS.map((metric) => ( +
+

{METRIC_NAMES_SHORT[metric]}

+ + + + approx(tick)} + label={{ value: METRIC_UNITS[metric], position: 'bottom', offset: 0 }} + type="number" + /> + + + + + + + +
+ ))} +
+
+ ) +} + +PortfolioResultsComponent.propTypes = { + portfolio: Shapes.Portfolio, + scenarios: PropTypes.arrayOf(Shapes.Scenario), +} + +export default PortfolioResultsComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js new file mode 100644 index 00000000..f7368f54 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js @@ -0,0 +1,53 @@ +import PropTypes from 'prop-types' +import classNames from 'classnames' +import React from 'react' +import './Sidebar.sass' + +class Sidebar extends React.Component { + static propTypes = { + isRight: PropTypes.bool.isRequired, + collapsible: PropTypes.bool, + } + + static defaultProps = { + collapsible: true, + } + + state = { + collapsed: false, + } + + render() { + const collapseButton = ( +
this.setState({ collapsed: !this.state.collapsed })} + > + {(this.state.collapsed && this.props.isRight) || (!this.state.collapsed && !this.props.isRight) ? ( + + ) : ( + + )} +
+ ) + + if (this.state.collapsed) { + return collapseButton + } + return ( +
e.stopPropagation()} + > + {this.props.children} + {this.props.collapsible && collapseButton} +
+ ) + } +} + +export default Sidebar diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass new file mode 100644 index 00000000..b8e15716 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass @@ -0,0 +1,50 @@ +@import ../../../style-globals/_variables.sass +@import ../../../style-globals/_mixins.sass + +.sidebar-collapse-button + position: absolute + left: 5px + top: 5px + padding: 5px 7px + + background: white + border: solid 1px $gray-semi-light + z-index: 99 + + +clickable + +border-radius(5px) + +transition(background, 200ms) + + &.sidebar-collapse-button-right + left: auto + right: 5px + top: 5px + + &:hover + background: #eeeeee + +.sidebar + position: absolute + top: 0 + left: 0 + width: $side-bar-width + + z-index: 100 + background: white + + border-right: $gray-semi-dark 1px solid + + .sidebar-collapse-button + left: auto + right: -25px + +.sidebar-right + left: auto + right: 0 + + border-left: $gray-semi-dark 1px solid + border-right: none + + .sidebar-collapse-button-right + left: -25px + right: auto diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js new file mode 100644 index 00000000..b000b9e2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js @@ -0,0 +1,66 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Shapes from '../../../../shapes' +import { Link } from 'react-router-dom' +import FontAwesome from 'react-fontawesome' +import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer' + +class PortfolioListComponent extends React.Component { + static propTypes = { + portfolios: PropTypes.arrayOf(Shapes.Portfolio), + currentProjectId: PropTypes.string.isRequired, + currentPortfolioId: PropTypes.string, + onNewPortfolio: PropTypes.func.isRequired, + onChoosePortfolio: PropTypes.func.isRequired, + onDeletePortfolio: PropTypes.func.isRequired, + } + + onDelete(id) { + this.props.onDeletePortfolio(id) + } + + render() { + return ( +
+

+ Portfolios + +

+ + {this.props.portfolios.map((portfolio, idx) => ( +
+
+
+ {portfolio.name} +
+
+ this.props.onChoosePortfolio(portfolio._id)} + /> + this.onDelete(portfolio._id)} + /> +
+
+ +
+ ))} +
+ ) + } +} + +export default PortfolioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js new file mode 100644 index 00000000..4789315e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js @@ -0,0 +1,15 @@ +import React from 'react' +import Sidebar from '../Sidebar' +import TopologyListContainer from '../../../../containers/app/sidebars/project/TopologyListContainer' +import PortfolioListContainer from '../../../../containers/app/sidebars/project/PortfolioListContainer' + +const ProjectSidebarComponent = ({ collapsible }) => ( + +
+ + +
+
+) + +export default ProjectSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js new file mode 100644 index 00000000..e775a663 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js @@ -0,0 +1,62 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Shapes from '../../../../shapes' +import { Link } from 'react-router-dom' +import FontAwesome from 'react-fontawesome' + +class ScenarioListComponent extends React.Component { + static propTypes = { + scenarios: PropTypes.arrayOf(Shapes.Scenario), + portfolioId: PropTypes.string, + currentProjectId: PropTypes.string.isRequired, + currentScenarioId: PropTypes.string, + onNewScenario: PropTypes.func.isRequired, + onChooseScenario: PropTypes.func.isRequired, + onDeleteScenario: PropTypes.func.isRequired, + } + + onDelete(id) { + this.props.onDeleteScenario(id) + } + + render() { + return ( + <> + {this.props.scenarios.map((scenario, idx) => ( +
+
+ {scenario.name} +
+
+ this.props.onChooseScenario(scenario.portfolioId, scenario._id)} + /> + (idx !== 0 ? this.onDelete(scenario._id) : undefined)} + /> +
+
+ ))} +
+
this.props.onNewScenario(this.props.portfolioId)} + > + + New scenario +
+
+ + ) + } +} + +export default ScenarioListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js new file mode 100644 index 00000000..2f42f7e4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js @@ -0,0 +1,60 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Shapes from '../../../../shapes' +import FontAwesome from 'react-fontawesome' + +class TopologyListComponent extends React.Component { + static propTypes = { + topologies: PropTypes.arrayOf(Shapes.Topology), + currentTopologyId: PropTypes.string, + onChooseTopology: PropTypes.func.isRequired, + onNewTopology: PropTypes.func.isRequired, + onDeleteTopology: PropTypes.func.isRequired, + } + + onChoose(id) { + this.props.onChooseTopology(id) + } + + onDelete(id) { + this.props.onDeleteTopology(id) + } + + render() { + return ( +
+

+ Topologies + +

+ + {this.props.topologies.map((topology, idx) => ( +
+
+ {topology.name} +
+
+ this.onChoose(topology._id)} + /> + (idx !== 0 ? this.onDelete(topology._id) : undefined)} + /> +
+
+ ))} +
+ ) + } +} + +export default TopologyListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js new file mode 100644 index 00000000..5fb0dc55 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js @@ -0,0 +1,13 @@ +import React from 'react' +import FontAwesome from 'react-fontawesome' + +const NameComponent = ({ name, onEdit }) => ( +

+ {name} + +

+) + +export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js new file mode 100644 index 00000000..f5eee36b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js @@ -0,0 +1,31 @@ +import React from 'react' +import BuildingSidebarContainer from '../../../../containers/app/sidebars/topology/building/BuildingSidebarContainer' +import MachineSidebarContainer from '../../../../containers/app/sidebars/topology/machine/MachineSidebarContainer' +import RackSidebarContainer from '../../../../containers/app/sidebars/topology/rack/RackSidebarContainer' +import RoomSidebarContainer from '../../../../containers/app/sidebars/topology/room/RoomSidebarContainer' +import Sidebar from '../Sidebar' + +const TopologySidebarComponent = ({ interactionLevel }) => { + let sidebarContent + + switch (interactionLevel.mode) { + case 'BUILDING': + sidebarContent = + break + case 'ROOM': + sidebarContent = + break + case 'RACK': + sidebarContent = + break + case 'MACHINE': + sidebarContent = + break + default: + sidebarContent = 'Missing Content' + } + + return {sidebarContent} +} + +export default TopologySidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js new file mode 100644 index 00000000..eea62f84 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js @@ -0,0 +1,13 @@ +import React from 'react' +import NewRoomConstructionContainer from '../../../../../containers/app/sidebars/topology/building/NewRoomConstructionContainer' + +const BuildingSidebarComponent = () => { + return ( +
+

Building

+ +
+ ) +} + +export default BuildingSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js new file mode 100644 index 00000000..fd552c1e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js @@ -0,0 +1,26 @@ +import React from 'react' + +const NewRoomConstructionComponent = ({ onStart, onFinish, onCancel, currentRoomInConstruction }) => { + if (currentRoomInConstruction === '-1') { + return ( +
+ + Construct a new room +
+ ) + } + return ( +
+
+ + Finalize new room +
+
+ + Cancel construction +
+
+ ) +} + +export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js new file mode 100644 index 00000000..70d522b2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const BackToRackComponent = ({ onClick }) => ( +
+ + Back to rack +
+) + +export default BackToRackComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js new file mode 100644 index 00000000..37820316 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const DeleteMachineComponent = ({ onClick }) => ( +
+ + Delete this machine +
+) + +export default DeleteMachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js new file mode 100644 index 00000000..992383c4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js @@ -0,0 +1,5 @@ +import React from 'react' + +const MachineNameComponent = ({ position }) =>

Machine at slot {position}

+ +export default MachineNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js new file mode 100644 index 00000000..7c78cf9e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js @@ -0,0 +1,18 @@ +import React from 'react' +import BackToRackContainer from '../../../../../containers/app/sidebars/topology/machine/BackToRackContainer' +import DeleteMachineContainer from '../../../../../containers/app/sidebars/topology/machine/DeleteMachineContainer' +import MachineNameContainer from '../../../../../containers/app/sidebars/topology/machine/MachineNameContainer' +import UnitTabsContainer from '../../../../../containers/app/sidebars/topology/machine/UnitTabsContainer' + +const MachineSidebarComponent = ({ machineId }) => { + return ( +
+ + + + +
+ ) +} + +export default MachineSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js new file mode 100644 index 00000000..4e9dbc7e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js @@ -0,0 +1,35 @@ +import PropTypes from 'prop-types' +import React from 'react' + +class UnitAddComponent extends React.Component { + static propTypes = { + units: PropTypes.array.isRequired, + onAdd: PropTypes.func.isRequired, + } + + render() { + return ( +
+
+ + +
+
+ ) + } +} + +export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js new file mode 100644 index 00000000..de55e506 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js @@ -0,0 +1,52 @@ +import React from 'react' +import { UncontrolledPopover, PopoverHeader, PopoverBody, Button } from 'reactstrap' + +function UnitComponent({ index, unitType, unit, onDelete }) { + let unitInfo + if (unitType === 'cpu' || unitType === 'gpu') { + unitInfo = ( + <> + Clockrate: + {unit.clockRateMhz} +
+ Num. Cores: + {unit.numberOfCores} +
+ Energy Cons.: + {unit.energyConsumptionW} W +
+ + ) + } else if (unitType === 'memory' || unitType === 'storage') { + unitInfo = ( + <> + Speed: + {unit.speedMbPerS} Mb/s +
+ Size: + {unit.sizeMb} MB +
+ Energy Cons.: + {unit.energyConsumptionW} W +
+ + ) + } + + return ( +
  • + {unit.name} + +
  • + ) +} + +export default UnitComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js new file mode 100644 index 00000000..2ade0f6a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js @@ -0,0 +1,20 @@ +import React from 'react' +import UnitContainer from '../../../../../containers/app/sidebars/topology/machine/UnitContainer' + +const UnitListComponent = ({ unitType, unitIds }) => ( +
      + {unitIds.length !== 0 ? ( + unitIds.map((unitId, index) => ( + + )) + ) : ( +
      + + No units... Add some with the menu above! + +
      + )} +
    +) + +export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js new file mode 100644 index 00000000..6599fefd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js @@ -0,0 +1,78 @@ +import React, { useState } from 'react' +import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap' +import UnitAddContainer from '../../../../../containers/app/sidebars/topology/machine/UnitAddContainer' +import UnitListContainer from '../../../../../containers/app/sidebars/topology/machine/UnitListContainer' + +const UnitTabsComponent = () => { + const [activeTab, setActiveTab] = useState('cpu-units') + const toggle = (tab) => { + if (activeTab !== tab) setActiveTab(tab) + } + + return ( +
    + + + + + + + + + + + + + + + + + + + +
    + ) +} + +export default UnitTabsComponent 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 new file mode 100644 index 00000000..75418f9d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const AddPrefabComponent = ({ onClick }) => ( +
    + + Save this rack to a prefab +
    +) + +export default AddPrefabComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js new file mode 100644 index 00000000..c14775bf --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const BackToRoomComponent = ({ onClick }) => ( +
    + + Back to room +
    +) + +export default BackToRoomComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js new file mode 100644 index 00000000..23b0daac --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const DeleteRackComponent = ({ onClick }) => ( +
    + + Delete this rack +
    +) + +export default DeleteRackComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js new file mode 100644 index 00000000..d7e30f1d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js @@ -0,0 +1,13 @@ +import React from 'react' + +const EmptySlotComponent = ({ position, onAdd }) => ( +
  • + {position} + +
  • +) + +export default EmptySlotComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js new file mode 100644 index 00000000..caa3dc04 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js @@ -0,0 +1,43 @@ +import React from 'react' +import Shapes from '../../../../../shapes' + +const UnitIcon = ({ id, type }) => ( +
    + {'Machine +
    +) + +const MachineComponent = ({ position, machine, onClick }) => { + const hasNoUnits = + machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length + machine.storageIds.length === 0 + + return ( +
  • + {position} +
    + {machine.cpuIds.length > 0 ? : undefined} + {machine.gpuIds.length > 0 ? : undefined} + {machine.memoryIds.length > 0 ? : undefined} + {machine.storageIds.length > 0 ? : undefined} + {hasNoUnits ? ( + Machine with no units + ) : undefined} +
    +
  • + ) +} + +MachineComponent.propTypes = { + machine: Shapes.Machine, +} + +export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js new file mode 100644 index 00000000..12be26bd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js @@ -0,0 +1,20 @@ +import React from 'react' +import EmptySlotContainer from '../../../../../containers/app/sidebars/topology/rack/EmptySlotContainer' +import MachineContainer from '../../../../../containers/app/sidebars/topology/rack/MachineContainer' +import './MachineListComponent.sass' + +const MachineListComponent = ({ machineIds }) => { + return ( +
      + {machineIds.map((machineId, index) => { + if (machineId === null) { + return + } else { + return + } + })} +
    + ) +} + +export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass new file mode 100644 index 00000000..11b82c93 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass @@ -0,0 +1,2 @@ +.machine-list li + min-height: 64px diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js new file mode 100644 index 00000000..b701909a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js @@ -0,0 +1,6 @@ +import React from 'react' +import NameComponent from '../NameComponent' + +const RackNameComponent = ({ rackName, onEdit }) => + +export default RackNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js new file mode 100644 index 00000000..ca41bf57 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js @@ -0,0 +1,25 @@ +import React from 'react' +import BackToRoomContainer from '../../../../../containers/app/sidebars/topology/rack/BackToRoomContainer' +import DeleteRackContainer from '../../../../../containers/app/sidebars/topology/rack/DeleteRackContainer' +import MachineListContainer from '../../../../../containers/app/sidebars/topology/rack/MachineListContainer' +import RackNameContainer from '../../../../../containers/app/sidebars/topology/rack/RackNameContainer' +import './RackSidebarComponent.sass' +import AddPrefabContainer from '../../../../../containers/app/sidebars/topology/rack/AddPrefabContainer' + +const RackSidebarComponent = () => { + return ( +
    +
    + + + + +
    +
    + +
    +
    + ) +} + +export default RackSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass new file mode 100644 index 00000000..29fec02a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass @@ -0,0 +1,11 @@ +.rack-sidebar-container + display: flex + height: 100% + max-height: 100% + +.rack-sidebar-header-container + flex: 0 + +.machine-list-container + flex: 1 + overflow-y: scroll diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js new file mode 100644 index 00000000..64c0a1f6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const BackToBuildingComponent = ({ onClick }) => ( +
    + + Back to building +
    +) + +export default BackToBuildingComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js new file mode 100644 index 00000000..78417359 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js @@ -0,0 +1,10 @@ +import React from 'react' + +const DeleteRoomComponent = ({ onClick }) => ( +
    + + Delete this room +
    +) + +export default DeleteRoomComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js new file mode 100644 index 00000000..857a646f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js @@ -0,0 +1,22 @@ +import classNames from 'classnames' +import React from 'react' + +const EditRoomComponent = ({ onEdit, onFinish, isEditing, isInRackConstructionMode }) => + isEditing ? ( +
    + + Finish editing room +
    + ) : ( +
    (isInRackConstructionMode ? undefined : onEdit())} + > + + Edit the tiles of this room +
    + ) + +export default EditRoomComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js new file mode 100644 index 00000000..44566f61 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js @@ -0,0 +1,27 @@ +import classNames from 'classnames' +import React from 'react' + +const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { + if (inRackConstructionMode) { + return ( +
    + + Stop rack construction +
    + ) + } + + return ( +
    (isEditingRoom ? undefined : onStart())} + > + + Start rack construction +
    + ) +} + +export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js new file mode 100644 index 00000000..d637828e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js @@ -0,0 +1,6 @@ +import React from 'react' +import NameComponent from '../NameComponent' + +const RoomNameComponent = ({ roomName, onEdit }) => + +export default RoomNameComponent diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js new file mode 100644 index 00000000..1bc6533e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js @@ -0,0 +1,20 @@ +import React from 'react' +import BackToBuildingContainer from '../../../../../containers/app/sidebars/topology/room/BackToBuildingContainer' +import DeleteRoomContainer from '../../../../../containers/app/sidebars/topology/room/DeleteRoomContainer' +import EditRoomContainer from '../../../../../containers/app/sidebars/topology/room/EditRoomContainer' +import RackConstructionContainer from '../../../../../containers/app/sidebars/topology/room/RackConstructionContainer' +import RoomNameContainer from '../../../../../containers/app/sidebars/topology/room/RoomNameContainer' + +const RoomSidebarComponent = () => { + return ( +
    + + + + + +
    + ) +} + +export default RoomSidebarComponent diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js b/opendc-web/opendc-web-ui/src/components/home/ContactSection.js new file mode 100644 index 00000000..42bdab8a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ContactSection.js @@ -0,0 +1,54 @@ +import React from 'react' +import FontAwesome from 'react-fontawesome' +import './ContactSection.sass' +import ContentSection from './ContentSection' + +const ContactSection = () => ( + +
    +
    + + + + +
    +
    + TU Delft +
    +
    +
    +
    + A project by the   + + @Large Research Group + + . +
    +
    +
    +
    + +
    + OpenDC is an experimental tool. Your data may get lost, overwritten, or otherwise become unavailable. +
    + The OpenDC authors should in no way be liable in the event this happens (see our{' '} + + license + + ). Sorry for the inconvenience. +
    +
    + +) + +export default ContactSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass new file mode 100644 index 00000000..997f8d98 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass @@ -0,0 +1,15 @@ +.contact-section + background-color: #444 + color: #ddd + + a + color: #ddd + + a:hover + color: #fff + + .tudelft-icon + height: 100px + + .disclaimer + color: #cccccc diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.js b/opendc-web/opendc-web-ui/src/components/home/ContentSection.js new file mode 100644 index 00000000..9d4832d9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ContentSection.js @@ -0,0 +1,19 @@ +import classNames from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' +import './ContentSection.sass' + +const ContentSection = ({ name, title, children }) => ( +
    +
    +

    {title}

    + {children} +
    +
    +) + +ContentSection.propTypes = { + name: PropTypes.string.isRequired, +} + +export default ContentSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass new file mode 100644 index 00000000..a4c8bd66 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass @@ -0,0 +1,9 @@ +@import ../../style-globals/_variables.sass + +.content-section + padding-top: 50px + padding-bottom: 150px + text-align: center + + h1 + margin-bottom: 30px diff --git a/opendc-web/opendc-web-ui/src/components/home/IntroSection.js b/opendc-web/opendc-web-ui/src/components/home/IntroSection.js new file mode 100644 index 00000000..a799272a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/IntroSection.js @@ -0,0 +1,40 @@ +import React from 'react' + +const IntroSection = () => ( +
    +
    +
    +
    +

    The datacenter (DC) industry...

    +
      +
    • Is worth over $15 bn, and growing
    • +
    • Has many hard-to-grasp concepts
    • +
    • Needs to become accessible to many
    • +
    +
    + +
    +

    OpenDC provides...

    +
      +
    • Collaborative online DC modeling
    • +
    • Diverse and effective DC simulation
    • +
    • Exploratory DC performance feedback
    • +
    +
    +
    +
    +
    +) + +export default IntroSection diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js new file mode 100644 index 00000000..7b410679 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js @@ -0,0 +1,18 @@ +import React from 'react' +import './JumbotronHeader.sass' + +const JumbotronHeader = () => ( +
    +
    +
    +

    + OpenDC +

    +

    Collaborative Datacenter Simulation and Exploration for Everybody

    + OpenDC +
    +
    +
    +) + +export default JumbotronHeader diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass new file mode 100644 index 00000000..1b6a89fd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass @@ -0,0 +1,24 @@ +.jumbotron-header + background: #00A6D6 + +.jumbotron + background-color: inherit + margin-bottom: 0 + + padding-top: 120px + padding-bottom: 120px + + img + max-width: 110px + + h1 + color: #fff + font-size: 4.5em + + .dc + color: #fff + font-weight: bold + + .lead + color: #fff + font-size: 1.4em diff --git a/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js b/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js new file mode 100644 index 00000000..643dca65 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js @@ -0,0 +1,22 @@ +import React from 'react' +import ScreenshotSection from './ScreenshotSection' + +const ModelingSection = () => ( + +

    Collaboratively...

    +
      +
    • Model DC layout, and room locations and types
    • +
    • Place racks in rooms and nodes in racks
    • +
    • Add real-world CPU, GPU, memory, storage and network units to each node
    • +
    • Select from diverse scheduling policies
    • +
    +
    +) + +export default ModelingSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js new file mode 100644 index 00000000..c987d5d0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js @@ -0,0 +1,24 @@ +import classNames from 'classnames' +import React from 'react' +import ContentSection from './ContentSection' +import './ScreenshotSection.sass' + +const ScreenshotSection = ({ name, title, imageUrl, caption, imageIsRight, children }) => ( + +
    +
    + {children} +
    +
    + {caption} +
    {caption}
    +
    +
    +
    +) + +export default ScreenshotSection diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass new file mode 100644 index 00000000..2f454cb4 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass @@ -0,0 +1,5 @@ +.screenshot + outline: 2px black solid + padding-left: 0 + padding-right: 0 + margin-bottom: 5px diff --git a/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js b/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js new file mode 100644 index 00000000..b0244cb5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js @@ -0,0 +1,22 @@ +import React from 'react' +import ScreenshotSection from './ScreenshotSection' + +const ModelingSection = () => ( + +

    Working with OpenDC:

    +
      +
    • Seamlessly switch between construction and simulation modes
    • +
    • Choose one of several predefined workloads (Big Data, Bag of Tasks, Hadoop, etc.)
    • +
    • Play, pause, and skip around the informative simulation timeline
    • +
    • Visualize and demo live
    • +
    +
    +) + +export default ModelingSection diff --git a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js b/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js new file mode 100644 index 00000000..e5ed9683 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js @@ -0,0 +1,30 @@ +import React from 'react' +import ContentSection from './ContentSection' + +const Stakeholder = ({ name, title, subtitle }) => ( +
    + {title} +
    +

    {title}

    +

    {subtitle}

    +
    +
    +) + +const StakeholderSection = () => ( + +
    + + + + + +
    +
    +) + +export default StakeholderSection diff --git a/opendc-web/opendc-web-ui/src/components/home/TeamSection.js b/opendc-web/opendc-web-ui/src/components/home/TeamSection.js new file mode 100644 index 00000000..4b6f1e25 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/TeamSection.js @@ -0,0 +1,53 @@ +import React from 'react' +import ContentSection from './ContentSection' + +const TeamMember = ({ photoId, name, description }) => ( +
    + {name} +
    +

    {name}

    +
    {description}
    +
    +
    +) + +const TeamSection = () => ( + +
    + + + + + +
    +
    + See{' '} + + atlarge.science/opendc + {' '} + for the full team! +
    +
    +) + +export default TeamSection diff --git a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js b/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js new file mode 100644 index 00000000..c6013c71 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js @@ -0,0 +1,40 @@ +import React from 'react' +import FontAwesome from 'react-fontawesome' +import ContentSection from './ContentSection' + +const TechnologiesSection = () => ( + +
      +
    • + + + Browser + + JavaScript, React, Redux, Konva +
    • +
    • + + + Server + + Python, Flask, FlaskSocketIO, OpenAPI +
    • +
    • + + + Database + + MongoDB +
    • +
    • + + + Simulator + + Kotlin +
    • +
    +
    +) + +export default TechnologiesSection diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js new file mode 100644 index 00000000..589047dc --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js @@ -0,0 +1,37 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Modal from './Modal' + +class ConfirmationModal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + message: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + } + + onConfirm() { + this.props.callback(true) + } + + onCancel() { + this.props.callback(false) + } + + render() { + return ( + + {this.props.message} + + ) + } +} + +export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/modals/Modal.js new file mode 100644 index 00000000..21b7f119 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/Modal.js @@ -0,0 +1,53 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' +import { Modal as RModal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' + +function Modal({ children, title, show, onSubmit, onCancel, submitButtonType, submitButtonText }) { + const [modal, setModal] = useState(show) + + useEffect(() => setModal(show), [show]) + + const toggle = () => setModal(!modal) + const cancel = () => { + if (onCancel() !== false) { + toggle() + } + } + const submit = () => { + if (onSubmit() !== false) { + toggle() + } + } + + return ( + + {title} + {children} + + + + + + ) +} + +Modal.propTypes = { + title: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + onSubmit: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + submitButtonType: PropTypes.string, + submitButtonText: PropTypes.string, +} + +Modal.defaultProps = { + submitButtonType: 'primary', + submitButtonText: 'Save', + show: false, +} + +export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js new file mode 100644 index 00000000..d0918c7e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js @@ -0,0 +1,54 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Modal from './Modal' + +class TextInputModal extends React.Component { + static propTypes = { + title: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, + initialValue: PropTypes.string, + } + + componentDidUpdate() { + if (this.props.initialValue && this.textInput) { + this.textInput.value = this.props.initialValue + } + } + + onSubmit() { + this.props.callback(this.textInput.value) + this.textInput.value = '' + } + + onCancel() { + this.props.callback(undefined) + this.textInput.value = '' + } + + render() { + return ( + +
    { + e.preventDefault() + this.onSubmit() + }} + > +
    + + (this.textInput = textInput)} /> +
    +
    +
    + ) + } +} + +export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js new file mode 100644 index 00000000..3c6b8724 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewPortfolioModalComponent.js @@ -0,0 +1,78 @@ +import PropTypes from 'prop-types' +import React, { useRef } from 'react' +import { Form, FormGroup, Input, Label } from 'reactstrap' +import Modal from '../Modal' +import { AVAILABLE_METRICS, METRIC_NAMES } from '../../../util/available-metrics' + +const NewPortfolioModalComponent = ({ show, callback }) => { + const form = useRef(null) + const textInput = useRef(null) + const repeatsInput = useRef(null) + const metricCheckboxes = useRef({}) + + const onSubmit = () => { + if (form.current.reportValidity()) { + callback(textInput.current.value, { + enabledMetrics: AVAILABLE_METRICS.filter((metric) => metricCheckboxes.current[metric].checked), + repeatsPerScenario: parseInt(repeatsInput.current.value), + }) + + return true + } else { + return false + } + } + const onCancel = () => callback(undefined) + + return ( + +
    { + e.preventDefault() + this.onSubmit() + }} + innerRef={form} + > + + + + +

    Targets

    +
    Metrics
    + + {AVAILABLE_METRICS.map((metric) => ( + + + + ))} + + + + + +
    +
    + ) +} + +NewPortfolioModalComponent.propTypes = { + show: PropTypes.bool.isRequired, + callback: PropTypes.func.isRequired, +} + +export default NewPortfolioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js new file mode 100644 index 00000000..01a5719c --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js @@ -0,0 +1,144 @@ +import PropTypes from 'prop-types' +import React, { useRef } from 'react' +import { Form, FormGroup, Input, Label } from 'reactstrap' +import Shapes from '../../../shapes' +import Modal from '../Modal' + +const NewScenarioModalComponent = ({ + show, + callback, + currentPortfolioId, + currentPortfolioScenarioIds, + traces, + topologies, + schedulers, +}) => { + const form = useRef(null) + const textInput = useRef(null) + const traceSelect = useRef(null) + const traceLoadInput = useRef(null) + const topologySelect = useRef(null) + const failuresCheckbox = useRef(null) + const performanceInterferenceCheckbox = useRef(null) + const schedulerSelect = useRef(null) + + const onSubmit = () => { + if (!form.current.reportValidity()) { + return false + } + callback( + textInput.current.value, + currentPortfolioId, + { + traceId: traceSelect.current.value, + loadSamplingFraction: parseFloat(traceLoadInput.current.value), + }, + { + topologyId: topologySelect.current.value, + }, + { + failuresEnabled: failuresCheckbox.current.checked, + performanceInterferenceEnabled: performanceInterferenceCheckbox.current.checked, + schedulerName: schedulerSelect.current.value, + } + ) + return true + } + const onCancel = () => { + callback(undefined) + } + + return ( + +
    { + e.preventDefault() + onSubmit() + }} + innerRef={form} + > + + + + +

    Trace

    + + + + {traces.map((trace) => ( + + ))} + + + + + + +

    Topology

    +
    + + + {topologies.map((topology) => ( + + ))} + +
    +

    Operational Phenomena

    + + + + + + + + + + {schedulers.map((scheduler) => ( + + ))} + + +
    +
    + ) +} + +NewScenarioModalComponent.propTypes = { + show: PropTypes.bool.isRequired, + currentPortfolioId: PropTypes.string.isRequired, + currentPortfolioScenarioIds: PropTypes.arrayOf(PropTypes.string), + traces: PropTypes.arrayOf(Shapes.Trace), + topologies: PropTypes.arrayOf(Shapes.Topology), + schedulers: PropTypes.arrayOf(Shapes.Scheduler), + callback: PropTypes.func.isRequired, +} + +export default NewScenarioModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js new file mode 100644 index 00000000..9fee8831 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js @@ -0,0 +1,71 @@ +import PropTypes from 'prop-types' +import { Form, FormGroup, Input, Label } from 'reactstrap' +import React, { useRef } from 'react' +import Shapes from '../../../shapes' +import Modal from '../Modal' + +const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => { + const form = useRef(null) + const textInput = useRef(null) + const originTopology = useRef(null) + + const onCreate = () => { + onCreateTopology(textInput.current.value) + } + + const onDuplicate = () => { + onDuplicateTopology(textInput.current.value, originTopology.current.value) + } + + const onSubmit = () => { + if (!form.current.reportValidity()) { + return false + } else if (originTopology.current.selectedIndex === 0) { + onCreate() + } else { + onDuplicate() + } + + return true + } + + return ( + +
    { + e.preventDefault() + onSubmit() + }} + innerRef={form} + > + + + + + + + + + {topologies.map((topology) => ( + + ))} + + +
    +
    + ) +} + +NewTopologyModalComponent.propTypes = { + show: PropTypes.bool.isRequired, + topologies: PropTypes.arrayOf(Shapes.Topology), + onCreateTopology: PropTypes.func.isRequired, + onDuplicateTopology: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, +} + +export default NewTopologyModalComponent diff --git a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js new file mode 100644 index 00000000..c5de3d0b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js @@ -0,0 +1,26 @@ +import React from 'react' +import FontAwesome from 'react-fontawesome' +import { Link } from 'react-router-dom' +import { NavLink } from 'reactstrap' +import Navbar, { NavItem } from './Navbar' +import './Navbar.sass' + +const AppNavbarComponent = ({ project, fullWidth }) => ( + + + + + My Projects + + + {project ? ( + + + {project.name} + + + ) : undefined} + +) + +export default AppNavbarComponent diff --git a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js b/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js new file mode 100644 index 00000000..08d222ea --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js @@ -0,0 +1,23 @@ +import React from 'react' +import { NavItem, NavLink } from 'reactstrap' +import Navbar from './Navbar' +import './Navbar.sass' + +const ScrollNavItem = ({ id, name }) => ( + + {name} + +) + +const HomeNavbar = () => ( + + + + + + + + +) + +export default HomeNavbar diff --git a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js new file mode 100644 index 00000000..78b02b44 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js @@ -0,0 +1,17 @@ +import PropTypes from 'prop-types' +import React from 'react' +import FontAwesome from 'react-fontawesome' +import { Link } from 'react-router-dom' +import { NavLink } from 'reactstrap' + +const LogoutButton = ({ onLogout }) => ( + + + +) + +LogoutButton.propTypes = { + onLogout: PropTypes.func.isRequired, +} + +export default LogoutButton diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js new file mode 100644 index 00000000..55f98900 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js @@ -0,0 +1,92 @@ +import React, { useState } from 'react' +import { Link, useLocation } from 'react-router-dom' +import { + Navbar as RNavbar, + NavItem as RNavItem, + NavLink, + NavbarBrand, + NavbarToggler, + Collapse, + Nav, + Container, +} from 'reactstrap' +import { userIsLoggedIn } from '../../auth/index' +import Login from '../../containers/auth/Login' +import Logout from '../../containers/auth/Logout' +import ProfileName from '../../containers/auth/ProfileName' +import './Navbar.sass' + +export const NAVBAR_HEIGHT = 60 + +const GitHubLink = () => ( + + + +) + +export const NavItem = ({ route, children }) => { + const location = useLocation() + return {children} +} + +export const LoggedInSection = () => { + const location = useLocation() + return ( + + ) +} + +const Navbar = ({ fullWidth, children }) => { + const [isOpen, setIsOpen] = useState(false) + const toggle = () => setIsOpen(!isOpen) + + return ( + + + + + OpenDC + + + + + + + + + ) +} + +export default Navbar diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass new file mode 100644 index 00000000..c9d2aad2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass @@ -0,0 +1,30 @@ +@import ../../style-globals/_mixins.sass +@import ../../style-globals/_variables.sass + +.navbar + border-top: $blue 3px solid + border-bottom: $gray-semi-dark 1px solid + color: $gray-very-dark + background: #fafafb + +.opendc-brand + display: inline-block + color: $gray-very-dark + + +transition(background, $transition-length) + + img + position: relative + bottom: 3px + display: inline-block + width: 30px + +.login + height: 40px + background: $blue + border: none + padding-top: 10px + +clickable + + &:hover + background: $blue-dark diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js new file mode 100644 index 00000000..dbdba212 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js @@ -0,0 +1,6 @@ +import React from 'react' +import './BlinkingCursor.sass' + +const BlinkingCursor = () => _ + +export default BlinkingCursor diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass new file mode 100644 index 00000000..ad91df85 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass @@ -0,0 +1,35 @@ +.blinking-cursor + -webkit-animation: 1s blink step-end infinite + -moz-animation: 1s blink step-end infinite + -o-animation: 1s blink step-end infinite + animation: 1s blink step-end infinite + +@keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-moz-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-webkit-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-ms-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 + +@-o-keyframes blink + from, to + color: #eeeeee + 50% + color: #333333 diff --git a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js new file mode 100644 index 00000000..bcc522c9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js @@ -0,0 +1,28 @@ +import React from 'react' +import './CodeBlock.sass' + +const CodeBlock = () => { + const textBlock = + ' oo oooo oo
    ' + + ' oo oo oo oo
    ' + + ' oo oo oo oo
    ' + + ' oooooo oo oo oooooo
    ' + + ' oo oo oo oo
    ' + + ' oo oooo oo
    ' + const charList = textBlock.split('') + + // Binary representation of the string "OpenDC!" ;) + const binaryString = '01001111011100000110010101101110010001000100001100100001' + + let binaryIndex = 0 + for (let i = 0; i < charList.length; i++) { + if (charList[i] === 'o') { + charList[i] = binaryString[binaryIndex] + binaryIndex++ + } + } + + return
    +} + +export default CodeBlock diff --git a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass new file mode 100644 index 00000000..e452f917 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass @@ -0,0 +1,3 @@ +.code-block + white-space: pre-wrap + margin-top: 60px diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js new file mode 100644 index 00000000..a25e558a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js @@ -0,0 +1,33 @@ +import React from 'react' +import { Link } from 'react-router-dom' +import BlinkingCursor from './BlinkingCursor' +import CodeBlock from './CodeBlock' +import './TerminalWindow.sass' + +const TerminalWindow = () => ( +
    +
    Terminal -- bash
    +
    +
    + $ status +
    + opendc[4264]: segfault at 0000051497be459d1 err 12 in libopendc.9.0.4 +
    + opendc[4269]: segfault at 000004234855fc2db err 3 in libopendc.9.0.4 +
    + opendc[4270]: STDERR Page does not exist +
    +
    + +
    + Got lost? + +
    + + GET ME BACK TO OPENDC + +
    +
    +) + +export default TerminalWindow diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass new file mode 100644 index 00000000..7f05335a --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass @@ -0,0 +1,70 @@ +.terminal-window + width: 600px + height: 400px + display: block + + position: absolute + top: 0 + bottom: 0 + left: 0 + right: 0 + + margin: auto + + -webkit-user-select: none + -moz-user-select: none + -ms-user-select: none + user-select: none + cursor: default + + overflow: hidden + + box-shadow: 5px 5px 20px #444444 + +.terminal-header + font-family: monospace + background: #cccccc + color: #444444 + height: 30px + line-height: 30px + padding-left: 10px + + border-top-left-radius: 7px + border-top-right-radius: 7px + +.terminal-body + font-family: monospace + text-align: center + background-color: #333333 + color: #eeeeee + padding: 10px + + height: 100% + +.segfault + text-align: left + +.sub-title + margin-top: 20px + +.home-btn + margin-top: 10px + padding: 5px + display: inline-block + border: 1px solid #eeeeee + color: #eeeeee + text-decoration: none + cursor: pointer + + -webkit-transition: all 200ms + -moz-transition: all 200ms + -o-transition: all 200ms + transition: all 200ms + +.home-btn:hover + background: #eeeeee + color: #333333 + +.home-btn:active + background: #333333 + color: #eeeeee diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js b/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js new file mode 100644 index 00000000..664f9b46 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js @@ -0,0 +1,24 @@ +import classNames from 'classnames' +import PropTypes from 'prop-types' +import React from 'react' + +const FilterButton = ({ active, children, onClick }) => ( + +) + +FilterButton.propTypes = { + active: PropTypes.bool.isRequired, + children: PropTypes.node.isRequired, + onClick: PropTypes.func.isRequired, +} + +export default FilterButton diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js new file mode 100644 index 00000000..2b9795d0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js @@ -0,0 +1,13 @@ +import React from 'react' +import FilterLink from '../../containers/projects/FilterLink' +import './FilterPanel.sass' + +const FilterPanel = () => ( +
    + All Projects + My Projects + Shared with me +
    +) + +export default FilterPanel diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass new file mode 100644 index 00000000..f71cf6c8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass @@ -0,0 +1,5 @@ +.filter-panel + display: flex + + button + flex: 1 !important diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js b/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js new file mode 100644 index 00000000..312671c6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js @@ -0,0 +1,17 @@ +import PropTypes from 'prop-types' +import React from 'react' + +const NewProjectButtonComponent = ({ onClick }) => ( +
    +
    + + New Project +
    +
    +) + +NewProjectButtonComponent.propTypes = { + onClick: PropTypes.func.isRequired, +} + +export default NewProjectButtonComponent diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js new file mode 100644 index 00000000..1c76cc7f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js @@ -0,0 +1,29 @@ +import PropTypes from 'prop-types' +import React from 'react' +import { Link } from 'react-router-dom' + +const ProjectActionButtons = ({ projectId, onViewUsers, onDelete }) => ( + + + + +
    onViewUsers(projectId)} + > + +
    +
    onDelete(projectId)}> + +
    + +) + +ProjectActionButtons.propTypes = { + projectId: PropTypes.string.isRequired, + onViewUsers: PropTypes.func, + onDelete: PropTypes.func, +} + +export default ProjectActionButtons diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js new file mode 100644 index 00000000..8eb4f93b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js @@ -0,0 +1,39 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Shapes from '../../shapes/index' +import ProjectAuthRow from './ProjectAuthRow' + +const ProjectAuthList = ({ authorizations }) => { + return ( +
    + {authorizations.length === 0 ? ( +
    + + No projects here yet... Add some with the 'New Project' button! +
    + ) : ( + + + + + + + + + + {authorizations.map((authorization) => ( + + ))} + +
    Project nameLast editedAccess rights +
    + )} +
    + ) +} + +ProjectAuthList.propTypes = { + authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired, +} + +export default ProjectAuthList diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js new file mode 100644 index 00000000..3f904061 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js @@ -0,0 +1,24 @@ +import classNames from 'classnames' +import React from 'react' +import ProjectActions from '../../containers/projects/ProjectActions' +import Shapes from '../../shapes/index' +import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' +import { parseAndFormatDateTime } from '../../util/date-time' + +const ProjectAuthRow = ({ projectAuth }) => ( + + {projectAuth.project.name} + {parseAndFormatDateTime(projectAuth.project.datetimeLastEdited)} + + + {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]} + + + +) + +ProjectAuthRow.propTypes = { + projectAuth: Shapes.Authorization.isRequired, +} + +export default ProjectAuthRow -- cgit v1.2.3