summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-05-18 20:34:13 +0200
committerGitHub <noreply@github.com>2021-05-18 20:34:13 +0200
commit56bd2ef6b0583fee1dd2da5dceaf57feb07649c9 (patch)
tree6d4cfbc44c97cd3ec1e30aa977cd08f404b41b0d /opendc-web/opendc-web-ui/src
parent02776c958a3254735b2be7d9fb1627f75e7f80cd (diff)
parentce95cfdf803043e66e2279d0f76c6bfc64e7864e (diff)
Migrate to Auth0 as Identity Provider
This pull request removes the hard dependency on Google for authenticating users and migrates to Auth0 as Identity Provider for OpenDC. This has as benefit that we can authenticate users without having to manage user data ourselves and do not have a dependency on Google accounts anymore. - Frontend cleanup: - Use CSS modules everywhere to encapsulate the styling of React components. - Perform all communication in the frontend via the REST API (as opposed to WebSockets). The original approach was aimed at collaborative editing, but made normal operations harder to implement and debug. If we want to implement collaborative editing in the future, we can expose only a small WebSocket API specifically for collaborative editing. - Move to FontAwesome 5 (using the official React libraries) - Use Reactstrap where possible. Previously, we mixed raw Bootstrap classes with Reactstrap, which is confusing. - Reduce the scope of the Redux state. Some state in the frontend application can be kept locally and does not need to be managed by Redux. - Migrate from Create React App (CRA) to Next.js since it allows us to pre-render multiple pages as well as opt-in to Server Side Rendering. - Remove the Google login and use Auth0 for authentication now. - Use Node 16 - Backend cleanup: - Remove Socket.IO endpoint from backend, since it is not needed by the frontend anymore. Removing it reduces the attack surface of OpenDC as well as the maintenance efforts. - Use Auth0 JWT token for authorizing API accesses - Refactor API endpoints to use Flask Restful as opposed to our custom in-house routing logic. Previously, this was needed to support the Socket.IO endpoint, but increases maintenance effort. - Expose Swagger UI from API - Use Python 3.9 and uwsgi to host Flask application - Actualize OpenAPI schema and update to version 3.0. **Breaking API Changes** * This pull request removes the users collection from the database table. Instead, we now use the user identifier passed by Auth0 to identify the data that belongs to a user.
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