summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-ui/src')
-rw-r--r--opendc-web/opendc-web-ui/src/actions/auth.js23
-rw-r--r--opendc-web/opendc-web-ui/src/actions/modals/portfolios.js14
-rw-r--r--opendc-web/opendc-web-ui/src/actions/modals/prefabs.js14
-rw-r--r--opendc-web/opendc-web-ui/src/actions/modals/profile.js14
-rw-r--r--opendc-web/opendc-web-ui/src/actions/modals/projects.js14
-rw-r--r--opendc-web/opendc-web-ui/src/actions/modals/scenarios.js14
-rw-r--r--opendc-web/opendc-web-ui/src/actions/modals/topology.js84
-rw-r--r--opendc-web/opendc-web-ui/src/actions/users.js37
-rw-r--r--opendc-web/opendc-web-ui/src/api/index.js60
-rw-r--r--opendc-web/opendc-web-ui/src/api/portfolios.js39
-rw-r--r--opendc-web/opendc-web-ui/src/api/prefabs.js39
-rw-r--r--opendc-web/opendc-web-ui/src/api/projects.js43
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/portfolios.js42
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/prefabs.js40
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/projects.js40
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/scenarios.js42
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/schedulers.js5
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/token-signin.js12
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/topologies.js42
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/traces.js5
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/users.js48
-rw-r--r--opendc-web/opendc-web-ui/src/api/routes/util.js37
-rw-r--r--opendc-web/opendc-web-ui/src/api/scenarios.js39
-rw-r--r--opendc-web/opendc-web-ui/src/api/schedulers.js (renamed from opendc-web/opendc-web-ui/src/util/hooks.js)8
-rw-r--r--opendc-web/opendc-web-ui/src/api/socket.js50
-rw-r--r--opendc-web/opendc-web-ui/src/api/topologies.js39
-rw-r--r--opendc-web/opendc-web-ui/src/api/traces.js27
-rw-r--r--opendc-web/opendc-web-ui/src/auth.js65
-rw-r--r--opendc-web/opendc-web-ui/src/auth/index.js57
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js118
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ExportCanvasComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.module.scss10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass9
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.module.scss6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass5
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/controls/ZoomControlComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/ImageComponent.js54
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/elements/WallSegment.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/RackGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js104
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js80
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.module.scss57
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass50
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/PortfolioListComponent.js119
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/ProjectSidebarComponent.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/ScenarioListComponent.js124
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/project/TopologyListComponent.js100
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js17
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js57
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js28
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js9
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js9
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js17
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js30
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.module.scss3
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass2
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js8
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss14
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass11
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js9
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js23
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ContactSection.js25
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ContactSection.module.scss20
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ContactSection.sass15
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ContentSection.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ContentSection.module.scss11
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ContentSection.sass9
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/IntroSection.js14
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js17
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.module.scss31
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass24
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ModelingSection.js3
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js18
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.module.scss5
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass4
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/SimulationSection.js21
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/TeamSection.js63
-rw-r--r--opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js15
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js48
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js75
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js8
-rw-r--r--opendc-web/opendc-web-ui/src/components/modals/custom-components/NewTopologyModalComponent.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js25
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js8
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/Navbar.js57
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/Navbar.module.scss36
-rw-r--r--opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass30
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.module.scss13
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass35
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.module.scss4
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass3
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.module.scss61
-rw-r--r--opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass70
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/FilterButton.js24
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js33
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss7
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass5
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js17
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js29
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js24
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectList.js (renamed from opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js)22
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js29
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/App.js111
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js9
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js7
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js64
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js6
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js81
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js73
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js33
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js33
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js29
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js34
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js32
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js30
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/Login.js42
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/Logout.js7
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js6
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js26
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js27
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js27
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js28
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js37
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js31
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js24
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js55
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js48
-rw-r--r--opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js6
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js11
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js35
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js34
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js32
-rw-r--r--opendc-web/opendc-web-ui/src/data/experiments.js37
-rw-r--r--opendc-web/opendc-web-ui/src/data/map.js41
-rw-r--r--opendc-web/opendc-web-ui/src/data/project.js85
-rw-r--r--opendc-web/opendc-web-ui/src/data/topology.js49
-rw-r--r--opendc-web/opendc-web-ui/src/hotkeys.js (renamed from opendc-web/opendc-web-ui/src/shortcuts/keymap.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/index.js32
-rw-r--r--opendc-web/opendc-web-ui/src/index.sass52
-rw-r--r--opendc-web/opendc-web-ui/src/index.scss68
-rw-r--r--opendc-web/opendc-web-ui/src/pages/404.js19
-rw-r--r--opendc-web/opendc-web-ui/src/pages/404.module.scss8
-rw-r--r--opendc-web/opendc-web-ui/src/pages/App.js103
-rw-r--r--opendc-web/opendc-web-ui/src/pages/Home.sass9
-rw-r--r--opendc-web/opendc-web-ui/src/pages/NotFound.js15
-rw-r--r--opendc-web/opendc-web-ui/src/pages/NotFound.sass11
-rw-r--r--opendc-web/opendc-web-ui/src/pages/Profile.js31
-rw-r--r--opendc-web/opendc-web-ui/src/pages/Projects.js30
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_app.js69
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_document.js95
-rw-r--r--opendc-web/opendc-web-ui/src/pages/index.js (renamed from opendc-web/opendc-web-ui/src/pages/Home.js)31
-rw-r--r--opendc-web/opendc-web-ui/src/pages/index.module.scss16
-rw-r--r--opendc-web/opendc-web-ui/src/pages/logout.js39
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js (renamed from opendc-web/opendc-web-ui/src/config.js)25
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js37
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/index.js37
-rw-r--r--opendc-web/opendc-web-ui/src/reducers/auth.js12
-rw-r--r--opendc-web/opendc-web-ui/src/reducers/modals.js45
-rw-r--r--opendc-web/opendc-web-ui/src/reducers/project-list.js30
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js (renamed from opendc-web/opendc-web-ui/src/actions/interaction-level.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/map.js (renamed from opendc-web/opendc-web-ui/src/actions/map.js)3
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/objects.js (renamed from opendc-web/opendc-web-ui/src/actions/objects.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/portfolios.js (renamed from opendc-web/opendc-web-ui/src/actions/portfolios.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/prefabs.js (renamed from opendc-web/opendc-web-ui/src/actions/prefabs.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/projects.js (renamed from opendc-web/opendc-web-ui/src/actions/projects.js)29
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/scenarios.js (renamed from opendc-web/opendc-web-ui/src/actions/scenarios.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topologies.js (renamed from opendc-web/opendc-web-ui/src/actions/topologies.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/building.js (renamed from opendc-web/opendc-web-ui/src/actions/topology/building.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js (renamed from opendc-web/opendc-web-ui/src/actions/topology/machine.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js (renamed from opendc-web/opendc-web-ui/src/actions/topology/rack.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/room.js (renamed from opendc-web/opendc-web-ui/src/actions/topology/room.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/redux/index.js59
-rw-r--r--opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js (renamed from opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js)4
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js (renamed from opendc-web/opendc-web-ui/src/reducers/construction-mode.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js (renamed from opendc-web/opendc-web-ui/src/reducers/current-ids.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/index.js (renamed from opendc-web/opendc-web-ui/src/reducers/index.js)8
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js (renamed from opendc-web/opendc-web-ui/src/reducers/interaction-level.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/map.js (renamed from opendc-web/opendc-web-ui/src/reducers/map.js)0
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/objects.js (renamed from opendc-web/opendc-web-ui/src/reducers/objects.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/projects.js14
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/index.js (renamed from opendc-web/opendc-web-ui/src/sagas/index.js)14
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/objects.js (renamed from opendc-web/opendc-web-ui/src/sagas/objects.js)33
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js (renamed from opendc-web/opendc-web-ui/src/sagas/portfolios.js)25
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js (renamed from opendc-web/opendc-web-ui/src/sagas/prefabs.js)7
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/projects.js (renamed from opendc-web/opendc-web-ui/src/sagas/projects.js)35
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js (renamed from opendc-web/opendc-web-ui/src/sagas/scenarios.js)18
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/topology.js (renamed from opendc-web/opendc-web-ui/src/sagas/topology.js)11
-rw-r--r--opendc-web/opendc-web-ui/src/routes/index.js40
-rw-r--r--opendc-web/opendc-web-ui/src/sagas/profile.js12
-rw-r--r--opendc-web/opendc-web-ui/src/sagas/users.js44
-rw-r--r--opendc-web/opendc-web-ui/src/shapes.js (renamed from opendc-web/opendc-web-ui/src/shapes/index.js)86
-rw-r--r--opendc-web/opendc-web-ui/src/store/configure-store.js35
-rw-r--r--opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js3
-rw-r--r--opendc-web/opendc-web-ui/src/style-globals/_mixins.sass21
-rw-r--r--opendc-web/opendc-web-ui/src/style-globals/_variables.sass31
-rw-r--r--opendc-web/opendc-web-ui/src/style/_mixins.scss5
-rw-r--r--opendc-web/opendc-web-ui/src/style/_variables.scss31
-rw-r--r--opendc-web/opendc-web-ui/src/util/authorizations.js8
-rw-r--r--opendc-web/opendc-web-ui/src/util/sidebar-space.js4
246 files changed, 2880 insertions, 3053 deletions
diff --git a/opendc-web/opendc-web-ui/src/actions/auth.js b/opendc-web/opendc-web-ui/src/actions/auth.js
deleted file mode 100644
index 38c1a782..00000000
--- a/opendc-web/opendc-web-ui/src/actions/auth.js
+++ /dev/null
@@ -1,23 +0,0 @@
-export const LOG_IN = 'LOG_IN'
-export const LOG_IN_SUCCEEDED = 'LOG_IN_SUCCEEDED'
-export const LOG_OUT = 'LOG_OUT'
-
-export function logIn(payload) {
- return {
- type: LOG_IN,
- payload,
- }
-}
-
-export function logInSucceeded(payload) {
- return {
- type: LOG_IN_SUCCEEDED,
- payload,
- }
-}
-
-export function logOut() {
- return {
- type: LOG_OUT,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js b/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js
deleted file mode 100644
index f6dce2e3..00000000
--- a/opendc-web/opendc-web-ui/src/actions/modals/portfolios.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const OPEN_NEW_PORTFOLIO_MODAL = 'OPEN_NEW_PORTFOLIO_MODAL'
-export const CLOSE_NEW_PORTFOLIO_MODAL = 'CLOSE_PORTFOLIO_MODAL'
-
-export function openNewPortfolioModal() {
- return {
- type: OPEN_NEW_PORTFOLIO_MODAL,
- }
-}
-
-export function closeNewPortfolioModal() {
- return {
- type: CLOSE_NEW_PORTFOLIO_MODAL,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js b/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js
deleted file mode 100644
index 826565d2..00000000
--- a/opendc-web/opendc-web-ui/src/actions/modals/prefabs.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const OPEN_NEW_PREFAB_MODAL = 'OPEN_NEW_PREFAB_MODAL'
-export const CLOSE_NEW_PREFAB_MODAL = 'CLOSE_PREFAB_MODAL'
-
-export function openNewPrefabModal() {
- return {
- type: OPEN_NEW_PREFAB_MODAL,
- }
-}
-
-export function closeNewPrefabModal() {
- return {
- type: CLOSE_NEW_PREFAB_MODAL,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/profile.js b/opendc-web/opendc-web-ui/src/actions/modals/profile.js
deleted file mode 100644
index 39c72c03..00000000
--- a/opendc-web/opendc-web-ui/src/actions/modals/profile.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const OPEN_DELETE_PROFILE_MODAL = 'OPEN_DELETE_PROFILE_MODAL'
-export const CLOSE_DELETE_PROFILE_MODAL = 'CLOSE_DELETE_PROFILE_MODAL'
-
-export function openDeleteProfileModal() {
- return {
- type: OPEN_DELETE_PROFILE_MODAL,
- }
-}
-
-export function closeDeleteProfileModal() {
- return {
- type: CLOSE_DELETE_PROFILE_MODAL,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/projects.js b/opendc-web/opendc-web-ui/src/actions/modals/projects.js
deleted file mode 100644
index d1043cbb..00000000
--- a/opendc-web/opendc-web-ui/src/actions/modals/projects.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const OPEN_NEW_PROJECT_MODAL = 'OPEN_NEW_PROJECT_MODAL'
-export const CLOSE_NEW_PROJECT_MODAL = 'CLOSE_PROJECT_MODAL'
-
-export function openNewProjectModal() {
- return {
- type: OPEN_NEW_PROJECT_MODAL,
- }
-}
-
-export function closeNewProjectModal() {
- return {
- type: CLOSE_NEW_PROJECT_MODAL,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js b/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js
deleted file mode 100644
index b71cb27b..00000000
--- a/opendc-web/opendc-web-ui/src/actions/modals/scenarios.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export const OPEN_NEW_SCENARIO_MODAL = 'OPEN_NEW_SCENARIO_MODAL'
-export const CLOSE_NEW_SCENARIO_MODAL = 'CLOSE_SCENARIO_MODAL'
-
-export function openNewScenarioModal() {
- return {
- type: OPEN_NEW_SCENARIO_MODAL,
- }
-}
-
-export function closeNewScenarioModal() {
- return {
- type: CLOSE_NEW_SCENARIO_MODAL,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/modals/topology.js b/opendc-web/opendc-web-ui/src/actions/modals/topology.js
deleted file mode 100644
index b5fecac1..00000000
--- a/opendc-web/opendc-web-ui/src/actions/modals/topology.js
+++ /dev/null
@@ -1,84 +0,0 @@
-export const OPEN_NEW_TOPOLOGY_MODAL = 'OPEN_NEW_TOPOLOGY_MODAL'
-export const CLOSE_NEW_TOPOLOGY_MODAL = 'CLOSE_NEW_TOPOLOGY_MODAL'
-export const OPEN_EDIT_ROOM_NAME_MODAL = 'OPEN_EDIT_ROOM_NAME_MODAL'
-export const CLOSE_EDIT_ROOM_NAME_MODAL = 'CLOSE_EDIT_ROOM_NAME_MODAL'
-export const OPEN_DELETE_ROOM_MODAL = 'OPEN_DELETE_ROOM_MODAL'
-export const CLOSE_DELETE_ROOM_MODAL = 'CLOSE_DELETE_ROOM_MODAL'
-export const OPEN_EDIT_RACK_NAME_MODAL = 'OPEN_EDIT_RACK_NAME_MODAL'
-export const CLOSE_EDIT_RACK_NAME_MODAL = 'CLOSE_EDIT_RACK_NAME_MODAL'
-export const OPEN_DELETE_RACK_MODAL = 'OPEN_DELETE_RACK_MODAL'
-export const CLOSE_DELETE_RACK_MODAL = 'CLOSE_DELETE_RACK_MODAL'
-export const OPEN_DELETE_MACHINE_MODAL = 'OPEN_DELETE_MACHINE_MODAL'
-export const CLOSE_DELETE_MACHINE_MODAL = 'CLOSE_DELETE_MACHINE_MODAL'
-
-export function openNewTopologyModal() {
- return {
- type: OPEN_NEW_TOPOLOGY_MODAL,
- }
-}
-
-export function closeNewTopologyModal() {
- return {
- type: CLOSE_NEW_TOPOLOGY_MODAL,
- }
-}
-
-export function openEditRoomNameModal() {
- return {
- type: OPEN_EDIT_ROOM_NAME_MODAL,
- }
-}
-
-export function closeEditRoomNameModal() {
- return {
- type: CLOSE_EDIT_ROOM_NAME_MODAL,
- }
-}
-
-export function openDeleteRoomModal() {
- return {
- type: OPEN_DELETE_ROOM_MODAL,
- }
-}
-
-export function closeDeleteRoomModal() {
- return {
- type: CLOSE_DELETE_ROOM_MODAL,
- }
-}
-
-export function openEditRackNameModal() {
- return {
- type: OPEN_EDIT_RACK_NAME_MODAL,
- }
-}
-
-export function closeEditRackNameModal() {
- return {
- type: CLOSE_EDIT_RACK_NAME_MODAL,
- }
-}
-
-export function openDeleteRackModal() {
- return {
- type: OPEN_DELETE_RACK_MODAL,
- }
-}
-
-export function closeDeleteRackModal() {
- return {
- type: CLOSE_DELETE_RACK_MODAL,
- }
-}
-
-export function openDeleteMachineModal() {
- return {
- type: OPEN_DELETE_MACHINE_MODAL,
- }
-}
-
-export function closeDeleteMachineModal() {
- return {
- type: CLOSE_DELETE_MACHINE_MODAL,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/actions/users.js b/opendc-web/opendc-web-ui/src/actions/users.js
deleted file mode 100644
index 4868ac34..00000000
--- a/opendc-web/opendc-web-ui/src/actions/users.js
+++ /dev/null
@@ -1,37 +0,0 @@
-export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER = 'FETCH_AUTHORIZATIONS_OF_CURRENT_USER'
-export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED = 'FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED'
-export const DELETE_CURRENT_USER = 'DELETE_CURRENT_USER'
-export const DELETE_CURRENT_USER_SUCCEEDED = 'DELETE_CURRENT_USER_SUCCEEDED'
-
-export function fetchAuthorizationsOfCurrentUser() {
- return (dispatch, getState) => {
- const { auth } = getState()
- dispatch({
- type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER,
- userId: auth.userId,
- })
- }
-}
-
-export function fetchAuthorizationsOfCurrentUserSucceeded(authorizationsOfCurrentUser) {
- return {
- type: FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED,
- authorizationsOfCurrentUser,
- }
-}
-
-export function deleteCurrentUser() {
- return (dispatch, getState) => {
- const { auth } = getState()
- dispatch({
- type: DELETE_CURRENT_USER,
- userId: auth.userId,
- })
- }
-}
-
-export function deleteCurrentUserSucceeded() {
- return {
- type: DELETE_CURRENT_USER_SUCCEEDED,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js
index cefcb2c5..680d49ce 100644
--- a/opendc-web/opendc-web-ui/src/api/index.js
+++ b/opendc-web/opendc-web-ui/src/api/index.js
@@ -1,13 +1,51 @@
-import { sendSocketRequest } from './socket'
-
-export function sendRequest(request) {
- return new Promise((resolve, reject) => {
- sendSocketRequest(request, (response) => {
- if (response.status.code === 200) {
- resolve(response.content)
- } else {
- reject(response)
- }
- })
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL
+
+/**
+ * Send the specified request to the OpenDC API.
+ *
+ * @param auth The authentication context.
+ * @param path Relative path for the API.
+ * @param method The method to use for the request.
+ * @param body The body of the request.
+ */
+export async function request(auth, path, method = 'GET', body) {
+ const { getAccessTokenSilently } = auth
+ const token = await getAccessTokenSilently()
+ const response = await fetch(`${apiUrl}/${path}`, {
+ method: method,
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ body: body && JSON.stringify(body),
})
+ const json = await response.json()
+
+ if (!response.ok) {
+ throw response.message
+ }
+
+ return json.data
}
diff --git a/opendc-web/opendc-web-ui/src/api/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js
new file mode 100644
index 00000000..28898e6a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/api/portfolios.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { request } from './index'
+
+export function addPortfolio(auth, projectId, portfolio) {
+ return request(auth, `projects/${projectId}/portfolios`, 'POST', { portfolio })
+}
+
+export function getPortfolio(auth, portfolioId) {
+ return request(auth, `portfolios/${portfolioId}`)
+}
+
+export function updatePortfolio(auth, portfolioId, portfolio) {
+ return request(auth, `portfolios/${portfolioId}`, 'PUT', { portfolio })
+}
+
+export function deletePortfolio(auth, portfolioId) {
+ return request(auth, `portfolios/${portfolioId}`, 'DELETE')
+}
diff --git a/opendc-web/opendc-web-ui/src/api/prefabs.js b/opendc-web/opendc-web-ui/src/api/prefabs.js
new file mode 100644
index 00000000..eb9aa23c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/api/prefabs.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { request } from './index'
+
+export function getPrefab(auth, prefabId) {
+ return request(auth, `prefabs/${prefabId}`)
+}
+
+export function addPrefab(auth, prefab) {
+ return request(auth, 'prefabs/', 'POST', { prefab })
+}
+
+export function updatePrefab(auth, prefab) {
+ return request(auth, `prefabs/${prefab._id}`, 'PUT', { prefab })
+}
+
+export function deletePrefab(auth, prefabId) {
+ return request(auth, `prefabs/${prefabId}`, 'DELETE')
+}
diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js
new file mode 100644
index 00000000..93052080
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/api/projects.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { request } from './index'
+
+export function getProjects(auth) {
+ return request(auth, `projects/`)
+}
+
+export function getProject(auth, projectId) {
+ return request(auth, `projects/${projectId}`)
+}
+
+export function addProject(auth, project) {
+ return request(auth, 'projects/', 'POST', { project })
+}
+
+export function updateProject(auth, project) {
+ return request(auth, `projects/${project._id}`, 'PUT', { project })
+}
+
+export function deleteProject(auth, projectId) {
+ return request(auth, `projects/${projectId}`, 'DELETE')
+}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js b/opendc-web/opendc-web-ui/src/api/routes/portfolios.js
deleted file mode 100644
index 7c9ea02a..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { deleteById, getById } from './util'
-import { sendRequest } from '../index'
-
-export function addPortfolio(projectId, portfolio) {
- return sendRequest({
- path: '/projects/{projectId}/portfolios',
- method: 'POST',
- parameters: {
- body: {
- portfolio,
- },
- path: {
- projectId,
- },
- query: {},
- },
- })
-}
-
-export function getPortfolio(portfolioId) {
- return getById('/portfolios/{portfolioId}', { portfolioId })
-}
-
-export function updatePortfolio(portfolioId, portfolio) {
- return sendRequest({
- path: '/portfolios/{projectId}',
- method: 'POST',
- parameters: {
- body: {
- portfolio,
- },
- path: {
- portfolioId,
- },
- query: {},
- },
- })
-}
-
-export function deletePortfolio(portfolioId) {
- return deleteById('/portfolios/{portfolioId}', { portfolioId })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js b/opendc-web/opendc-web-ui/src/api/routes/prefabs.js
deleted file mode 100644
index 8a1debfa..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { sendRequest } from '../index'
-import { deleteById, getById } from './util'
-
-export function getPrefab(prefabId) {
- return getById('/prefabs/{prefabId}', { prefabId })
-}
-
-export function addPrefab(prefab) {
- return sendRequest({
- path: '/prefabs',
- method: 'POST',
- parameters: {
- body: {
- prefab,
- },
- path: {},
- query: {},
- },
- })
-}
-
-export function updatePrefab(prefab) {
- return sendRequest({
- path: '/prefabs/{prefabId}',
- method: 'PUT',
- parameters: {
- body: {
- prefab,
- },
- path: {
- prefabId: prefab._id,
- },
- query: {},
- },
- })
-}
-
-export function deletePrefab(prefabId) {
- return deleteById('/prefabs/{prefabId}', { prefabId })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/projects.js b/opendc-web/opendc-web-ui/src/api/routes/projects.js
deleted file mode 100644
index 4109079c..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/projects.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import { sendRequest } from '../index'
-import { deleteById, getById } from './util'
-
-export function getProject(projectId) {
- return getById('/projects/{projectId}', { projectId })
-}
-
-export function addProject(project) {
- return sendRequest({
- path: '/projects',
- method: 'POST',
- parameters: {
- body: {
- project,
- },
- path: {},
- query: {},
- },
- })
-}
-
-export function updateProject(project) {
- return sendRequest({
- path: '/projects/{projectId}',
- method: 'PUT',
- parameters: {
- body: {
- project,
- },
- path: {
- projectId: project._id,
- },
- query: {},
- },
- })
-}
-
-export function deleteProject(projectId) {
- return deleteById('/projects/{projectId}', { projectId })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js b/opendc-web/opendc-web-ui/src/api/routes/scenarios.js
deleted file mode 100644
index ab2e8b86..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { deleteById, getById } from './util'
-import { sendRequest } from '../index'
-
-export function addScenario(portfolioId, scenario) {
- return sendRequest({
- path: '/portfolios/{portfolioId}/scenarios',
- method: 'POST',
- parameters: {
- body: {
- scenario,
- },
- path: {
- portfolioId,
- },
- query: {},
- },
- })
-}
-
-export function getScenario(scenarioId) {
- return getById('/scenarios/{scenarioId}', { scenarioId })
-}
-
-export function updateScenario(scenarioId, scenario) {
- return sendRequest({
- path: '/scenarios/{projectId}',
- method: 'POST',
- parameters: {
- body: {
- scenario,
- },
- path: {
- scenarioId,
- },
- query: {},
- },
- })
-}
-
-export function deleteScenario(scenarioId) {
- return deleteById('/scenarios/{scenarioId}', { scenarioId })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js b/opendc-web/opendc-web-ui/src/api/routes/schedulers.js
deleted file mode 100644
index 4481fb2a..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { getAll } from './util'
-
-export function getAllSchedulers() {
- return getAll('/schedulers')
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js b/opendc-web/opendc-web-ui/src/api/routes/token-signin.js
deleted file mode 100644
index ced5d2e0..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import config from '../../config'
-
-export function performTokenSignIn(token) {
- const apiUrl = config['API_BASE_URL']
-
- return fetch(`${apiUrl}/tokensignin`, {
- method: 'POST',
- body: new URLSearchParams({
- idtoken: token,
- }),
- }).then((res) => res.json())
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/topologies.js b/opendc-web/opendc-web-ui/src/api/routes/topologies.js
deleted file mode 100644
index a8f0d6b1..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/topologies.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { deleteById, getById } from './util'
-import { sendRequest } from '../index'
-
-export function addTopology(topology) {
- return sendRequest({
- path: '/projects/{projectId}/topologies',
- method: 'POST',
- parameters: {
- body: {
- topology,
- },
- path: {
- projectId: topology.projectId,
- },
- query: {},
- },
- })
-}
-
-export function getTopology(topologyId) {
- return getById('/topologies/{topologyId}', { topologyId })
-}
-
-export function updateTopology(topology) {
- return sendRequest({
- path: '/topologies/{topologyId}',
- method: 'PUT',
- parameters: {
- body: {
- topology,
- },
- path: {
- topologyId: topology._id,
- },
- query: {},
- },
- })
-}
-
-export function deleteTopology(topologyId) {
- return deleteById('/topologies/{topologyId}', { topologyId })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/traces.js b/opendc-web/opendc-web-ui/src/api/routes/traces.js
deleted file mode 100644
index 67895a87..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/traces.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { getAll } from './util'
-
-export function getAllTraces() {
- return getAll('/traces')
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/users.js b/opendc-web/opendc-web-ui/src/api/routes/users.js
deleted file mode 100644
index 3028f3f7..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/users.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import { sendRequest } from '../index'
-import { deleteById } from './util'
-
-export function getUserByEmail(email) {
- return sendRequest({
- path: '/users',
- method: 'GET',
- parameters: {
- body: {},
- path: {},
- query: {
- email,
- },
- },
- })
-}
-
-export function addUser(user) {
- return sendRequest({
- path: '/users',
- method: 'POST',
- parameters: {
- body: {
- user,
- },
- path: {},
- query: {},
- },
- })
-}
-
-export function getUser(userId) {
- return sendRequest({
- path: '/users/{userId}',
- method: 'GET',
- parameters: {
- body: {},
- path: {
- userId,
- },
- query: {},
- },
- })
-}
-
-export function deleteUser(userId) {
- return deleteById('/users/{userId}', { userId })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/routes/util.js b/opendc-web/opendc-web-ui/src/api/routes/util.js
deleted file mode 100644
index 67e7173b..00000000
--- a/opendc-web/opendc-web-ui/src/api/routes/util.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import { sendRequest } from '../index'
-
-export function getAll(path) {
- return sendRequest({
- path,
- method: 'GET',
- parameters: {
- body: {},
- path: {},
- query: {},
- },
- })
-}
-
-export function getById(path, pathObject) {
- return sendRequest({
- path,
- method: 'GET',
- parameters: {
- body: {},
- path: pathObject,
- query: {},
- },
- })
-}
-
-export function deleteById(path, pathObject) {
- return sendRequest({
- path,
- method: 'DELETE',
- parameters: {
- body: {},
- path: pathObject,
- query: {},
- },
- })
-}
diff --git a/opendc-web/opendc-web-ui/src/api/scenarios.js b/opendc-web/opendc-web-ui/src/api/scenarios.js
new file mode 100644
index 00000000..095aa788
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/api/scenarios.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { request } from './index'
+
+export function addScenario(auth, portfolioId, scenario) {
+ return request(auth, `portfolios/${portfolioId}/scenarios`, 'POST', { scenario })
+}
+
+export function getScenario(auth, scenarioId) {
+ return request(auth, `scenarios/${scenarioId}`)
+}
+
+export function updateScenario(auth, scenarioId, scenario) {
+ return request(auth, `scenarios/${scenarioId}`, 'PUT', { scenario })
+}
+
+export function deleteScenario(auth, scenarioId) {
+ return request(auth, `scenarios/${scenarioId}`, 'DELETE')
+}
diff --git a/opendc-web/opendc-web-ui/src/util/hooks.js b/opendc-web/opendc-web-ui/src/api/schedulers.js
index 7780a778..1b69f1a1 100644
--- a/opendc-web/opendc-web-ui/src/util/hooks.js
+++ b/opendc-web/opendc-web-ui/src/api/schedulers.js
@@ -20,10 +20,8 @@
* SOFTWARE.
*/
-import { useEffect } from 'react'
+import { request } from './index'
-export function useDocumentTitle(title) {
- useEffect(() => {
- document.title = title
- }, [title])
+export function getAllSchedulers(auth) {
+ return request(auth, 'schedulers/')
}
diff --git a/opendc-web/opendc-web-ui/src/api/socket.js b/opendc-web/opendc-web-ui/src/api/socket.js
deleted file mode 100644
index 87facda8..00000000
--- a/opendc-web/opendc-web-ui/src/api/socket.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import io from 'socket.io-client'
-import { getAuthToken } from '../auth/index'
-import config from '../config'
-
-let socket
-let requestIdCounter = 0
-const callbacks = {}
-
-export function setupSocketConnection(onConnect) {
- const apiUrl =
- config['API_BASE_URL'] || `${window.location.protocol}//${window.location.hostname}:${window.location.port}`
-
- socket = io.connect(apiUrl)
- socket.on('connect', onConnect)
- socket.on('response', onSocketResponse)
-}
-
-export function sendSocketRequest(request, callback) {
- if (!socket.connected) {
- console.error('Attempted to send request over unconnected socket')
- return
- }
-
- const newId = requestIdCounter++
- callbacks[newId] = callback
-
- request.id = newId
- request.token = getAuthToken()
-
- if (!request.isRootRoute) {
- request.path = '/v2' + request.path
- }
-
- socket.emit('request', request)
-
- if (process.env.NODE_ENV !== 'production') {
- console.log('Sent socket request:', request)
- }
-}
-
-function onSocketResponse(json) {
- const response = JSON.parse(json)
-
- if (process.env.NODE_ENV !== 'production') {
- console.log('Received socket response:', response)
- }
-
- callbacks[response.id](response)
- delete callbacks[response.id]
-}
diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js
new file mode 100644
index 00000000..c8744e6c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/api/topologies.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { request } from './index'
+
+export function addTopology(auth, topology) {
+ return request(auth, `projects/${topology.projectId}/topologies`, 'POST', { topology })
+}
+
+export function getTopology(auth, topologyId) {
+ return request(auth, `topologies/${topologyId}`)
+}
+
+export function updateTopology(auth, topology) {
+ return request(auth, `topologies/${topology._id}`, 'PUT', { topology })
+}
+
+export function deleteTopology(auth, topologyId) {
+ return request(auth, `topologies/${topologyId}`, 'DELETE')
+}
diff --git a/opendc-web/opendc-web-ui/src/api/traces.js b/opendc-web/opendc-web-ui/src/api/traces.js
new file mode 100644
index 00000000..df03a2dd
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/api/traces.js
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { request } from './index'
+
+export function getAllTraces(auth) {
+ return request(auth, 'traces/')
+}
diff --git a/opendc-web/opendc-web-ui/src/auth.js b/opendc-web/opendc-web-ui/src/auth.js
new file mode 100644
index 00000000..706151bf
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/auth.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { Auth0Provider, useAuth0 } from '@auth0/auth0-react'
+import { useEffect } from 'react'
+import { useRouter } from 'next/router'
+
+/**
+ * Obtain the authentication context.
+ */
+export function useAuth() {
+ return useAuth0()
+}
+
+/**
+ * Force the user to be authenticated or redirect to the homepage.
+ */
+export function useRequireAuth() {
+ const auth = useAuth()
+ const router = useRouter()
+ const { isLoading, isAuthenticated } = auth
+
+ useEffect(() => {
+ if (!isLoading && !isAuthenticated) {
+ router.replace('/')
+ }
+ }, [isLoading, isAuthenticated])
+
+ return auth
+}
+
+/**
+ * AuthProvider which provides an authentication context.
+ */
+export function AuthProvider({ children }) {
+ return (
+ <Auth0Provider
+ domain={process.env.NEXT_PUBLIC_AUTH0_DOMAIN}
+ clientId={process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID}
+ redirectUri={global.window && global.window.location.origin}
+ audience={process.env.NEXT_PUBLIC_AUTH0_AUDIENCE}
+ >
+ {children}
+ </Auth0Provider>
+ )
+}
diff --git a/opendc-web/opendc-web-ui/src/auth/index.js b/opendc-web/opendc-web-ui/src/auth/index.js
deleted file mode 100644
index b5953990..00000000
--- a/opendc-web/opendc-web-ui/src/auth/index.js
+++ /dev/null
@@ -1,57 +0,0 @@
-import { LOG_IN_SUCCEEDED, LOG_OUT } from '../actions/auth'
-import { DELETE_CURRENT_USER_SUCCEEDED } from '../actions/users'
-
-const getAuthObject = () => {
- const authItem = localStorage.getItem('auth')
- if (!authItem || authItem === '{}') {
- return undefined
- }
- return JSON.parse(authItem)
-}
-
-export const userIsLoggedIn = () => {
- const authObj = getAuthObject()
-
- if (!authObj || !authObj.googleId) {
- return false
- }
-
- const currentTime = new Date().getTime()
- return parseInt(authObj.expiresAt, 10) - currentTime > 0
-}
-
-export const getAuthToken = () => {
- const authObj = getAuthObject()
- if (!authObj) {
- return undefined
- }
-
- return authObj.authToken
-}
-
-export const saveAuthLocalStorage = (payload) => {
- localStorage.setItem('auth', JSON.stringify(payload))
-}
-
-export const clearAuthLocalStorage = () => {
- localStorage.setItem('auth', '')
-}
-
-export const authRedirectMiddleware = (store) => (next) => (action) => {
- switch (action.type) {
- case LOG_IN_SUCCEEDED:
- saveAuthLocalStorage(action.payload)
- window.location.href = '/projects'
- break
- case LOG_OUT:
- case DELETE_CURRENT_USER_SUCCEEDED:
- clearAuthLocalStorage()
- window.location.href = '/'
- break
- default:
- next(action)
- return
- }
-
- next(action)
-}
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
index 7efea9b0..ddb94990 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/LoadingScreen.js
@@ -1,9 +1,10 @@
import React from 'react'
-import FontAwesome from 'react-fontawesome'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faSpinner } from '@fortawesome/free-solid-svg-icons'
const LoadingScreen = () => (
<div className="display-4">
- <FontAwesome name="refresh" className="mr-4" spin />
+ <FontAwesomeIcon icon={faSpinner} spin className="mr-4" />
Loading your project...
</div>
)
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
index 7ca10792..7c97f3e4 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/MapStageComponent.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useEffect, useRef, useState } from 'react'
import { HotKeys } from 'react-hotkeys'
import { Stage } from 'react-konva'
import MapLayer from '../../../containers/app/map/layers/MapLayer'
@@ -6,85 +6,75 @@ import ObjectHoverLayer from '../../../containers/app/map/layers/ObjectHoverLaye
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'
+import { Provider, useStore } from 'react-redux'
-class MapStageComponent extends React.Component {
- state = {
- mouseX: 0,
- mouseY: 0,
+function MapStageComponent({
+ mapDimensions,
+ mapPosition,
+ setMapDimensions,
+ setMapPositionWithBoundsCheck,
+ zoomInOnPosition,
+}) {
+ const [pos, setPos] = useState([0, 0])
+ const stage = useRef(null)
+ const [x, y] = pos
+ const handlers = {
+ MOVE_LEFT: () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0),
+ MOVE_RIGHT: () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0),
+ MOVE_UP: () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT),
+ MOVE_DOWN: () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT),
}
- constructor(props) {
- super(props)
+ const moveWithDelta = (deltaX, deltaY) =>
+ setMapPositionWithBoundsCheck(mapPosition.x + deltaX, mapPosition.y + deltaY)
+ const updateMousePosition = () => {
+ if (!stage.current) {
+ return
+ }
- this.updateDimensions = this.updateDimensions.bind(this)
- this.updateScale = this.updateScale.bind(this)
+ const mousePos = stage.current.getStage().getPointerPosition()
+ setPos([mousePos.x, mousePos.y])
}
+ const updateDimensions = () => setMapDimensions(window.innerWidth, window.innerHeight - NAVBAR_HEIGHT)
+ const updateScale = (e) => zoomInOnPosition(e.deltaY < 0, x, y)
- componentDidMount() {
- this.updateDimensions()
+ useEffect(() => {
+ updateDimensions()
- window.addEventListener('resize', this.updateDimensions)
- window.addEventListener('wheel', this.updateScale)
+ window.addEventListener('resize', updateDimensions)
+ window.addEventListener('wheel', updateScale)
window['exportCanvasToImage'] = () => {
const download = document.createElement('a')
- download.href = this.stage.getStage().toDataURL()
+ download.href = stage.current.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) {
- 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 })
- }
- handlers = {
- MOVE_LEFT: () => this.moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0),
- MOVE_RIGHT: () => this.moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0),
- MOVE_UP: () => this.moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT),
- MOVE_DOWN: () => this.moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT),
- }
+ return () => {
+ window.removeEventListener('resize', updateDimensions)
+ window.removeEventListener('wheel', updateScale)
+ }
+ }, [])
- moveWithDelta(deltaX, deltaY) {
- this.props.setMapPositionWithBoundsCheck(this.props.mapPosition.x + deltaX, this.props.mapPosition.y + deltaY)
- }
+ const store = useStore()
- render() {
- return (
- <HotKeys handlers={this.handlers}>
- <Stage
- ref={(stage) => {
- this.stage = stage
- }}
- width={this.props.mapDimensions.width}
- height={this.props.mapDimensions.height}
- onMouseMove={this.updateMousePosition.bind(this)}
- >
- <Provider store={store}>
- <MapLayer />
- <RoomHoverLayer mouseX={this.state.mouseX} mouseY={this.state.mouseY} />
- <ObjectHoverLayer mouseX={this.state.mouseX} mouseY={this.state.mouseY} />
- </Provider>
- </Stage>
- </HotKeys>
- )
- }
+ return (
+ <HotKeys handlers={handlers} allowChanges={true}>
+ <Stage
+ ref={stage}
+ width={mapDimensions.width}
+ height={mapDimensions.height}
+ onMouseMove={updateMousePosition}
+ >
+ <Provider store={store}>
+ <MapLayer />
+ <RoomHoverLayer mouseX={x} mouseY={y} />
+ <ObjectHoverLayer mouseX={x} mouseY={y} />
+ </Provider>
+ </Stage>
+ </HotKeys>
+ )
}
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
index 8487f47b..9e8cb36a 100644
--- 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
@@ -1,4 +1,6 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faCamera } from '@fortawesome/free-solid-svg-icons'
const ExportCanvasComponent = () => (
<button
@@ -6,7 +8,7 @@ const ExportCanvasComponent = () => (
title="Export Canvas to PNG Image"
onClick={() => window['exportCanvasToImage']()}
>
- <span className="fa fa-camera" />
+ <FontAwesomeIcon icon={faCamera} />
</button>
)
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
index 7cbb45c0..13226602 100644
--- 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
@@ -1,9 +1,9 @@
import React from 'react'
import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants'
-import './ScaleIndicatorComponent.sass'
+import { scaleIndicator } from './ScaleIndicatorComponent.module.scss'
const ScaleIndicatorComponent = ({ scale }) => (
- <div className="scale-indicator" style={{ width: TILE_SIZE_IN_PIXELS * scale }}>
+ <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}>
{TILE_SIZE_IN_METERS}m
</div>
)
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.module.scss b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.module.scss
new file mode 100644
index 00000000..f19e0ff2
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.module.scss
@@ -0,0 +1,10 @@
+.scaleIndicator {
+ position: absolute;
+ right: 10px;
+ bottom: 10px;
+ z-index: 50;
+
+ border: solid 2px #212529;
+ border-top: none;
+ border-left: none;
+}
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass b/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass
deleted file mode 100644
index 03a72c99..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ScaleIndicatorComponent.sass
+++ /dev/null
@@ -1,9 +0,0 @@
-.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
index f372734d..d2f70953 100644
--- 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
@@ -1,10 +1,10 @@
import React from 'react'
import ZoomControlContainer from '../../../../containers/app/map/controls/ZoomControlContainer'
import ExportCanvasComponent from './ExportCanvasComponent'
-import './ToolPanelComponent.sass'
+import { toolPanel } from './ToolPanelComponent.module.scss'
const ToolPanelComponent = () => (
- <div className="tool-panel">
+ <div className={toolPanel}>
<ZoomControlContainer />
<ExportCanvasComponent />
</div>
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.module.scss b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.module.scss
new file mode 100644
index 00000000..970b1ce2
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.module.scss
@@ -0,0 +1,6 @@
+.toolPanel {
+ position: absolute;
+ left: 10px;
+ bottom: 10px;
+ z-index: 50;
+}
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
deleted file mode 100644
index 8b27d24a..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/map/controls/ToolPanelComponent.sass
+++ /dev/null
@@ -1,5 +0,0 @@
-.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
index 65944bea..6bae792c 100644
--- 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
@@ -1,4 +1,6 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus, faMinus } from '@fortawesome/free-solid-svg-icons'
const ZoomControlComponent = ({ zoomInOnCenter }) => {
return (
@@ -8,14 +10,14 @@ const ZoomControlComponent = ({ zoomInOnCenter }) => {
title="Zoom in"
onClick={() => zoomInOnCenter(true)}
>
- <span className="fa fa-plus" />
+ <FontAwesomeIcon icon={faPlus} />
</button>
<button
className="btn btn-default btn-circle btn-sm mr-1"
title="Zoom out"
onClick={() => zoomInOnCenter(false)}
>
- <span className="fa fa-minus" />
+ <FontAwesomeIcon icon={faMinus} />
</button>
</span>
)
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
index 2b5c569f..7d304b6b 100644
--- 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
@@ -1,48 +1,36 @@
import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useEffect, useState } 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,
- }
+const imageCaches = {}
- state = {
- image: null,
- }
+function ImageComponent({ src, x, y, width, height, opacity }) {
+ const [image, setImage] = useState(null)
- componentDidMount() {
- if (ImageComponent.imageCaches[this.props.src]) {
- this.setState({ image: ImageComponent.imageCaches[this.props.src] })
+ useEffect(() => {
+ if (imageCaches[src]) {
+ setImage(imageCaches[src])
return
}
const image = new window.Image()
- image.src = this.props.src
+ image.src = src
image.onload = () => {
- this.setState({ image })
- ImageComponent.imageCaches[this.props.src] = image
+ setImage(image)
+ imageCaches[src] = image
}
- }
+ }, [src])
- render() {
- return (
- <Image
- image={this.state.image}
- x={this.props.x}
- y={this.props.y}
- width={this.props.width}
- height={this.props.height}
- opacity={this.props.opacity}
- />
- )
- }
+ return <Image image={image} x={x} y={y} width={width} height={height} opacity={opacity} />
+}
+
+ImageComponent.propTypes = {
+ src: PropTypes.string.isRequired,
+ x: PropTypes.number.isRequired,
+ y: PropTypes.number.isRequired,
+ width: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired,
+ opacity: PropTypes.number.isRequired,
}
export default ImageComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/app/map/elements/RoomTile.js
index 43bf918e..b2cc1273 100644
--- 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
@@ -1,6 +1,6 @@
import React from 'react'
import { Rect } from 'react-konva'
-import Shapes from '../../../../shapes/index'
+import { Tile } from '../../../../shapes'
import { TILE_SIZE_IN_PIXELS } from '../MapConstants'
const RoomTile = ({ tile, color }) => (
@@ -14,7 +14,7 @@ const RoomTile = ({ tile, color }) => (
)
RoomTile.propTypes = {
- tile: Shapes.Tile,
+ tile: Tile,
}
export default RoomTile
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
index 8aa2aebf..ad6412c3 100644
--- 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
@@ -1,6 +1,6 @@
import React from 'react'
import { Line } from 'react-konva'
-import Shapes from '../../../../shapes/index'
+import { WallSegment as WallSegmentShape } from '../../../../shapes'
import { WALL_COLOR } from '../../../../util/colors'
import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants'
@@ -26,7 +26,7 @@ const WallSegment = ({ wallSegment }) => {
}
WallSegment.propTypes = {
- wallSegment: Shapes.WallSegment,
+ wallSegment: WallSegmentShape,
}
export default WallSegment
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
index eb6dc24a..40e28f01 100644
--- 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
@@ -2,7 +2,7 @@ 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 { Tile } from '../../../../shapes'
import { RACK_BACKGROUND_COLOR } from '../../../../util/colors'
import TileObject from '../elements/TileObject'
@@ -19,7 +19,7 @@ const RackGroup = ({ tile }) => {
}
RackGroup.propTypes = {
- tile: Shapes.Tile,
+ tile: Tile,
}
export default RackGroup
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js
index 1fd54687..d7c207ca 100644
--- 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
@@ -3,7 +3,7 @@ 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'
+import { Room } from '../../../../shapes'
const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick }) => {
if (currentRoomInConstruction === room._id) {
@@ -42,7 +42,7 @@ const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick
}
RoomGroup.propTypes = {
- room: Shapes.Room,
+ room: 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
index 1e106823..ff6ec7ec 100644
--- 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
@@ -2,7 +2,7 @@ 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 { Tile } from '../../../../shapes'
import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors'
import RoomTile from '../elements/RoomTile'
@@ -28,7 +28,7 @@ const TileGroup = ({ tile, newTile, roomLoad, onClick }) => {
}
TileGroup.propTypes = {
- tile: Shapes.Tile,
+ tile: Tile,
newTile: PropTypes.bool,
}
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
index 6096fc8b..57107768 100644
--- 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
@@ -2,7 +2,7 @@ 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'
+import { InteractionLevel, Topology } from '../../../../shapes'
const TopologyGroup = ({ topology, interactionLevel }) => {
if (!topology) {
@@ -37,8 +37,8 @@ const TopologyGroup = ({ topology, interactionLevel }) => {
}
TopologyGroup.propTypes = {
- topology: Shapes.Topology,
- interactionLevel: Shapes.InteractionLevel,
+ topology: Topology,
+ interactionLevel: InteractionLevel,
}
export default TopologyGroup
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js
index 7b0f5ca0..c73a95a7 100644
--- 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
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'
import React from 'react'
import { Group } from 'react-konva'
-import Shapes from '../../../../shapes/index'
+import { Tile } from '../../../../shapes'
import { deriveWallLocations } from '../../../../util/tile-calculations'
import WallSegment from '../elements/WallSegment'
@@ -16,7 +16,7 @@ const WallGroup = ({ tiles }) => {
}
WallGroup.propTypes = {
- tiles: PropTypes.arrayOf(Shapes.Tile).isRequired,
+ tiles: PropTypes.arrayOf(Tile).isRequired,
}
export default WallGroup
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
index bead87de..08d31dac 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/layers/HoverLayerComponent.js
@@ -1,75 +1,63 @@
import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useEffect, useState } 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,
- }
+function HoverLayerComponent({ mouseX, mouseY, mapPosition, mapScale, isEnabled, isValid, onClick, children }) {
+ const [pos, setPos] = useState([-1, -1])
+ const [x, y] = pos
+ const [valid, setValid] = useState(false)
- componentDidUpdate() {
- if (!this.props.isEnabled()) {
+ useEffect(() => {
+ if (!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)
- )
+ const positionX = Math.floor((mouseX - mapPosition.x) / (mapScale * TILE_SIZE_IN_PIXELS))
+ const positionY = Math.floor((mouseY - mapPosition.y) / (mapScale * TILE_SIZE_IN_PIXELS))
- if (positionX !== this.state.positionX || positionY !== this.state.positionY) {
- this.setState({
- positionX,
- positionY,
- validity: this.props.isValid(positionX, positionY),
- })
+ if (positionX !== x || positionY !== y) {
+ setPos([positionX, positionY])
+ setValid(isValid(positionX, positionY))
}
- }
+ }, [mouseX, mouseY, mapPosition, mapScale])
- render() {
- if (!this.props.isEnabled()) {
- return <Layer />
- }
+ if (!isEnabled()) {
+ return <Layer />
+ }
- 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
+ const pixelX = mapScale * x * TILE_SIZE_IN_PIXELS + mapPosition.x
+ const pixelY = mapScale * y * TILE_SIZE_IN_PIXELS + mapPosition.y
+
+ return (
+ <Layer opacity={0.6}>
+ <HoverTile
+ pixelX={pixelX}
+ pixelY={pixelY}
+ scale={mapScale}
+ isValid={valid}
+ onClick={() => (valid ? onClick(x, y) : undefined)}
+ />
+ {children
+ ? React.cloneElement(children, {
+ pixelX,
+ pixelY,
+ scale: mapScale,
+ })
+ : undefined}
+ </Layer>
+ )
+}
- return (
- <Layer opacity={0.6}>
- <HoverTile
- pixelX={pixelX}
- pixelY={pixelY}
- scale={this.props.mapScale}
- isValid={this.state.validity}
- onClick={() =>
- 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}
- </Layer>
- )
- }
+HoverLayerComponent.propTypes = {
+ mouseX: PropTypes.number.isRequired,
+ mouseY: PropTypes.number.isRequired,
+ mapPosition: PropTypes.object.isRequired,
+ mapScale: PropTypes.number.isRequired,
+ isEnabled: PropTypes.func.isRequired,
+ isValid: PropTypes.func.isRequired,
+ onClick: PropTypes.func.isRequired,
}
export default HoverLayerComponent
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
index 759acd57..983a5c1d 100644
--- a/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/results/PortfolioResultsComponent.js
@@ -3,7 +3,7 @@ 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 { Portfolio, Scenario } from '../../../shapes'
import approx from 'approximate-number'
const PortfolioResultsComponent = ({ portfolio, scenarios }) => {
@@ -86,8 +86,8 @@ const PortfolioResultsComponent = ({ portfolio, scenarios }) => {
}
PortfolioResultsComponent.propTypes = {
- portfolio: Shapes.Portfolio,
- scenarios: PropTypes.arrayOf(Shapes.Scenario),
+ portfolio: Portfolio,
+ scenarios: PropTypes.arrayOf(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
index f7368f54..ccaa4144 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.js
@@ -1,53 +1,47 @@
import PropTypes from 'prop-types'
import classNames from 'classnames'
-import React from 'react'
-import './Sidebar.sass'
+import React, { useState } from 'react'
+import { collapseButton, collapseButtonRight, sidebar, sidebarRight } from './Sidebar.module.scss'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons'
-class Sidebar extends React.Component {
- static propTypes = {
- isRight: PropTypes.bool.isRequired,
- collapsible: PropTypes.bool,
- }
+function Sidebar({ isRight, collapsible = true, children }) {
+ const [isCollapsed, setCollapsed] = useState(false)
- static defaultProps = {
- collapsible: true,
- }
+ const button = (
+ <div
+ className={classNames(collapseButton, {
+ [collapseButtonRight]: isRight,
+ })}
+ onClick={() => setCollapsed(!isCollapsed)}
+ >
+ {(isCollapsed && isRight) || (!isCollapsed && !isRight) ? (
+ <FontAwesomeIcon icon={faAngleLeft} title={isRight ? 'Expand' : 'Collapse'} />
+ ) : (
+ <FontAwesomeIcon icon={faAngleRight} title={isRight ? 'Collapse' : 'Expand'} />
+ )}
+ </div>
+ )
- state = {
- collapsed: false,
+ if (isCollapsed) {
+ return button
}
+ return (
+ <div
+ className={classNames(`${sidebar} p-3 h-100`, {
+ [sidebarRight]: isRight,
+ })}
+ onWheel={(e) => e.stopPropagation()}
+ >
+ {children}
+ {collapsible && button}
+ </div>
+ )
+}
- render() {
- const collapseButton = (
- <div
- className={classNames('sidebar-collapse-button', {
- 'sidebar-collapse-button-right': this.props.isRight,
- })}
- onClick={() => this.setState({ collapsed: !this.state.collapsed })}
- >
- {(this.state.collapsed && this.props.isRight) || (!this.state.collapsed && !this.props.isRight) ? (
- <span className="fa fa-angle-left" title={this.props.isRight ? 'Expand' : 'Collapse'} />
- ) : (
- <span className="fa fa-angle-right" title={this.props.isRight ? 'Collapse' : 'Expand'} />
- )}
- </div>
- )
-
- if (this.state.collapsed) {
- return collapseButton
- }
- return (
- <div
- className={classNames('sidebar p-3 h-100', {
- 'sidebar-right': this.props.isRight,
- })}
- onWheel={(e) => e.stopPropagation()}
- >
- {this.props.children}
- {this.props.collapsible && collapseButton}
- </div>
- )
- }
+Sidebar.propTypes = {
+ isRight: PropTypes.bool.isRequired,
+ collapsible: PropTypes.bool,
}
export default Sidebar
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.module.scss
new file mode 100644
index 00000000..19c6a97f
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.module.scss
@@ -0,0 +1,57 @@
+@import 'src/style/_variables.scss';
+@import 'src/style/_mixins.scss';
+
+.collapseButton {
+ position: absolute;
+ left: 5px;
+ top: 5px;
+ padding: 5px 7px;
+
+ background: white;
+ border: solid 1px $gray-semi-light;
+ z-index: 99;
+
+ @include clickable;
+ border-radius: 5px;
+ transition: background 200ms;
+
+ &.collapseButtonRight {
+ 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;
+
+ .collapseButton {
+ left: auto;
+ right: -25px;
+ }
+}
+
+.sidebarRight {
+ left: auto;
+ right: 0;
+
+ border-left: $gray-semi-dark 1px solid;
+ border-right: none;
+
+ .collapseButtonRight {
+ left: -25px;
+ right: auto;
+ }
+}
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
deleted file mode 100644
index b8e15716..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/Sidebar.sass
+++ /dev/null
@@ -1,50 +0,0 @@
-@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
index b000b9e2..9dd36d5e 100644
--- 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
@@ -1,66 +1,71 @@
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 { Portfolio } from '../../../../shapes'
+import Link from 'next/link'
import ScenarioListContainer from '../../../../containers/app/sidebars/project/ScenarioListContainer'
+import { Button, Col, Row } from 'reactstrap'
+import classNames from 'classnames'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus, faPlay, faTrash } from '@fortawesome/free-solid-svg-icons'
-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,
- }
+function PortfolioListComponent({
+ portfolios,
+ currentProjectId,
+ currentPortfolioId,
+ onNewPortfolio,
+ onChoosePortfolio,
+ onDeletePortfolio,
+}) {
+ return (
+ <div className="pb-3">
+ <h2>
+ Portfolios
+ <Button color="primary" outline className="float-right" onClick={(e) => onNewPortfolio(e)}>
+ <FontAwesomeIcon icon={faPlus} />
+ </Button>
+ </h2>
- onDelete(id) {
- this.props.onDeletePortfolio(id)
- }
-
- render() {
- return (
- <div className="pb-3">
- <h2>
- Portfolios
- <button
- className="btn btn-outline-primary float-right"
- onClick={this.props.onNewPortfolio.bind(this)}
- >
- <FontAwesome name="plus" />
- </button>
- </h2>
+ {portfolios.map((portfolio, idx) => (
+ <div key={portfolio._id}>
+ <Row className="row mb-1">
+ <Col
+ xs="7"
+ className={classNames('align-self-center', {
+ 'font-weight-bold': portfolio._id === currentPortfolioId,
+ })}
+ >
+ {portfolio.name}
+ </Col>
+ <Col xs="5" className="text-right">
+ <Link href={`/projects/${currentProjectId}/portfolios/${portfolio._id}`}>
+ <Button
+ color="primary"
+ outline
+ className="mr-1"
+ onClick={() => onChoosePortfolio(portfolio._id)}
+ >
+ <FontAwesomeIcon icon={faPlay} />
+ </Button>
+ </Link>
+ <Button color="danger" outline onClick={() => onDeletePortfolio(portfolio._id)}>
+ <FontAwesomeIcon icon={faTrash} />
+ </Button>
+ </Col>
+ </Row>
+ <ScenarioListContainer portfolioId={portfolio._id} />
+ </div>
+ ))}
+ </div>
+ )
+}
- {this.props.portfolios.map((portfolio, idx) => (
- <div key={portfolio._id}>
- <div className="row mb-1">
- <div
- className={
- 'col-7 align-self-center ' +
- (portfolio._id === this.props.currentPortfolioId ? 'font-weight-bold' : '')
- }
- >
- {portfolio.name}
- </div>
- <div className="col-5 text-right">
- <Link
- className="btn btn-outline-primary mr-1 fa fa-play"
- to={`/projects/${this.props.currentProjectId}/portfolios/${portfolio._id}`}
- onClick={() => this.props.onChoosePortfolio(portfolio._id)}
- />
- <span
- className="btn btn-outline-danger fa fa-trash"
- onClick={() => this.onDelete(portfolio._id)}
- />
- </div>
- </div>
- <ScenarioListContainer portfolioId={portfolio._id} />
- </div>
- ))}
- </div>
- )
- }
+PortfolioListComponent.propTypes = {
+ portfolios: PropTypes.arrayOf(Portfolio),
+ currentProjectId: PropTypes.string.isRequired,
+ currentPortfolioId: PropTypes.string,
+ onNewPortfolio: PropTypes.func.isRequired,
+ onChoosePortfolio: PropTypes.func.isRequired,
+ onDeletePortfolio: PropTypes.func.isRequired,
}
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
index 4789315e..7dd13663 100644
--- 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
@@ -2,13 +2,14 @@ import React from 'react'
import Sidebar from '../Sidebar'
import TopologyListContainer from '../../../../containers/app/sidebars/project/TopologyListContainer'
import PortfolioListContainer from '../../../../containers/app/sidebars/project/PortfolioListContainer'
+import { Container } from 'reactstrap'
const ProjectSidebarComponent = ({ collapsible }) => (
<Sidebar isRight={false} collapsible={collapsible}>
- <div className="h-100 overflow-auto container-fluid">
+ <Container fluid className="h-100 overflow-auto">
<TopologyListContainer />
<PortfolioListContainer />
- </div>
+ </Container>
</Sidebar>
)
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
index e775a663..131a00b5 100644
--- 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
@@ -1,62 +1,76 @@
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 { Scenario } from '../../../../shapes'
+import Link from 'next/link'
+import { Button, Col, Row } from 'reactstrap'
+import classNames from 'classnames'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus, faPlay, faTrash } from '@fortawesome/free-solid-svg-icons'
-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) => (
- <div key={scenario._id} className="row mb-1">
- <div
- className={
- 'col-7 pl-5 align-self-center ' +
- (scenario._id === this.props.currentScenarioId ? 'font-weight-bold' : '')
- }
- >
- {scenario.name}
- </div>
- <div className="col-5 text-right">
- <Link
- className="btn btn-outline-primary mr-1 fa fa-play disabled"
- to={`/projects/${this.props.currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`}
- onClick={() => this.props.onChooseScenario(scenario.portfolioId, scenario._id)}
- />
- <span
- className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')}
- onClick={() => (idx !== 0 ? this.onDelete(scenario._id) : undefined)}
- />
- </div>
- </div>
- ))}
- <div className="pl-4 mb-2">
- <div
- className="btn btn-outline-primary"
- onClick={() => this.props.onNewScenario(this.props.portfolioId)}
+function ScenarioListComponent({
+ scenarios,
+ portfolioId,
+ currentProjectId,
+ currentScenarioId,
+ onNewScenario,
+ onChooseScenario,
+ onDeleteScenario,
+}) {
+ return (
+ <>
+ {scenarios.map((scenario, idx) => (
+ <Row key={scenario._id} className="mb-1">
+ <Col
+ xs="7"
+ className={classNames('pl-5 align-self-center', {
+ 'font-weight-bold': scenario._id === currentScenarioId,
+ })}
>
- <FontAwesome name="plus" className="mr-1" />
- New scenario
- </div>
- </div>
- </>
- )
- }
+ {scenario.name}
+ </Col>
+ <Col xs="5" className="text-right">
+ <Link
+ href={`/projects/${currentProjectId}/portfolios/${scenario.portfolioId}/scenarios/${scenario._id}`}
+ >
+ <Button
+ color="primary"
+ outline
+ disabled
+ className="mr-1"
+ onClick={() => onChooseScenario(scenario.portfolioId, scenario._id)}
+ >
+ <FontAwesomeIcon icon={faPlay} />
+ </Button>
+ </Link>
+ <Button
+ color="danger"
+ outline
+ disabled={idx === 0}
+ onClick={() => (idx !== 0 ? onDeleteScenario(scenario._id) : undefined)}
+ >
+ <FontAwesomeIcon icon={faTrash} />
+ </Button>
+ </Col>
+ </Row>
+ ))}
+ <div className="pl-4 mb-2">
+ <Button color="primary" outline onClick={() => onNewScenario(portfolioId)}>
+ <FontAwesomeIcon icon={faPlus} className="mr-1" />
+ New scenario
+ </Button>
+ </div>
+ </>
+ )
+}
+
+ScenarioListComponent.propTypes = {
+ scenarios: PropTypes.arrayOf(Scenario),
+ portfolioId: PropTypes.string,
+ currentProjectId: PropTypes.string.isRequired,
+ currentScenarioId: PropTypes.string,
+ onNewScenario: PropTypes.func.isRequired,
+ onChooseScenario: PropTypes.func.isRequired,
+ onDeleteScenario: PropTypes.func.isRequired,
}
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
index 2f42f7e4..ac58669b 100644
--- 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
@@ -1,60 +1,56 @@
import PropTypes from 'prop-types'
import React from 'react'
-import Shapes from '../../../../shapes'
-import FontAwesome from 'react-fontawesome'
+import { Topology } from '../../../../shapes'
+import { Button, Col, Row } from 'reactstrap'
+import classNames from 'classnames'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus, faPlay, faTrash } from '@fortawesome/free-solid-svg-icons'
-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,
- }
+function TopologyListComponent({ topologies, currentTopologyId, onChooseTopology, onNewTopology, onDeleteTopology }) {
+ return (
+ <div className="pb-3">
+ <h2>
+ Topologies
+ <Button color="primary" outline className="float-right" onClick={onNewTopology}>
+ <FontAwesomeIcon icon={faPlus} />
+ </Button>
+ </h2>
- onChoose(id) {
- this.props.onChooseTopology(id)
- }
-
- onDelete(id) {
- this.props.onDeleteTopology(id)
- }
-
- render() {
- return (
- <div className="pb-3">
- <h2>
- Topologies
- <button className="btn btn-outline-primary float-right" onClick={this.props.onNewTopology}>
- <FontAwesome name="plus" />
- </button>
- </h2>
-
- {this.props.topologies.map((topology, idx) => (
- <div key={topology._id} className="row mb-1">
- <div
- className={
- 'col-7 align-self-center ' +
- (topology._id === this.props.currentTopologyId ? 'font-weight-bold' : '')
- }
+ {topologies.map((topology, idx) => (
+ <Row key={topology._id} className="mb-1">
+ <Col
+ xs="7"
+ className={classNames('align-self-center', {
+ 'font-weight-bold': topology._id === currentTopologyId,
+ })}
+ >
+ {topology.name}
+ </Col>
+ <Col xs="5" className="text-right">
+ <Button color="primary" outline className="mr-1" onClick={() => onChooseTopology(topology._id)}>
+ <FontAwesomeIcon icon={faPlay} />
+ </Button>
+ <Button
+ color="danger"
+ outline
+ disabled={idx === 0}
+ onClick={() => (idx !== 0 ? onDeleteTopology(topology._id) : undefined)}
>
- {topology.name}
- </div>
- <div className="col-5 text-right">
- <span
- className="btn btn-outline-primary mr-1 fa fa-play"
- onClick={() => this.onChoose(topology._id)}
- />
- <span
- className={'btn btn-outline-danger fa fa-trash ' + (idx === 0 ? 'disabled' : '')}
- onClick={() => (idx !== 0 ? this.onDelete(topology._id) : undefined)}
- />
- </div>
- </div>
- ))}
- </div>
- )
- }
+ <FontAwesomeIcon icon={faTrash} />
+ </Button>
+ </Col>
+ </Row>
+ ))}
+ </div>
+ )
+}
+
+TopologyListComponent.propTypes = {
+ topologies: PropTypes.arrayOf(Topology),
+ currentTopologyId: PropTypes.string,
+ onChooseTopology: PropTypes.func.isRequired,
+ onNewTopology: PropTypes.func.isRequired,
+ onDeleteTopology: PropTypes.func.isRequired,
}
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
index 5fb0dc55..b4cbc78f 100644
--- 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
@@ -1,11 +1,12 @@
import React from 'react'
-import FontAwesome from 'react-fontawesome'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPencilAlt } from '@fortawesome/free-solid-svg-icons'
const NameComponent = ({ name, onEdit }) => (
<h2>
{name}
<button className="btn btn-outline-secondary float-right" onClick={onEdit}>
- <FontAwesome name="pencil" />
+ <FontAwesomeIcon icon={faPencilAlt} />
</button>
</h2>
)
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js
index fd552c1e..b1461743 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js
@@ -1,24 +1,27 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus, faCheck, faTimes } from '@fortawesome/free-solid-svg-icons'
+import { Button } from 'reactstrap'
const NewRoomConstructionComponent = ({ onStart, onFinish, onCancel, currentRoomInConstruction }) => {
if (currentRoomInConstruction === '-1') {
return (
<div className="btn btn-outline-primary btn-block" onClick={onStart}>
- <span className="fa fa-plus mr-2" />
+ <FontAwesomeIcon icon={faPlus} className="mr-2" />
Construct a new room
</div>
)
}
return (
<div>
- <div className="btn btn-primary btn-block" onClick={onFinish}>
- <span className="fa fa-check mr-2" />
+ <Button color="primary" block onClick={onFinish}>
+ <FontAwesomeIcon icon={faCheck} className="mr-2" />
Finalize new room
- </div>
- <div className="btn btn-default btn-block" onClick={onCancel}>
- <span className="fa fa-times mr-2" />
+ </Button>
+ <Button color="default" block onClick={onCancel}>
+ <FontAwesomeIcon icon={faTimes} className="mr-2" />
Cancel construction
- </div>
+ </Button>
</div>
)
}
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
index 70d522b2..eac99643 100644
--- 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
@@ -1,8 +1,10 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'
const BackToRackComponent = ({ onClick }) => (
<div className="btn btn-secondary btn-block" onClick={onClick}>
- <span className="fa fa-angle-left mr-2" />
+ <FontAwesomeIcon icon={faAngleLeft} className="mr-2" />
Back to rack
</div>
)
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
deleted file mode 100644
index 37820316..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react'
-
-const DeleteMachineComponent = ({ onClick }) => (
- <div className="btn btn-outline-danger btn-block" onClick={onClick}>
- <span className="fa fa-trash mr-2" />
- Delete this machine
- </div>
-)
-
-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
deleted file mode 100644
index 992383c4..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import React from 'react'
-
-const MachineNameComponent = ({ position }) => <h2>Machine at slot {position}</h2>
-
-export default MachineNameComponent
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
index 4e9dbc7e..532add37 100644
--- 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
@@ -1,35 +1,34 @@
import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useRef } from 'react'
+import { Button, Form, FormGroup, Input } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus } from '@fortawesome/free-solid-svg-icons'
-class UnitAddComponent extends React.Component {
- static propTypes = {
- units: PropTypes.array.isRequired,
- onAdd: PropTypes.func.isRequired,
- }
+function UnitAddComponent({ units, onAdd }) {
+ const unitSelect = useRef(null)
- render() {
- return (
- <div className="form-inline">
- <div className="form-group w-100">
- <select className="form-control w-70 mr-1" ref={(unitSelect) => (this.unitSelect = unitSelect)}>
- {this.props.units.map((unit) => (
- <option value={unit._id} key={unit._id}>
- {unit.name}
- </option>
- ))}
- </select>
- <button
- type="submit"
- className="btn btn-outline-primary"
- onClick={() => this.props.onAdd(this.unitSelect.value)}
- >
- <span className="fa fa-plus mr-2" />
- Add
- </button>
- </div>
- </div>
- )
- }
+ return (
+ <Form inline>
+ <FormGroup className="w-100">
+ <Input type="select" className="w-70 mr-1" innerRef={unitSelect}>
+ {units.map((unit) => (
+ <option value={unit._id} key={unit._id}>
+ {unit.name}
+ </option>
+ ))}
+ </Input>
+ <Button color="primary" outline onClick={() => onAdd(unitSelect.current.value)}>
+ <FontAwesomeIcon icon={faPlus} className="mr-2" />
+ Add
+ </Button>
+ </FormGroup>
+ </Form>
+ )
+}
+
+UnitAddComponent.propTypes = {
+ units: PropTypes.array.isRequired,
+ onAdd: PropTypes.func.isRequired,
}
export default UnitAddComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js
index de55e506..aa473f91 100644
--- 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
@@ -1,5 +1,7 @@
import React from 'react'
import { UncontrolledPopover, PopoverHeader, PopoverBody, Button } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTrash, faInfoCircle } from '@fortawesome/free-solid-svg-icons'
function UnitComponent({ index, unitType, unit, onDelete }) {
let unitInfo
@@ -37,13 +39,17 @@ function UnitComponent({ index, unitType, unit, onDelete }) {
<li className="d-flex list-group-item justify-content-between align-items-center">
<span style={{ maxWidth: '60%' }}>{unit.name}</span>
<span>
- <Button outline={true} color="info" className="mr-1 fa fa-info-circle" id={`unit-${index}`} />
+ <Button outline={true} color="info" className="mr-1" id={`unit-${index}`}>
+ <FontAwesomeIcon icon={faInfoCircle} />
+ </Button>
<UncontrolledPopover trigger="focus" placement="left" target={`unit-${index}`}>
<PopoverHeader>Unit Information</PopoverHeader>
<PopoverBody>{unitInfo}</PopoverBody>
</UncontrolledPopover>
- <span className="btn btn-outline-danger fa fa-trash" onClick={onDelete} />
+ <Button outline color="danger" onClick={onDelete}>
+ <FontAwesomeIcon icon={faTrash} />
+ </Button>
</span>
</li>
)
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
index 6599fefd..ebb5f479 100644
--- 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
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
-import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap'
+import { Nav, NavItem, NavLink, Row, TabContent, TabPane } from 'reactstrap'
import UnitAddContainer from '../../../../../containers/app/sidebars/topology/machine/UnitAddContainer'
import UnitListContainer from '../../../../../containers/app/sidebars/topology/machine/UnitListContainer'
@@ -10,7 +10,7 @@ const UnitTabsComponent = () => {
}
return (
- <div>
+ <div className="mt-2">
<Nav tabs>
<NavItem>
<NavLink
@@ -55,20 +55,28 @@ const UnitTabsComponent = () => {
</Nav>
<TabContent activeTab={activeTab}>
<TabPane tabId="cpu-units">
- <UnitAddContainer unitType="cpu" />
- <UnitListContainer unitType="cpu" />
+ <div className="py-2">
+ <UnitAddContainer unitType="cpu" />
+ <UnitListContainer unitType="cpu" />
+ </div>
</TabPane>
<TabPane tabId="gpu-units">
- <UnitAddContainer unitType="gpu" />
- <UnitListContainer unitType="gpu" />
+ <div className="py-2">
+ <UnitAddContainer unitType="gpu" />
+ <UnitListContainer unitType="gpu" />
+ </div>
</TabPane>
<TabPane tabId="memory-units">
- <UnitAddContainer unitType="memory" />
- <UnitListContainer unitType="memory" />
+ <div className="py-2">
+ <UnitAddContainer unitType="memory" />
+ <UnitListContainer unitType="memory" />
+ </div>
</TabPane>
<TabPane tabId="storage-units">
- <UnitAddContainer unitType="storage" />
- <UnitListContainer unitType="storage" />
+ <div className="py-2">
+ <UnitAddContainer unitType="storage" />
+ <UnitListContainer unitType="storage" />
+ </div>
</TabPane>
</TabContent>
</div>
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
index 75418f9d..d0218f38 100644
--- 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
@@ -1,10 +1,13 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faSave } from '@fortawesome/free-solid-svg-icons'
+import { Button } from 'reactstrap'
const AddPrefabComponent = ({ onClick }) => (
- <div className="btn btn-primary btn-block" onClick={onClick}>
- <span className="fa fa-floppy-o mr-2" />
+ <Button color="primary" block onClick={onClick}>
+ <FontAwesomeIcon icon={faSave} className="mr-2" />
Save this rack to a prefab
- </div>
+ </Button>
)
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
index c14775bf..f6a6f79b 100644
--- 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
@@ -1,10 +1,13 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'
+import { Button } from 'reactstrap'
const BackToRoomComponent = ({ onClick }) => (
- <div className="btn btn-secondary btn-block mb-2" onClick={onClick}>
- <span className="fa fa-angle-left mr-2" />
+ <Button color="secondary" block className="mb-2" onClick={onClick}>
+ <FontAwesomeIcon icon={faAngleLeft} className="mr-2" />
Back to room
- </div>
+ </Button>
)
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
deleted file mode 100644
index 23b0daac..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react'
-
-const DeleteRackComponent = ({ onClick }) => (
- <div className="btn btn-outline-danger btn-block" onClick={onClick}>
- <span className="fa fa-trash mr-2" />
- Delete this rack
- </div>
-)
-
-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
index d7e30f1d..d6fa9dc9 100644
--- 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
@@ -1,13 +1,18 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus } from '@fortawesome/free-solid-svg-icons'
+import { ListGroupItem, Badge, Button } from 'reactstrap'
const EmptySlotComponent = ({ position, onAdd }) => (
- <li className="list-group-item d-flex justify-content-between align-items-center">
- <span className="badge badge-default badge-info mr-1 disabled">{position}</span>
- <button className="btn btn-outline-primary" onClick={onAdd}>
- <span className="fa fa-plus mr-2" />
+ <ListGroupItem className="d-flex justify-content-between align-items-center">
+ <Badge color="info" className="mr-1">
+ {position}
+ </Badge>
+ <Button color="primary" outline onClick={onAdd}>
+ <FontAwesomeIcon icon={faPlus} className="mr-2" />
Add machine
- </button>
- </li>
+ </Button>
+ </ListGroupItem>
)
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
index caa3dc04..36b15c71 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
@@ -1,13 +1,16 @@
import React from 'react'
-import Shapes from '../../../../../shapes'
+import Image from 'next/image'
+import { Machine } from '../../../../../shapes'
+import { Badge, ListGroupItem } from 'reactstrap'
const UnitIcon = ({ id, type }) => (
- <div>
- <img
+ <div className="ml-1">
+ <Image
src={'/img/topology/' + id + '-icon.png'}
alt={'Machine contains ' + type + ' units'}
- className="img-fluid ml-1"
- style={{ maxHeight: '35px' }}
+ layout="intrinsic"
+ height={35}
+ width={35}
/>
</div>
)
@@ -17,27 +20,28 @@ const MachineComponent = ({ position, machine, onClick }) => {
machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length + machine.storageIds.length === 0
return (
- <li
- className="d-flex list-group-item list-group-item-action justify-content-between align-items-center"
+ <ListGroupItem
+ action
+ className="d-flex justify-content-between align-items-center"
onClick={onClick}
style={{ backgroundColor: 'white' }}
>
- <span className="badge badge-default badge-info mr-1">{position}</span>
+ <Badge color="info" className="mr-1">
+ {position}
+ </Badge>
<div className="d-inline-flex">
{machine.cpuIds.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined}
{machine.gpuIds.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined}
{machine.memoryIds.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined}
{machine.storageIds.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined}
- {hasNoUnits ? (
- <span className="badge badge-default badge-warning">Machine with no units</span>
- ) : undefined}
+ {hasNoUnits ? <Badge color="warning">Machine with no units</Badge> : undefined}
</div>
- </li>
+ </ListGroupItem>
)
}
MachineComponent.propTypes = {
- machine: Shapes.Machine,
+ machine: 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
index 12be26bd..1c07d237 100644
--- 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
@@ -1,11 +1,11 @@
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'
+import { machineList } from './MachineListComponent.module.scss'
const MachineListComponent = ({ machineIds }) => {
return (
- <ul className="list-group machine-list">
+ <ul className={`list-group ${machineList}`}>
{machineIds.map((machineId, index) => {
if (machineId === null) {
return <EmptySlotContainer key={index} position={index + 1} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.module.scss
new file mode 100644
index 00000000..f075aac9
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.module.scss
@@ -0,0 +1,3 @@
+.machineList li {
+ min-height: 64px;
+}
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
deleted file mode 100644
index 11b82c93..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass
+++ /dev/null
@@ -1,2 +0,0 @@
-.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
deleted file mode 100644
index b701909a..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import React from 'react'
-import NameComponent from '../NameComponent'
-
-const RackNameComponent = ({ rackName, onEdit }) => <NameComponent name={rackName} onEdit={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
index ca41bf57..74313bf7 100644
--- 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
@@ -3,19 +3,19 @@ import BackToRoomContainer from '../../../../../containers/app/sidebars/topology
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 { sidebarContainer, sidebarHeaderContainer, machineListContainer } from './RackSidebarComponent.module.scss'
import AddPrefabContainer from '../../../../../containers/app/sidebars/topology/rack/AddPrefabContainer'
const RackSidebarComponent = () => {
return (
- <div className="rack-sidebar-container flex-column">
- <div className="rack-sidebar-header-container">
+ <div className={`${sidebarContainer} flex-column`}>
+ <div className={sidebarHeaderContainer}>
<RackNameContainer />
<BackToRoomContainer />
<AddPrefabContainer />
<DeleteRackContainer />
</div>
- <div className="machine-list-container mt-2">
+ <div className={`${machineListContainer} mt-2`}>
<MachineListContainer />
</div>
</div>
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss
new file mode 100644
index 00000000..8ce3836a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.module.scss
@@ -0,0 +1,14 @@
+.sidebarContainer {
+ display: flex;
+ height: 100%;
+ max-height: 100%;
+}
+
+.sidebarHeaderContainer {
+ flex: 0;
+}
+
+.machineListContainer {
+ flex: 1;
+ overflow-y: scroll;
+}
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
deleted file mode 100644
index 29fec02a..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass
+++ /dev/null
@@ -1,11 +0,0 @@
-.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
index 64c0a1f6..696b345b 100644
--- 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
@@ -1,8 +1,10 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faAngleLeft } from '@fortawesome/free-solid-svg-icons'
const BackToBuildingComponent = ({ onClick }) => (
<div className="btn btn-secondary btn-block mb-2" onClick={onClick}>
- <span className="fa fa-angle-left mr-2" />
+ <FontAwesomeIcon icon={faAngleLeft} className="mr-2" />
Back to building
</div>
)
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
index 78417359..242f7a2b 100644
--- 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
@@ -1,10 +1,13 @@
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTrash } from '@fortawesome/free-solid-svg-icons'
+import { Button } from 'reactstrap'
const DeleteRoomComponent = ({ onClick }) => (
- <div className="btn btn-outline-danger btn-block" onClick={onClick}>
- <span className="fa fa-trash mr-2" />
+ <Button color="danger" outline block onClick={onClick}>
+ <FontAwesomeIcon icon={faTrash} className="mr-2" />
Delete this room
- </div>
+ </Button>
)
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
deleted file mode 100644
index 857a646f..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import classNames from 'classnames'
-import React from 'react'
-
-const EditRoomComponent = ({ onEdit, onFinish, isEditing, isInRackConstructionMode }) =>
- isEditing ? (
- <div className="btn btn-info btn-block" onClick={onFinish}>
- <span className="fa fa-check mr-2" />
- Finish editing room
- </div>
- ) : (
- <div
- className={classNames('btn btn-outline-info btn-block', {
- disabled: isInRackConstructionMode,
- })}
- onClick={() => (isInRackConstructionMode ? undefined : onEdit())}
- >
- <span className="fa fa-pencil mr-2" />
- Edit the tiles of this room
- </div>
- )
-
-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
index 44566f61..19d6b309 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js
@@ -1,26 +1,29 @@
-import classNames from 'classnames'
import React from 'react'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTimes, faPlus } from '@fortawesome/free-solid-svg-icons'
+import { Button } from 'reactstrap'
const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => {
if (inRackConstructionMode) {
return (
- <div className="btn btn-primary btn-block" onClick={onStop}>
- <span className="fa fa-times mr-2" />
+ <Button color="primary" block onClick={onStop}>
+ <FontAwesomeIcon icon={faTimes} className="mr-2" />
Stop rack construction
- </div>
+ </Button>
)
}
return (
- <div
- className={classNames('btn btn-outline-primary btn-block', {
- disabled: isEditingRoom,
- })}
+ <Button
+ color="primary"
+ outline
+ block
+ disabled={isEditingRoom}
onClick={() => (isEditingRoom ? undefined : onStart())}
>
- <span className="fa fa-plus mr-2" />
+ <FontAwesomeIcon icon={faPlus} className="mr-2" />
Start rack construction
- </div>
+ </Button>
)
}
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
deleted file mode 100644
index d637828e..00000000
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import React from 'react'
-import NameComponent from '../NameComponent'
-
-const RoomNameComponent = ({ roomName, onEdit }) => <NameComponent name={roomName} onEdit={onEdit} />
-
-export default RoomNameComponent
diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js b/opendc-web/opendc-web-ui/src/components/home/ContactSection.js
index d25a1bc4..60a7e6a3 100644
--- a/opendc-web/opendc-web-ui/src/components/home/ContactSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/ContactSection.js
@@ -1,23 +1,25 @@
import React from 'react'
-import FontAwesome from 'react-fontawesome'
+import Image from 'next/image'
import { Row, Col } from 'reactstrap'
import ContentSection from './ContentSection'
-
-import './ContactSection.sass'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faExclamationTriangle, faEnvelope } from '@fortawesome/free-solid-svg-icons'
+import { faGithub } from '@fortawesome/free-brands-svg-icons'
+import { contactSection, tudelftIcon } from './ContactSection.module.scss'
const ContactSection = () => (
- <ContentSection name="contact" title="Contact" className="contact-section">
+ <ContentSection name="contact" title="Contact" className={contactSection}>
<Row className="justify-content-center">
<Col md="4">
<a href="https://github.com/atlarge-research/opendc">
- <FontAwesome name="github" size="3x" className="mb-2" />
+ <FontAwesomeIcon icon={faGithub} size="3x" className="mb-2" />
<div className="w-100" />
atlarge-research/opendc
</a>
</Col>
<Col md="4">
<a href="mailto:opendc@atlarge-research.com">
- <FontAwesome name="envelope" size="3x" className="mb-2" />
+ <FontAwesomeIcon icon={faEnvelope} size="3x" className="mb-2" />
<div className="w-100" />
opendc@atlarge-research.com
</a>
@@ -25,7 +27,14 @@ const ContactSection = () => (
</Row>
<Row>
<Col className="text-center">
- <img src="img/tudelft-icon.png" className="img-fluid tudelft-icon" height="100" alt="TU Delft" />
+ <Image
+ src="/img/tudelft-icon.png"
+ className={tudelftIcon}
+ layout="intrinsic"
+ width={162}
+ height={100}
+ alt="TU Delft"
+ />
</Col>
</Row>
<Row>
@@ -39,7 +48,7 @@ const ContactSection = () => (
</Row>
<Row>
<Col className="text-center disclaimer mt-5 small">
- <FontAwesome name="exclamation-triangle" size="2x" className="mr-2" />
+ <FontAwesomeIcon icon={faExclamationTriangle} size="2x" className="mr-2" />
<br />
<strong>Disclaimer: </strong>
OpenDC is an experimental tool. Your data may get lost, overwritten, or otherwise become unavailable.
diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.module.scss b/opendc-web/opendc-web-ui/src/components/home/ContactSection.module.scss
new file mode 100644
index 00000000..9ab4fcb1
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/home/ContactSection.module.scss
@@ -0,0 +1,20 @@
+.contactSection {
+ background-color: #444;
+ color: #ddd;
+
+ a {
+ color: #ddd;
+
+ &:hover {
+ color: #fff;
+ }
+ }
+
+ .tudelftIcon {
+ height: 100px;
+ }
+
+ .disclaimer {
+ color: #cccccc;
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass
deleted file mode 100644
index 997f8d98..00000000
--- a/opendc-web/opendc-web-ui/src/components/home/ContactSection.sass
+++ /dev/null
@@ -1,15 +0,0 @@
-.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
index 3a8960d9..3e9ad50a 100644
--- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/ContentSection.js
@@ -2,10 +2,10 @@ import React from 'react'
import classNames from 'classnames'
import { Container } from 'reactstrap'
import PropTypes from 'prop-types'
-import './ContentSection.sass'
+import { contentSection } from './ContentSection.module.scss'
const ContentSection = ({ name, title, children, className }) => (
- <section id={name} className={classNames(className, name + '-section', 'content-section')}>
+ <section id={name} className={classNames(className, contentSection)}>
<Container>
<h1>{title}</h1>
{children}
diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.module.scss b/opendc-web/opendc-web-ui/src/components/home/ContentSection.module.scss
new file mode 100644
index 00000000..d27a0ce0
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/home/ContentSection.module.scss
@@ -0,0 +1,11 @@
+@import 'src/style/_variables.scss';
+
+.contentSection {
+ padding-top: 50px;
+ padding-bottom: 50px;
+ text-align: center;
+
+ h1 {
+ margin-bottom: 30px;
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass b/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass
deleted file mode 100644
index a4c8bd66..00000000
--- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.sass
+++ /dev/null
@@ -1,9 +0,0 @@
-@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
index bc6ee83b..67e8cd8b 100644
--- a/opendc-web/opendc-web-ui/src/components/home/IntroSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/IntroSection.js
@@ -1,8 +1,9 @@
import React from 'react'
+import Image from 'next/image'
import { Container, Row, Col } from 'reactstrap'
-const IntroSection = () => (
- <section id="intro" className="intro-section">
+const IntroSection = ({ className }) => (
+ <section id="intro" className={className}>
<Container className="pt-5 pb-3">
<Row className="justify-content-center">
<Col xl="4" lg="4" md="4" sm="8">
@@ -14,9 +15,12 @@ const IntroSection = () => (
</ul>
</Col>
<Col xl="4" lg="4" md="4" sm="8">
- <img
- src="img/datacenter-drawing.png"
- className="col-12 img-fluid"
+ <Image
+ src="/img/datacenter-drawing.png"
+ className="col-12"
+ layout="intrinsic"
+ width={350}
+ height={197}
alt="Schematic top-down view of a datacenter"
/>
<p className="col-12 figure-caption text-center">
diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js
index 6a9ea00c..98aa0af2 100644
--- a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js
+++ b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.js
@@ -1,16 +1,21 @@
import React from 'react'
+import Image from 'next/image'
import { Container, Jumbotron, Button } from 'reactstrap'
-import './JumbotronHeader.sass'
+import { jumbotronHeader, jumbotron, dc } from './JumbotronHeader.module.scss'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'
const JumbotronHeader = () => (
- <section className="jumbotron-header">
+ <section className={jumbotronHeader}>
<Container>
- <Jumbotron className="text-center">
+ <Jumbotron className={jumbotron}>
<h1>
- Open<span className="dc">DC</span>
+ Open<span className={dc}>DC</span>
</h1>
<p className="lead">Collaborative Datacenter Simulation and Exploration for Everybody</p>
- <img src="img/logo.png" className="img-responsive mt-3" alt="OpenDC" />
+ <div className="mt-5">
+ <Image src="/img/logo.png" layout="intrinsic" height={110} width={110} alt="OpenDC" />
+ </div>
<p className="lead mt-5">
<Button
tag="a"
@@ -18,7 +23,7 @@ const JumbotronHeader = () => (
href="https://atlarge-research.com/pdfs/ccgrid21-opendc-paper.pdf"
color="warning"
>
- Read about <strong>OpenDC 2.0</strong> <i className="fa fa-external-link" />
+ Read about <strong>OpenDC 2.0</strong> <FontAwesomeIcon icon={faExternalLinkAlt} />
</Button>
</p>
</Jumbotron>
diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.module.scss b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.module.scss
new file mode 100644
index 00000000..567b3e73
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.module.scss
@@ -0,0 +1,31 @@
+.jumbotronHeader {
+ background: #00a6d6;
+}
+
+.jumbotron {
+ background-color: inherit;
+ margin-bottom: 0;
+ text-align: center;
+
+ padding-top: 120px;
+ padding-bottom: 120px;
+
+ img {
+ max-width: 110px;
+ }
+
+ h1 {
+ color: #fff;
+ font-size: 4.5em;
+
+ .dc {
+ color: #fff;
+ font-weight: bold;
+ }
+ }
+
+ :global(.lead) {
+ color: #fff;
+ font-size: 1.4em;
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass b/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass
deleted file mode 100644
index 1b6a89fd..00000000
--- a/opendc-web/opendc-web-ui/src/components/home/JumbotronHeader.sass
+++ /dev/null
@@ -1,24 +0,0 @@
-.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
index 643dca65..af36aa45 100644
--- a/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/ModelingSection.js
@@ -1,13 +1,14 @@
import React from 'react'
import ScreenshotSection from './ScreenshotSection'
-const ModelingSection = () => (
+const ModelingSection = ({ className }) => (
<ScreenshotSection
name="modeling"
title="Datacenter Modeling"
imageUrl="/img/screenshot-construction.png"
caption="Building a datacenter in OpenDC"
imageIsRight={true}
+ className={className}
>
<h3>Collaboratively...</h3>
<ul>
diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js
index 33aab17f..9673b7b7 100644
--- a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.js
@@ -1,16 +1,24 @@
import React from 'react'
+import Image from 'next/image'
import { Row, Col } from 'reactstrap'
import ContentSection from './ContentSection'
-import './ScreenshotSection.sass'
+import { screenshot } from './ScreenshotSection.module.scss'
-const ScreenshotSection = ({ name, title, imageUrl, caption, imageIsRight, children }) => (
- <ContentSection name={name} title={title}>
+const ScreenshotSection = ({ className, name, title, imageUrl, caption, imageIsRight, children }) => (
+ <ContentSection name={name} title={title} className={className}>
<Row>
- <Col xl="5" lg="5" md="5" sm="12" className={`text-left ${!imageIsRight ? 'order-1' : ''}`}>
+ <Col xl="5" lg="5" md="5" sm="12" className={`text-left my-auto ${!imageIsRight ? 'order-1' : ''}`}>
{children}
</Col>
<Col xl="7" lg="7" md="7" sm="12">
- <img src={imageUrl} className="col-12 screenshot" alt={caption} />
+ <Image
+ src={imageUrl}
+ className={`col-12 ${screenshot}`}
+ layout="intrinsic"
+ width={635}
+ height={419}
+ alt={caption}
+ />
<div className="row text-muted justify-content-center">{caption}</div>
</Col>
</Row>
diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.module.scss b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.module.scss
new file mode 100644
index 00000000..7e22de32
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.module.scss
@@ -0,0 +1,5 @@
+.screenshot {
+ padding-left: 0;
+ padding-right: 0;
+ margin-bottom: 5px;
+}
diff --git a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass b/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass
deleted file mode 100644
index 6b1a6ec4..00000000
--- a/opendc-web/opendc-web-ui/src/components/home/ScreenshotSection.sass
+++ /dev/null
@@ -1,4 +0,0 @@
-.screenshot
- 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
index 8e98717a..c154cc26 100644
--- a/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/SimulationSection.js
@@ -1,10 +1,11 @@
import React from 'react'
+import Image from 'next/image'
import { Col, Row } from 'reactstrap'
import ContentSection from './ContentSection'
-const SimulationSection = () => {
+const SimulationSection = ({ className }) => {
return (
- <ContentSection name="project" title="Datecenter Simulation">
+ <ContentSection name="project" title="Datecenter Simulation" className={className}>
<Row>
<Col xl="5" lg="5" md="5" sm="2" className="text-left my-auto order-1">
<h3>Working with OpenDC:</h3>
@@ -18,9 +19,12 @@ const SimulationSection = () => {
</ul>
</Col>
<Col xl="7" lg="7" md="7" sm="12">
- <img
+ <Image
src="/img/screenshot-simulation.png"
- className="col-12 screenshot"
+ className="col-12"
+ layout="intrinsic"
+ width={635}
+ height={419}
alt="Running an experiment in OpenDC"
/>
<Row className="text-muted justify-content-center">Running an experiment in OpenDC</Row>
@@ -39,7 +43,14 @@ const SimulationSection = () => {
</Col>
<Col xl="7" lg="7" md="7" sm="12">
- <img src="/img/opendc-architecture.png" className="col-12 screenshot" alt="OpenDC's Architecture" />
+ <Image
+ src="/img/opendc-architecture.png"
+ className="col-12"
+ layout="intrinsic"
+ width={635}
+ height={232}
+ alt="OpenDC's Architecture"
+ />
<Row className="text-muted justify-content-center">OpenDC's Architecture</Row>
</Col>
</Row>
diff --git a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js b/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js
index 1624b4d2..9a4892ed 100644
--- a/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/StakeholderSection.js
@@ -21,8 +21,8 @@ const Stakeholder = ({ name, title, subtitle }) => (
</Col>
)
-const StakeholderSection = () => (
- <ContentSection name="stakeholders" title="Stakeholders">
+const StakeholderSection = ({ className }) => (
+ <ContentSection name="stakeholders" title="Stakeholders" className={className}>
<Row className="justify-content-center">
<Stakeholder name="Manager" title="Managers" subtitle="Seeing is deciding" />
<Stakeholder name="Sales" title="Sales" subtitle="Demo concepts" />
diff --git a/opendc-web/opendc-web-ui/src/components/home/TeamSection.js b/opendc-web/opendc-web-ui/src/components/home/TeamSection.js
index 1ee1cbf5..bbbe241e 100644
--- a/opendc-web/opendc-web-ui/src/components/home/TeamSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/TeamSection.js
@@ -1,48 +1,49 @@
import React from 'react'
+import Image from 'next/image'
import { Row, Col } from 'reactstrap'
import ContentSection from './ContentSection'
const TeamLead = ({ photoId, name, description }) => (
<Col xl="3" lg="3" md="4" sm="6" className="justify-content-center">
- <Col
- tag="img"
- src={'img/portraits/' + photoId + '.png'}
- xl="10"
- lg="10"
- md="10"
- sm="8"
- col="5"
- className="mb-2 mt-2"
- alt={name}
- />
- <Col>
- <h4>{name}</h4>
- <div className="team-member-description">{description}</div>
- </Col>
+ <Row>
+ <Col xl="10" lg="10" md="10" sm="8" col="5" className="my-2 mx-auto" alt={name}>
+ <Image
+ src={'/img/portraits/' + photoId + '.png'}
+ layout="intrinsic"
+ width={182}
+ height={182}
+ alt={name}
+ />
+ </Col>
+ <Col>
+ <h4>{name}</h4>
+ <div className="team-member-description">{description}</div>
+ </Col>
+ </Row>
</Col>
)
const TeamMember = ({ photoId, name }) => (
<Col xl="2" lg="2" md="3" sm="4" className="justify-content-center">
- <Col
- tag="img"
- src={'img/portraits/' + photoId + '.png'}
- xl="10"
- lg="10"
- md="10"
- sm="8"
- col="5"
- className="mb-2 mt-2"
- alt={name}
- />
- <Col>
- <h5>{name}</h5>
- </Col>
+ <Row>
+ <Col xl="10" lg="10" md="10" sm="8" xs="5" className="my-2 mx-auto">
+ <Image
+ src={'/img/portraits/' + photoId + '.png'}
+ layout="intrinsic"
+ width={100}
+ height={100}
+ alt={name}
+ />
+ </Col>
+ <Col>
+ <h5>{name}</h5>
+ </Col>
+ </Row>
</Col>
)
-const TeamSection = () => (
- <ContentSection name="team" title="OpenDC Team">
+const TeamSection = ({ className }) => (
+ <ContentSection name="team" title="OpenDC Team" className={className}>
<Row className="justify-content-center">
<TeamLead photoId="aiosup" name="Prof. dr. ir. Alexandru Iosup" description="Project Lead" />
<TeamLead photoId="fmastenbroek" name="Fabian Mastenbroek" description="Technology Lead" />
diff --git a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js b/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js
index efd77edf..efedebb7 100644
--- a/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js
+++ b/opendc-web/opendc-web-ui/src/components/home/TechnologiesSection.js
@@ -1,35 +1,36 @@
import React from 'react'
-import FontAwesome from 'react-fontawesome'
import { ListGroup, ListGroupItem } from 'reactstrap'
import ContentSection from './ContentSection'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faWindowMaximize, faTv, faDatabase, faCogs } from '@fortawesome/free-solid-svg-icons'
-const TechnologiesSection = () => (
- <ContentSection name="technologies" title="Technologies">
+const TechnologiesSection = ({ className }) => (
+ <ContentSection name="technologies" title="Technologies" className={className}>
<ListGroup className="list-group text-left">
<ListGroupItem color="primary" className="d-flex justify-content-between align-items-center">
<span style={{ minWidth: 100 }}>
- <FontAwesome name="window-maximize" className="mr-2" />
+ <FontAwesomeIcon icon={faWindowMaximize} className="mr-2" />
<strong className="">Browser</strong>
</span>
<span className="text-right">JavaScript, React, Redux, Konva</span>
</ListGroupItem>
<ListGroupItem color="warning" className="d-flex justify-content-between align-items-center">
<span style={{ minWidth: 100 }}>
- <FontAwesome name="television" className="mr-2" />
+ <FontAwesomeIcon icon={faTv} className="mr-2" />
<strong>Server</strong>
</span>
<span className="text-right">Python, Flask, FlaskSocketIO, OpenAPI</span>
</ListGroupItem>
<ListGroupItem color="success" className="d-flex justify-content-between align-items-center">
<span style={{ minWidth: 100 }}>
- <FontAwesome name="database" className="mr-2" />
+ <FontAwesomeIcon icon={faDatabase} className="mr-2" />
<strong>Database</strong>
</span>
<span className="text-right">MongoDB</span>
</ListGroupItem>
<ListGroupItem color="danger" className="d-flex justify-content-between align-items-center">
<span style={{ minWidth: 100 }}>
- <FontAwesome name="cogs" className="mr-2" />
+ <FontAwesomeIcon icon={faCogs} className="mr-2" />
<strong>Simulator</strong>
</span>
<span className="text-right">Kotlin</span>
diff --git a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js
index 589047dc..5a95810a 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js
+++ b/opendc-web/opendc-web-ui/src/components/modals/ConfirmationModal.js
@@ -2,36 +2,26 @@ 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)
- }
+function ConfirmationModal({ title, message, show, callback }) {
+ return (
+ <Modal
+ title={title}
+ show={show}
+ onSubmit={() => callback(true)}
+ onCancel={() => callback(false)}
+ submitButtonType="danger"
+ submitButtonText="Confirm"
+ >
+ {message}
+ </Modal>
+ )
+}
- render() {
- return (
- <Modal
- title={this.props.title}
- show={this.props.show}
- onSubmit={this.onConfirm.bind(this)}
- onCancel={this.onCancel.bind(this)}
- submitButtonType="danger"
- submitButtonText="Confirm"
- >
- {this.props.message}
- </Modal>
- )
- }
+ConfirmationModal.propTypes = {
+ title: PropTypes.string.isRequired,
+ message: PropTypes.string.isRequired,
+ show: PropTypes.bool.isRequired,
+ callback: PropTypes.func.isRequired,
}
export default ConfirmationModal
diff --git a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js
index d0918c7e..6758fdc0 100644
--- a/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js
+++ b/opendc-web/opendc-web-ui/src/components/modals/TextInputModal.js
@@ -1,54 +1,41 @@
import PropTypes from 'prop-types'
-import React from 'react'
+import React, { useRef } 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,
+function TextInputModal({ title, label, show, callback, initialValue }) {
+ const textInput = useRef(null)
+ const onSubmit = () => {
+ callback(textInput.current.value)
+ textInput.current.value = ''
}
-
- 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 = ''
+ const onCancel = () => {
+ callback(undefined)
+ textInput.current.value = ''
}
- render() {
- return (
- <Modal
- title={this.props.title}
- show={this.props.show}
- onSubmit={this.onSubmit.bind(this)}
- onCancel={this.onCancel.bind(this)}
+ return (
+ <Modal title={title} show={show} onSubmit={onSubmit} onCancel={onCancel}>
+ <form
+ onSubmit={(e) => {
+ e.preventDefault()
+ onSubmit()
+ }}
>
- <form
- onSubmit={(e) => {
- e.preventDefault()
- this.onSubmit()
- }}
- >
- <div className="form-group">
- <label className="form-control-label">{this.props.label}</label>
- <input type="text" className="form-control" ref={(textInput) => (this.textInput = textInput)} />
- </div>
- </form>
- </Modal>
- )
- }
+ <div className="form-group">
+ <label className="form-control-label">{label}</label>
+ <input type="text" className="form-control" ref={textInput} defaultValue={initialValue} />
+ </div>
+ </form>
+ </Modal>
+ )
+}
+
+TextInputModal.propTypes = {
+ title: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ show: PropTypes.bool.isRequired,
+ callback: PropTypes.func.isRequired,
+ initialValue: PropTypes.string,
}
export default TextInputModal
diff --git a/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js b/opendc-web/opendc-web-ui/src/components/modals/custom-components/NewScenarioModalComponent.js
index 01a5719c..782812ac 100644
--- 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
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'
import React, { useRef } from 'react'
import { Form, FormGroup, Input, Label } from 'reactstrap'
-import Shapes from '../../../shapes'
+import { Scheduler, Topology, Trace } from '../../../shapes'
import Modal from '../Modal'
const NewScenarioModalComponent = ({
@@ -135,9 +135,9 @@ 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),
+ traces: PropTypes.arrayOf(Trace),
+ topologies: PropTypes.arrayOf(Topology),
+ schedulers: PropTypes.arrayOf(Scheduler),
callback: PropTypes.func.isRequired,
}
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
index 9fee8831..f06fe797 100644
--- 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
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'
import { Form, FormGroup, Input, Label } from 'reactstrap'
import React, { useRef } from 'react'
-import Shapes from '../../../shapes'
+import { Topology } from '../../../shapes'
import Modal from '../Modal'
const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology, onCancel, topologies }) => {
@@ -62,7 +62,7 @@ const NewTopologyModalComponent = ({ show, onCreateTopology, onDuplicateTopology
NewTopologyModalComponent.propTypes = {
show: PropTypes.bool.isRequired,
- topologies: PropTypes.arrayOf(Shapes.Topology),
+ topologies: PropTypes.arrayOf(Topology),
onCreateTopology: PropTypes.func.isRequired,
onDuplicateTopology: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
diff --git a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js
index c5de3d0b..28207968 100644
--- a/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/navigation/AppNavbarComponent.js
@@ -1,23 +1,28 @@
import React from 'react'
-import FontAwesome from 'react-fontawesome'
-import { Link } from 'react-router-dom'
+import Link from 'next/link'
import { NavLink } from 'reactstrap'
import Navbar, { NavItem } from './Navbar'
-import './Navbar.sass'
+import {} from './Navbar.module.scss'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faList } from '@fortawesome/free-solid-svg-icons'
const AppNavbarComponent = ({ project, fullWidth }) => (
<Navbar fullWidth={fullWidth}>
<NavItem route="/projects">
- <NavLink tag={Link} title="My Projects" to="/projects">
- <FontAwesome name="list" className="mr-2" />
- My Projects
- </NavLink>
+ <Link href="/projects">
+ <NavLink title="My Projects">
+ <FontAwesomeIcon icon={faList} className="mr-2" />
+ My Projects
+ </NavLink>
+ </Link>
</NavItem>
{project ? (
<NavItem>
- <NavLink tag={Link} title="Current Project" to={`/projects/${project._id}`}>
- <span>{project.name}</span>
- </NavLink>
+ <Link href={`/projects/${project._id}`}>
+ <NavLink title="Current Project">
+ <span>{project.name}</span>
+ </NavLink>
+ </Link>
</NavItem>
) : undefined}
</Navbar>
diff --git a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js b/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js
index 08d222ea..46d01a25 100644
--- a/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js
+++ b/opendc-web/opendc-web-ui/src/components/navigation/HomeNavbar.js
@@ -1,7 +1,7 @@
import React from 'react'
import { NavItem, NavLink } from 'reactstrap'
import Navbar from './Navbar'
-import './Navbar.sass'
+import {} from './Navbar.module.scss'
const ScrollNavItem = ({ id, name }) => (
<NavItem>
diff --git a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js
index 78b02b44..4ab577e0 100644
--- a/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js
+++ b/opendc-web/opendc-web-ui/src/components/navigation/LogoutButton.js
@@ -1,12 +1,12 @@
import PropTypes from 'prop-types'
import React from 'react'
-import FontAwesome from 'react-fontawesome'
-import { Link } from 'react-router-dom'
import { NavLink } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
const LogoutButton = ({ onLogout }) => (
- <NavLink tag={Link} className="logout" title="Sign out" to="#" onClick={onLogout}>
- <FontAwesome name="power-off" size="lg" />
+ <NavLink className="logout" title="Sign out" onClick={onLogout}>
+ <FontAwesomeIcon icon={faSignOutAlt} size="lg" />
</NavLink>
)
diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js
index 55f98900..690a7bdf 100644
--- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js
+++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js
@@ -1,5 +1,7 @@
import React, { useState } from 'react'
-import { Link, useLocation } from 'react-router-dom'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import Image from 'next/image'
import {
Navbar as RNavbar,
NavItem as RNavItem,
@@ -10,11 +12,13 @@ import {
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'
+import { login, navbar, opendcBrand } from './Navbar.module.scss'
+import { useAuth } from '../../auth'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faGithub } from '@fortawesome/free-brands-svg-icons'
export const NAVBAR_HEIGHT = 60
@@ -24,30 +28,41 @@ const GitHubLink = () => (
className="ml-2 mr-3 text-dark"
style={{ position: 'relative', top: 7 }}
>
- <span className="fa fa-github fa-2x" />
+ <FontAwesomeIcon icon={faGithub} size="2x" />
</a>
)
export const NavItem = ({ route, children }) => {
- const location = useLocation()
- return <RNavItem active={location.pathname === route}>{children}</RNavItem>
+ const router = useRouter()
+ const handleClick = (e) => {
+ e.preventDefault()
+ router.push(route)
+ }
+ return (
+ <RNavItem onClick={handleClick} active={router.asPath === route}>
+ {children}
+ </RNavItem>
+ )
}
export const LoggedInSection = () => {
- const location = useLocation()
+ const router = useRouter()
+ const { isAuthenticated } = useAuth()
return (
<Nav navbar className="auth-links">
- {userIsLoggedIn() ? (
+ {isAuthenticated ? (
[
- location.pathname === '/' ? (
+ router.asPath === '/' ? (
<NavItem route="/projects" key="projects">
- <NavLink tag={Link} title="My Projects" to="/projects">
- My Projects
- </NavLink>
+ <Link href="/projects">
+ <NavLink title="My Projects" to="/projects">
+ My Projects
+ </NavLink>
+ </Link>
</NavItem>
) : (
- <NavItem route="/profile" key="profile">
- <NavLink tag={Link} title="My Profile" to="/profile">
+ <NavItem key="profile">
+ <NavLink title="My Profile">
<ProfileName />
</NavLink>
</NavItem>
@@ -57,10 +72,10 @@ export const LoggedInSection = () => {
</NavItem>,
]
) : (
- <NavItem route="login">
+ <RNavItem>
<GitHubLink />
- <Login visible={true} />
- </NavItem>
+ <Login visible={true} className={login} />
+ </RNavItem>
)}
</Nav>
)
@@ -71,11 +86,13 @@ const Navbar = ({ fullWidth, children }) => {
const toggle = () => setIsOpen(!isOpen)
return (
- <RNavbar fixed="top" color="light" light expand="lg" id="navbar">
+ <RNavbar fixed="top" color="light" light expand="lg" id="navbar" className={navbar}>
<Container fluid={fullWidth}>
<NavbarToggler onClick={toggle} />
- <NavbarBrand tag={Link} to="/" title="OpenDC" className="opendc-brand">
- <img src="/img/logo.png" alt="OpenDC" />
+ <NavbarBrand href="/" title="OpenDC" className={opendcBrand}>
+ <div className="mb-n1">
+ <Image src="/img/logo.png" layout="fixed" width={30} height={30} alt="OpenDC" />
+ </div>
</NavbarBrand>
<Collapse isOpen={isOpen} navbar>
diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.module.scss b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.module.scss
new file mode 100644
index 00000000..8b9e4c97
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.module.scss
@@ -0,0 +1,36 @@
+@import 'src/style/_mixins.scss';
+@import 'src/style/_variables.scss';
+
+.navbar {
+ border-top: $blue 3px solid;
+ border-bottom: $gray-semi-dark 1px solid;
+ color: $gray-very-dark;
+ background: #fafafb;
+}
+
+.opendcBrand {
+ 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;
+
+ @include clickable;
+
+ &:hover {
+ background: $blue-dark;
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass
deleted file mode 100644
index c9d2aad2..00000000
--- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.sass
+++ /dev/null
@@ -1,30 +0,0 @@
-@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
index dbdba212..03a4894b 100644
--- a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js
+++ b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.js
@@ -1,6 +1,6 @@
import React from 'react'
-import './BlinkingCursor.sass'
+import { blinkingCursor } from './BlinkingCursor.module.scss'
-const BlinkingCursor = () => <span className="blinking-cursor">_</span>
+const BlinkingCursor = () => <span className={blinkingCursor}>_</span>
export default BlinkingCursor
diff --git a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.module.scss b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.module.scss
new file mode 100644
index 00000000..aba0c604
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.module.scss
@@ -0,0 +1,13 @@
+.blinkingCursor {
+ animation: blink 1s step-end infinite;
+}
+
+@keyframes blink {
+ from,
+ to {
+ color: #eeeeee;
+ }
+ 50% {
+ color: #333333;
+ }
+}
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
deleted file mode 100644
index ad91df85..00000000
--- a/opendc-web/opendc-web-ui/src/components/not-found/BlinkingCursor.sass
+++ /dev/null
@@ -1,35 +0,0 @@
-.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
index bcc522c9..6ded4350 100644
--- a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js
+++ b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.js
@@ -1,5 +1,5 @@
import React from 'react'
-import './CodeBlock.sass'
+import { codeBlock } from './CodeBlock.module.scss'
const CodeBlock = () => {
const textBlock =
@@ -22,7 +22,7 @@ const CodeBlock = () => {
}
}
- return <div className="code-block" dangerouslySetInnerHTML={{ __html: textBlock }} />
+ return <div className={codeBlock} dangerouslySetInnerHTML={{ __html: textBlock }} />
}
export default CodeBlock
diff --git a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.module.scss b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.module.scss
new file mode 100644
index 00000000..8af3ee6d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.module.scss
@@ -0,0 +1,4 @@
+.codeBlock {
+ white-space: pre-wrap;
+ margin-top: 60px;
+}
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
deleted file mode 100644
index e452f917..00000000
--- a/opendc-web/opendc-web-ui/src/components/not-found/CodeBlock.sass
+++ /dev/null
@@ -1,3 +0,0 @@
-.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
index a25e558a..e6200b10 100644
--- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js
+++ b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.js
@@ -1,14 +1,16 @@
import React from 'react'
-import { Link } from 'react-router-dom'
+import Link from 'next/link'
import BlinkingCursor from './BlinkingCursor'
import CodeBlock from './CodeBlock'
-import './TerminalWindow.sass'
+import { terminalWindow, terminalHeader, terminalBody, segfault, subTitle, homeBtn } from './TerminalWindow.module.scss'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faHome } from '@fortawesome/free-solid-svg-icons'
const TerminalWindow = () => (
- <div className="terminal-window">
- <div className="terminal-header">Terminal -- bash</div>
- <div className="terminal-body">
- <div className="segfault">
+ <div className={terminalWindow}>
+ <div className={terminalHeader}>Terminal -- bash</div>
+ <div className={terminalBody}>
+ <div className={segfault}>
$ status
<br />
opendc[4264]: segfault at 0000051497be459d1 err 12 in libopendc.9.0.4
@@ -19,12 +21,14 @@ const TerminalWindow = () => (
<br />
</div>
<CodeBlock />
- <div className="sub-title">
+ <div className={subTitle}>
Got lost?
<BlinkingCursor />
</div>
- <Link to="/" className="home-btn">
- <span className="fa fa-home" /> GET ME BACK TO OPENDC
+ <Link href="/">
+ <a className={homeBtn}>
+ <FontAwesomeIcon icon={faHome} /> GET ME BACK TO OPENDC
+ </a>
</Link>
</div>
</div>
diff --git a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.module.scss b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.module.scss
new file mode 100644
index 00000000..614852d3
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.module.scss
@@ -0,0 +1,61 @@
+.terminalWindow {
+ display: block;
+ align-self: center;
+
+ margin: auto;
+
+ user-select: none;
+ cursor: default;
+
+ overflow: hidden;
+
+ box-shadow: 5px 5px 20px #444444;
+}
+
+.terminalHeader {
+ 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;
+}
+
+.terminalBody {
+ font-family: monospace;
+ text-align: center;
+ background-color: #333333;
+ color: #eeeeee;
+ padding: 10px;
+
+ height: 100%;
+}
+
+.segfault {
+ text-align: left;
+}
+
+.subTitle {
+ margin-top: 20px;
+}
+
+.homeBtn {
+ margin-top: 10px;
+ padding: 5px;
+ display: inline-block;
+ border: 1px solid #eeeeee;
+ color: #eeeeee;
+ text-decoration: none;
+ cursor: pointer;
+
+ transition: all 200ms;
+
+ &:hover,
+ &:active {
+ background: #eeeeee;
+ color: #333333;
+ }
+}
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
deleted file mode 100644
index 7f05335a..00000000
--- a/opendc-web/opendc-web-ui/src/components/not-found/TerminalWindow.sass
+++ /dev/null
@@ -1,70 +0,0 @@
-.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
deleted file mode 100644
index 664f9b46..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/FilterButton.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import classNames from 'classnames'
-import PropTypes from 'prop-types'
-import React from 'react'
-
-const FilterButton = ({ active, children, onClick }) => (
- <button
- className={classNames('btn btn-secondary', { active: active })}
- onClick={() => {
- if (!active) {
- onClick()
- }
- }}
- >
- {children}
- </button>
-)
-
-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
index 2b9795d0..5129c013 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js
@@ -1,13 +1,28 @@
import React from 'react'
-import FilterLink from '../../containers/projects/FilterLink'
-import './FilterPanel.sass'
-
-const FilterPanel = () => (
- <div className="btn-group filter-panel mb-2">
- <FilterLink filter="SHOW_ALL">All Projects</FilterLink>
- <FilterLink filter="SHOW_OWN">My Projects</FilterLink>
- <FilterLink filter="SHOW_SHARED">Shared with me</FilterLink>
- </div>
+import PropTypes from 'prop-types'
+import { Button, ButtonGroup } from 'reactstrap'
+import { filterPanel } from './FilterPanel.module.scss'
+
+export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' }
+
+const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => (
+ <ButtonGroup className={`${filterPanel} mb-2`}>
+ {Object.keys(FILTERS).map((filter) => (
+ <Button
+ color="secondary"
+ key={filter}
+ onClick={() => activeFilter === filter || onSelect(filter)}
+ active={activeFilter === filter}
+ >
+ {FILTERS[filter]}
+ </Button>
+ ))}
+ </ButtonGroup>
)
+FilterPanel.propTypes = {
+ onSelect: PropTypes.func.isRequired,
+ activeFilter: PropTypes.string,
+}
+
export default FilterPanel
diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss
new file mode 100644
index 00000000..79cdf81a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.scss
@@ -0,0 +1,7 @@
+.filterPanel {
+ display: flex;
+
+ button {
+ flex: 1 !important;
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass
deleted file mode 100644
index f71cf6c8..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.sass
+++ /dev/null
@@ -1,5 +0,0 @@
-.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
deleted file mode 100644
index 312671c6..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/NewProjectButtonComponent.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import PropTypes from 'prop-types'
-import React from 'react'
-
-const NewProjectButtonComponent = ({ onClick }) => (
- <div className="bottom-btn-container">
- <div className="btn btn-primary float-right" onClick={onClick}>
- <span className="fa fa-plus mr-2" />
- New Project
- </div>
- </div>
-)
-
-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
index 1c76cc7f..0725e42b 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectActionButtons.js
@@ -1,22 +1,31 @@
import PropTypes from 'prop-types'
import React from 'react'
-import { Link } from 'react-router-dom'
+import Link from 'next/link'
+import { Button } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlay, faUsers, faTrash } from '@fortawesome/free-solid-svg-icons'
const ProjectActionButtons = ({ projectId, onViewUsers, onDelete }) => (
<td className="text-right">
- <Link to={'/projects/' + projectId} className="btn btn-outline-primary btn-sm mr-2" title="Open this project">
- <span className="fa fa-play" />
+ <Link href={`/projects/${projectId}`}>
+ <Button color="primary" outline size="sm" className="mr-2" title="Open this project">
+ <FontAwesomeIcon icon={faPlay} />
+ </Button>
</Link>
- <div
- className="btn btn-outline-success btn-sm disabled mr-2"
+ <Button
+ color="success"
+ outline
+ size="sm"
+ disabled
+ className="mr-2"
title="View and edit collaborators (not supported currently)"
onClick={() => onViewUsers(projectId)}
>
- <span className="fa fa-users" />
- </div>
- <div className="btn btn-outline-danger btn-sm" title="Delete this project" onClick={() => onDelete(projectId)}>
- <span className="fa fa-trash" />
- </div>
+ <FontAwesomeIcon icon={faUsers} />
+ </Button>
+ <Button color="danger" outline size="sm" title="Delete this project" onClick={() => onDelete(projectId)}>
+ <FontAwesomeIcon icon={faTrash} />
+ </Button>
</td>
)
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js
deleted file mode 100644
index 3f904061..00000000
--- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthRow.js
+++ /dev/null
@@ -1,24 +0,0 @@
-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 }) => (
- <tr>
- <td className="pt-3">{projectAuth.project.name}</td>
- <td className="pt-3">{parseAndFormatDateTime(projectAuth.project.datetimeLastEdited)}</td>
- <td className="pt-3">
- <span className={classNames('fa', 'fa-' + AUTH_ICON_MAP[projectAuth.authorizationLevel], 'mr-2')} />
- {AUTH_DESCRIPTION_MAP[projectAuth.authorizationLevel]}
- </td>
- <ProjectActions projectId={projectAuth.project._id} />
- </tr>
-)
-
-ProjectAuthRow.propTypes = {
- projectAuth: Shapes.Authorization.isRequired,
-}
-
-export default ProjectAuthRow
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js
index 8eb4f93b..dc3f85ec 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/ProjectAuthList.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectList.js
@@ -1,14 +1,16 @@
import PropTypes from 'prop-types'
import React from 'react'
-import Shapes from '../../shapes/index'
-import ProjectAuthRow from './ProjectAuthRow'
+import { Project } from '../../shapes'
+import ProjectRow from './ProjectRow'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons'
-const ProjectAuthList = ({ authorizations }) => {
+const ProjectList = ({ projects }) => {
return (
<div className="vertically-expanding-container">
- {authorizations.length === 0 ? (
+ {projects.length === 0 ? (
<div className="alert alert-info">
- <span className="info-icon fa fa-question-circle mr-2" />
+ <FontAwesomeIcon icon={faQuestionCircle} className="info-icon mr-2" />
<strong>No projects here yet...</strong> Add some with the 'New Project' button!
</div>
) : (
@@ -22,8 +24,8 @@ const ProjectAuthList = ({ authorizations }) => {
</tr>
</thead>
<tbody>
- {authorizations.map((authorization) => (
- <ProjectAuthRow projectAuth={authorization} key={authorization.project._id} />
+ {projects.map((project) => (
+ <ProjectRow project={project} key={project._id} />
))}
</tbody>
</table>
@@ -32,8 +34,8 @@ const ProjectAuthList = ({ authorizations }) => {
)
}
-ProjectAuthList.propTypes = {
- authorizations: PropTypes.arrayOf(Shapes.Authorization).isRequired,
+ProjectList.propTypes = {
+ projects: PropTypes.arrayOf(Project).isRequired,
}
-export default ProjectAuthList
+export default ProjectList
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js
new file mode 100644
index 00000000..91368de8
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js
@@ -0,0 +1,29 @@
+import React from 'react'
+import ProjectActions from '../../containers/projects/ProjectActions'
+import { Project } from '../../shapes'
+import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations'
+import { parseAndFormatDateTime } from '../../util/date-time'
+import { useAuth } from '../../auth'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+
+const ProjectRow = ({ project }) => {
+ const { user } = useAuth()
+ const { level } = project.authorizations.find((auth) => auth.userId === user.sub)
+ return (
+ <tr>
+ <td className="pt-3">{project.name}</td>
+ <td className="pt-3">{parseAndFormatDateTime(project.datetimeLastEdited)}</td>
+ <td className="pt-3">
+ <FontAwesomeIcon icon={AUTH_ICON_MAP[level]} className="mr-2" />
+ {AUTH_DESCRIPTION_MAP[level]}
+ </td>
+ <ProjectActions projectId={project._id} />
+ </tr>
+ )
+}
+
+ProjectRow.propTypes = {
+ project: Project.isRequired,
+}
+
+export default ProjectRow
diff --git a/opendc-web/opendc-web-ui/src/containers/app/App.js b/opendc-web/opendc-web-ui/src/containers/app/App.js
new file mode 100644
index 00000000..ec9714ce
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/App.js
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import PropTypes from 'prop-types'
+import React, { useEffect } from 'react'
+import Head from 'next/head'
+import { HotKeys } from 'react-hotkeys'
+import { useDispatch, useSelector } from 'react-redux'
+import { openPortfolioSucceeded } from '../../redux/actions/portfolios'
+import { openProjectSucceeded } from '../../redux/actions/projects'
+import ToolPanelComponent from '../../components/app/map/controls/ToolPanelComponent'
+import LoadingScreen from '../../components/app/map/LoadingScreen'
+import ScaleIndicatorContainer from '../../containers/app/map/controls/ScaleIndicatorContainer'
+import MapStage from '../../containers/app/map/MapStage'
+import TopologySidebarContainer from '../../containers/app/sidebars/topology/TopologySidebarContainer'
+import AppNavbarContainer from '../../containers/navigation/AppNavbarContainer'
+import ProjectSidebarContainer from '../../containers/app/sidebars/project/ProjectSidebarContainer'
+import { openScenarioSucceeded } from '../../redux/actions/scenarios'
+import PortfolioResultsContainer from '../../containers/app/results/PortfolioResultsContainer'
+import { KeymapConfiguration } from '../../hotkeys'
+import { useRequireAuth } from '../../auth'
+import { useActiveProject } from '../../data/project'
+
+const App = ({ projectId, portfolioId, scenarioId }) => {
+ useRequireAuth()
+
+ const projectName = useActiveProject()?.name
+ const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1')
+
+ const dispatch = useDispatch()
+ useEffect(() => {
+ if (scenarioId) {
+ dispatch(openScenarioSucceeded(projectId, portfolioId, scenarioId))
+ } else if (portfolioId) {
+ dispatch(openPortfolioSucceeded(projectId, portfolioId))
+ } else {
+ dispatch(openProjectSucceeded(projectId))
+ }
+ }, [projectId, portfolioId, scenarioId, dispatch])
+
+ const constructionElements = topologyIsLoading ? (
+ <div className="full-height d-flex align-items-center justify-content-center">
+ <LoadingScreen />
+ </div>
+ ) : (
+ <div className="full-height">
+ <MapStage />
+ <ScaleIndicatorContainer />
+ <ToolPanelComponent />
+ <ProjectSidebarContainer />
+ <TopologySidebarContainer />
+ </div>
+ )
+
+ const portfolioElements = (
+ <div className="full-height app-page-container">
+ <ProjectSidebarContainer />
+ <div className="container-fluid full-height">
+ <PortfolioResultsContainer />
+ </div>
+ </div>
+ )
+
+ const scenarioElements = (
+ <div className="full-height app-page-container">
+ <ProjectSidebarContainer />
+ <div className="container-fluid full-height">
+ <h2>Scenario loading</h2>
+ </div>
+ </div>
+ )
+
+ const title = projectName ? projectName + ' - OpenDC' : 'Simulation - OpenDC'
+
+ return (
+ <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="page-container full-height">
+ <Head>
+ <title>{title}</title>
+ </Head>
+ <AppNavbarContainer fullWidth={true} />
+ {scenarioId ? scenarioElements : portfolioId ? portfolioElements : constructionElements}
+ </HotKeys>
+ )
+}
+
+App.propTypes = {
+ projectId: PropTypes.string.isRequired,
+ portfolioId: PropTypes.string,
+ scenarioId: PropTypes.string,
+}
+
+export default App
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js
index 651b3459..bac24c8b 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { goDownOneInteractionLevel } from '../../../actions/interaction-level'
+import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level'
import GrayLayer from '../../../components/app/map/elements/GrayLayer'
const GrayContainer = () => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js
index 61d123e8..91ceb35d 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js
@@ -1,14 +1,17 @@
import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../actions/map'
+import { useDispatch } from 'react-redux'
+import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../redux/actions/map'
import MapStageComponent from '../../../components/app/map/MapStageComponent'
+import { useMapDimensions, useMapPosition } from '../../../data/map'
const MapStage = () => {
- const { position, dimensions } = useSelector((state) => state.map)
+ const position = useMapPosition()
+ const dimensions = useMapDimensions()
const dispatch = useDispatch()
const zoomInOnPositionA = (zoomIn, x, y) => dispatch(zoomInOnPosition(zoomIn, x, y))
const setMapPositionWithBoundsCheckA = (x, y) => dispatch(setMapPositionWithBoundsCheck(x, y))
const setMapDimensionsA = (width, height) => dispatch(setMapDimensions(width, height))
+
return (
<MapStageComponent
mapPosition={position}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
index 877233fc..52d48317 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { goFromBuildingToRoom } from '../../../actions/interaction-level'
+import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level'
import RoomGroup from '../../../components/app/map/groups/RoomGroup'
const RoomContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
index ad7301a7..f97e89a1 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { goFromRoomToRack } from '../../../actions/interaction-level'
+import { goFromRoomToRack } from '../../../redux/actions/interaction-level'
import TileGroup from '../../../components/app/map/groups/TileGroup'
const TileContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js
index 612ca41c..e7ab3c72 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js
@@ -1,11 +1,10 @@
import React from 'react'
import { useSelector } from 'react-redux'
import TopologyGroup from '../../../components/app/map/groups/TopologyGroup'
+import { useActiveTopology } from '../../../data/topology'
const TopologyContainer = () => {
- const topology = useSelector(
- (state) => state.currentTopologyId !== '-1' && state.objects.topology[state.currentTopologyId]
- )
+ const topology = useActiveTopology()
const interactionLevel = useSelector((state) => state.interactionLevel)
return <TopologyGroup topology={topology} interactionLevel={interactionLevel} />
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js
index e9d58b9f..a10eea22 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js
@@ -1,9 +1,9 @@
import React from 'react'
-import { useSelector } from 'react-redux'
import ScaleIndicatorComponent from '../../../../components/app/map/controls/ScaleIndicatorComponent'
+import { useMapScale } from '../../../../data/map'
const ScaleIndicatorContainer = (props) => {
- const scale = useSelector((state) => state.map.scale)
+ const scale = useMapScale()
return <ScaleIndicatorComponent {...props} scale={scale} />
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js
index a18dfd5b..a39c6077 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js
@@ -1,11 +1,12 @@
import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { zoomInOnCenter } from '../../../../actions/map'
+import { useDispatch } from 'react-redux'
+import { zoomInOnCenter } from '../../../../redux/actions/map'
import ZoomControlComponent from '../../../../components/app/map/controls/ZoomControlComponent'
+import { useMapScale } from '../../../../data/map'
const ZoomControlContainer = () => {
const dispatch = useDispatch()
- const scale = useSelector((state) => state.map.scale)
+ const scale = useMapScale()
return <ZoomControlComponent mapScale={scale} zoomInOnCenter={(zoomIn) => dispatch(zoomInOnCenter(zoomIn))} />
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js
index 5f701b4b..633ebcc7 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js
@@ -1,9 +1,10 @@
import React from 'react'
-import { useSelector } from 'react-redux'
import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent'
+import { useMapPosition, useMapScale } from '../../../../data/map'
const MapLayer = (props) => {
- const { position, scale } = useSelector((state) => state.map)
+ const position = useMapPosition()
+ const scale = useMapScale()
return <MapLayerComponent {...props} mapPosition={position} mapScale={scale} />
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
index cefdf35c..8e934a01 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { addRackToTile } from '../../../../actions/topology/room'
+import { addRackToTile } from '../../../../redux/actions/topology/room'
import ObjectHoverLayerComponent from '../../../../components/app/map/layers/ObjectHoverLayerComponent'
import { findTileWithPosition } from '../../../../util/tile-calculations'
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
index 2717d890..1bfadb6d 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { toggleTileAtLocation } from '../../../../actions/topology/building'
+import { toggleTileAtLocation } from '../../../../redux/actions/topology/building'
import RoomHoverLayerComponent from '../../../../components/app/map/layers/RoomHoverLayerComponent'
import {
deriveValidNextTilePositions,
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js
index 86f465b6..a36997ff 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js
@@ -1,34 +1,23 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { useHistory } from 'react-router-dom'
+import React, { useState } from 'react'
+import { useDispatch } from 'react-redux'
+import { useRouter } from 'next/router'
import PortfolioListComponent from '../../../../components/app/sidebars/project/PortfolioListComponent'
-import { deletePortfolio, setCurrentPortfolio } from '../../../../actions/portfolios'
-import { openNewPortfolioModal } from '../../../../actions/modals/portfolios'
+import { addPortfolio, deletePortfolio, setCurrentPortfolio } from '../../../../redux/actions/portfolios'
import { getState } from '../../../../util/state-utils'
-import { setCurrentTopology } from '../../../../actions/topology/building'
+import { setCurrentTopology } from '../../../../redux/actions/topology/building'
+import NewPortfolioModalComponent from '../../../../components/modals/custom-components/NewPortfolioModalComponent'
+import { useActivePortfolio, useActiveProject, usePortfolios } from '../../../../data/project'
-const PortfolioListContainer = (props) => {
- const state = useSelector((state) => {
- let portfolios = state.objects.project[state.currentProjectId]
- ? state.objects.project[state.currentProjectId].portfolioIds.map((t) => state.objects.portfolio[t])
- : []
- if (portfolios.filter((t) => !t).length > 0) {
- portfolios = []
- }
-
- return {
- currentProjectId: state.currentProjectId,
- currentPortfolioId: state.currentPortfolioId,
- portfolios,
- }
- })
+const PortfolioListContainer = () => {
+ const currentProjectId = useActiveProject()?._id
+ const currentPortfolioId = useActivePortfolio()?._id
+ const portfolios = usePortfolios(currentProjectId)
const dispatch = useDispatch()
- const history = useHistory()
+ const [isVisible, setVisible] = useState(false)
+ const router = useRouter()
const actions = {
- onNewPortfolio: () => {
- dispatch(openNewPortfolioModal())
- },
+ onNewPortfolio: () => setVisible(true),
onChoosePortfolio: (portfolioId) => {
dispatch(setCurrentPortfolio(portfolioId))
},
@@ -37,11 +26,32 @@ const PortfolioListContainer = (props) => {
const state = await getState(dispatch)
dispatch(deletePortfolio(id))
dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0]))
- history.push(`/projects/${state.currentProjectId}`)
+ router.push(`/projects/${state.currentProjectId}`)
}
},
}
- return <PortfolioListComponent {...props} {...state} {...actions} />
+ const callback = (name, targets) => {
+ if (name) {
+ dispatch(
+ addPortfolio({
+ name,
+ targets,
+ })
+ )
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <PortfolioListComponent
+ currentProjectId={currentProjectId}
+ currentPortfolioId={currentPortfolioId}
+ portfolios={portfolios}
+ {...actions}
+ />
+ <NewPortfolioModalComponent callback={callback} show={isVisible} />
+ </>
+ )
}
export default PortfolioListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js
index 35e6c52b..06c7f0f7 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js
@@ -1,11 +1,11 @@
import React from 'react'
-import { useLocation } from 'react-router-dom'
+import { useRouter } from 'next/router'
import ProjectSidebarComponent from '../../../../components/app/sidebars/project/ProjectSidebarComponent'
import { isCollapsible } from '../../../../util/sidebar-space'
const ProjectSidebarContainer = (props) => {
- const location = useLocation()
- return <ProjectSidebarComponent collapsible={isCollapsible(location)} {...props} />
+ const router = useRouter()
+ return <ProjectSidebarComponent collapsible={isCollapsible(router)} {...props} />
}
export default ProjectSidebarContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
index 18d0735e..e1be51dc 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
@@ -1,28 +1,27 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
+import React, { useState } from 'react'
+import { useDispatch } from 'react-redux'
import ScenarioListComponent from '../../../../components/app/sidebars/project/ScenarioListComponent'
-import { openNewScenarioModal } from '../../../../actions/modals/scenarios'
-import { deleteScenario, setCurrentScenario } from '../../../../actions/scenarios'
-import { setCurrentPortfolio } from '../../../../actions/portfolios'
+import { addScenario, deleteScenario, setCurrentScenario } from '../../../../redux/actions/scenarios'
+import { setCurrentPortfolio } from '../../../../redux/actions/portfolios'
+import NewScenarioModalComponent from '../../../../components/modals/custom-components/NewScenarioModalComponent'
+import { useProjectTopologies } from '../../../../data/topology'
+import { useActiveScenario, useActiveProject, useScenarios } from '../../../../data/project'
+import { useSchedulers, useTraces } from '../../../../data/experiments'
-const ScenarioListContainer = ({ portfolioId, children }) => {
- const currentProjectId = useSelector((state) => state.currentProjectId)
- const currentScenarioId = useSelector((state) => state.currentScenarioId)
- const scenarios = useSelector((state) => {
- let scenarios = state.objects.portfolio[portfolioId]
- ? state.objects.portfolio[portfolioId].scenarioIds.map((t) => state.objects.scenario[t])
- : []
- if (scenarios.filter((t) => !t).length > 0) {
- scenarios = []
- }
-
- return scenarios
- })
+const ScenarioListContainer = ({ portfolioId }) => {
+ const currentProjectId = useActiveProject()?._id
+ const currentScenarioId = useActiveScenario()?._id
+ const scenarios = useScenarios(portfolioId)
+ const topologies = useProjectTopologies()
+ const traces = useTraces()
+ const schedulers = useSchedulers()
const dispatch = useDispatch()
+ const [isVisible, setVisible] = useState(false)
+
const onNewScenario = (currentPortfolioId) => {
dispatch(setCurrentPortfolio(currentPortfolioId))
- dispatch(openNewScenarioModal())
+ setVisible(true)
}
const onChooseScenario = (portfolioId, scenarioId) => {
dispatch(setCurrentScenario(portfolioId, scenarioId))
@@ -32,17 +31,43 @@ const ScenarioListContainer = ({ portfolioId, children }) => {
dispatch(deleteScenario(id))
}
}
+ const callback = (name, portfolioId, trace, topology, operational) => {
+ if (name) {
+ dispatch(
+ addScenario({
+ portfolioId,
+ name,
+ trace,
+ topology,
+ operational,
+ })
+ )
+ }
+
+ setVisible(false)
+ }
return (
- <ScenarioListComponent
- portfolioId={portfolioId}
- currentProjectId={currentProjectId}
- currentScenarioId={currentScenarioId}
- scenarios={scenarios}
- onNewScenario={onNewScenario}
- onChooseScenario={onChooseScenario}
- onDeleteScenario={onDeleteScenario}
- />
+ <>
+ <ScenarioListComponent
+ portfolioId={portfolioId}
+ currentProjectId={currentProjectId}
+ currentScenarioId={currentScenarioId}
+ scenarios={scenarios}
+ onNewScenario={onNewScenario}
+ onChooseScenario={onChooseScenario}
+ onDeleteScenario={onDeleteScenario}
+ />
+ <NewScenarioModalComponent
+ show={isVisible}
+ currentPortfolioId={portfolioId}
+ currentPortfolioScenarioIds={scenarios.map((s) => s._id)}
+ traces={traces}
+ schedulers={schedulers}
+ topologies={topologies}
+ callback={callback}
+ />
+ </>
)
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
index 954284a6..266ca495 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
@@ -1,53 +1,64 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
+import React, { useState } from 'react'
+import { useDispatch } from 'react-redux'
import TopologyListComponent from '../../../../components/app/sidebars/project/TopologyListComponent'
-import { setCurrentTopology } from '../../../../actions/topology/building'
-import { openNewTopologyModal } from '../../../../actions/modals/topology'
-import { useHistory } from 'react-router-dom'
+import { setCurrentTopology } from '../../../../redux/actions/topology/building'
+import { useRouter } from 'next/router'
import { getState } from '../../../../util/state-utils'
-import { deleteTopology } from '../../../../actions/topologies'
+import { addTopology, deleteTopology } from '../../../../redux/actions/topologies'
+import NewTopologyModalComponent from '../../../../components/modals/custom-components/NewTopologyModalComponent'
+import { useActiveTopology, useProjectTopologies } from '../../../../data/topology'
const TopologyListContainer = () => {
const dispatch = useDispatch()
- const history = useHistory()
-
- const topologies = useSelector((state) => {
- let topologies = state.objects.project[state.currentProjectId]
- ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t])
- : []
- if (topologies.filter((t) => !t).length > 0) {
- topologies = []
- }
-
- return topologies
- })
- const currentTopologyId = useSelector((state) => state.currentTopologyId)
+ const router = useRouter()
+ const topologies = useProjectTopologies()
+ const currentTopologyId = useActiveTopology()?._id
+ const [isVisible, setVisible] = useState(false)
const onChooseTopology = async (id) => {
dispatch(setCurrentTopology(id))
const state = await getState(dispatch)
- history.push(`/projects/${state.currentProjectId}`)
- }
- const onNewTopology = () => {
- dispatch(openNewTopologyModal())
+ router.push(`/projects/${state.currentProjectId}`)
}
const onDeleteTopology = async (id) => {
if (id) {
const state = await getState(dispatch)
dispatch(deleteTopology(id))
dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0]))
- history.push(`/projects/${state.currentProjectId}`)
+ router.push(`/projects/${state.currentProjectId}`)
+ }
+ }
+ const onCreateTopology = (name) => {
+ if (name) {
+ dispatch(addTopology(name, undefined))
+ }
+ setVisible(false)
+ }
+ const onDuplicateTopology = (name, id) => {
+ if (name) {
+ dispatch(addTopology(name, id))
}
+ setVisible(false)
}
+ const onCancel = () => setVisible(false)
return (
- <TopologyListComponent
- topologies={topologies}
- currentTopologyId={currentTopologyId}
- onChooseTopology={onChooseTopology}
- onNewTopology={onNewTopology}
- onDeleteTopology={onDeleteTopology}
- />
+ <>
+ <TopologyListComponent
+ topologies={topologies}
+ currentTopologyId={currentTopologyId}
+ onChooseTopology={onChooseTopology}
+ onNewTopology={() => setVisible(true)}
+ onDeleteTopology={onDeleteTopology}
+ />
+ <NewTopologyModalComponent
+ show={isVisible}
+ topologies={topologies}
+ onCreateTopology={onCreateTopology}
+ onDuplicateTopology={onDuplicateTopology}
+ onCancel={onCancel}
+ />
+ </>
)
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js
index ea36539c..96f42a44 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js
@@ -4,7 +4,7 @@ import {
cancelNewRoomConstruction,
finishNewRoomConstruction,
startNewRoomConstruction,
-} from '../../../../../actions/topology/building'
+} from '../../../../../redux/actions/topology/building'
import StartNewRoomConstructionComponent from '../../../../../components/app/sidebars/topology/building/NewRoomConstructionComponent'
const NewRoomConstructionButton = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js
index 46862472..ea250767 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level'
+import { goDownOneInteractionLevel } from '../../../../../redux/actions/interaction-level'
import BackToRackComponent from '../../../../../components/app/sidebars/topology/machine/BackToRackComponent'
const BackToRackContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js
index 1510a436..54e406f4 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js
@@ -1,11 +1,34 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
-import { openDeleteMachineModal } from '../../../../../actions/modals/topology'
-import DeleteMachineComponent from '../../../../../components/app/sidebars/topology/machine/DeleteMachineComponent'
+import ConfirmationModal from '../../../../../components/modals/ConfirmationModal'
+import { deleteMachine } from '../../../../../redux/actions/topology/machine'
+import { Button } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTrash } from '@fortawesome/free-solid-svg-icons'
-const DeleteMachineContainer = (props) => {
+const DeleteMachineContainer = () => {
const dispatch = useDispatch()
- return <DeleteMachineComponent {...props} onClick={() => dispatch(openDeleteMachineModal())} />
+ const [isVisible, setVisible] = useState(false)
+ const callback = (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteMachine())
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <Button color="danger" outline block onClick={() => setVisible(true)}>
+ <FontAwesomeIcon icon={faTrash} className="mr-2" />
+ Delete this machine
+ </Button>
+ <ConfirmationModal
+ title="Delete this machine"
+ message="Are you sure you want to delete this machine?"
+ show={isVisible}
+ callback={callback}
+ />
+ </>
+ )
}
export default DeleteMachineContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js
index 6f4285b2..9cbb32c5 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js
@@ -1,10 +1,9 @@
import React from 'react'
import { useSelector } from 'react-redux'
-import MachineNameComponent from '../../../../../components/app/sidebars/topology/machine/MachineNameComponent'
-const MachineNameContainer = (props) => {
+const MachineNameContainer = () => {
const position = useSelector((state) => state.interactionLevel.position)
- return <MachineNameComponent {...props} position={position} />
+ return <h2>Machine at slot {position}</h2>
}
export default MachineNameContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js
index 3795cdff..0f85aa76 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { addUnit } from '../../../../../actions/topology/machine'
+import { addUnit } from '../../../../../redux/actions/topology/machine'
import UnitAddComponent from '../../../../../components/app/sidebars/topology/machine/UnitAddComponent'
const UnitAddContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js
index 3d24859e..acb16a21 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { deleteUnit } from '../../../../../actions/topology/machine'
+import { deleteUnit } from '../../../../../redux/actions/topology/machine'
import UnitComponent from '../../../../../components/app/sidebars/topology/machine/UnitComponent'
const UnitContainer = ({ unitId, unitType }) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js
index 3708e33e..c2a0fc48 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { addPrefab } from '../../../../../actions/prefabs'
+import { addPrefab } from '../../../../../redux/actions/prefabs'
import AddPrefabComponent from '../../../../../components/app/sidebars/topology/rack/AddPrefabComponent'
const AddPrefabContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js
index 93bb749f..a98728a6 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level'
+import { goDownOneInteractionLevel } from '../../../../../redux/actions/interaction-level'
import BackToRoomComponent from '../../../../../components/app/sidebars/topology/rack/BackToRoomComponent'
const BackToRoomContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js
index de46e491..4463530e 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js
@@ -1,11 +1,34 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
-import { openDeleteRackModal } from '../../../../../actions/modals/topology'
-import DeleteRackComponent from '../../../../../components/app/sidebars/topology/rack/DeleteRackComponent'
+import ConfirmationModal from '../../../../../components/modals/ConfirmationModal'
+import { deleteRack } from '../../../../../redux/actions/topology/rack'
+import { Button } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTrash } from '@fortawesome/free-solid-svg-icons'
-const DeleteRackContainer = (props) => {
+const DeleteRackContainer = () => {
const dispatch = useDispatch()
- return <DeleteRackComponent {...props} onClick={() => dispatch(openDeleteRackModal())} />
+ const [isVisible, setVisible] = useState(false)
+ const callback = (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteRack())
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <Button color="danger" outline block onClick={() => setVisible(true)}>
+ <FontAwesomeIcon icon={faTrash} className="mr-2" />
+ Delete this rack
+ </Button>
+ <ConfirmationModal
+ title="Delete this rack"
+ message="Are you sure you want to delete this rack?"
+ show={isVisible}
+ callback={callback}
+ />
+ </>
+ )
}
export default DeleteRackContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
index 5bb2c784..2134e411 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { addMachine } from '../../../../../actions/topology/rack'
+import { addMachine } from '../../../../../redux/actions/topology/rack'
import EmptySlotComponent from '../../../../../components/app/sidebars/topology/rack/EmptySlotComponent'
const EmptySlotContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js
index 149b4d18..7d8e32c1 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { goFromRackToMachine } from '../../../../../actions/interaction-level'
+import { goFromRackToMachine } from '../../../../../redux/actions/interaction-level'
import MachineComponent from '../../../../../components/app/sidebars/topology/rack/MachineComponent'
const MachineContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
index 7dfdb473..eaa1e78e 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
@@ -1,14 +1,33 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { openEditRackNameModal } from '../../../../../actions/modals/topology'
-import RackNameComponent from '../../../../../components/app/sidebars/topology/rack/RackNameComponent'
+import NameComponent from '../../../../../components/app/sidebars/topology/NameComponent'
+import TextInputModal from '../../../../../components/modals/TextInputModal'
+import { editRackName } from '../../../../../redux/actions/topology/rack'
-const RackNameContainer = (props) => {
+const RackNameContainer = () => {
+ const [isVisible, setVisible] = useState(false)
const rackName = useSelector(
(state) => state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name
)
const dispatch = useDispatch()
- return <RackNameComponent {...props} rackName={rackName} onEdit={() => dispatch(openEditRackNameModal())} />
+ const callback = (name) => {
+ if (name) {
+ dispatch(editRackName(name))
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <NameComponent name={rackName} onEdit={() => setVisible(true)} />
+ <TextInputModal
+ title="Edit rack name"
+ label="Rack name"
+ show={isVisible}
+ initialValue={rackName}
+ callback={callback}
+ />
+ </>
+ )
}
export default RackNameContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js
index a48bf0a7..9fa1e95f 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level'
+import { goDownOneInteractionLevel } from '../../../../../redux/actions/interaction-level'
import BackToBuildingComponent from '../../../../../components/app/sidebars/topology/room/BackToBuildingComponent'
const BackToBuildingContainer = () => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js
index 6a80e9b0..0fbbb036 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js
@@ -1,12 +1,34 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useDispatch } from 'react-redux'
-import { openDeleteRoomModal } from '../../../../../actions/modals/topology'
-import DeleteRoomComponent from '../../../../../components/app/sidebars/topology/room/DeleteRoomComponent'
+import { Button } from 'reactstrap'
+import ConfirmationModal from '../../../../../components/modals/ConfirmationModal'
+import { deleteRoom } from '../../../../../redux/actions/topology/room'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTrash } from '@fortawesome/free-solid-svg-icons'
-const DeleteRoomContainer = (props) => {
+const DeleteRoomContainer = () => {
const dispatch = useDispatch()
- const onClick = () => dispatch(openDeleteRoomModal())
- return <DeleteRoomComponent {...props} onClick={onClick} />
+ const [isVisible, setVisible] = useState(false)
+ const callback = (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteRoom())
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <Button color="danger" outline block onClick={() => setVisible(true)}>
+ <FontAwesomeIcon icon={faTrash} className="mr-2" />
+ Delete this room
+ </Button>
+ <ConfirmationModal
+ title="Delete this room"
+ message="Are you sure you want to delete this room?"
+ show={isVisible}
+ callback={callback}
+ />
+ </>
+ )
}
export default DeleteRoomContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js
index 37027fc5..ec4f586b 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js
@@ -1,9 +1,11 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { finishRoomEdit, startRoomEdit } from '../../../../../actions/topology/building'
-import EditRoomComponent from '../../../../../components/app/sidebars/topology/room/EditRoomComponent'
+import { finishRoomEdit, startRoomEdit } from '../../../../../redux/actions/topology/building'
+import { Button } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faCheck, faPencilAlt } from '@fortawesome/free-solid-svg-icons'
-const EditRoomContainer = (props) => {
+const EditRoomContainer = () => {
const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1')
const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode)
@@ -11,14 +13,22 @@ const EditRoomContainer = (props) => {
const onEdit = () => dispatch(startRoomEdit())
const onFinish = () => dispatch(finishRoomEdit())
- return (
- <EditRoomComponent
- {...props}
- onEdit={onEdit}
- onFinish={onFinish}
- isEditing={isEditing}
- isInRackConstructionMode={isInRackConstructionMode}
- />
+ return isEditing ? (
+ <Button color="info" outline block onClick={onFinish}>
+ <FontAwesomeIcon icon={faCheck} className="mr-2" />
+ Finish editing room
+ </Button>
+ ) : (
+ <Button
+ color="info"
+ outline
+ block
+ disabled={isInRackConstructionMode}
+ onClick={() => (isInRackConstructionMode ? undefined : onEdit())}
+ >
+ <FontAwesomeIcon icon={faPencilAlt} className="mr-2" />
+ Edit the tiles of this room
+ </Button>
)
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js
index 726e9d37..79584e98 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { startRackConstruction, stopRackConstruction } from '../../../../../actions/topology/room'
+import { startRackConstruction, stopRackConstruction } from '../../../../../redux/actions/topology/room'
import RackConstructionComponent from '../../../../../components/app/sidebars/topology/room/RackConstructionComponent'
const RackConstructionContainer = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js
index 1f53aeb6..3b35a849 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js
@@ -1,13 +1,31 @@
-import React from 'react'
+import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
-import { openEditRoomNameModal } from '../../../../../actions/modals/topology'
-import RoomNameComponent from '../../../../../components/app/sidebars/topology/room/RoomNameComponent'
+import NameComponent from '../../../../../components/app/sidebars/topology/NameComponent'
+import TextInputModal from '../../../../../components/modals/TextInputModal'
+import { editRoomName } from '../../../../../redux/actions/topology/room'
-const RoomNameContainer = (props) => {
+const RoomNameContainer = () => {
+ const [isVisible, setVisible] = useState(false)
const roomName = useSelector((state) => state.objects.room[state.interactionLevel.roomId].name)
const dispatch = useDispatch()
- const onEdit = () => dispatch(openEditRoomNameModal())
- return <RoomNameComponent {...props} onEdit={onEdit} roomName={roomName} />
+ const callback = (name) => {
+ if (name) {
+ dispatch(editRoomName(name))
+ }
+ setVisible(false)
+ }
+ return (
+ <>
+ <NameComponent name={roomName} onEdit={() => setVisible(true)} />
+ <TextInputModal
+ title="Edit room name"
+ label="Room name"
+ show={isVisible}
+ initialValue={roomName}
+ callback={callback}
+ />
+ </>
+ )
}
export default RoomNameContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Login.js b/opendc-web/opendc-web-ui/src/containers/auth/Login.js
index 54605775..d8083d89 100644
--- a/opendc-web/opendc-web-ui/src/containers/auth/Login.js
+++ b/opendc-web/opendc-web-ui/src/containers/auth/Login.js
@@ -1,44 +1,20 @@
import React from 'react'
-import GoogleLogin from 'react-google-login'
-import { useDispatch } from 'react-redux'
-import { logIn } from '../../actions/auth'
-import config from '../../config'
+import { Button } from 'reactstrap'
+import { useAuth } from '../../auth'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faSignInAlt } from '@fortawesome/free-solid-svg-icons'
-const Login = (props) => {
- const { visible } = props
- const dispatch = useDispatch()
-
- const onLogin = (payload) => dispatch(logIn(payload))
- const onAuthResponse = (response) => {
- onLogin({
- email: response.getBasicProfile().getEmail(),
- givenName: response.getBasicProfile().getGivenName(),
- familyName: response.getBasicProfile().getFamilyName(),
- googleId: response.googleId,
- authToken: response.getAuthResponse().id_token,
- expiresAt: response.getAuthResponse().expires_at,
- })
- }
- const onAuthFailure = (error) => {
- // TODO Show error alert
- console.error(error)
- }
+function Login({ visible, className }) {
+ const { loginWithRedirect } = useAuth()
if (!visible) {
return <span />
}
return (
- <GoogleLogin
- clientId={config.OAUTH_CLIENT_ID}
- onSuccess={onAuthResponse}
- onFailure={onAuthFailure}
- render={(renderProps) => (
- <span onClick={renderProps.onClick} className="login btn btn-primary">
- <span className="fa fa-google" /> Login with Google
- </span>
- )}
- />
+ <Button color="primary" onClick={() => loginWithRedirect()} className={className}>
+ <FontAwesomeIcon icon={faSignInAlt} /> Sign In
+ </Button>
)
}
diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js
index 66f0f6db..37705c5d 100644
--- a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js
+++ b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js
@@ -1,11 +1,10 @@
import React from 'react'
-import { useDispatch } from 'react-redux'
-import { logOut } from '../../actions/auth'
import LogoutButton from '../../components/navigation/LogoutButton'
+import { useAuth } from '../../auth'
const Logout = (props) => {
- const dispatch = useDispatch()
- return <LogoutButton {...props} onLogout={() => dispatch(logOut())} />
+ const { logout } = useAuth()
+ return <LogoutButton {...props} onLogout={() => logout({ returnTo: window.location.origin })} />
}
export default Logout
diff --git a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js
index 291c0068..70f5b884 100644
--- a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js
+++ b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js
@@ -1,9 +1,9 @@
import React from 'react'
-import { useSelector } from 'react-redux'
+import { useAuth } from '../../auth'
function ProfileName() {
- const name = useSelector((state) => `${state.auth.givenName} ${state.auth.familyName}`)
- return <span>{name}</span>
+ const { isLoading, user } = useAuth()
+ return isLoading ? <span>Loading...</span> : <span>{user.name}</span>
}
export default ProfileName
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js
deleted file mode 100644
index 33b2612f..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeDeleteMachineModal } from '../../actions/modals/topology'
-import { deleteMachine } from '../../actions/topology/machine'
-import ConfirmationModal from '../../components/modals/ConfirmationModal'
-
-const DeleteMachineModal = () => {
- const dispatch = useDispatch()
- const callback = (isConfirmed) => {
- if (isConfirmed) {
- dispatch(deleteMachine())
- }
- dispatch(closeDeleteMachineModal())
- }
- const visible = useSelector((state) => state.modals.deleteMachineModalVisible)
- return (
- <ConfirmationModal
- title="Delete this machine"
- message="Are you sure you want to delete this machine?"
- show={visible}
- callback={callback}
- />
- )
-}
-
-export default DeleteMachineModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js
deleted file mode 100644
index 93a38642..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeDeleteProfileModal } from '../../actions/modals/profile'
-import { deleteCurrentUser } from '../../actions/users'
-import ConfirmationModal from '../../components/modals/ConfirmationModal'
-
-const DeleteProfileModal = () => {
- const visible = useSelector((state) => state.modals.deleteProfileModalVisible)
-
- const dispatch = useDispatch()
- const callback = (isConfirmed) => {
- if (isConfirmed) {
- dispatch(deleteCurrentUser())
- }
- dispatch(closeDeleteProfileModal())
- }
- return (
- <ConfirmationModal
- title="Delete my account"
- message="Are you sure you want to delete your OpenDC account?"
- show={visible}
- callback={callback}
- />
- )
-}
-
-export default DeleteProfileModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js
deleted file mode 100644
index ca76fd04..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeDeleteRackModal } from '../../actions/modals/topology'
-import { deleteRack } from '../../actions/topology/rack'
-import ConfirmationModal from '../../components/modals/ConfirmationModal'
-
-const DeleteRackModal = (props) => {
- const visible = useSelector((state) => state.modals.deleteRackModalVisible)
- const dispatch = useDispatch()
- const callback = (isConfirmed) => {
- if (isConfirmed) {
- dispatch(deleteRack())
- }
- dispatch(closeDeleteRackModal())
- }
- return (
- <ConfirmationModal
- title="Delete this rack"
- message="Are you sure you want to delete this rack?"
- show={visible}
- callback={callback}
- {...props}
- />
- )
-}
-
-export default DeleteRackModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js
deleted file mode 100644
index 9a7be6a6..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeDeleteRoomModal } from '../../actions/modals/topology'
-import { deleteRoom } from '../../actions/topology/room'
-import ConfirmationModal from '../../components/modals/ConfirmationModal'
-
-const DeleteRoomModal = (props) => {
- const visible = useSelector((state) => state.modals.deleteRoomModalVisible)
-
- const dispatch = useDispatch()
- const callback = (isConfirmed) => {
- if (isConfirmed) {
- dispatch(deleteRoom())
- }
- dispatch(closeDeleteRoomModal())
- }
- return (
- <ConfirmationModal
- title="Delete this room"
- message="Are you sure you want to delete this room?"
- show={visible}
- callback={callback}
- {...props}
- />
- )
-}
-
-export default DeleteRoomModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js
deleted file mode 100644
index edb57217..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeEditRackNameModal } from '../../actions/modals/topology'
-import { editRackName } from '../../actions/topology/rack'
-import TextInputModal from '../../components/modals/TextInputModal'
-
-const EditRackNameModal = (props) => {
- const { visible, previousName } = useSelector((state) => {
- return {
- visible: state.modals.editRackNameModalVisible,
- previousName:
- state.interactionLevel.mode === 'RACK'
- ? state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name
- : '',
- }
- })
-
- const dispatch = useDispatch()
- const callback = (name) => {
- if (name) {
- dispatch(editRackName(name))
- }
- dispatch(closeEditRackNameModal())
- }
- return (
- <TextInputModal
- title="Edit rack name"
- label="Rack name"
- show={visible}
- initialValue={previousName}
- callback={callback}
- {...props}
- />
- )
-}
-
-export default EditRackNameModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js
deleted file mode 100644
index a804c0b0..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeEditRoomNameModal } from '../../actions/modals/topology'
-import { editRoomName } from '../../actions/topology/room'
-import TextInputModal from '../../components/modals/TextInputModal'
-
-const EditRoomNameModal = (props) => {
- const visible = useSelector((state) => state.modals.editRoomNameModalVisible)
- const previousName = useSelector((state) =>
- state.interactionLevel.mode === 'ROOM' ? state.objects.room[state.interactionLevel.roomId].name : ''
- )
-
- const dispatch = useDispatch()
- const callback = (name) => {
- if (name) {
- dispatch(editRoomName(name))
- }
- dispatch(closeEditRoomNameModal())
- }
- return (
- <TextInputModal
- title="Edit room name"
- label="Room name"
- show={visible}
- initialValue={previousName}
- callback={callback}
- {...props}
- />
- )
-}
-export default EditRoomNameModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js
deleted file mode 100644
index b364ed4c..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeNewPortfolioModal } from '../../actions/modals/portfolios'
-import NewPortfolioModalComponent from '../../components/modals/custom-components/NewPortfolioModalComponent'
-import { addPortfolio } from '../../actions/portfolios'
-
-const NewPortfolioModal = (props) => {
- const show = useSelector((state) => state.modals.newPortfolioModalVisible)
- const dispatch = useDispatch()
- const callback = (name, targets) => {
- if (name) {
- dispatch(
- addPortfolio({
- name,
- targets,
- })
- )
- }
- dispatch(closeNewPortfolioModal())
- }
- return <NewPortfolioModalComponent {...props} callback={callback} show={show} />
-}
-
-export default NewPortfolioModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js
deleted file mode 100644
index e63ba76b..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { closeNewProjectModal } from '../../actions/modals/projects'
-import { addProject } from '../../actions/projects'
-import TextInputModal from '../../components/modals/TextInputModal'
-
-const NewProjectModal = (props) => {
- const visible = useSelector((state) => state.modals.newProjectModalVisible)
- const dispatch = useDispatch()
- const callback = (text) => {
- if (text) {
- dispatch(addProject(text))
- }
- dispatch(closeNewProjectModal())
- }
- return <TextInputModal title="New Project" label="Project title" show={visible} callback={callback} {...props} />
-}
-
-export default NewProjectModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js
deleted file mode 100644
index b588b4bc..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import NewScenarioModalComponent from '../../components/modals/custom-components/NewScenarioModalComponent'
-import { addScenario } from '../../actions/scenarios'
-import { closeNewScenarioModal } from '../../actions/modals/scenarios'
-
-const NewScenarioModal = (props) => {
- const topologies = useSelector(({ currentProjectId, objects }) => {
- console.log(currentProjectId, objects)
-
- if (currentProjectId === '-1' || !objects.project[currentProjectId]) {
- return []
- }
-
- const topologies = objects.project[currentProjectId].topologyIds.map((t) => objects.topology[t])
-
- if (topologies.filter((t) => !t).length > 0) {
- return []
- }
-
- return topologies
- })
- const state = useSelector((state) => {
- return {
- show: state.modals.newScenarioModalVisible,
- currentPortfolioId: state.currentPortfolioId,
- currentPortfolioScenarioIds:
- state.currentPortfolioId !== '-1' && state.objects.portfolio[state.currentPortfolioId]
- ? state.objects.portfolio[state.currentPortfolioId].scenarioIds
- : [],
- traces: Object.values(state.objects.trace),
- schedulers: Object.values(state.objects.scheduler),
- }
- })
-
- const dispatch = useDispatch()
- const callback = (name, portfolioId, trace, topology, operational) => {
- if (name) {
- dispatch(
- addScenario({
- portfolioId,
- name,
- trace,
- topology,
- operational,
- })
- )
- }
- dispatch(closeNewScenarioModal())
- }
-
- return <NewScenarioModalComponent {...props} {...state} topologies={topologies} callback={callback} />
-}
-
-export default NewScenarioModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js
deleted file mode 100644
index 2f81706e..00000000
--- a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import NewTopologyModalComponent from '../../components/modals/custom-components/NewTopologyModalComponent'
-import { closeNewTopologyModal } from '../../actions/modals/topology'
-import { addTopology } from '../../actions/topologies'
-
-const NewTopologyModal = () => {
- const show = useSelector((state) => state.modals.changeTopologyModalVisible)
- const topologies = useSelector((state) => {
- let topologies = state.objects.project[state.currentProjectId]
- ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t])
- : []
- if (topologies.filter((t) => !t).length > 0) {
- topologies = []
- }
-
- return topologies
- })
-
- const dispatch = useDispatch()
- const onCreateTopology = (name) => {
- if (name) {
- dispatch(addTopology(name, undefined))
- }
- dispatch(closeNewTopologyModal())
- }
- const onDuplicateTopology = (name, id) => {
- if (name) {
- dispatch(addTopology(name, id))
- }
- dispatch(closeNewTopologyModal())
- }
- const onCancel = () => {
- dispatch(closeNewTopologyModal())
- }
-
- return (
- <NewTopologyModalComponent
- show={show}
- topologies={topologies}
- onCreateTopology={onCreateTopology}
- onDuplicateTopology={onDuplicateTopology}
- onCancel={onCancel}
- />
- )
-}
-
-export default NewTopologyModal
diff --git a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js
index 42a44345..6742bc26 100644
--- a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js
@@ -1,11 +1,9 @@
import React from 'react'
-import { useSelector } from 'react-redux'
import AppNavbarComponent from '../../components/navigation/AppNavbarComponent'
+import { useActiveProject } from '../../data/project'
const AppNavbarContainer = (props) => {
- const project = useSelector((state) =>
- state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined
- )
+ const project = useActiveProject()
return <AppNavbarComponent {...props} project={project} />
}
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js b/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js
deleted file mode 100644
index 26f95c55..00000000
--- a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { setAuthVisibilityFilter } from '../../actions/projects'
-import FilterButton from '../../components/projects/FilterButton'
-
-const FilterLink = (props) => {
- const active = useSelector((state) => state.projectList.authVisibilityFilter === props.filter)
- const dispatch = useDispatch()
-
- return <FilterButton {...props} onClick={() => dispatch(setAuthVisibilityFilter(props.filter))} active={active} />
-}
-
-export default FilterLink
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js
deleted file mode 100644
index b8f6fef5..00000000
--- a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react'
-import { useDispatch } from 'react-redux'
-import { openNewProjectModal } from '../../actions/modals/projects'
-import NewProjectButtonComponent from '../../components/projects/NewProjectButtonComponent'
-
-const NewProjectButtonContainer = (props) => {
- const dispatch = useDispatch()
- return <NewProjectButtonComponent {...props} onClick={() => dispatch(openNewProjectModal())} />
-}
-
-export default NewProjectButtonContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js
new file mode 100644
index 00000000..e03b5c07
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js
@@ -0,0 +1,35 @@
+import React, { useState } from 'react'
+import { useDispatch } from 'react-redux'
+import { addProject } from '../../redux/actions/projects'
+import TextInputModal from '../../components/modals/TextInputModal'
+import { Button } from 'reactstrap'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faPlus } from '@fortawesome/free-solid-svg-icons'
+
+/**
+ * A container for creating a new project.
+ */
+const NewProjectContainer = () => {
+ const [isVisible, setVisible] = useState(false)
+ const dispatch = useDispatch()
+ const callback = (text) => {
+ if (text) {
+ dispatch(addProject(text))
+ }
+ setVisible(false)
+ }
+
+ return (
+ <>
+ <div className="bottom-btn-container">
+ <Button color="primary" className="float-right" onClick={() => setVisible(true)}>
+ <FontAwesomeIcon icon={faPlus} className="mr-2" />
+ New Project
+ </Button>
+ </div>
+ <TextInputModal title="New Project" label="Project title" show={isVisible} callback={callback} />
+ </>
+ )
+}
+
+export default NewProjectContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js
index a13034e9..bdb422dc 100644
--- a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js
+++ b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js
@@ -1,6 +1,6 @@
import React from 'react'
import { useDispatch } from 'react-redux'
-import { deleteProject } from '../../actions/projects'
+import { deleteProject } from '../../redux/actions/projects'
import ProjectActionButtons from '../../components/projects/ProjectActionButtons'
const ProjectActions = (props) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js
new file mode 100644
index 00000000..6632a8b5
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/projects/ProjectListContainer.js
@@ -0,0 +1,34 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import ProjectList from '../../components/projects/ProjectList'
+import { useAuth } from '../../auth'
+import { useProjects } from '../../data/project'
+
+const getVisibleProjects = (projects, filter, userId) => {
+ switch (filter) {
+ case 'SHOW_ALL':
+ return projects
+ case 'SHOW_OWN':
+ return projects.filter((project) =>
+ project.authorizations.some((a) => a.userId === userId && a.level === 'OWN')
+ )
+ case 'SHOW_SHARED':
+ return projects.filter((project) =>
+ project.authorizations.some((a) => a.userId === userId && a.level !== 'OWN')
+ )
+ default:
+ return projects
+ }
+}
+
+const ProjectListContainer = ({ filter }) => {
+ const { user } = useAuth()
+ const projects = useProjects()
+ return <ProjectList projects={getVisibleProjects(projects, filter, user?.sub)} />
+}
+
+ProjectListContainer.propTypes = {
+ filter: PropTypes.string.isRequired,
+}
+
+export default ProjectListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js
deleted file mode 100644
index b869775c..00000000
--- a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react'
-import { useSelector } from 'react-redux'
-import ProjectList from '../../components/projects/ProjectAuthList'
-
-const getVisibleProjectAuths = (projectAuths, filter) => {
- switch (filter) {
- case 'SHOW_ALL':
- return projectAuths
- case 'SHOW_OWN':
- return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel === 'OWN')
- case 'SHOW_SHARED':
- return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel !== 'OWN')
- default:
- return projectAuths
- }
-}
-
-const VisibleProjectAuthList = (props) => {
- const authorizations = useSelector((state) => {
- const denormalizedAuthorizations = state.projectList.authorizationsOfCurrentUser.map((authorizationIds) => {
- const authorization = state.objects.authorization[authorizationIds]
- authorization.user = state.objects.user[authorization.userId]
- authorization.project = state.objects.project[authorization.projectId]
- return authorization
- })
-
- return getVisibleProjectAuths(denormalizedAuthorizations, state.projectList.authVisibilityFilter)
- })
- return <ProjectList {...props} authorizations={authorizations} />
-}
-
-export default VisibleProjectAuthList
diff --git a/opendc-web/opendc-web-ui/src/data/experiments.js b/opendc-web/opendc-web-ui/src/data/experiments.js
new file mode 100644
index 00000000..aef512e5
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/data/experiments.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { useSelector } from 'react-redux'
+
+/**
+ * Return the available traces to experiment with.
+ */
+export function useTraces() {
+ return useSelector((state) => Object.values(state.objects.trace))
+}
+
+/**
+ * Return the available schedulers to experiment with.
+ */
+export function useSchedulers() {
+ return useSelector((state) => Object.values(state.objects.scheduler))
+}
diff --git a/opendc-web/opendc-web-ui/src/data/map.js b/opendc-web/opendc-web-ui/src/data/map.js
new file mode 100644
index 00000000..6aef6ac5
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/data/map.js
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { useSelector } from 'react-redux'
+
+/**
+ * Return the map scale.
+ */
+export function useMapScale() {
+ return useSelector((state) => state.map.scale)
+}
+
+/**
+ * Return the map position.
+ */
+export function useMapPosition() {
+ return useSelector((state) => state.map.position)
+}
+
+export function useMapDimensions() {
+ return useSelector((state) => state.map.dimensions)
+}
diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js
new file mode 100644
index 00000000..de2bc0d3
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/data/project.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { useSelector } from 'react-redux'
+
+/**
+ * Return the available projects.
+ */
+export function useProjects() {
+ return useSelector((state) => state.projects)
+}
+
+/**
+ * Return the current active project.
+ */
+export function useActiveProject() {
+ return useSelector((state) =>
+ state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined
+ )
+}
+
+/**
+ * Return the active portfolio.
+ */
+export function useActivePortfolio() {
+ return useSelector((state) => state.objects.portfolio[state.currentPortfolioId])
+}
+
+/**
+ * Return the active scenario.
+ */
+export function useActiveScenario() {
+ return useSelector((state) => state.objects.scenario[state.currentScenarioId])
+}
+
+/**
+ * Return the portfolios for the specified project id.
+ */
+export function usePortfolios(projectId) {
+ return useSelector((state) => {
+ let portfolios = state.objects.project[projectId]
+ ? state.objects.project[projectId].portfolioIds.map((t) => state.objects.portfolio[t])
+ : []
+ if (portfolios.filter((t) => !t).length > 0) {
+ portfolios = []
+ }
+
+ return portfolios
+ })
+}
+
+/**
+ * Return the scenarios for the specified portfolio id.
+ */
+export function useScenarios(portfolioId) {
+ return useSelector((state) => {
+ let scenarios = state.objects.portfolio[portfolioId]
+ ? state.objects.portfolio[portfolioId].scenarioIds.map((t) => state.objects.scenario[t])
+ : []
+ if (scenarios.filter((t) => !t).length > 0) {
+ scenarios = []
+ }
+
+ return scenarios
+ })
+}
diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js
new file mode 100644
index 00000000..d3ffb3e1
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/data/topology.js
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { useSelector } from 'react-redux'
+
+/**
+ * Return the current active topology.
+ */
+export function useActiveTopology() {
+ return useSelector((state) => state.currentTopologyId !== '-1' && state.objects.topology[state.currentTopologyId])
+}
+
+/**
+ * Return the topologies for the active project.
+ */
+export function useProjectTopologies() {
+ return useSelector(({ currentProjectId, objects }) => {
+ if (currentProjectId === '-1' || !objects.project[currentProjectId]) {
+ return []
+ }
+
+ const topologies = objects.project[currentProjectId].topologyIds.map((t) => objects.topology[t])
+
+ if (topologies.filter((t) => !t).length > 0) {
+ return []
+ }
+
+ return topologies
+ })
+}
diff --git a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js b/opendc-web/opendc-web-ui/src/hotkeys.js
index 8260ace2..1c4d2621 100644
--- a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js
+++ b/opendc-web/opendc-web-ui/src/hotkeys.js
@@ -1,8 +1,6 @@
-const KeymapConfiguration = {
+export const KeymapConfiguration = {
MOVE_LEFT: ['a', 'left'],
MOVE_RIGHT: ['d', 'right'],
MOVE_UP: ['w', 'up'],
MOVE_DOWN: ['s', 'down'],
}
-
-export default KeymapConfiguration
diff --git a/opendc-web/opendc-web-ui/src/index.js b/opendc-web/opendc-web-ui/src/index.js
deleted file mode 100644
index ae3a5ddc..00000000
--- a/opendc-web/opendc-web-ui/src/index.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import * as Sentry from '@sentry/react'
-import { Integrations } from '@sentry/tracing'
-import { Provider } from 'react-redux'
-import { setupSocketConnection } from './api/socket'
-import './index.sass'
-import Routes from './routes'
-import config from './config'
-import configureStore from './store/configure-store'
-
-setupSocketConnection(() => {
- const store = configureStore()
-
- // Initialize Sentry if the user has configured a DSN
- const dsn = config['SENTRY_DSN']
- if (dsn) {
- Sentry.init({
- environment: process.env.NODE_ENV,
- dsn: dsn,
- integrations: [new Integrations.BrowserTracing()],
- tracesSampleRate: 0.1,
- })
- }
-
- ReactDOM.render(
- <Provider store={store}>
- <Routes />
- </Provider>,
- document.getElementById('root')
- )
-})
diff --git a/opendc-web/opendc-web-ui/src/index.sass b/opendc-web/opendc-web-ui/src/index.sass
deleted file mode 100644
index a78f7a19..00000000
--- a/opendc-web/opendc-web-ui/src/index.sass
+++ /dev/null
@@ -1,52 +0,0 @@
-@import "~bootstrap/scss/bootstrap"
-
-@import ./style-globals/_mixins.sass
-@import ./style-globals/_variables.sass
-
-html, body, #root
- margin: 0
- padding: 0
- width: 100%
- height: 100%
-
- font-family: Roboto, Helvetica, Verdana, sans-serif
- background: #eee
-
- // Scroll padding for top navbar
- scroll-padding-top: 60px
-
-.full-height
- position: relative
- height: 100% !important
-
-.page-container
- padding-top: 60px
-
-.text-page-container
- padding-top: 80px
- display: flex
- flex-flow: column
-
-.vertically-expanding-container
- flex: 1 1 auto
- overflow-y: auto
-
-.bottom-btn-container
- flex: 0 1 auto
- padding: 20px 0
-
-.btn, .list-group-item-action, .clickable
- +clickable
-
-.btn-circle
- +border-radius(50%)
-
-a, a:hover
- text-decoration: none
-
-.app-page-container
- padding-left: $side-bar-width
- padding-top: 15px
-
-.w-70
- width: 70% !important
diff --git a/opendc-web/opendc-web-ui/src/index.scss b/opendc-web/opendc-web-ui/src/index.scss
new file mode 100644
index 00000000..dbd9550c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/index.scss
@@ -0,0 +1,68 @@
+@import '~bootstrap/scss/bootstrap';
+
+@import './style/_mixins.scss';
+@import './style/_variables.scss';
+
+html,
+body,
+#__next {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+
+ font-family: Roboto, Helvetica, Verdana, sans-serif;
+ background: #eee;
+
+ // Scroll padding for top navbar
+ scroll-padding-top: 60px;
+}
+
+.full-height {
+ position: relative;
+ height: 100% !important;
+}
+
+.page-container {
+ padding-top: 60px;
+}
+
+.text-page-container {
+ padding-top: 80px;
+ display: flex;
+ flex-flow: column;
+}
+
+.vertically-expanding-container {
+ flex: 1 1 auto;
+ overflow-y: auto;
+}
+
+.bottom-btn-container {
+ flex: 0 1 auto;
+ padding: 20px 0;
+}
+
+.btn,
+.list-group-item-action,
+.clickable {
+ @include clickable;
+}
+
+.btn-circle {
+ border-radius: 50%;
+}
+
+a,
+a:hover {
+ text-decoration: none;
+}
+
+.app-page-container {
+ padding-left: $side-bar-width;
+ padding-top: 15px;
+}
+
+.w-70 {
+ width: 70% !important;
+}
diff --git a/opendc-web/opendc-web-ui/src/pages/404.js b/opendc-web/opendc-web-ui/src/pages/404.js
new file mode 100644
index 00000000..cc9281fc
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/404.js
@@ -0,0 +1,19 @@
+import React from 'react'
+import Head from 'next/head'
+import TerminalWindow from '../components/not-found/TerminalWindow'
+import style from './404.module.scss'
+
+const NotFound = () => {
+ return (
+ <>
+ <Head>
+ <title>Page Not Found - OpenDC</title>
+ </Head>
+ <div className={style['not-found-backdrop']}>
+ <TerminalWindow />
+ </div>
+ </>
+ )
+}
+
+export default NotFound
diff --git a/opendc-web/opendc-web-ui/src/pages/404.module.scss b/opendc-web/opendc-web-ui/src/pages/404.module.scss
new file mode 100644
index 00000000..e91c2780
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/404.module.scss
@@ -0,0 +1,8 @@
+.not-found-backdrop {
+ display: flex;
+
+ width: 100%;
+ height: 100%;
+
+ background-image: linear-gradient(135deg, #00678a, #008fbf, #00a6d6);
+}
diff --git a/opendc-web/opendc-web-ui/src/pages/App.js b/opendc-web/opendc-web-ui/src/pages/App.js
deleted file mode 100644
index ea62e8dc..00000000
--- a/opendc-web/opendc-web-ui/src/pages/App.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import PropTypes from 'prop-types'
-import React, { useEffect } from 'react'
-import { HotKeys } from 'react-hotkeys'
-import { useDispatch, useSelector } from 'react-redux'
-import { openPortfolioSucceeded } from '../actions/portfolios'
-import { openProjectSucceeded } from '../actions/projects'
-import ToolPanelComponent from '../components/app/map/controls/ToolPanelComponent'
-import LoadingScreen from '../components/app/map/LoadingScreen'
-import ScaleIndicatorContainer from '../containers/app/map/controls/ScaleIndicatorContainer'
-import MapStage from '../containers/app/map/MapStage'
-import TopologySidebarContainer from '../containers/app/sidebars/topology/TopologySidebarContainer'
-import DeleteMachineModal from '../containers/modals/DeleteMachineModal'
-import DeleteRackModal from '../containers/modals/DeleteRackModal'
-import DeleteRoomModal from '../containers/modals/DeleteRoomModal'
-import EditRackNameModal from '../containers/modals/EditRackNameModal'
-import EditRoomNameModal from '../containers/modals/EditRoomNameModal'
-import NewTopologyModal from '../containers/modals/NewTopologyModal'
-import AppNavbarContainer from '../containers/navigation/AppNavbarContainer'
-import ProjectSidebarContainer from '../containers/app/sidebars/project/ProjectSidebarContainer'
-import { openScenarioSucceeded } from '../actions/scenarios'
-import NewPortfolioModal from '../containers/modals/NewPortfolioModal'
-import NewScenarioModal from '../containers/modals/NewScenarioModal'
-import PortfolioResultsContainer from '../containers/app/results/PortfolioResultsContainer'
-import KeymapConfiguration from '../shortcuts/keymap'
-import { useDocumentTitle } from '../util/hooks'
-
-const App = ({ projectId, portfolioId, scenarioId }) => {
- const projectName = useSelector(
- (state) =>
- state.currentProjectId !== '-1' &&
- state.objects.project[state.currentProjectId] &&
- state.objects.project[state.currentProjectId].name
- )
- const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1')
-
- const dispatch = useDispatch()
- useEffect(() => {
- if (scenarioId) {
- dispatch(openScenarioSucceeded(projectId, portfolioId, scenarioId))
- } else if (portfolioId) {
- dispatch(openPortfolioSucceeded(projectId, portfolioId))
- } else {
- dispatch(openProjectSucceeded(projectId))
- }
- }, [projectId, portfolioId, scenarioId, dispatch])
-
- const constructionElements = topologyIsLoading ? (
- <div className="full-height d-flex align-items-center justify-content-center">
- <LoadingScreen />
- </div>
- ) : (
- <div className="full-height">
- <MapStage />
- <ScaleIndicatorContainer />
- <ToolPanelComponent />
- <ProjectSidebarContainer />
- <TopologySidebarContainer />
- </div>
- )
-
- const portfolioElements = (
- <div className="full-height app-page-container">
- <ProjectSidebarContainer />
- <div className="container-fluid full-height">
- <PortfolioResultsContainer />
- </div>
- </div>
- )
-
- const scenarioElements = (
- <div className="full-height app-page-container">
- <ProjectSidebarContainer />
- <div className="container-fluid full-height">
- <h2>Scenario loading</h2>
- </div>
- </div>
- )
-
- useDocumentTitle(projectName ? projectName + ' - OpenDC' : 'Simulation - OpenDC')
-
- return (
- <HotKeys keyMap={KeymapConfiguration} className="page-container full-height">
- <AppNavbarContainer fullWidth={true} />
- {scenarioId ? scenarioElements : portfolioId ? portfolioElements : constructionElements}
- <NewTopologyModal />
- <NewPortfolioModal />
- <NewScenarioModal />
- <EditRoomNameModal />
- <DeleteRoomModal />
- <EditRackNameModal />
- <DeleteRackModal />
- <DeleteMachineModal />
- </HotKeys>
- )
-}
-
-App.propTypes = {
- projectId: PropTypes.string.isRequired,
- portfolioId: PropTypes.string,
- scenarioId: PropTypes.string,
-}
-
-export default App
diff --git a/opendc-web/opendc-web-ui/src/pages/Home.sass b/opendc-web/opendc-web-ui/src/pages/Home.sass
deleted file mode 100644
index 79cb9698..00000000
--- a/opendc-web/opendc-web-ui/src/pages/Home.sass
+++ /dev/null
@@ -1,9 +0,0 @@
-.body-wrapper
- position: relative
- overflow-y: hidden
-
-.intro-section, .modeling-section, .technologies-section
- background-color: #fff
-
-.stakeholder-section, .simulation-section, .team-section
- background-color: #f2f2f2
diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.js b/opendc-web/opendc-web-ui/src/pages/NotFound.js
deleted file mode 100644
index b933ffa5..00000000
--- a/opendc-web/opendc-web-ui/src/pages/NotFound.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-import TerminalWindow from '../components/not-found/TerminalWindow'
-import './NotFound.sass'
-import { useDocumentTitle } from '../util/hooks'
-
-const NotFound = () => {
- useDocumentTitle('Page Not Found - OpenDC')
- return (
- <div className="not-found-backdrop">
- <TerminalWindow />
- </div>
- )
-}
-
-export default NotFound
diff --git a/opendc-web/opendc-web-ui/src/pages/NotFound.sass b/opendc-web/opendc-web-ui/src/pages/NotFound.sass
deleted file mode 100644
index 59231f7a..00000000
--- a/opendc-web/opendc-web-ui/src/pages/NotFound.sass
+++ /dev/null
@@ -1,11 +0,0 @@
-.not-found-backdrop
- position: absolute
- left: 0
- top: 0
-
- margin: 0
- padding: 0
- width: 100%
- height: 100%
-
- background-image: linear-gradient(135deg, #00678a, #008fbf, #00A6D6)
diff --git a/opendc-web/opendc-web-ui/src/pages/Profile.js b/opendc-web/opendc-web-ui/src/pages/Profile.js
deleted file mode 100644
index ea781686..00000000
--- a/opendc-web/opendc-web-ui/src/pages/Profile.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React from 'react'
-import { useDispatch } from 'react-redux'
-import { openDeleteProfileModal } from '../actions/modals/profile'
-import DeleteProfileModal from '../containers/modals/DeleteProfileModal'
-import AppNavbarContainer from '../containers/navigation/AppNavbarContainer'
-import { useDocumentTitle } from '../util/hooks'
-
-const Profile = () => {
- const dispatch = useDispatch()
- const onDelete = () => dispatch(openDeleteProfileModal())
-
- useDocumentTitle('My Profile - OpenDC')
- return (
- <div className="full-height">
- <AppNavbarContainer fullWidth={false} />
- <div className="container text-page-container full-height">
- <button className="btn btn-danger mb-2 ml-auto mr-auto" style={{ maxWidth: 300 }} onClick={onDelete}>
- Delete my account on OpenDC
- </button>
- <p className="text-muted text-center">
- This does not delete your Google account, but simply disconnects it from the OpenDC platform and
- deletes any project info that is associated with you (projects you own and any authorizations you
- may have on other projects).
- </p>
- </div>
- <DeleteProfileModal />
- </div>
- )
-}
-
-export default Profile
diff --git a/opendc-web/opendc-web-ui/src/pages/Projects.js b/opendc-web/opendc-web-ui/src/pages/Projects.js
deleted file mode 100644
index 5e642a03..00000000
--- a/opendc-web/opendc-web-ui/src/pages/Projects.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React, { useEffect } from 'react'
-import { useDispatch } from 'react-redux'
-import { fetchAuthorizationsOfCurrentUser } from '../actions/users'
-import ProjectFilterPanel from '../components/projects/FilterPanel'
-import NewProjectModal from '../containers/modals/NewProjectModal'
-import NewProjectButtonContainer from '../containers/projects/NewProjectButtonContainer'
-import VisibleProjectList from '../containers/projects/VisibleProjectAuthList'
-import AppNavbarContainer from '../containers/navigation/AppNavbarContainer'
-import { useDocumentTitle } from '../util/hooks'
-
-function Projects() {
- const dispatch = useDispatch()
-
- useEffect(() => dispatch(fetchAuthorizationsOfCurrentUser()))
- useDocumentTitle('My Projects - OpenDC')
-
- return (
- <div className="full-height">
- <AppNavbarContainer fullWidth={false} />
- <div className="container text-page-container full-height">
- <ProjectFilterPanel />
- <VisibleProjectList />
- <NewProjectButtonContainer />
- </div>
- <NewProjectModal />
- </div>
- )
-}
-
-export default Projects
diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js
new file mode 100644
index 00000000..4ef4445c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/_app.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import Head from 'next/head'
+import { Provider } from 'react-redux'
+import { useStore } from '../redux'
+import '../index.scss'
+import { AuthProvider, useAuth } from '../auth'
+import * as Sentry from '@sentry/react'
+import { Integrations } from '@sentry/tracing'
+
+// This setup is necessary to forward the Auth0 context to the Redux context
+const Inner = ({ Component, pageProps }) => {
+ const auth = useAuth()
+ const store = useStore(pageProps.initialReduxState, { auth })
+ return (
+ <Provider store={store}>
+ <Component {...pageProps} />
+ </Provider>
+ )
+}
+
+const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN
+// Initialize Sentry if the user has configured a DSN
+if (process.browser && dsn) {
+ if (dsn) {
+ Sentry.init({
+ environment: process.env.NODE_ENV,
+ dsn: dsn,
+ integrations: [new Integrations.BrowserTracing()],
+ tracesSampleRate: 0.1,
+ })
+ }
+}
+
+export default function App(props) {
+ return (
+ <>
+ <Head>
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
+ <meta name="theme-color" content="#00A6D6" />
+ </Head>
+ <Sentry.ErrorBoundary fallback={"An error has occurred"}>
+ <AuthProvider>
+ <Inner {...props} />
+ </AuthProvider>
+ </Sentry.ErrorBoundary>
+ </>
+ )
+}
diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js
new file mode 100644
index 00000000..8e4680c0
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/_document.js
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import Document, { Html, Head, Main, NextScript } from 'next/document'
+
+class OpenDCDocument extends Document {
+ render() {
+ return (
+ <Html>
+ <Head>
+ <meta charSet="utf-8" />
+ <meta name="theme-color" content="#00A6D6" />
+ <meta
+ name="description"
+ content="Collaborative Datacenter Simulation and Exploration for Everybody"
+ />
+ <meta name="author" content="@Large Research" />
+ <meta
+ name="keywords"
+ content="OpenDC, Datacenter, Simulation, Simulator, Collaborative, Distributed, Cluster"
+ />
+ <link rel="manifest" href="/manifest.json" />
+ <link rel="shortcut icon" href="/favicon.ico" />
+
+ {/* Twitter Card data */}
+ <meta name="twitter:card" content="summary" />
+ <meta name="twitter:site" content="@LargeResearch" />
+ <meta name="twitter:title" content="OpenDC" />
+ <meta
+ name="twitter:description"
+ content="Collaborative Datacenter Simulation and Exploration for Everybody"
+ />
+ <meta name="twitter:creator" content="@LargeResearch" />
+ <meta name="twitter:image" content="http://opendc.org/img/logo.png" />
+
+ {/* OpenGraph meta tags */}
+ <meta property="og:title" content="OpenDC" />
+ <meta property="og:site_name" content="OpenDC" />
+ <meta property="og:type" content="website" />
+ <meta property="og:image" content="http://opendc.org/img/logo.png" />
+ <meta property="og:url" content="http://opendc.org/" />
+ <meta
+ property="og:description"
+ content="OpenDC provides collaborative online datacenter modeling, diverse and effective datacenter simulation, and exploratory datacenter performance feedback."
+ />
+ <meta property="og:locale" content="en_US" />
+
+ {/* CDN Dependencies */}
+ <link
+ href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
+ rel="stylesheet"
+ />
+
+ {/* Google Analytics */}
+ <script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3" />
+ <script
+ dangerouslySetInnerHTML={{
+ __html: `
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'UA-84285092-3');
+ `,
+ }}
+ />
+ </Head>
+ <body>
+ <Main />
+ <NextScript />
+ </body>
+ </Html>
+ )
+ }
+}
+
+export default OpenDCDocument
diff --git a/opendc-web/opendc-web-ui/src/pages/Home.js b/opendc-web/opendc-web-ui/src/pages/index.js
index fb383426..bb904eb6 100644
--- a/opendc-web/opendc-web-ui/src/pages/Home.js
+++ b/opendc-web/opendc-web-ui/src/pages/index.js
@@ -1,4 +1,5 @@
import React from 'react'
+import Head from 'next/head'
import ContactSection from '../components/home/ContactSection'
import IntroSection from '../components/home/IntroSection'
import JumbotronHeader from '../components/home/JumbotronHeader'
@@ -8,25 +9,33 @@ import StakeholderSection from '../components/home/StakeholderSection'
import TeamSection from '../components/home/TeamSection'
import TechnologiesSection from '../components/home/TechnologiesSection'
import HomeNavbar from '../components/navigation/HomeNavbar'
-import './Home.sass'
-import { useDocumentTitle } from '../util/hooks'
+import {
+ introSection,
+ stakeholderSection,
+ modelingSection,
+ simulationSection,
+ technologiesSection,
+ teamSection,
+} from './index.module.scss'
function Home() {
- useDocumentTitle('OpenDC')
return (
- <div>
+ <>
+ <Head>
+ <title>OpenDC</title>
+ </Head>
<HomeNavbar />
<div className="body-wrapper page-container">
<JumbotronHeader />
- <IntroSection />
- <StakeholderSection />
- <ModelingSection />
- <SimulationSection />
- <TechnologiesSection />
- <TeamSection />
+ <IntroSection className={introSection} />
+ <StakeholderSection className={stakeholderSection} />
+ <ModelingSection className={modelingSection} />
+ <SimulationSection className={simulationSection} />
+ <TechnologiesSection className={technologiesSection} />
+ <TeamSection className={teamSection} />
<ContactSection />
</div>
- </div>
+ </>
)
}
diff --git a/opendc-web/opendc-web-ui/src/pages/index.module.scss b/opendc-web/opendc-web-ui/src/pages/index.module.scss
new file mode 100644
index 00000000..aed1d88f
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/index.module.scss
@@ -0,0 +1,16 @@
+.bodyWrapper {
+ position: relative;
+ overflow-y: hidden;
+}
+
+.introSection,
+.modelingSection,
+.technologiesSection {
+ background-color: #fff;
+}
+
+.stakeholderSection,
+.simulationSection,
+.teamSection {
+ background-color: #f2f2f2;
+}
diff --git a/opendc-web/opendc-web-ui/src/pages/logout.js b/opendc-web/opendc-web-ui/src/pages/logout.js
new file mode 100644
index 00000000..e96e0605
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/logout.js
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import React from 'react'
+import Head from 'next/head'
+import AppNavbarContainer from '../containers/navigation/AppNavbarContainer'
+
+function Logout() {
+ return (
+ <>
+ <Head>
+ <title>Logged Out - OpenDC</title>
+ </Head>
+ <AppNavbarContainer fullWidth={false} />
+ <span>Logged out successfully</span>
+ </>
+ )
+}
+
+export default Logout
diff --git a/opendc-web/opendc-web-ui/src/config.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js
index 13f4abf2..72316bc9 100644
--- a/opendc-web/opendc-web-ui/src/config.js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js
@@ -20,21 +20,18 @@
* SOFTWARE.
*/
-function getConfig(name) {
- if (process.env.NODE_ENV === 'production' && window.config_overrides) {
- const value = window.config_overrides[name]
- if (value !== `$${name}`) {
- return value
- }
- }
+import { useRouter } from 'next/router'
+import App from '../../../containers/app/App'
- return process.env[name]
-}
+function Project() {
+ const router = useRouter()
+ const { project } = router.query
+
+ if (project) {
+ return <App projectId={project} />
+ }
-const config = {
- API_BASE_URL: getConfig('REACT_APP_API_BASE_URL'),
- OAUTH_CLIENT_ID: getConfig('REACT_APP_OAUTH_CLIENT_ID'),
- SENTRY_DSN: getConfig('REACT_APP_SENTRY_DSN'),
+ return <div />
}
-export default config
+export default Project
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js
new file mode 100644
index 00000000..76a8d23b
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { useRouter } from 'next/router'
+import App from '../../../../containers/app/App'
+
+function Project() {
+ const router = useRouter()
+ const { project, portfolio } = router.query
+
+ if (project && portfolio) {
+ return <App projectId={project} portfolioId={portfolio} />
+ }
+
+ return <div />
+}
+
+export default Project
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js
new file mode 100644
index 00000000..8603c228
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js
@@ -0,0 +1,37 @@
+import React, { useEffect, useState } from 'react'
+import Head from 'next/head'
+import { useDispatch } from 'react-redux'
+import ProjectFilterPanel from '../../components/projects/FilterPanel'
+import NewProjectContainer from '../../containers/projects/NewProjectContainer'
+import ProjectListContainer from '../../containers/projects/ProjectListContainer'
+import AppNavbarContainer from '../../containers/navigation/AppNavbarContainer'
+import { useRequireAuth } from '../../auth'
+import { Container } from 'reactstrap'
+import { fetchProjects } from '../../redux/actions/projects'
+
+function Projects() {
+ useRequireAuth()
+
+ const dispatch = useDispatch()
+ const [filter, setFilter] = useState('SHOW_ALL')
+
+ useEffect(() => dispatch(fetchProjects()), [])
+
+ return (
+ <>
+ <Head>
+ <title>My Projects - OpenDC</title>
+ </Head>
+ <div className="full-height">
+ <AppNavbarContainer fullWidth={false} />
+ <Container className="text-page-container">
+ <ProjectFilterPanel onSelect={setFilter} activeFilter={filter} />
+ <ProjectListContainer filter={filter} />
+ <NewProjectContainer />
+ </Container>
+ </div>
+ </>
+ )
+}
+
+export default Projects
diff --git a/opendc-web/opendc-web-ui/src/reducers/auth.js b/opendc-web/opendc-web-ui/src/reducers/auth.js
deleted file mode 100644
index 399a4b10..00000000
--- a/opendc-web/opendc-web-ui/src/reducers/auth.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { LOG_IN_SUCCEEDED, LOG_OUT } from '../actions/auth'
-
-export function auth(state = {}, action) {
- switch (action.type) {
- case LOG_IN_SUCCEEDED:
- return action.payload
- case LOG_OUT:
- return {}
- default:
- return state
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/reducers/modals.js b/opendc-web/opendc-web-ui/src/reducers/modals.js
deleted file mode 100644
index a7656373..00000000
--- a/opendc-web/opendc-web-ui/src/reducers/modals.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { combineReducers } from 'redux'
-import { CLOSE_DELETE_PROFILE_MODAL, OPEN_DELETE_PROFILE_MODAL } from '../actions/modals/profile'
-import { CLOSE_NEW_PROJECT_MODAL, OPEN_NEW_PROJECT_MODAL } from '../actions/modals/projects'
-import {
- CLOSE_NEW_TOPOLOGY_MODAL,
- CLOSE_DELETE_MACHINE_MODAL,
- CLOSE_DELETE_RACK_MODAL,
- CLOSE_DELETE_ROOM_MODAL,
- CLOSE_EDIT_RACK_NAME_MODAL,
- CLOSE_EDIT_ROOM_NAME_MODAL,
- OPEN_NEW_TOPOLOGY_MODAL,
- OPEN_DELETE_MACHINE_MODAL,
- OPEN_DELETE_RACK_MODAL,
- OPEN_DELETE_ROOM_MODAL,
- OPEN_EDIT_RACK_NAME_MODAL,
- OPEN_EDIT_ROOM_NAME_MODAL,
-} from '../actions/modals/topology'
-import { CLOSE_NEW_PORTFOLIO_MODAL, OPEN_NEW_PORTFOLIO_MODAL } from '../actions/modals/portfolios'
-import { CLOSE_NEW_SCENARIO_MODAL, OPEN_NEW_SCENARIO_MODAL } from '../actions/modals/scenarios'
-
-function modal(openAction, closeAction) {
- return function (state = false, action) {
- switch (action.type) {
- case openAction:
- return true
- case closeAction:
- return false
- default:
- return state
- }
- }
-}
-
-export const modals = combineReducers({
- newProjectModalVisible: modal(OPEN_NEW_PROJECT_MODAL, CLOSE_NEW_PROJECT_MODAL),
- deleteProfileModalVisible: modal(OPEN_DELETE_PROFILE_MODAL, CLOSE_DELETE_PROFILE_MODAL),
- changeTopologyModalVisible: modal(OPEN_NEW_TOPOLOGY_MODAL, CLOSE_NEW_TOPOLOGY_MODAL),
- editRoomNameModalVisible: modal(OPEN_EDIT_ROOM_NAME_MODAL, CLOSE_EDIT_ROOM_NAME_MODAL),
- deleteRoomModalVisible: modal(OPEN_DELETE_ROOM_MODAL, CLOSE_DELETE_ROOM_MODAL),
- editRackNameModalVisible: modal(OPEN_EDIT_RACK_NAME_MODAL, CLOSE_EDIT_RACK_NAME_MODAL),
- deleteRackModalVisible: modal(OPEN_DELETE_RACK_MODAL, CLOSE_DELETE_RACK_MODAL),
- deleteMachineModalVisible: modal(OPEN_DELETE_MACHINE_MODAL, CLOSE_DELETE_MACHINE_MODAL),
- newPortfolioModalVisible: modal(OPEN_NEW_PORTFOLIO_MODAL, CLOSE_NEW_PORTFOLIO_MODAL),
- newScenarioModalVisible: modal(OPEN_NEW_SCENARIO_MODAL, CLOSE_NEW_SCENARIO_MODAL),
-})
diff --git a/opendc-web/opendc-web-ui/src/reducers/project-list.js b/opendc-web/opendc-web-ui/src/reducers/project-list.js
deleted file mode 100644
index 1f1aa8d0..00000000
--- a/opendc-web/opendc-web-ui/src/reducers/project-list.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { combineReducers } from 'redux'
-import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, SET_AUTH_VISIBILITY_FILTER } from '../actions/projects'
-import { FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED } from '../actions/users'
-
-export function authorizationsOfCurrentUser(state = [], action) {
- switch (action.type) {
- case FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED:
- return action.authorizationsOfCurrentUser
- case ADD_PROJECT_SUCCEEDED:
- return [...state, action.authorization]
- case DELETE_PROJECT_SUCCEEDED:
- return state.filter((authorization) => authorization[1] !== action.id)
- default:
- return state
- }
-}
-
-export function authVisibilityFilter(state = 'SHOW_ALL', action) {
- switch (action.type) {
- case SET_AUTH_VISIBILITY_FILTER:
- return action.filter
- default:
- return state
- }
-}
-
-export const projectList = combineReducers({
- authorizationsOfCurrentUser,
- authVisibilityFilter,
-})
diff --git a/opendc-web/opendc-web-ui/src/actions/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js
index ff6b1fa3..ff6b1fa3 100644
--- a/opendc-web/opendc-web-ui/src/actions/interaction-level.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js
diff --git a/opendc-web/opendc-web-ui/src/actions/map.js b/opendc-web/opendc-web-ui/src/redux/actions/map.js
index 0d49d849..aa14cacd 100644
--- a/opendc-web/opendc-web-ui/src/actions/map.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/map.js
@@ -3,7 +3,7 @@ import {
MAP_MIN_SCALE,
MAP_SCALE_PER_EVENT,
MAP_SIZE_IN_PIXELS,
-} from '../components/app/map/MapConstants'
+} from '../../components/app/map/MapConstants'
export const SET_MAP_POSITION = 'SET_MAP_POSITION'
export const SET_MAP_DIMENSIONS = 'SET_MAP_DIMENSIONS'
@@ -64,6 +64,7 @@ export function setMapPositionWithBoundsCheck(x, y) {
const state = getState()
const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale
+
const updatedX =
x > 0
? 0
diff --git a/opendc-web/opendc-web-ui/src/actions/objects.js b/opendc-web/opendc-web-ui/src/redux/actions/objects.js
index 7b648b18..7b648b18 100644
--- a/opendc-web/opendc-web-ui/src/actions/objects.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/objects.js
diff --git a/opendc-web/opendc-web-ui/src/actions/portfolios.js b/opendc-web/opendc-web-ui/src/redux/actions/portfolios.js
index d37886d8..d37886d8 100644
--- a/opendc-web/opendc-web-ui/src/actions/portfolios.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/portfolios.js
diff --git a/opendc-web/opendc-web-ui/src/actions/prefabs.js b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js
index c112feed..c112feed 100644
--- a/opendc-web/opendc-web-ui/src/actions/prefabs.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js
diff --git a/opendc-web/opendc-web-ui/src/actions/projects.js b/opendc-web/opendc-web-ui/src/redux/actions/projects.js
index add0f242..a6324c43 100644
--- a/opendc-web/opendc-web-ui/src/actions/projects.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/projects.js
@@ -1,32 +1,35 @@
-export const SET_AUTH_VISIBILITY_FILTER = 'SET_AUTH_VISIBILITY_FILTER'
+export const FETCH_PROJECTS = 'FETCH_PROJECTS'
+export const FETCH_PROJECTS_SUCCEEDED = 'FETCH_PROJECTS_SUCCEEDED'
export const ADD_PROJECT = 'ADD_PROJECT'
export const ADD_PROJECT_SUCCEEDED = 'ADD_PROJECT_SUCCEEDED'
export const DELETE_PROJECT = 'DELETE_PROJECT'
export const DELETE_PROJECT_SUCCEEDED = 'DELETE_PROJECT_SUCCEEDED'
export const OPEN_PROJECT_SUCCEEDED = 'OPEN_PROJECT_SUCCEEDED'
-export function setAuthVisibilityFilter(filter) {
+export function fetchProjects() {
return {
- type: SET_AUTH_VISIBILITY_FILTER,
- filter,
+ type: FETCH_PROJECTS,
+ }
+}
+
+export function fetchProjectsSucceeded(projects) {
+ return {
+ type: FETCH_PROJECTS_SUCCEEDED,
+ projects,
}
}
export function addProject(name) {
- return (dispatch, getState) => {
- const { auth } = getState()
- dispatch({
- type: ADD_PROJECT,
- name,
- userId: auth.userId,
- })
+ return {
+ type: ADD_PROJECT,
+ name,
}
}
-export function addProjectSucceeded(authorization) {
+export function addProjectSucceeded(project) {
return {
type: ADD_PROJECT_SUCCEEDED,
- authorization,
+ project,
}
}
diff --git a/opendc-web/opendc-web-ui/src/actions/scenarios.js b/opendc-web/opendc-web-ui/src/redux/actions/scenarios.js
index c8a90762..c8a90762 100644
--- a/opendc-web/opendc-web-ui/src/actions/scenarios.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/scenarios.js
diff --git a/opendc-web/opendc-web-ui/src/actions/topologies.js b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
index dcce3b7d..dcce3b7d 100644
--- a/opendc-web/opendc-web-ui/src/actions/topologies.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
diff --git a/opendc-web/opendc-web-ui/src/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js
index 72deda6f..72deda6f 100644
--- a/opendc-web/opendc-web-ui/src/actions/topology/building.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js
diff --git a/opendc-web/opendc-web-ui/src/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js
index 17ccce5d..17ccce5d 100644
--- a/opendc-web/opendc-web-ui/src/actions/topology/machine.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js
diff --git a/opendc-web/opendc-web-ui/src/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js
index b117402e..b117402e 100644
--- a/opendc-web/opendc-web-ui/src/actions/topology/rack.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js
diff --git a/opendc-web/opendc-web-ui/src/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js
index 52cba680..61eea7fe 100644
--- a/opendc-web/opendc-web-ui/src/actions/topology/room.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js
@@ -1,4 +1,4 @@
-import { findTileWithPosition } from '../../util/tile-calculations'
+import { findTileWithPosition } from '../../../util/tile-calculations'
export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME'
export const DELETE_ROOM = 'DELETE_ROOM'
diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js
new file mode 100644
index 00000000..5c908957
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/index.js
@@ -0,0 +1,59 @@
+import { useMemo } from 'react'
+import {applyMiddleware, compose, createStore} from 'redux'
+import { createLogger } from 'redux-logger'
+import createSagaMiddleware from 'redux-saga'
+import thunk from 'redux-thunk'
+import rootReducer from './reducers'
+import rootSaga from './sagas'
+import { viewportAdjustmentMiddleware } from './middleware/viewport-adjustment'
+import { createReduxEnhancer } from "@sentry/react";
+
+let store
+
+function initStore(initialState, ctx) {
+ const sagaMiddleware = createSagaMiddleware({ context: ctx })
+
+ const middlewares = [thunk, sagaMiddleware, viewportAdjustmentMiddleware]
+
+ if (process.env.NODE_ENV !== 'production') {
+ middlewares.push(createLogger())
+ }
+
+ let middleware = applyMiddleware(...middlewares)
+
+ if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
+ middleware = compose(middleware, createReduxEnhancer())
+ }
+
+ const configuredStore = createStore(rootReducer, initialState, middleware)
+ sagaMiddleware.run(rootSaga)
+ store = configuredStore
+
+ return configuredStore
+}
+
+export const initializeStore = (preloadedState, ctx) => {
+ let _store = store ?? initStore(preloadedState, ctx)
+
+ // After navigating to a page with an initial Redux state, merge that state
+ // with the current state in the store, and create a new store
+ if (preloadedState && store) {
+ _store = initStore({
+ ...store.getState(),
+ ...preloadedState,
+ })
+ // Reset the current store
+ store = undefined
+ }
+
+ // For SSG and SSR always create a new store
+ if (typeof window === 'undefined') return _store
+ // Create the store once in the client
+ if (!store) store = _store
+
+ return _store
+}
+
+export function useStore(initialState, ctx) {
+ return useMemo(() => initializeStore(initialState, ctx), [initialState, ctx])
+}
diff --git a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js
index b4472c54..6b22eb80 100644
--- a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js
+++ b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js
@@ -1,5 +1,5 @@
-import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../../actions/map'
-import { SET_CURRENT_TOPOLOGY } from '../../actions/topology/building'
+import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../actions/map'
+import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building'
import {
MAP_MAX_SCALE,
MAP_MIN_SCALE,
diff --git a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js
index 257dddd2..257dddd2 100644
--- a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js
diff --git a/opendc-web/opendc-web-ui/src/reducers/current-ids.js b/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js
index 9b46aa60..9b46aa60 100644
--- a/opendc-web/opendc-web-ui/src/reducers/current-ids.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js
diff --git a/opendc-web/opendc-web-ui/src/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js
index 787d5a74..b143d417 100644
--- a/opendc-web/opendc-web-ui/src/reducers/index.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js
@@ -1,17 +1,14 @@
import { combineReducers } from 'redux'
-import { auth } from './auth'
import { construction } from './construction-mode'
import { currentPortfolioId, currentProjectId, currentScenarioId, currentTopologyId } from './current-ids'
import { interactionLevel } from './interaction-level'
import { map } from './map'
-import { modals } from './modals'
import { objects } from './objects'
-import { projectList } from './project-list'
+import { projects } from './projects'
const rootReducer = combineReducers({
objects,
- modals,
- projectList,
+ projects,
construction,
map,
currentProjectId,
@@ -19,7 +16,6 @@ const rootReducer = combineReducers({
currentPortfolioId,
currentScenarioId,
interactionLevel,
- auth,
})
export default rootReducer
diff --git a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js
index eafcb269..eafcb269 100644
--- a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js
diff --git a/opendc-web/opendc-web-ui/src/reducers/map.js b/opendc-web/opendc-web-ui/src/redux/reducers/map.js
index de712c15..de712c15 100644
--- a/opendc-web/opendc-web-ui/src/reducers/map.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/map.js
diff --git a/opendc-web/opendc-web-ui/src/reducers/objects.js b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
index 1f721b2e..a2483b43 100644
--- a/opendc-web/opendc-web-ui/src/reducers/objects.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
@@ -5,7 +5,7 @@ import {
ADD_TO_STORE,
REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP,
} from '../actions/objects'
-import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../util/unit-specifications'
+import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../../util/unit-specifications'
export const objects = combineReducers({
project: object('project'),
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/projects.js b/opendc-web/opendc-web-ui/src/redux/reducers/projects.js
new file mode 100644
index 00000000..a920e47f
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/projects.js
@@ -0,0 +1,14 @@
+import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED, FETCH_PROJECTS_SUCCEEDED } from '../actions/projects'
+
+export function projects(state = [], action) {
+ switch (action.type) {
+ case FETCH_PROJECTS_SUCCEEDED:
+ return action.projects
+ case ADD_PROJECT_SUCCEEDED:
+ return [...state, action.project]
+ case DELETE_PROJECT_SUCCEEDED:
+ return state.filter((project) => project._id !== action.id)
+ default:
+ return state
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js
index 6332b2fb..a8f44843 100644
--- a/opendc-web/opendc-web-ui/src/sagas/index.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js
@@ -1,7 +1,6 @@
import { takeEvery } from 'redux-saga/effects'
-import { LOG_IN } from '../actions/auth'
import { ADD_PORTFOLIO, DELETE_PORTFOLIO, OPEN_PORTFOLIO_SUCCEEDED, UPDATE_PORTFOLIO } from '../actions/portfolios'
-import { ADD_PROJECT, DELETE_PROJECT, OPEN_PROJECT_SUCCEEDED } from '../actions/projects'
+import { ADD_PROJECT, DELETE_PROJECT, FETCH_PROJECTS, OPEN_PROJECT_SUCCEEDED } from '../actions/projects'
import {
ADD_TILE,
CANCEL_NEW_ROOM_CONSTRUCTION,
@@ -11,10 +10,8 @@ import {
import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machine'
import { ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME } from '../actions/topology/rack'
import { ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME } from '../actions/topology/room'
-import { DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER } from '../actions/users'
import { onAddPortfolio, onDeletePortfolio, onOpenPortfolioSucceeded, onUpdatePortfolio } from './portfolios'
-import { onDeleteCurrentUser } from './profile'
-import { onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects'
+import { onFetchProjects, onOpenProjectSucceeded, onProjectAdd, onProjectDelete } from './projects'
import {
onAddMachine,
onAddRackToTile,
@@ -32,7 +29,6 @@ import {
onEditRoomName,
onStartNewRoomConstruction,
} from './topology'
-import { onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser } from './users'
import { ADD_TOPOLOGY, DELETE_TOPOLOGY } from '../actions/topologies'
import { ADD_SCENARIO, DELETE_SCENARIO, OPEN_SCENARIO_SUCCEEDED, UPDATE_SCENARIO } from '../actions/scenarios'
import { onAddScenario, onDeleteScenario, onOpenScenarioSucceeded, onUpdateScenario } from './scenarios'
@@ -40,14 +36,10 @@ import { onAddPrefab } from './prefabs'
import { ADD_PREFAB } from '../actions/prefabs'
export default function* rootSaga() {
- yield takeEvery(LOG_IN, onFetchLoggedInUser)
-
- yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser)
+ yield takeEvery(FETCH_PROJECTS, onFetchProjects)
yield takeEvery(ADD_PROJECT, onProjectAdd)
yield takeEvery(DELETE_PROJECT, onProjectDelete)
- yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser)
-
yield takeEvery(OPEN_PROJECT_SUCCEEDED, onOpenProjectSucceeded)
yield takeEvery(OPEN_PORTFOLIO_SUCCEEDED, onOpenPortfolioSucceeded)
yield takeEvery(OPEN_SCENARIO_SUCCEEDED, onOpenScenarioSucceeded)
diff --git a/opendc-web/opendc-web-ui/src/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js
index 313d9976..e5fd092d 100644
--- a/opendc-web/opendc-web-ui/src/sagas/objects.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js
@@ -1,10 +1,9 @@
-import { call, put, select } from 'redux-saga/effects'
+import { call, put, select, getContext } from 'redux-saga/effects'
import { addToStore } from '../actions/objects'
-import { getAllSchedulers } from '../api/routes/schedulers'
-import { getProject } from '../api/routes/projects'
-import { getAllTraces } from '../api/routes/traces'
-import { getUser } from '../api/routes/users'
-import { getTopology, updateTopology } from '../api/routes/topologies'
+import { getAllSchedulers } from '../../api/schedulers'
+import { getProject } from '../../api/projects'
+import { getAllTraces } from '../../api/traces'
+import { getTopology, updateTopology } from '../../api/topologies'
import { uuid } from 'uuidv4'
export const OBJECT_SELECTORS = {
@@ -42,9 +41,10 @@ function* fetchAndStoreObjects(objectType, apiCall) {
return objects
}
-export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id))
-
-export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id))
+export const fetchAndStoreProject = function* (id) {
+ const auth = yield getContext('auth')
+ return yield fetchAndStoreObject('project', id, call(getProject, auth, id))
+}
export const fetchAndStoreTopology = function* (id) {
const topologyStore = yield select(OBJECT_SELECTORS['topology'])
@@ -52,10 +52,11 @@ export const fetchAndStoreTopology = function* (id) {
const tileStore = yield select(OBJECT_SELECTORS['tile'])
const rackStore = yield select(OBJECT_SELECTORS['rack'])
const machineStore = yield select(OBJECT_SELECTORS['machine'])
+ const auth = yield getContext('auth')
let topology = topologyStore[id]
if (!topology) {
- const fullTopology = yield call(getTopology, id)
+ const fullTopology = yield call(getTopology, auth, id)
for (let roomIdx in fullTopology.rooms) {
const fullRoom = fullTopology.rooms[roomIdx]
@@ -142,8 +143,8 @@ const generateIdIfNotPresent = (obj) => {
export const updateTopologyOnServer = function* (id) {
const topology = yield getTopologyAsObject(id, true)
-
- yield call(updateTopology, topology)
+ const auth = yield getContext('auth')
+ yield call(updateTopology, auth, topology)
}
export const getTopologyAsObject = function* (id, keepIds) {
@@ -217,10 +218,14 @@ export const getRackById = function* (id, keepIds) {
}
}
-export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces))
+export const fetchAndStoreAllTraces = function* () {
+ const auth = yield getContext('auth')
+ return yield fetchAndStoreObjects('trace', call(getAllTraces, auth))
+}
export const fetchAndStoreAllSchedulers = function* () {
- const objects = yield call(getAllSchedulers)
+ const auth = yield getContext('auth')
+ const objects = yield call(getAllSchedulers, auth)
for (let object of objects) {
object._id = object.name
yield put(addToStore('scheduler', object))
diff --git a/opendc-web/opendc-web-ui/src/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js
index ed9bfd29..340cb490 100644
--- a/opendc-web/opendc-web-ui/src/sagas/portfolios.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js
@@ -1,14 +1,15 @@
-import { call, put, select, delay } from 'redux-saga/effects'
+import { call, put, select, delay, getContext } from 'redux-saga/effects'
import { addPropToStoreObject, addToStore } from '../actions/objects'
-import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../api/routes/portfolios'
-import { getProject } from '../api/routes/projects'
+import { addPortfolio, deletePortfolio, getPortfolio, updatePortfolio } from '../../api/portfolios'
+import { getProject } from '../../api/projects'
import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects'
import { fetchAndStoreAllTopologiesOfProject } from './topology'
-import { getScenario } from '../api/routes/scenarios'
+import { getScenario } from '../../api/scenarios'
export function* onOpenPortfolioSucceeded(action) {
try {
- const project = yield call(getProject, action.projectId)
+ const auth = yield getContext('auth')
+ const project = yield call(getProject, auth, action.projectId)
yield put(addToStore('project', project))
yield fetchAndStoreAllTopologiesOfProject(project._id)
yield fetchPortfoliosOfProject()
@@ -66,11 +67,12 @@ export function* fetchPortfoliosOfProject() {
export function* fetchPortfolioWithScenarios(portfolioId) {
try {
- const portfolio = yield call(getPortfolio, portfolioId)
+ const auth = yield getContext('auth')
+ const portfolio = yield call(getPortfolio, auth, portfolioId)
yield put(addToStore('portfolio', portfolio))
for (let i in portfolio.scenarioIds) {
- const scenario = yield call(getScenario, portfolio.scenarioIds[i])
+ const scenario = yield call(getScenario, auth, portfolio.scenarioIds[i])
yield put(addToStore('scenario', scenario))
}
return portfolio
@@ -82,9 +84,10 @@ export function* fetchPortfolioWithScenarios(portfolioId) {
export function* onAddPortfolio(action) {
try {
const currentProjectId = yield select((state) => state.currentProjectId)
-
+ const auth = yield getContext('auth')
const portfolio = yield call(
addPortfolio,
+ auth,
currentProjectId,
Object.assign({}, action.portfolio, {
projectId: currentProjectId,
@@ -106,7 +109,8 @@ export function* onAddPortfolio(action) {
export function* onUpdatePortfolio(action) {
try {
- const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio)
+ const auth = yield getContext('auth')
+ const portfolio = yield call(updatePortfolio, auth, action.portfolio._id, action.portfolio)
yield put(addToStore('portfolio', portfolio))
} catch (error) {
console.error(error)
@@ -115,7 +119,8 @@ export function* onUpdatePortfolio(action) {
export function* onDeletePortfolio(action) {
try {
- yield call(deletePortfolio, action.id)
+ const auth = yield getContext('auth')
+ yield call(deletePortfolio, auth, action.id)
const currentProjectId = yield select((state) => state.currentProjectId)
const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds)
diff --git a/opendc-web/opendc-web-ui/src/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
index 16cf3d62..ec679391 100644
--- a/opendc-web/opendc-web-ui/src/sagas/prefabs.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
@@ -1,13 +1,14 @@
-import { call, put, select } from 'redux-saga/effects'
+import { call, put, select, getContext } from 'redux-saga/effects'
import { addToStore } from '../actions/objects'
-import { addPrefab } from '../api/routes/prefabs'
+import { addPrefab } from '../../api/prefabs'
import { getRackById } from './objects'
export function* onAddPrefab(action) {
try {
const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId)
const currentRackJson = yield getRackById(currentRackId, false)
- const prefab = yield call(addPrefab, { name: action.name, rack: currentRackJson })
+ const auth = yield getContext('auth')
+ const prefab = yield call(addPrefab, auth, { name: action.name, rack: currentRackJson })
yield put(addToStore('prefab', prefab))
} catch (error) {
console.error(error)
diff --git a/opendc-web/opendc-web-ui/src/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js
index fdeea132..506df6ed 100644
--- a/opendc-web/opendc-web-ui/src/sagas/projects.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js
@@ -1,14 +1,15 @@
-import { call, put } from 'redux-saga/effects'
+import { call, put, getContext } from 'redux-saga/effects'
import { addToStore } from '../actions/objects'
-import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects'
-import { addProject, deleteProject, getProject } from '../api/routes/projects'
+import { addProjectSucceeded, deleteProjectSucceeded, fetchProjectsSucceeded } from '../actions/projects'
+import { addProject, deleteProject, getProject, getProjects } from '../../api/projects'
import { fetchAndStoreAllTopologiesOfProject } from './topology'
import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects'
import { fetchPortfoliosOfProject } from './portfolios'
export function* onOpenProjectSucceeded(action) {
try {
- const project = yield call(getProject, action.id)
+ const auth = yield getContext('auth')
+ const project = yield call(getProject, auth, action.id)
yield put(addToStore('project', project))
yield fetchAndStoreAllTopologiesOfProject(action.id, true)
@@ -22,17 +23,10 @@ export function* onOpenProjectSucceeded(action) {
export function* onProjectAdd(action) {
try {
- const project = yield call(addProject, { name: action.name })
+ const auth = yield getContext('auth')
+ const project = yield call(addProject, auth, { name: action.name })
yield put(addToStore('project', project))
-
- const authorization = {
- projectId: project._id,
- userId: action.userId,
- authorizationLevel: 'OWN',
- project,
- }
- yield put(addToStore('authorization', authorization))
- yield put(addProjectSucceeded([authorization.userId, authorization.projectId]))
+ yield put(addProjectSucceeded(project))
} catch (error) {
console.error(error)
}
@@ -40,9 +34,20 @@ export function* onProjectAdd(action) {
export function* onProjectDelete(action) {
try {
- yield call(deleteProject, action.id)
+ const auth = yield getContext('auth')
+ yield call(deleteProject, auth, action.id)
yield put(deleteProjectSucceeded(action.id))
} catch (error) {
console.error(error)
}
}
+
+export function* onFetchProjects(action) {
+ try {
+ const auth = yield getContext('auth')
+ const projects = yield call(getProjects, auth)
+ yield put(fetchProjectsSucceeded(projects))
+ } catch (error) {
+ console.error(error)
+ }
+}
diff --git a/opendc-web/opendc-web-ui/src/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js
index 59223610..bdb7c45d 100644
--- a/opendc-web/opendc-web-ui/src/sagas/scenarios.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js
@@ -1,14 +1,15 @@
-import { call, put, select } from 'redux-saga/effects'
+import { call, put, select, getContext } from 'redux-saga/effects'
import { addPropToStoreObject, addToStore } from '../actions/objects'
-import { getProject } from '../api/routes/projects'
+import { getProject } from '../../api/projects'
import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects'
import { fetchAndStoreAllTopologiesOfProject } from './topology'
-import { addScenario, deleteScenario, updateScenario } from '../api/routes/scenarios'
+import { addScenario, deleteScenario, updateScenario } from '../../api/scenarios'
import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios'
export function* onOpenScenarioSucceeded(action) {
try {
- const project = yield call(getProject, action.projectId)
+ const auth = yield getContext('auth')
+ const project = yield call(getProject, auth, action.projectId)
yield put(addToStore('project', project))
yield fetchAndStoreAllTopologiesOfProject(project._id)
yield fetchAndStoreAllSchedulers()
@@ -23,7 +24,8 @@ export function* onOpenScenarioSucceeded(action) {
export function* onAddScenario(action) {
try {
- const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario)
+ const auth = yield getContext('auth')
+ const scenario = yield call(addScenario, auth, action.scenario.portfolioId, action.scenario)
yield put(addToStore('scenario', scenario))
const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds)
@@ -40,7 +42,8 @@ export function* onAddScenario(action) {
export function* onUpdateScenario(action) {
try {
- const scenario = yield call(updateScenario, action.scenario._id, action.scenario)
+ const auth = yield getContext('auth')
+ const scenario = yield call(updateScenario, auth, action.scenario._id, action.scenario)
yield put(addToStore('scenario', scenario))
} catch (error) {
console.error(error)
@@ -49,7 +52,8 @@ export function* onUpdateScenario(action) {
export function* onDeleteScenario(action) {
try {
- yield call(deleteScenario, action.id)
+ const auth = yield getContext('auth')
+ yield call(deleteScenario, auth, action.id)
const currentPortfolioId = yield select((state) => state.currentPortfolioId)
const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds)
diff --git a/opendc-web/opendc-web-ui/src/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
index bba1ebb1..e5fd3d39 100644
--- a/opendc-web/opendc-web-ui/src/sagas/topology.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
@@ -1,4 +1,4 @@
-import { call, put, select } from 'redux-saga/effects'
+import { call, put, select, getContext } from 'redux-saga/effects'
import { goDownOneInteractionLevel } from '../actions/interaction-level'
import {
addIdToStoreObjectListProp,
@@ -15,10 +15,10 @@ import {
DEFAULT_RACK_POWER_CAPACITY,
DEFAULT_RACK_SLOT_CAPACITY,
MAX_NUM_UNITS_PER_MACHINE,
-} from '../components/app/map/MapConstants'
+} from '../../components/app/map/MapConstants'
import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects'
import { uuid } from 'uuidv4'
-import { addTopology, deleteTopology } from '../api/routes/topologies'
+import { addTopology, deleteTopology } from '../../api/topologies'
export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) {
try {
@@ -50,8 +50,10 @@ export function* onAddTopology(action) {
topologyToBeCreated = { name: action.name, rooms: [] }
}
+ const auth = yield getContext('auth')
const topology = yield call(
addTopology,
+ auth,
Object.assign({}, topologyToBeCreated, {
projectId: currentProjectId,
})
@@ -79,7 +81,8 @@ export function* onDeleteTopology(action) {
yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0]))
}
- yield call(deleteTopology, action.id)
+ const auth = yield getContext('auth')
+ yield call(deleteTopology, auth, action.id)
yield put(
addPropToStoreObject('project', currentProjectId, {
diff --git a/opendc-web/opendc-web-ui/src/routes/index.js b/opendc-web/opendc-web-ui/src/routes/index.js
deleted file mode 100644
index 4291a046..00000000
--- a/opendc-web/opendc-web-ui/src/routes/index.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react'
-import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
-import { userIsLoggedIn } from '../auth/index'
-import App from '../pages/App'
-import Home from '../pages/Home'
-import NotFound from '../pages/NotFound'
-import Profile from '../pages/Profile'
-import Projects from '../pages/Projects'
-
-const ProtectedComponent = (component) => () => (userIsLoggedIn() ? component : <Redirect to="/" />)
-const AppComponent = ({ match }) =>
- userIsLoggedIn() ? (
- <App
- projectId={match.params.projectId}
- portfolioId={match.params.portfolioId}
- scenarioId={match.params.scenarioId}
- />
- ) : (
- <Redirect to="/" />
- )
-
-const Routes = () => (
- <BrowserRouter>
- <Switch>
- <Route exact path="/" component={Home} />
- <Route exact path="/projects" render={ProtectedComponent(<Projects />)} />
- <Route exact path="/projects/:projectId" component={AppComponent} />
- <Route exact path="/projects/:projectId/portfolios/:portfolioId" component={AppComponent} />
- <Route
- exact
- path="/projects/:projectId/portfolios/:portfolioId/scenarios/:scenarioId"
- component={AppComponent}
- />
- <Route exact path="/profile" render={ProtectedComponent(<Profile />)} />
- <Route path="/*" component={NotFound} />
- </Switch>
- </BrowserRouter>
-)
-
-export default Routes
diff --git a/opendc-web/opendc-web-ui/src/sagas/profile.js b/opendc-web/opendc-web-ui/src/sagas/profile.js
deleted file mode 100644
index e914ba56..00000000
--- a/opendc-web/opendc-web-ui/src/sagas/profile.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { call, put } from 'redux-saga/effects'
-import { deleteCurrentUserSucceeded } from '../actions/users'
-import { deleteUser } from '../api/routes/users'
-
-export function* onDeleteCurrentUser(action) {
- try {
- yield call(deleteUser, action.userId)
- yield put(deleteCurrentUserSucceeded())
- } catch (error) {
- console.error(error)
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/sagas/users.js b/opendc-web/opendc-web-ui/src/sagas/users.js
deleted file mode 100644
index 74e652f6..00000000
--- a/opendc-web/opendc-web-ui/src/sagas/users.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import { call, put } from 'redux-saga/effects'
-import { logInSucceeded } from '../actions/auth'
-import { addToStore } from '../actions/objects'
-import { fetchAuthorizationsOfCurrentUserSucceeded } from '../actions/users'
-import { performTokenSignIn } from '../api/routes/token-signin'
-import { addUser } from '../api/routes/users'
-import { saveAuthLocalStorage } from '../auth/index'
-import { fetchAndStoreProject, fetchAndStoreUser } from './objects'
-
-export function* onFetchLoggedInUser(action) {
- try {
- const tokenResponse = yield call(performTokenSignIn, action.payload.authToken)
-
- let userId = tokenResponse.userId
-
- if (tokenResponse.isNewUser) {
- saveAuthLocalStorage({ authToken: action.payload.authToken })
- const newUser = yield call(addUser, action.payload)
- userId = newUser._id
- }
-
- yield put(logInSucceeded(Object.assign({ userId }, action.payload)))
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onFetchAuthorizationsOfCurrentUser(action) {
- try {
- const user = yield call(fetchAndStoreUser, action.userId)
-
- for (const authorization of user.authorizations) {
- authorization.userId = action.userId
- yield put(addToStore('authorization', authorization))
- yield fetchAndStoreProject(authorization.projectId)
- }
-
- const authorizationIds = user.authorizations.map((authorization) => [action.userId, authorization.projectId])
-
- yield put(fetchAuthorizationsOfCurrentUserSucceeded(authorizationIds))
- } catch (error) {
- console.error(error)
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/shapes/index.js b/opendc-web/opendc-web-ui/src/shapes.js
index 9fab6f5d..6c29eab0 100644
--- a/opendc-web/opendc-web-ui/src/shapes/index.js
+++ b/opendc-web/opendc-web-ui/src/shapes.js
@@ -1,8 +1,28 @@
-import PropTypes from 'prop-types'
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
-const Shapes = {}
+import PropTypes from 'prop-types'
-Shapes.User = PropTypes.shape({
+export const User = PropTypes.shape({
_id: PropTypes.string.isRequired,
googleId: PropTypes.string.isRequired,
email: PropTypes.string.isRequired,
@@ -11,7 +31,7 @@ Shapes.User = PropTypes.shape({
authorizations: PropTypes.array.isRequired,
})
-Shapes.Project = PropTypes.shape({
+export const Project = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
datetimeCreated: PropTypes.string.isRequired,
@@ -20,15 +40,15 @@ Shapes.Project = PropTypes.shape({
portfolioIds: PropTypes.array.isRequired,
})
-Shapes.Authorization = PropTypes.shape({
+export const Authorization = PropTypes.shape({
userId: PropTypes.string.isRequired,
- user: Shapes.User,
+ user: User,
projectId: PropTypes.string.isRequired,
- project: Shapes.Project,
- authorizationLevel: PropTypes.string.isRequired,
+ project: Project,
+ level: PropTypes.string.isRequired,
})
-Shapes.ProcessingUnit = PropTypes.shape({
+export const ProcessingUnit = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
clockRateMhz: PropTypes.number.isRequired,
@@ -36,7 +56,7 @@ Shapes.ProcessingUnit = PropTypes.shape({
energyConsumptionW: PropTypes.number.isRequired,
})
-Shapes.StorageUnit = PropTypes.shape({
+export const StorageUnit = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
speedMbPerS: PropTypes.number.isRequired,
@@ -44,59 +64,59 @@ Shapes.StorageUnit = PropTypes.shape({
energyConsumptionW: PropTypes.number.isRequired,
})
-Shapes.Machine = PropTypes.shape({
+export const Machine = PropTypes.shape({
_id: PropTypes.string.isRequired,
rackId: PropTypes.string.isRequired,
position: PropTypes.number.isRequired,
cpuIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- cpus: PropTypes.arrayOf(Shapes.ProcessingUnit),
+ cpus: PropTypes.arrayOf(ProcessingUnit),
gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- gpus: PropTypes.arrayOf(Shapes.ProcessingUnit),
+ gpus: PropTypes.arrayOf(ProcessingUnit),
memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- memories: PropTypes.arrayOf(Shapes.StorageUnit),
+ memories: PropTypes.arrayOf(StorageUnit),
storageIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- storages: PropTypes.arrayOf(Shapes.StorageUnit),
+ storages: PropTypes.arrayOf(StorageUnit),
})
-Shapes.Rack = PropTypes.shape({
+export const Rack = PropTypes.shape({
_id: PropTypes.string.isRequired,
capacity: PropTypes.number.isRequired,
powerCapacityW: PropTypes.number.isRequired,
- machines: PropTypes.arrayOf(Shapes.Machine),
+ machines: PropTypes.arrayOf(Machine),
})
-Shapes.Tile = PropTypes.shape({
+export const Tile = PropTypes.shape({
_id: PropTypes.string.isRequired,
roomId: PropTypes.string.isRequired,
positionX: PropTypes.number.isRequired,
positionY: PropTypes.number.isRequired,
rackId: PropTypes.string,
- rack: Shapes.Rack,
+ rack: Rack,
})
-Shapes.Room = PropTypes.shape({
+export const Room = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- tiles: PropTypes.arrayOf(Shapes.Tile),
+ tiles: PropTypes.arrayOf(Tile),
})
-Shapes.Topology = PropTypes.shape({
+export const Topology = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- rooms: PropTypes.arrayOf(Shapes.Room),
+ rooms: PropTypes.arrayOf(Room),
})
-Shapes.Scheduler = PropTypes.shape({
+export const Scheduler = PropTypes.shape({
name: PropTypes.string.isRequired,
})
-Shapes.Trace = PropTypes.shape({
+export const Trace = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
})
-Shapes.Portfolio = PropTypes.shape({
+export const Portfolio = PropTypes.shape({
_id: PropTypes.string.isRequired,
projectId: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
@@ -107,7 +127,7 @@ Shapes.Portfolio = PropTypes.shape({
}).isRequired,
})
-Shapes.Scenario = PropTypes.shape({
+export const Scenario = PropTypes.shape({
_id: PropTypes.string.isRequired,
portfolioId: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
@@ -116,33 +136,31 @@ Shapes.Scenario = PropTypes.shape({
}).isRequired,
trace: PropTypes.shape({
traceId: PropTypes.string.isRequired,
- trace: Shapes.Trace,
+ trace: Trace,
loadSamplingFraction: PropTypes.number.isRequired,
}).isRequired,
topology: PropTypes.shape({
topologyId: PropTypes.string.isRequired,
- topology: Shapes.Topology,
+ topology: Topology,
}).isRequired,
operational: PropTypes.shape({
failuresEnabled: PropTypes.bool.isRequired,
performanceInterferenceEnabled: PropTypes.bool.isRequired,
schedulerName: PropTypes.string.isRequired,
- scheduler: Shapes.Scheduler,
+ scheduler: Scheduler,
}).isRequired,
results: PropTypes.object,
})
-Shapes.WallSegment = PropTypes.shape({
+export const WallSegment = PropTypes.shape({
startPosX: PropTypes.number.isRequired,
startPosY: PropTypes.number.isRequired,
isHorizontal: PropTypes.bool.isRequired,
length: PropTypes.number.isRequired,
})
-Shapes.InteractionLevel = PropTypes.shape({
+export const InteractionLevel = PropTypes.shape({
mode: PropTypes.string.isRequired,
roomId: PropTypes.string,
rackId: PropTypes.string,
})
-
-export default Shapes
diff --git a/opendc-web/opendc-web-ui/src/store/configure-store.js b/opendc-web/opendc-web-ui/src/store/configure-store.js
deleted file mode 100644
index d8f343ed..00000000
--- a/opendc-web/opendc-web-ui/src/store/configure-store.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { applyMiddleware, compose, createStore } from 'redux'
-import persistState from 'redux-localstorage'
-import { createLogger } from 'redux-logger'
-import createSagaMiddleware from 'redux-saga'
-import thunk from 'redux-thunk'
-import { authRedirectMiddleware } from '../auth/index'
-import rootReducer from '../reducers/index'
-import rootSaga from '../sagas/index'
-import { dummyMiddleware } from './middlewares/dummy-middleware'
-import { viewportAdjustmentMiddleware } from './middlewares/viewport-adjustment'
-
-const sagaMiddleware = createSagaMiddleware()
-
-let logger
-if (process.env.NODE_ENV !== 'production') {
- logger = createLogger()
-}
-
-const middlewares = [
- process.env.NODE_ENV === 'production' ? dummyMiddleware : logger,
- thunk,
- sagaMiddleware,
- authRedirectMiddleware,
- viewportAdjustmentMiddleware,
-]
-
-export let store = undefined
-
-export default function configureStore() {
- const configuredStore = createStore(rootReducer, compose(persistState('auth'), applyMiddleware(...middlewares)))
- sagaMiddleware.run(rootSaga)
- store = configuredStore
-
- return configuredStore
-}
diff --git a/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js b/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js
deleted file mode 100644
index 5ba35691..00000000
--- a/opendc-web/opendc-web-ui/src/store/middlewares/dummy-middleware.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const dummyMiddleware = (store) => (next) => (action) => {
- next(action)
-}
diff --git a/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass b/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass
deleted file mode 100644
index d0a8d1ac..00000000
--- a/opendc-web/opendc-web-ui/src/style-globals/_mixins.sass
+++ /dev/null
@@ -1,21 +0,0 @@
-=transition($property, $time)
- -webkit-transition: $property $time
- -moz-transition: $property $time
- -o-transition: $property $time
- transition: $property $time
-
-=user-select
- -webkit-user-select: none
- -moz-user-select: none
- -ms-user-select: none
- user-select: none
-
-=border-radius($length)
- -webkit-border-radius: $length
- -moz-border-radius: $length
- border-radius: $length
-
-/* General Button Abstractions */
-=clickable
- cursor: pointer
- +user-select
diff --git a/opendc-web/opendc-web-ui/src/style-globals/_variables.sass b/opendc-web/opendc-web-ui/src/style-globals/_variables.sass
deleted file mode 100644
index 7553caa0..00000000
--- a/opendc-web/opendc-web-ui/src/style-globals/_variables.sass
+++ /dev/null
@@ -1,31 +0,0 @@
-// Sizes and Margins
-$document-padding: 20px
-$inter-element-margin: 5px
-$standard-border-radius: 5px
-$side-menu-width: 350px
-$color-indicator-width: 140px
-
-$global-padding: 30px
-$side-bar-width: 350px
-$navbar-height: 50px
-$navbar-padding: 10px
-
-// Durations
-$transition-length: 150ms
-
-// Colors
-$gray-very-dark: #5c5c5c
-$gray-dark: #aaa
-$gray-semi-dark: #bbb
-$gray-semi-light: #ccc
-$gray-light: #ddd
-$gray-very-light: #eee
-$blue: #00A6D6
-$blue-dark: #0087b5
-$blue-very-dark: #006182
-$blue-light: #deebf7
-
-// Media queries
-$screen-sm: 768px
-$screen-md: 992px
-$screen-lg: 1200px
diff --git a/opendc-web/opendc-web-ui/src/style/_mixins.scss b/opendc-web/opendc-web-ui/src/style/_mixins.scss
new file mode 100644
index 00000000..5f103cd7
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/style/_mixins.scss
@@ -0,0 +1,5 @@
+/* General Button Abstractions */
+@mixin clickable {
+ cursor: pointer;
+ user-select: none;
+}
diff --git a/opendc-web/opendc-web-ui/src/style/_variables.scss b/opendc-web/opendc-web-ui/src/style/_variables.scss
new file mode 100644
index 00000000..e3df6cbd
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/style/_variables.scss
@@ -0,0 +1,31 @@
+// Sizes and Margins
+$document-padding: 20px;
+$inter-element-margin: 5px;
+$standard-border-radius: 5px;
+$side-menu-width: 350px;
+$color-indicator-width: 140px;
+
+$global-padding: 30px;
+$side-bar-width: 350px;
+$navbar-height: 50px;
+$navbar-padding: 10px;
+
+// Durations
+$transition-length: 150ms;
+
+// Colors
+$gray-very-dark: #5c5c5c;
+$gray-dark: #aaa;
+$gray-semi-dark: #bbb;
+$gray-semi-light: #ccc;
+$gray-light: #ddd;
+$gray-very-light: #eee;
+$blue: #00a6d6;
+$blue-dark: #0087b5;
+$blue-very-dark: #006182;
+$blue-light: #deebf7;
+
+// Media queries
+$screen-sm: 768px;
+$screen-md: 992px;
+$screen-lg: 1200px;
diff --git a/opendc-web/opendc-web-ui/src/util/authorizations.js b/opendc-web/opendc-web-ui/src/util/authorizations.js
index 4086b35d..eb492d26 100644
--- a/opendc-web/opendc-web-ui/src/util/authorizations.js
+++ b/opendc-web/opendc-web-ui/src/util/authorizations.js
@@ -1,7 +1,9 @@
+import { faHome, faPencilAlt, faEye } from '@fortawesome/free-solid-svg-icons'
+
export const AUTH_ICON_MAP = {
- OWN: 'home',
- EDIT: 'pencil',
- VIEW: 'eye',
+ OWN: faHome,
+ EDIT: faPencilAlt,
+ VIEW: faEye,
}
export const AUTH_DESCRIPTION_MAP = {
diff --git a/opendc-web/opendc-web-ui/src/util/sidebar-space.js b/opendc-web/opendc-web-ui/src/util/sidebar-space.js
index ef09d40a..005c40f3 100644
--- a/opendc-web/opendc-web-ui/src/util/sidebar-space.js
+++ b/opendc-web/opendc-web-ui/src/util/sidebar-space.js
@@ -1,2 +1,2 @@
-export const isCollapsible = (location) =>
- location.pathname.indexOf('portfolios') === -1 && location.pathname.indexOf('scenarios') === -1
+export const isCollapsible = (router) =>
+ router.asPath.indexOf('portfolios') === -1 && router.asPath.indexOf('scenarios') === -1