From d9e65dceb38cdb8dc4e464d388755f9456620566 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Sun, 16 May 2021 17:07:58 +0200 Subject: ui: Restructure OpenDC frontend This change updates the structure of the OpenDC frontend in order to improve the maintainability of the frontend. --- opendc-web/opendc-web-ui/src/actions/auth.js | 23 -- .../opendc-web-ui/src/actions/interaction-level.js | 50 ---- opendc-web/opendc-web-ui/src/actions/map.js | 83 ------ opendc-web/opendc-web-ui/src/actions/objects.js | 41 --- opendc-web/opendc-web-ui/src/actions/portfolios.js | 41 --- opendc-web/opendc-web-ui/src/actions/prefabs.js | 32 --- opendc-web/opendc-web-ui/src/actions/projects.js | 44 --- opendc-web/opendc-web-ui/src/actions/scenarios.js | 43 --- opendc-web/opendc-web-ui/src/actions/topologies.js | 17 -- .../opendc-web-ui/src/actions/topology/building.js | 105 ------- .../opendc-web-ui/src/actions/topology/machine.js | 25 -- .../opendc-web-ui/src/actions/topology/rack.js | 23 -- .../opendc-web-ui/src/actions/topology/room.js | 48 ---- opendc-web/opendc-web-ui/src/actions/users.js | 37 --- opendc-web/opendc-web-ui/src/api/portfolios.js | 39 +++ opendc-web/opendc-web-ui/src/api/prefabs.js | 39 +++ opendc-web/opendc-web-ui/src/api/projects.js | 39 +++ .../opendc-web-ui/src/api/routes/portfolios.js | 17 -- opendc-web/opendc-web-ui/src/api/routes/prefabs.js | 17 -- .../opendc-web-ui/src/api/routes/projects.js | 17 -- .../opendc-web-ui/src/api/routes/scenarios.js | 17 -- .../opendc-web-ui/src/api/routes/schedulers.js | 5 - .../opendc-web-ui/src/api/routes/token-signin.js | 10 - .../opendc-web-ui/src/api/routes/topologies.js | 17 -- opendc-web/opendc-web-ui/src/api/routes/traces.js | 5 - opendc-web/opendc-web-ui/src/api/routes/users.js | 17 -- opendc-web/opendc-web-ui/src/api/scenarios.js | 39 +++ opendc-web/opendc-web-ui/src/api/schedulers.js | 27 ++ opendc-web/opendc-web-ui/src/api/token-signin.js | 32 +++ opendc-web/opendc-web-ui/src/api/topologies.js | 39 +++ opendc-web/opendc-web-ui/src/api/traces.js | 27 ++ opendc-web/opendc-web-ui/src/api/users.js | 39 +++ opendc-web/opendc-web-ui/src/auth.js | 83 ++++++ opendc-web/opendc-web-ui/src/auth/hook.js | 49 ---- opendc-web/opendc-web-ui/src/auth/index.js | 57 ---- .../src/components/app/map/groups/WallGroup.js | 2 +- .../components/app/sidebars/Sidebar.module.scss | 4 +- .../src/components/home/ContentSection.module.scss | 2 +- .../src/components/navigation/Navbar.js | 2 +- .../src/components/navigation/Navbar.module.scss | 4 +- .../src/components/projects/ProjectRow.js | 2 +- opendc-web/opendc-web-ui/src/containers/app/App.js | 12 +- .../src/containers/app/map/GrayContainer.js | 2 +- .../src/containers/app/map/MapStage.js | 4 +- .../src/containers/app/map/RoomContainer.js | 2 +- .../src/containers/app/map/TileContainer.js | 2 +- .../src/containers/app/map/TopologyContainer.js | 2 +- .../app/map/controls/ScaleIndicatorContainer.js | 2 +- .../app/map/controls/ZoomControlContainer.js | 4 +- .../src/containers/app/map/layers/MapLayer.js | 2 +- .../containers/app/map/layers/ObjectHoverLayer.js | 2 +- .../containers/app/map/layers/RoomHoverLayer.js | 2 +- .../app/sidebars/project/PortfolioListContainer.js | 6 +- .../app/sidebars/project/ScenarioListContainer.js | 10 +- .../app/sidebars/project/TopologyListContainer.js | 6 +- .../building/NewRoomConstructionContainer.js | 2 +- .../topology/machine/BackToRackContainer.js | 2 +- .../topology/machine/DeleteMachineContainer.js | 2 +- .../sidebars/topology/machine/UnitAddContainer.js | 2 +- .../app/sidebars/topology/machine/UnitContainer.js | 2 +- .../sidebars/topology/rack/AddPrefabContainer.js | 2 +- .../sidebars/topology/rack/BackToRoomContainer.js | 2 +- .../sidebars/topology/rack/DeleteRackContainer.js | 2 +- .../sidebars/topology/rack/EmptySlotContainer.js | 2 +- .../app/sidebars/topology/rack/MachineContainer.js | 2 +- .../sidebars/topology/rack/RackNameContainer.js | 2 +- .../topology/room/BackToBuildingContainer.js | 2 +- .../sidebars/topology/room/DeleteRoomContainer.js | 2 +- .../sidebars/topology/room/EditRoomContainer.js | 2 +- .../topology/room/RackConstructionContainer.js | 2 +- .../sidebars/topology/room/RoomNameContainer.js | 2 +- .../opendc-web-ui/src/containers/auth/Login.js | 2 +- .../opendc-web-ui/src/containers/auth/Logout.js | 2 +- .../src/containers/auth/ProfileName.js | 2 +- .../containers/navigation/AppNavbarContainer.js | 2 +- .../src/containers/projects/NewProjectContainer.js | 2 +- .../src/containers/projects/ProjectActions.js | 2 +- opendc-web/opendc-web-ui/src/data/experiments.js | 37 +++ opendc-web/opendc-web-ui/src/data/map.js | 41 +++ opendc-web/opendc-web-ui/src/data/project.js | 78 ++++++ opendc-web/opendc-web-ui/src/data/topology.js | 49 ++++ opendc-web/opendc-web-ui/src/hotkeys.js | 6 + opendc-web/opendc-web-ui/src/index.scss | 4 +- opendc-web/opendc-web-ui/src/pages/_app.js | 2 +- opendc-web/opendc-web-ui/src/pages/profile.js | 26 +- .../opendc-web-ui/src/pages/projects/index.js | 4 +- opendc-web/opendc-web-ui/src/reducers/auth.js | 12 - .../src/reducers/construction-mode.js | 52 ---- .../opendc-web-ui/src/reducers/current-ids.js | 54 ---- opendc-web/opendc-web-ui/src/reducers/index.js | 23 -- .../src/reducers/interaction-level.js | 61 ---- opendc-web/opendc-web-ui/src/reducers/map.js | 35 --- opendc-web/opendc-web-ui/src/reducers/objects.js | 64 ----- .../opendc-web-ui/src/reducers/project-list.js | 18 -- opendc-web/opendc-web-ui/src/redux/actions/auth.js | 23 ++ .../src/redux/actions/interaction-level.js | 50 ++++ opendc-web/opendc-web-ui/src/redux/actions/map.js | 83 ++++++ .../opendc-web-ui/src/redux/actions/objects.js | 41 +++ .../opendc-web-ui/src/redux/actions/portfolios.js | 41 +++ .../opendc-web-ui/src/redux/actions/prefabs.js | 32 +++ .../opendc-web-ui/src/redux/actions/projects.js | 44 +++ .../opendc-web-ui/src/redux/actions/scenarios.js | 43 +++ .../opendc-web-ui/src/redux/actions/topologies.js | 17 ++ .../src/redux/actions/topology/building.js | 105 +++++++ .../src/redux/actions/topology/machine.js | 25 ++ .../src/redux/actions/topology/rack.js | 23 ++ .../src/redux/actions/topology/room.js | 48 ++++ .../opendc-web-ui/src/redux/actions/users.js | 37 +++ opendc-web/opendc-web-ui/src/redux/index.js | 60 ++++ .../src/redux/middleware/viewport-adjustment.js | 73 +++++ .../opendc-web-ui/src/redux/reducers/auth.js | 12 + .../src/redux/reducers/construction-mode.js | 52 ++++ .../src/redux/reducers/current-ids.js | 54 ++++ .../opendc-web-ui/src/redux/reducers/index.js | 23 ++ .../src/redux/reducers/interaction-level.js | 61 ++++ opendc-web/opendc-web-ui/src/redux/reducers/map.js | 35 +++ .../opendc-web-ui/src/redux/reducers/objects.js | 64 +++++ .../src/redux/reducers/project-list.js | 18 ++ opendc-web/opendc-web-ui/src/redux/sagas/index.js | 80 ++++++ .../opendc-web-ui/src/redux/sagas/objects.js | 229 +++++++++++++++ .../opendc-web-ui/src/redux/sagas/portfolios.js | 131 +++++++++ .../opendc-web-ui/src/redux/sagas/prefabs.js | 15 + .../opendc-web-ui/src/redux/sagas/profile.js | 12 + .../opendc-web-ui/src/redux/sagas/projects.js | 48 ++++ .../opendc-web-ui/src/redux/sagas/scenarios.js | 65 +++++ .../opendc-web-ui/src/redux/sagas/topology.js | 311 +++++++++++++++++++++ opendc-web/opendc-web-ui/src/redux/sagas/users.js | 44 +++ opendc-web/opendc-web-ui/src/sagas/index.js | 80 ------ opendc-web/opendc-web-ui/src/sagas/objects.js | 229 --------------- opendc-web/opendc-web-ui/src/sagas/portfolios.js | 131 --------- opendc-web/opendc-web-ui/src/sagas/prefabs.js | 15 - opendc-web/opendc-web-ui/src/sagas/profile.js | 12 - opendc-web/opendc-web-ui/src/sagas/projects.js | 48 ---- opendc-web/opendc-web-ui/src/sagas/scenarios.js | 65 ----- opendc-web/opendc-web-ui/src/sagas/topology.js | 311 --------------------- opendc-web/opendc-web-ui/src/sagas/users.js | 44 --- opendc-web/opendc-web-ui/src/shapes.js | 144 ++++++++++ opendc-web/opendc-web-ui/src/shapes/index.js | 144 ---------- opendc-web/opendc-web-ui/src/shortcuts/keymap.js | 8 - .../opendc-web-ui/src/store/configure-store.js | 60 ---- .../opendc-web-ui/src/store/hooks/experiments.js | 37 --- opendc-web/opendc-web-ui/src/store/hooks/map.js | 41 --- .../opendc-web-ui/src/store/hooks/project.js | 78 ------ .../opendc-web-ui/src/store/hooks/topology.js | 49 ---- .../src/store/middlewares/viewport-adjustment.js | 73 ----- .../opendc-web-ui/src/style-globals/_mixins.scss | 5 - .../src/style-globals/_variables.scss | 31 -- opendc-web/opendc-web-ui/src/style/_mixins.scss | 5 + opendc-web/opendc-web-ui/src/style/_variables.scss | 31 ++ 149 files changed, 2881 insertions(+), 2686 deletions(-) delete mode 100644 opendc-web/opendc-web-ui/src/actions/auth.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/interaction-level.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/map.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/objects.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/portfolios.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/prefabs.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/projects.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/scenarios.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/topologies.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/topology/building.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/topology/machine.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/topology/rack.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/topology/room.js delete mode 100644 opendc-web/opendc-web-ui/src/actions/users.js create mode 100644 opendc-web/opendc-web-ui/src/api/portfolios.js create mode 100644 opendc-web/opendc-web-ui/src/api/prefabs.js create mode 100644 opendc-web/opendc-web-ui/src/api/projects.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/portfolios.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/prefabs.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/projects.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/scenarios.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/schedulers.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/token-signin.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/topologies.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/traces.js delete mode 100644 opendc-web/opendc-web-ui/src/api/routes/users.js create mode 100644 opendc-web/opendc-web-ui/src/api/scenarios.js create mode 100644 opendc-web/opendc-web-ui/src/api/schedulers.js create mode 100644 opendc-web/opendc-web-ui/src/api/token-signin.js create mode 100644 opendc-web/opendc-web-ui/src/api/topologies.js create mode 100644 opendc-web/opendc-web-ui/src/api/traces.js create mode 100644 opendc-web/opendc-web-ui/src/api/users.js create mode 100644 opendc-web/opendc-web-ui/src/auth.js delete mode 100644 opendc-web/opendc-web-ui/src/auth/hook.js delete mode 100644 opendc-web/opendc-web-ui/src/auth/index.js create mode 100644 opendc-web/opendc-web-ui/src/data/experiments.js create mode 100644 opendc-web/opendc-web-ui/src/data/map.js create mode 100644 opendc-web/opendc-web-ui/src/data/project.js create mode 100644 opendc-web/opendc-web-ui/src/data/topology.js create mode 100644 opendc-web/opendc-web-ui/src/hotkeys.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/auth.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/construction-mode.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/current-ids.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/index.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/interaction-level.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/map.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/objects.js delete mode 100644 opendc-web/opendc-web-ui/src/reducers/project-list.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/auth.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/map.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/objects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/portfolios.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/prefabs.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/projects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/scenarios.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/topologies.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/topology/building.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/topology/room.js create mode 100644 opendc-web/opendc-web-ui/src/redux/actions/users.js create mode 100644 opendc-web/opendc-web-ui/src/redux/index.js create mode 100644 opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/auth.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/index.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/map.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/objects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/reducers/project-list.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/index.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/objects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/profile.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/projects.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/topology.js create mode 100644 opendc-web/opendc-web-ui/src/redux/sagas/users.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/index.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/objects.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/portfolios.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/prefabs.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/profile.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/projects.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/scenarios.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/topology.js delete mode 100644 opendc-web/opendc-web-ui/src/sagas/users.js create mode 100644 opendc-web/opendc-web-ui/src/shapes.js delete mode 100644 opendc-web/opendc-web-ui/src/shapes/index.js delete mode 100644 opendc-web/opendc-web-ui/src/shortcuts/keymap.js delete mode 100644 opendc-web/opendc-web-ui/src/store/configure-store.js delete mode 100644 opendc-web/opendc-web-ui/src/store/hooks/experiments.js delete mode 100644 opendc-web/opendc-web-ui/src/store/hooks/map.js delete mode 100644 opendc-web/opendc-web-ui/src/store/hooks/project.js delete mode 100644 opendc-web/opendc-web-ui/src/store/hooks/topology.js delete mode 100644 opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js delete mode 100644 opendc-web/opendc-web-ui/src/style-globals/_mixins.scss delete mode 100644 opendc-web/opendc-web-ui/src/style-globals/_variables.scss create mode 100644 opendc-web/opendc-web-ui/src/style/_mixins.scss create mode 100644 opendc-web/opendc-web-ui/src/style/_variables.scss (limited to 'opendc-web/opendc-web-ui/src') 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/interaction-level.js b/opendc-web/opendc-web-ui/src/actions/interaction-level.js deleted file mode 100644 index ff6b1fa3..00000000 --- a/opendc-web/opendc-web-ui/src/actions/interaction-level.js +++ /dev/null @@ -1,50 +0,0 @@ -export const GO_FROM_BUILDING_TO_ROOM = 'GO_FROM_BUILDING_TO_ROOM' -export const GO_FROM_ROOM_TO_RACK = 'GO_FROM_ROOM_TO_RACK' -export const GO_FROM_RACK_TO_MACHINE = 'GO_FROM_RACK_TO_MACHINE' -export const GO_DOWN_ONE_INTERACTION_LEVEL = 'GO_DOWN_ONE_INTERACTION_LEVEL' - -export function goFromBuildingToRoom(roomId) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'BUILDING') { - return - } - - dispatch({ - type: GO_FROM_BUILDING_TO_ROOM, - roomId, - }) - } -} - -export function goFromRoomToRack(tileId) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'ROOM') { - return - } - dispatch({ - type: GO_FROM_ROOM_TO_RACK, - tileId, - }) - } -} - -export function goFromRackToMachine(position) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'RACK') { - return - } - dispatch({ - type: GO_FROM_RACK_TO_MACHINE, - position, - }) - } -} - -export function goDownOneInteractionLevel() { - return { - type: GO_DOWN_ONE_INTERACTION_LEVEL, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/map.js b/opendc-web/opendc-web-ui/src/actions/map.js deleted file mode 100644 index 09196dca..00000000 --- a/opendc-web/opendc-web-ui/src/actions/map.js +++ /dev/null @@ -1,83 +0,0 @@ -import { - MAP_MAX_SCALE, - MAP_MIN_SCALE, - MAP_SCALE_PER_EVENT, - MAP_SIZE_IN_PIXELS, -} from '../components/app/map/MapConstants' - -export const SET_MAP_POSITION = 'SET_MAP_POSITION' -export const SET_MAP_DIMENSIONS = 'SET_MAP_DIMENSIONS' -export const SET_MAP_SCALE = 'SET_MAP_SCALE' - -export function setMapPosition(x, y) { - return { - type: SET_MAP_POSITION, - x, - y, - } -} - -export function setMapDimensions(width, height) { - return { - type: SET_MAP_DIMENSIONS, - width, - height, - } -} - -export function setMapScale(scale) { - return { - type: SET_MAP_SCALE, - scale, - } -} - -export function zoomInOnCenter(zoomIn) { - return (dispatch, getState) => { - const state = getState() - - dispatch(zoomInOnPosition(zoomIn, state.map.dimensions.width / 2, state.map.dimensions.height / 2)) - } -} - -export function zoomInOnPosition(zoomIn, x, y) { - return (dispatch, getState) => { - const state = getState() - - const centerPoint = { - x: x / state.map.scale - state.map.position.x / state.map.scale, - y: y / state.map.scale - state.map.position.y / state.map.scale, - } - const newScale = zoomIn ? state.map.scale * MAP_SCALE_PER_EVENT : state.map.scale / MAP_SCALE_PER_EVENT - const boundedScale = Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) - - const newX = -(centerPoint.x - x / boundedScale) * boundedScale - const newY = -(centerPoint.y - y / boundedScale) * boundedScale - - dispatch(setMapPositionWithBoundsCheck(newX, newY)) - dispatch(setMapScale(boundedScale)) - } -} - -export function setMapPositionWithBoundsCheck(x, y) { - return (dispatch, getState) => { - const state = getState() - - const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale - - const updatedX = - x > 0 - ? 0 - : x < -scaledMapSize + state.map.dimensions.width - ? -scaledMapSize + state.map.dimensions.width - : x - const updatedY = - y > 0 - ? 0 - : y < -scaledMapSize + state.map.dimensions.height - ? -scaledMapSize + state.map.dimensions.height - : y - - dispatch(setMapPosition(updatedX, updatedY)) - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/objects.js b/opendc-web/opendc-web-ui/src/actions/objects.js deleted file mode 100644 index 7b648b18..00000000 --- a/opendc-web/opendc-web-ui/src/actions/objects.js +++ /dev/null @@ -1,41 +0,0 @@ -export const ADD_TO_STORE = 'ADD_TO_STORE' -export const ADD_PROP_TO_STORE_OBJECT = 'ADD_PROP_TO_STORE_OBJECT' -export const ADD_ID_TO_STORE_OBJECT_LIST_PROP = 'ADD_ID_TO_STORE_OBJECT_LIST_PROP' -export const REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP = 'REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP' - -export function addToStore(objectType, object) { - return { - type: ADD_TO_STORE, - objectType, - object, - } -} - -export function addPropToStoreObject(objectType, objectId, propObject) { - return { - type: ADD_PROP_TO_STORE_OBJECT, - objectType, - objectId, - propObject, - } -} - -export function addIdToStoreObjectListProp(objectType, objectId, propName, id) { - return { - type: ADD_ID_TO_STORE_OBJECT_LIST_PROP, - objectType, - objectId, - propName, - id, - } -} - -export function removeIdFromStoreObjectListProp(objectType, objectId, propName, id) { - return { - type: REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP, - objectType, - objectId, - propName, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/portfolios.js b/opendc-web/opendc-web-ui/src/actions/portfolios.js deleted file mode 100644 index d37886d8..00000000 --- a/opendc-web/opendc-web-ui/src/actions/portfolios.js +++ /dev/null @@ -1,41 +0,0 @@ -export const ADD_PORTFOLIO = 'ADD_PORTFOLIO' -export const UPDATE_PORTFOLIO = 'UPDATE_PORTFOLIO' -export const DELETE_PORTFOLIO = 'DELETE_PORTFOLIO' -export const OPEN_PORTFOLIO_SUCCEEDED = 'OPEN_PORTFOLIO_SUCCEEDED' -export const SET_CURRENT_PORTFOLIO = 'SET_CURRENT_PORTFOLIO' - -export function addPortfolio(portfolio) { - return { - type: ADD_PORTFOLIO, - portfolio, - } -} - -export function updatePortfolio(portfolio) { - return { - type: UPDATE_PORTFOLIO, - portfolio, - } -} - -export function deletePortfolio(id) { - return { - type: DELETE_PORTFOLIO, - id, - } -} - -export function openPortfolioSucceeded(projectId, portfolioId) { - return { - type: OPEN_PORTFOLIO_SUCCEEDED, - projectId, - portfolioId, - } -} - -export function setCurrentPortfolio(portfolioId) { - return { - type: SET_CURRENT_PORTFOLIO, - portfolioId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/prefabs.js b/opendc-web/opendc-web-ui/src/actions/prefabs.js deleted file mode 100644 index c112feed..00000000 --- a/opendc-web/opendc-web-ui/src/actions/prefabs.js +++ /dev/null @@ -1,32 +0,0 @@ -export const ADD_PREFAB = 'ADD_PREFAB' -export const DELETE_PREFAB = 'DELETE_PREFAB' -export const DELETE_PREFAB_SUCCEEDED = 'DELETE_PREFAB_SUCCEEDED' -export const OPEN_PREFAB_SUCCEEDED = 'OPEN_PREFAB_SUCCEEDED' - -export function addPrefab(name) { - return { - type: ADD_PREFAB, - name, - } -} - -export function deletePrefab(id) { - return { - type: DELETE_PREFAB, - id, - } -} - -export function deletePrefabSucceeded(id) { - return { - type: DELETE_PREFAB_SUCCEEDED, - id, - } -} - -export function openPrefabSucceeded(id) { - return { - type: OPEN_PREFAB_SUCCEEDED, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/projects.js b/opendc-web/opendc-web-ui/src/actions/projects.js deleted file mode 100644 index 15158164..00000000 --- a/opendc-web/opendc-web-ui/src/actions/projects.js +++ /dev/null @@ -1,44 +0,0 @@ -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 addProject(name) { - return (dispatch, getState) => { - const { auth } = getState() - dispatch({ - type: ADD_PROJECT, - name, - userId: auth.userId, - }) - } -} - -export function addProjectSucceeded(authorization) { - return { - type: ADD_PROJECT_SUCCEEDED, - authorization, - } -} - -export function deleteProject(id) { - return { - type: DELETE_PROJECT, - id, - } -} - -export function deleteProjectSucceeded(id) { - return { - type: DELETE_PROJECT_SUCCEEDED, - id, - } -} - -export function openProjectSucceeded(id) { - return { - type: OPEN_PROJECT_SUCCEEDED, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/scenarios.js b/opendc-web/opendc-web-ui/src/actions/scenarios.js deleted file mode 100644 index c8a90762..00000000 --- a/opendc-web/opendc-web-ui/src/actions/scenarios.js +++ /dev/null @@ -1,43 +0,0 @@ -export const ADD_SCENARIO = 'ADD_SCENARIO' -export const UPDATE_SCENARIO = 'UPDATE_SCENARIO' -export const DELETE_SCENARIO = 'DELETE_SCENARIO' -export const OPEN_SCENARIO_SUCCEEDED = 'OPEN_SCENARIO_SUCCEEDED' -export const SET_CURRENT_SCENARIO = 'SET_CURRENT_SCENARIO' - -export function addScenario(scenario) { - return { - type: ADD_SCENARIO, - scenario, - } -} - -export function updateScenario(scenario) { - return { - type: UPDATE_SCENARIO, - scenario, - } -} - -export function deleteScenario(id) { - return { - type: DELETE_SCENARIO, - id, - } -} - -export function openScenarioSucceeded(projectId, portfolioId, scenarioId) { - return { - type: OPEN_SCENARIO_SUCCEEDED, - projectId, - portfolioId, - scenarioId, - } -} - -export function setCurrentScenario(portfolioId, scenarioId) { - return { - type: SET_CURRENT_SCENARIO, - portfolioId, - scenarioId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topologies.js b/opendc-web/opendc-web-ui/src/actions/topologies.js deleted file mode 100644 index dcce3b7d..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topologies.js +++ /dev/null @@ -1,17 +0,0 @@ -export const ADD_TOPOLOGY = 'ADD_TOPOLOGY' -export const DELETE_TOPOLOGY = 'DELETE_TOPOLOGY' - -export function addTopology(name, duplicateId) { - return { - type: ADD_TOPOLOGY, - name, - duplicateId, - } -} - -export function deleteTopology(id) { - return { - type: DELETE_TOPOLOGY, - id, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/building.js b/opendc-web/opendc-web-ui/src/actions/topology/building.js deleted file mode 100644 index 72deda6f..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/building.js +++ /dev/null @@ -1,105 +0,0 @@ -export const SET_CURRENT_TOPOLOGY = 'SET_CURRENT_TOPOLOGY' -export const START_NEW_ROOM_CONSTRUCTION = 'START_NEW_ROOM_CONSTRUCTION' -export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'START_NEW_ROOM_CONSTRUCTION_SUCCEEDED' -export const FINISH_NEW_ROOM_CONSTRUCTION = 'FINISH_NEW_ROOM_CONSTRUCTION' -export const CANCEL_NEW_ROOM_CONSTRUCTION = 'CANCEL_NEW_ROOM_CONSTRUCTION' -export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED' -export const START_ROOM_EDIT = 'START_ROOM_EDIT' -export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT' -export const ADD_TILE = 'ADD_TILE' -export const DELETE_TILE = 'DELETE_TILE' - -export function setCurrentTopology(topologyId) { - return { - type: SET_CURRENT_TOPOLOGY, - topologyId, - } -} - -export function startNewRoomConstruction() { - return { - type: START_NEW_ROOM_CONSTRUCTION, - } -} - -export function startNewRoomConstructionSucceeded(roomId) { - return { - type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - roomId, - } -} - -export function finishNewRoomConstruction() { - return (dispatch, getState) => { - const { objects, construction } = getState() - if (objects.room[construction.currentRoomInConstruction].tileIds.length === 0) { - dispatch(cancelNewRoomConstruction()) - return - } - - dispatch({ - type: FINISH_NEW_ROOM_CONSTRUCTION, - }) - } -} - -export function cancelNewRoomConstruction() { - return { - type: CANCEL_NEW_ROOM_CONSTRUCTION, - } -} - -export function cancelNewRoomConstructionSucceeded() { - return { - type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - } -} - -export function startRoomEdit() { - return (dispatch, getState) => { - const { interactionLevel } = getState() - dispatch({ - type: START_ROOM_EDIT, - roomId: interactionLevel.roomId, - }) - } -} - -export function finishRoomEdit() { - return { - type: FINISH_ROOM_EDIT, - } -} - -export function toggleTileAtLocation(positionX, positionY) { - return (dispatch, getState) => { - const { objects, construction } = getState() - - const tileIds = objects.room[construction.currentRoomInConstruction].tileIds - for (let index in tileIds) { - if ( - objects.tile[tileIds[index]].positionX === positionX && - objects.tile[tileIds[index]].positionY === positionY - ) { - dispatch(deleteTile(tileIds[index])) - return - } - } - dispatch(addTile(positionX, positionY)) - } -} - -export function addTile(positionX, positionY) { - return { - type: ADD_TILE, - positionX, - positionY, - } -} - -export function deleteTile(tileId) { - return { - type: DELETE_TILE, - tileId, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/actions/topology/machine.js deleted file mode 100644 index 17ccce5d..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/machine.js +++ /dev/null @@ -1,25 +0,0 @@ -export const DELETE_MACHINE = 'DELETE_MACHINE' -export const ADD_UNIT = 'ADD_UNIT' -export const DELETE_UNIT = 'DELETE_UNIT' - -export function deleteMachine() { - return { - type: DELETE_MACHINE, - } -} - -export function addUnit(unitType, id) { - return { - type: ADD_UNIT, - unitType, - id, - } -} - -export function deleteUnit(unitType, index) { - return { - type: DELETE_UNIT, - unitType, - index, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/actions/topology/rack.js deleted file mode 100644 index b117402e..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/rack.js +++ /dev/null @@ -1,23 +0,0 @@ -export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' -export const DELETE_RACK = 'DELETE_RACK' -export const ADD_MACHINE = 'ADD_MACHINE' - -export function editRackName(name) { - return { - type: EDIT_RACK_NAME, - name, - } -} - -export function deleteRack() { - return { - type: DELETE_RACK, - } -} - -export function addMachine(position) { - return { - type: ADD_MACHINE, - position, - } -} diff --git a/opendc-web/opendc-web-ui/src/actions/topology/room.js b/opendc-web/opendc-web-ui/src/actions/topology/room.js deleted file mode 100644 index 52cba680..00000000 --- a/opendc-web/opendc-web-ui/src/actions/topology/room.js +++ /dev/null @@ -1,48 +0,0 @@ -import { findTileWithPosition } from '../../util/tile-calculations' - -export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME' -export const DELETE_ROOM = 'DELETE_ROOM' -export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION' -export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION' -export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE' - -export function editRoomName(name) { - return { - type: EDIT_ROOM_NAME, - name, - } -} - -export function startRackConstruction() { - return { - type: START_RACK_CONSTRUCTION, - } -} - -export function stopRackConstruction() { - return { - type: STOP_RACK_CONSTRUCTION, - } -} - -export function addRackToTile(positionX, positionY) { - return (dispatch, getState) => { - const { objects, interactionLevel } = getState() - const currentRoom = objects.room[interactionLevel.roomId] - const tiles = currentRoom.tileIds.map((tileId) => objects.tile[tileId]) - const tile = findTileWithPosition(tiles, positionX, positionY) - - if (tile !== null) { - dispatch({ - type: ADD_RACK_TO_TILE, - tileId: tile._id, - }) - } - } -} - -export function deleteRoom() { - return { - type: DELETE_ROOM, - } -} 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/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js new file mode 100644 index 00000000..6202e702 --- /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(projectId, portfolio) { + return request(`projects/${projectId}/portfolios`, 'POST', { portfolio }) +} + +export function getPortfolio(portfolioId) { + return request(`portfolios/${portfolioId}`) +} + +export function updatePortfolio(portfolioId, portfolio) { + return request(`portfolios/${portfolioId}`, 'PUT', { portfolio }) +} + +export function deletePortfolio(portfolioId) { + return request(`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..a8bd3f3b --- /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(prefabId) { + return request(`prefabs/${prefabId}`) +} + +export function addPrefab(prefab) { + return request('prefabs', 'POST', { prefab }) +} + +export function updatePrefab(prefab) { + return request(`prefabs/${prefab._id}`, 'PUT', { prefab }) +} + +export function deletePrefab(prefabId) { + return request(`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..9ff7deda --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/projects.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 getProject(projectId) { + return request(`projects/${projectId}`) +} + +export function addProject(project) { + return request('projects', 'POST', { project }) +} + +export function updateProject(project) { + return request(`projects/${project._id}`, 'PUT', { project }) +} + +export function deleteProject(projectId) { + return request(`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 ba15e828..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/portfolios.js +++ /dev/null @@ -1,17 +0,0 @@ -import { request } from '../index' - -export function addPortfolio(projectId, portfolio) { - return request(`projects/${projectId}/portfolios`, 'POST', { portfolio }) -} - -export function getPortfolio(portfolioId) { - return request(`portfolios/${portfolioId}`) -} - -export function updatePortfolio(portfolioId, portfolio) { - return request(`portfolios/${portfolioId}`, 'PUT', { portfolio }) -} - -export function deletePortfolio(portfolioId) { - return request(`portfolios/${portfolioId}`, 'DELETE') -} 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 032e12bc..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/prefabs.js +++ /dev/null @@ -1,17 +0,0 @@ -import { request } from '../index' - -export function getPrefab(prefabId) { - return request(`prefabs/${prefabId}`) -} - -export function addPrefab(prefab) { - return request('prefabs', 'POST', { prefab }) -} - -export function updatePrefab(prefab) { - return request(`prefabs/${prefab._id}`, 'PUT', { prefab }) -} - -export function deletePrefab(prefabId) { - return request(`prefabs/${prefabId}`, 'DELETE') -} 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 cd46036f..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/projects.js +++ /dev/null @@ -1,17 +0,0 @@ -import { request } from '../index' - -export function getProject(projectId) { - return request(`projects/${projectId}`) -} - -export function addProject(project) { - return request('projects', 'POST', { project }) -} - -export function updateProject(project) { - return request(`projects/${project._id}`, 'PUT', { project }) -} - -export function deleteProject(projectId) { - return request(`projects/${projectId}`, 'DELETE') -} 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 00cc1eb0..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/scenarios.js +++ /dev/null @@ -1,17 +0,0 @@ -import { request } from '../index' - -export function addScenario(portfolioId, scenario) { - return request(`portfolios/${portfolioId}/scenarios`, 'POST', { scenario }) -} - -export function getScenario(scenarioId) { - return request(`scenarios/${scenarioId}`) -} - -export function updateScenario(scenarioId, scenario) { - return request(`scenarios/${scenarioId}`, 'PUT', { scenario }) -} - -export function deleteScenario(scenarioId) { - return request(`scenarios/${scenarioId}`, 'DELETE') -} 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 5e129d33..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/schedulers.js +++ /dev/null @@ -1,5 +0,0 @@ -import { request } from '../index' - -export function getAllSchedulers() { - return request('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 1c285bdb..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/token-signin.js +++ /dev/null @@ -1,10 +0,0 @@ -export function performTokenSignIn(token) { - const apiUrl = process.env.NEXT_PUBLIC_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 076895ff..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/topologies.js +++ /dev/null @@ -1,17 +0,0 @@ -import { request } from '../index' - -export function addTopology(topology) { - return request(`projects/${topology.projectId}/topologies`, 'POST', { topology }) -} - -export function getTopology(topologyId) { - return request(`topologies/${topologyId}`) -} - -export function updateTopology(topology) { - return request(`topologies/${topology._id}`, 'PUT', { topology }) -} - -export function deleteTopology(topologyId) { - return request(`topologies/${topologyId}`, 'DELETE') -} 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 eb2526ee..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/traces.js +++ /dev/null @@ -1,5 +0,0 @@ -import { request } from '../index' - -export function getAllTraces() { - return request('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 619aec1f..00000000 --- a/opendc-web/opendc-web-ui/src/api/routes/users.js +++ /dev/null @@ -1,17 +0,0 @@ -import { request } from '../index' - -export function getUserByEmail(email) { - return request(`users` + new URLSearchParams({ email })) -} - -export function addUser(user) { - return request('users', 'POST', { user }) -} - -export function getUser(userId) { - return request(`users/${userId}`) -} - -export function deleteUser(userId) { - return request(`users/${userId}`, 'DELETE') -} 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..9f8c717b --- /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(portfolioId, scenario) { + return request(`portfolios/${portfolioId}/scenarios`, 'POST', { scenario }) +} + +export function getScenario(scenarioId) { + return request(`scenarios/${scenarioId}`) +} + +export function updateScenario(scenarioId, scenario) { + return request(`scenarios/${scenarioId}`, 'PUT', { scenario }) +} + +export function deleteScenario(scenarioId) { + return request(`scenarios/${scenarioId}`, 'DELETE') +} diff --git a/opendc-web/opendc-web-ui/src/api/schedulers.js b/opendc-web/opendc-web-ui/src/api/schedulers.js new file mode 100644 index 00000000..7791e51e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/schedulers.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 getAllSchedulers() { + return request('schedulers') +} diff --git a/opendc-web/opendc-web-ui/src/api/token-signin.js b/opendc-web/opendc-web-ui/src/api/token-signin.js new file mode 100644 index 00000000..a3761fa1 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/token-signin.js @@ -0,0 +1,32 @@ +/* + * 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. + */ + +export function performTokenSignIn(token) { + const apiUrl = process.env.NEXT_PUBLIC_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/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js new file mode 100644 index 00000000..e6df73c7 --- /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(topology) { + return request(`projects/${topology.projectId}/topologies`, 'POST', { topology }) +} + +export function getTopology(topologyId) { + return request(`topologies/${topologyId}`) +} + +export function updateTopology(topology) { + return request(`topologies/${topology._id}`, 'PUT', { topology }) +} + +export function deleteTopology(topologyId) { + return request(`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..1c5cfa1d --- /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() { + return request('traces') +} diff --git a/opendc-web/opendc-web-ui/src/api/users.js b/opendc-web/opendc-web-ui/src/api/users.js new file mode 100644 index 00000000..3da030ad --- /dev/null +++ b/opendc-web/opendc-web-ui/src/api/users.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 getUserByEmail(email) { + return request(`users` + new URLSearchParams({ email })) +} + +export function addUser(user) { + return request('users', 'POST', { user }) +} + +export function getUser(userId) { + return request(`users/${userId}`) +} + +export function deleteUser(userId) { + return request(`users/${userId}`, 'DELETE') +} 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..faed9829 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/auth.js @@ -0,0 +1,83 @@ +import { LOG_IN_SUCCEEDED, LOG_OUT } from './redux/actions/auth' +import { DELETE_CURRENT_USER_SUCCEEDED } from './redux/actions/users' +import { useEffect, useState } from 'react' +import { useRouter } from 'next/router' +import { useSelector } from 'react-redux' + +const getAuthObject = () => { + const authItem = global.localStorage && 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) +} + +export function useIsLoggedIn() { + const [isLoggedIn, setLoggedIn] = useState(false) + + useEffect(() => { + setLoggedIn(userIsLoggedIn()) + }, []) + + return isLoggedIn +} + +export function useRequireAuth() { + const router = useRouter() + useEffect(() => { + if (!userIsLoggedIn()) { + router.replace('/') + } + }) +} + +export function useUser() { + return useSelector((state) => state.auth) +} diff --git a/opendc-web/opendc-web-ui/src/auth/hook.js b/opendc-web/opendc-web-ui/src/auth/hook.js deleted file mode 100644 index 8dd85fdd..00000000 --- a/opendc-web/opendc-web-ui/src/auth/hook.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useEffect, useState } from 'react' -import { userIsLoggedIn } from './index' -import { useRouter } from 'next/router' -import { useSelector } from 'react-redux' - -export function useIsLoggedIn() { - const [isLoggedIn, setLoggedIn] = useState(false) - - useEffect(() => { - setLoggedIn(userIsLoggedIn()) - }, []) - - return isLoggedIn -} - -export function useRequireAuth() { - const router = useRouter() - useEffect(() => { - if (!userIsLoggedIn()) { - router.replace('/') - } - }) -} - -export function useUser() { - return useSelector((state) => state.auth) -} 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 148e2e13..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 = global.localStorage && 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/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/WallGroup.js index 855d444f..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 { Tile } from '../../../../shapes/index' +import { Tile } from '../../../../shapes' import { deriveWallLocations } from '../../../../util/tile-calculations' import WallSegment from '../elements/WallSegment' 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 index d6be4d9b..19c6a97f 100644 --- 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 @@ -1,5 +1,5 @@ -@import '../../../style-globals/_variables.scss'; -@import '../../../style-globals/_mixins.scss'; +@import 'src/style/_variables.scss'; +@import 'src/style/_mixins.scss'; .collapseButton { position: absolute; 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 index 3d150c93..d27a0ce0 100644 --- a/opendc-web/opendc-web-ui/src/components/home/ContentSection.module.scss +++ b/opendc-web/opendc-web-ui/src/components/home/ContentSection.module.scss @@ -1,4 +1,4 @@ -@import '../../style-globals/_variables.scss'; +@import 'src/style/_variables.scss'; .contentSection { padding-top: 50px; 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 025d33a1..f16a3feb 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.js @@ -15,7 +15,7 @@ import Login from '../../containers/auth/Login' import Logout from '../../containers/auth/Logout' import ProfileName from '../../containers/auth/ProfileName' import { login, navbar, opendcBrand } from './Navbar.module.scss' -import { useIsLoggedIn } from '../../auth/hook' +import { useIsLoggedIn } from '../../auth' export const NAVBAR_HEIGHT = 60 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 index 2ea59a0f..8b9e4c97 100644 --- a/opendc-web/opendc-web-ui/src/components/navigation/Navbar.module.scss +++ b/opendc-web/opendc-web-ui/src/components/navigation/Navbar.module.scss @@ -1,5 +1,5 @@ -@import '../../style-globals/_mixins.scss'; -@import '../../style-globals/_variables.scss'; +@import 'src/style/_mixins.scss'; +@import 'src/style/_variables.scss'; .navbar { border-top: $blue 3px solid; diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js index bc63c805..a0aac098 100644 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js +++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectRow.js @@ -1,7 +1,7 @@ import classNames from 'classnames' import React from 'react' import ProjectActions from '../../containers/projects/ProjectActions' -import { Authorization } from '../../shapes/index' +import { Authorization } from '../../shapes' import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations' import { parseAndFormatDateTime } from '../../util/date-time' diff --git a/opendc-web/opendc-web-ui/src/containers/app/App.js b/opendc-web/opendc-web-ui/src/containers/app/App.js index 432435cf..ec9714ce 100644 --- a/opendc-web/opendc-web-ui/src/containers/app/App.js +++ b/opendc-web/opendc-web-ui/src/containers/app/App.js @@ -25,8 +25,8 @@ import React, { useEffect } from 'react' import Head from 'next/head' import { HotKeys } from 'react-hotkeys' import { useDispatch, useSelector } from 'react-redux' -import { openPortfolioSucceeded } from '../../actions/portfolios' -import { openProjectSucceeded } from '../../actions/projects' +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' @@ -34,11 +34,11 @@ 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 '../../actions/scenarios' +import { openScenarioSucceeded } from '../../redux/actions/scenarios' import PortfolioResultsContainer from '../../containers/app/results/PortfolioResultsContainer' -import KeymapConfiguration from '../../shortcuts/keymap' -import { useRequireAuth } from '../../auth/hook' -import { useActiveProject } from '../../store/hooks/project' +import { KeymapConfiguration } from '../../hotkeys' +import { useRequireAuth } from '../../auth' +import { useActiveProject } from '../../data/project' const App = ({ projectId, portfolioId, scenarioId }) => { useRequireAuth() 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 db9a2dd1..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,8 +1,8 @@ import React from 'react' import { useDispatch } from 'react-redux' -import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../actions/map' +import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../redux/actions/map' import MapStageComponent from '../../../components/app/map/MapStageComponent' -import { useMapDimensions, useMapPosition } from '../../../store/hooks/map' +import { useMapDimensions, useMapPosition } from '../../../data/map' const MapStage = () => { const position = useMapPosition() 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 25fbe3c0..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,7 +1,7 @@ import React from 'react' import { useSelector } from 'react-redux' import TopologyGroup from '../../../components/app/map/groups/TopologyGroup' -import { useActiveTopology } from '../../../store/hooks/topology' +import { useActiveTopology } from '../../../data/topology' const TopologyContainer = () => { const topology = useActiveTopology() 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 03834188..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,6 +1,6 @@ import React from 'react' import ScaleIndicatorComponent from '../../../../components/app/map/controls/ScaleIndicatorComponent' -import { useMapScale } from '../../../../store/hooks/map' +import { useMapScale } from '../../../../data/map' const ScaleIndicatorContainer = (props) => { const scale = useMapScale() 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 797bdf7f..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,8 +1,8 @@ import React from 'react' import { useDispatch } from 'react-redux' -import { zoomInOnCenter } from '../../../../actions/map' +import { zoomInOnCenter } from '../../../../redux/actions/map' import ZoomControlComponent from '../../../../components/app/map/controls/ZoomControlComponent' -import { useMapScale } from '../../../../store/hooks/map' +import { useMapScale } from '../../../../data/map' const ZoomControlContainer = () => { const dispatch = useDispatch() 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 ccd0ea8f..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,6 +1,6 @@ import React from 'react' import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent' -import { useMapPosition, useMapScale } from '../../../../store/hooks/map' +import { useMapPosition, useMapScale } from '../../../../data/map' const MapLayer = (props) => { const position = useMapPosition() 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 dca7d7db..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 @@ -2,11 +2,11 @@ import React, { useState } from 'react' import { useDispatch } from 'react-redux' import { useRouter } from 'next/router' import PortfolioListComponent from '../../../../components/app/sidebars/project/PortfolioListComponent' -import { addPortfolio, deletePortfolio, setCurrentPortfolio } from '../../../../actions/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 '../../../../store/hooks/project' +import { useActivePortfolio, useActiveProject, usePortfolios } from '../../../../data/project' const PortfolioListContainer = () => { const currentProjectId = useActiveProject()?._id 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 5d747820..7aaa1886 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,12 +1,12 @@ import React, { useState } from 'react' import { useDispatch } from 'react-redux' import ScenarioListComponent from '../../../../components/app/sidebars/project/ScenarioListComponent' -import { addScenario, 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 '../../../../store/hooks/topology' -import { useActiveScenario, useActiveProject, useScenarios } from '../../../../store/hooks/project' -import { useSchedulers, useTraces } from '../../../../store/hooks/experiments' +import { useProjectTopologies } from '../../../../data/topology' +import { useActiveScenario, useActiveProject, useScenarios } from '../../../../data/project' +import { useSchedulers, useTraces } from '../../../../data/experiments' const ScenarioListContainer = ({ portfolioId }) => { const currentProjectId = useActiveProject()?._id 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 3779705a..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,12 +1,12 @@ 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 { setCurrentTopology } from '../../../../redux/actions/topology/building' import { useRouter } from 'next/router' import { getState } from '../../../../util/state-utils' -import { addTopology, deleteTopology } from '../../../../actions/topologies' +import { addTopology, deleteTopology } from '../../../../redux/actions/topologies' import NewTopologyModalComponent from '../../../../components/modals/custom-components/NewTopologyModalComponent' -import { useActiveTopology, useProjectTopologies } from '../../../../store/hooks/topology' +import { useActiveTopology, useProjectTopologies } from '../../../../data/topology' const TopologyListContainer = () => { const dispatch = useDispatch() 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 2f171ce8..f3344424 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,7 +1,7 @@ import React, { useState } from 'react' import { useDispatch } from 'react-redux' import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteMachine } from '../../../../../actions/topology/machine' +import { deleteMachine } from '../../../../../redux/actions/topology/machine' import { Button } from 'reactstrap' const DeleteMachineContainer = () => { 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 4c7fd8bf..290803c2 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,7 +1,7 @@ import React, { useState } from 'react' import { useDispatch } from 'react-redux' import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteRack } from '../../../../../actions/topology/rack' +import { deleteRack } from '../../../../../redux/actions/topology/rack' import { Button } from 'reactstrap' const 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 dd9b05fc..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 @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import NameComponent from '../../../../../components/app/sidebars/topology/NameComponent' import TextInputModal from '../../../../../components/modals/TextInputModal' -import { editRackName } from '../../../../../actions/topology/rack' +import { editRackName } from '../../../../../redux/actions/topology/rack' const RackNameContainer = () => { const [isVisible, setVisible] = useState(false) 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 2062462f..3560c381 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 @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { useDispatch } from 'react-redux' import { Button } from 'reactstrap' import ConfirmationModal from '../../../../../components/modals/ConfirmationModal' -import { deleteRoom } from '../../../../../actions/topology/room' +import { deleteRoom } from '../../../../../redux/actions/topology/room' const DeleteRoomContainer = () => { const dispatch = useDispatch() 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 f03b0efb..2ecdfbcf 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,6 +1,6 @@ import React from 'react' import { useDispatch, useSelector } from 'react-redux' -import { finishRoomEdit, startRoomEdit } from '../../../../../actions/topology/building' +import { finishRoomEdit, startRoomEdit } from '../../../../../redux/actions/topology/building' import { Button } from 'reactstrap' const EditRoomContainer = () => { 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 946bb864..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 @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import NameComponent from '../../../../../components/app/sidebars/topology/NameComponent' import TextInputModal from '../../../../../components/modals/TextInputModal' -import { editRoomName } from '../../../../../actions/topology/room' +import { editRoomName } from '../../../../../redux/actions/topology/room' const RoomNameContainer = () => { const [isVisible, setVisible] = useState(false) 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 178f269e..b0da0d0e 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/Login.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/Login.js @@ -1,7 +1,7 @@ import React from 'react' import GoogleLogin from 'react-google-login' import { useDispatch } from 'react-redux' -import { logIn } from '../../actions/auth' +import { logIn } from '../../redux/actions/auth' import { Button } from 'reactstrap' function Login({ visible, className }) { 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..94d4d061 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js @@ -1,6 +1,6 @@ import React from 'react' import { useDispatch } from 'react-redux' -import { logOut } from '../../actions/auth' +import { logOut } from '../../redux/actions/auth' import LogoutButton from '../../components/navigation/LogoutButton' const Logout = (props) => { 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 cbbe42b4..3992c00f 100644 --- a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js +++ b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js @@ -1,5 +1,5 @@ import React from 'react' -import { useUser } from '../../auth/hook' +import { useUser } from '../../auth' function ProfileName() { const user = useUser() 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 47988827..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,6 +1,6 @@ import React from 'react' import AppNavbarComponent from '../../components/navigation/AppNavbarComponent' -import { useActiveProject } from '../../store/hooks/project' +import { useActiveProject } from '../../data/project' const AppNavbarContainer = (props) => { const project = useActiveProject() diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js index 5a8a2dcf..a0e607b2 100644 --- a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js +++ b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectContainer.js @@ -1,6 +1,6 @@ import React, { useState } from 'react' import { useDispatch } from 'react-redux' -import { addProject } from '../../actions/projects' +import { addProject } from '../../redux/actions/projects' import TextInputModal from '../../components/modals/TextInputModal' import { Button } from 'reactstrap' 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/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..0db49fdd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/data/project.js @@ -0,0 +1,78 @@ +/* + * 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 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/hotkeys.js b/opendc-web/opendc-web-ui/src/hotkeys.js new file mode 100644 index 00000000..1c4d2621 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/hotkeys.js @@ -0,0 +1,6 @@ +export const KeymapConfiguration = { + MOVE_LEFT: ['a', 'left'], + MOVE_RIGHT: ['d', 'right'], + MOVE_UP: ['w', 'up'], + MOVE_DOWN: ['s', 'down'], +} diff --git a/opendc-web/opendc-web-ui/src/index.scss b/opendc-web/opendc-web-ui/src/index.scss index 1a4d1303..dbd9550c 100644 --- a/opendc-web/opendc-web-ui/src/index.scss +++ b/opendc-web/opendc-web-ui/src/index.scss @@ -1,7 +1,7 @@ @import '~bootstrap/scss/bootstrap'; -@import './style-globals/_mixins.scss'; -@import './style-globals/_variables.scss'; +@import './style/_mixins.scss'; +@import './style/_variables.scss'; html, body, diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js index 77ffa698..761ae0cd 100644 --- a/opendc-web/opendc-web-ui/src/pages/_app.js +++ b/opendc-web/opendc-web-ui/src/pages/_app.js @@ -22,7 +22,7 @@ import Head from 'next/head' import { Provider } from 'react-redux' -import { useStore } from '../store/configure-store' +import { useStore } from '../redux' import '../index.scss' export default function App({ Component, pageProps }) { diff --git a/opendc-web/opendc-web-ui/src/pages/profile.js b/opendc-web/opendc-web-ui/src/pages/profile.js index de60037b..97afc4d9 100644 --- a/opendc-web/opendc-web-ui/src/pages/profile.js +++ b/opendc-web/opendc-web-ui/src/pages/profile.js @@ -1,10 +1,32 @@ +/* + * Copyright (c) 2021 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + import React, { useState } from 'react' import Head from 'next/head' import { useDispatch } from 'react-redux' import AppNavbarContainer from '../containers/navigation/AppNavbarContainer' import ConfirmationModal from '../components/modals/ConfirmationModal' -import { deleteCurrentUser } from '../actions/users' -import { useRequireAuth } from '../auth/hook' +import { deleteCurrentUser } from '../redux/actions/users' +import { useRequireAuth } from '../auth' const Profile = () => { useRequireAuth() diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js index dd61751f..05e9514f 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js @@ -1,12 +1,12 @@ import React, { useEffect, useState } from 'react' import Head from 'next/head' import { useDispatch } from 'react-redux' -import { fetchAuthorizationsOfCurrentUser } from '../../actions/users' +import { fetchAuthorizationsOfCurrentUser } from '../../redux/actions/users' import ProjectFilterPanel from '../../components/projects/FilterPanel' import NewProjectContainer from '../../containers/projects/NewProjectContainer' import VisibleProjectList from '../../containers/projects/VisibleProjectAuthList' import AppNavbarContainer from '../../containers/navigation/AppNavbarContainer' -import { useRequireAuth } from '../../auth/hook' +import { useRequireAuth } from '../../auth' import { Container } from 'reactstrap' function 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/construction-mode.js b/opendc-web/opendc-web-ui/src/reducers/construction-mode.js deleted file mode 100644 index 257dddd2..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/construction-mode.js +++ /dev/null @@ -1,52 +0,0 @@ -import { combineReducers } from 'redux' -import { GO_DOWN_ONE_INTERACTION_LEVEL } from '../actions/interaction-level' -import { - CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - FINISH_NEW_ROOM_CONSTRUCTION, - FINISH_ROOM_EDIT, - SET_CURRENT_TOPOLOGY, - START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - START_ROOM_EDIT, -} from '../actions/topology/building' -import { DELETE_ROOM, START_RACK_CONSTRUCTION, STOP_RACK_CONSTRUCTION } from '../actions/topology/room' -import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' -import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' - -export function currentRoomInConstruction(state = '-1', action) { - switch (action.type) { - case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED: - return action.roomId - case START_ROOM_EDIT: - return action.roomId - case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: - case FINISH_NEW_ROOM_CONSTRUCTION: - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case FINISH_ROOM_EDIT: - case SET_CURRENT_TOPOLOGY: - case DELETE_ROOM: - return '-1' - default: - return state - } -} - -export function inRackConstructionMode(state = false, action) { - switch (action.type) { - case START_RACK_CONSTRUCTION: - return true - case STOP_RACK_CONSTRUCTION: - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - case GO_DOWN_ONE_INTERACTION_LEVEL: - return false - default: - return state - } -} - -export const construction = combineReducers({ - currentRoomInConstruction, - inRackConstructionMode, -}) diff --git a/opendc-web/opendc-web-ui/src/reducers/current-ids.js b/opendc-web/opendc-web-ui/src/reducers/current-ids.js deleted file mode 100644 index 9b46aa60..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/current-ids.js +++ /dev/null @@ -1,54 +0,0 @@ -import { OPEN_PORTFOLIO_SUCCEEDED, SET_CURRENT_PORTFOLIO } from '../actions/portfolios' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { OPEN_SCENARIO_SUCCEEDED, SET_CURRENT_SCENARIO } from '../actions/scenarios' - -export function currentTopologyId(state = '-1', action) { - switch (action.type) { - case SET_CURRENT_TOPOLOGY: - return action.topologyId - default: - return state - } -} - -export function currentProjectId(state = '-1', action) { - switch (action.type) { - case OPEN_PROJECT_SUCCEEDED: - return action.id - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - return action.projectId - default: - return state - } -} - -export function currentPortfolioId(state = '-1', action) { - switch (action.type) { - case OPEN_PORTFOLIO_SUCCEEDED: - case SET_CURRENT_PORTFOLIO: - case SET_CURRENT_SCENARIO: - return action.portfolioId - case OPEN_SCENARIO_SUCCEEDED: - return action.portfolioId - case OPEN_PROJECT_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - return '-1' - default: - return state - } -} -export function currentScenarioId(state = '-1', action) { - switch (action.type) { - case OPEN_SCENARIO_SUCCEEDED: - case SET_CURRENT_SCENARIO: - return action.scenarioId - case OPEN_PORTFOLIO_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - case OPEN_PROJECT_SUCCEEDED: - return '-1' - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/index.js b/opendc-web/opendc-web-ui/src/reducers/index.js deleted file mode 100644 index 9dff379b..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/index.js +++ /dev/null @@ -1,23 +0,0 @@ -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 { objects } from './objects' -import { projectList } from './project-list' - -const rootReducer = combineReducers({ - objects, - projectList, - construction, - map, - currentProjectId, - currentTopologyId, - 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/reducers/interaction-level.js deleted file mode 100644 index eafcb269..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/interaction-level.js +++ /dev/null @@ -1,61 +0,0 @@ -import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' -import { - GO_DOWN_ONE_INTERACTION_LEVEL, - GO_FROM_BUILDING_TO_ROOM, - GO_FROM_RACK_TO_MACHINE, - GO_FROM_ROOM_TO_RACK, -} from '../actions/interaction-level' -import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' -import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' -import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' - -export function interactionLevel(state = { mode: 'BUILDING' }, action) { - switch (action.type) { - case OPEN_PORTFOLIO_SUCCEEDED: - case OPEN_SCENARIO_SUCCEEDED: - case OPEN_PROJECT_SUCCEEDED: - case SET_CURRENT_TOPOLOGY: - return { - mode: 'BUILDING', - } - case GO_FROM_BUILDING_TO_ROOM: - return { - mode: 'ROOM', - roomId: action.roomId, - } - case GO_FROM_ROOM_TO_RACK: - return { - mode: 'RACK', - roomId: state.roomId, - tileId: action.tileId, - } - case GO_FROM_RACK_TO_MACHINE: - return { - mode: 'MACHINE', - roomId: state.roomId, - tileId: state.tileId, - position: action.position, - } - case GO_DOWN_ONE_INTERACTION_LEVEL: - if (state.mode === 'ROOM') { - return { - mode: 'BUILDING', - } - } else if (state.mode === 'RACK') { - return { - mode: 'ROOM', - roomId: state.roomId, - } - } else if (state.mode === 'MACHINE') { - return { - mode: 'RACK', - roomId: state.roomId, - tileId: state.tileId, - } - } else { - return state - } - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/reducers/map.js b/opendc-web/opendc-web-ui/src/reducers/map.js deleted file mode 100644 index de712c15..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/map.js +++ /dev/null @@ -1,35 +0,0 @@ -import { combineReducers } from 'redux' -import { SET_MAP_DIMENSIONS, SET_MAP_POSITION, SET_MAP_SCALE } from '../actions/map' - -export function position(state = { x: 0, y: 0 }, action) { - switch (action.type) { - case SET_MAP_POSITION: - return { x: action.x, y: action.y } - default: - return state - } -} - -export function dimensions(state = { width: 600, height: 400 }, action) { - switch (action.type) { - case SET_MAP_DIMENSIONS: - return { width: action.width, height: action.height } - default: - return state - } -} - -export function scale(state = 1, action) { - switch (action.type) { - case SET_MAP_SCALE: - return action.scale - default: - return state - } -} - -export const map = combineReducers({ - position, - dimensions, - scale, -}) diff --git a/opendc-web/opendc-web-ui/src/reducers/objects.js b/opendc-web/opendc-web-ui/src/reducers/objects.js deleted file mode 100644 index 1f721b2e..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/objects.js +++ /dev/null @@ -1,64 +0,0 @@ -import { combineReducers } from 'redux' -import { - ADD_ID_TO_STORE_OBJECT_LIST_PROP, - ADD_PROP_TO_STORE_OBJECT, - 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' - -export const objects = combineReducers({ - project: object('project'), - user: object('user'), - authorization: objectWithId('authorization', (object) => [object.userId, object.projectId]), - cpu: object('cpu', CPU_UNITS), - gpu: object('gpu', GPU_UNITS), - memory: object('memory', MEMORY_UNITS), - storage: object('storage', STORAGE_UNITS), - machine: object('machine'), - rack: object('rack'), - tile: object('tile'), - room: object('room'), - topology: object('topology'), - trace: object('trace'), - scheduler: object('scheduler'), - portfolio: object('portfolio'), - scenario: object('scenario'), - prefab: object('prefab'), -}) - -function object(type, defaultState = {}) { - return objectWithId(type, (object) => object._id, defaultState) -} - -function objectWithId(type, getId, defaultState = {}) { - return (state = defaultState, action) => { - if (action.objectType !== type) { - return state - } - - if (action.type === ADD_TO_STORE) { - return Object.assign({}, state, { - [getId(action.object)]: action.object, - }) - } else if (action.type === ADD_PROP_TO_STORE_OBJECT) { - return Object.assign({}, state, { - [action.objectId]: Object.assign({}, state[action.objectId], action.propObject), - }) - } else if (action.type === ADD_ID_TO_STORE_OBJECT_LIST_PROP) { - return Object.assign({}, state, { - [action.objectId]: Object.assign({}, state[action.objectId], { - [action.propName]: [...state[action.objectId][action.propName], action.id], - }), - }) - } else if (action.type === REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP) { - return Object.assign({}, state, { - [action.objectId]: Object.assign({}, state[action.objectId], { - [action.propName]: state[action.objectId][action.propName].filter((id) => id !== action.id), - }), - }) - } - - return state - } -} 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 ad803db0..00000000 --- a/opendc-web/opendc-web-ui/src/reducers/project-list.js +++ /dev/null @@ -1,18 +0,0 @@ -import { combineReducers } from 'redux' -import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED } 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 const projectList = combineReducers({ authorizationsOfCurrentUser }) diff --git a/opendc-web/opendc-web-ui/src/redux/actions/auth.js b/opendc-web/opendc-web-ui/src/redux/actions/auth.js new file mode 100644 index 00000000..38c1a782 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/auth.js @@ -0,0 +1,23 @@ +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/redux/actions/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js new file mode 100644 index 00000000..ff6b1fa3 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js @@ -0,0 +1,50 @@ +export const GO_FROM_BUILDING_TO_ROOM = 'GO_FROM_BUILDING_TO_ROOM' +export const GO_FROM_ROOM_TO_RACK = 'GO_FROM_ROOM_TO_RACK' +export const GO_FROM_RACK_TO_MACHINE = 'GO_FROM_RACK_TO_MACHINE' +export const GO_DOWN_ONE_INTERACTION_LEVEL = 'GO_DOWN_ONE_INTERACTION_LEVEL' + +export function goFromBuildingToRoom(roomId) { + return (dispatch, getState) => { + const { interactionLevel } = getState() + if (interactionLevel.mode !== 'BUILDING') { + return + } + + dispatch({ + type: GO_FROM_BUILDING_TO_ROOM, + roomId, + }) + } +} + +export function goFromRoomToRack(tileId) { + return (dispatch, getState) => { + const { interactionLevel } = getState() + if (interactionLevel.mode !== 'ROOM') { + return + } + dispatch({ + type: GO_FROM_ROOM_TO_RACK, + tileId, + }) + } +} + +export function goFromRackToMachine(position) { + return (dispatch, getState) => { + const { interactionLevel } = getState() + if (interactionLevel.mode !== 'RACK') { + return + } + dispatch({ + type: GO_FROM_RACK_TO_MACHINE, + position, + }) + } +} + +export function goDownOneInteractionLevel() { + return { + type: GO_DOWN_ONE_INTERACTION_LEVEL, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/map.js b/opendc-web/opendc-web-ui/src/redux/actions/map.js new file mode 100644 index 00000000..aa14cacd --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/map.js @@ -0,0 +1,83 @@ +import { + MAP_MAX_SCALE, + MAP_MIN_SCALE, + MAP_SCALE_PER_EVENT, + MAP_SIZE_IN_PIXELS, +} from '../../components/app/map/MapConstants' + +export const SET_MAP_POSITION = 'SET_MAP_POSITION' +export const SET_MAP_DIMENSIONS = 'SET_MAP_DIMENSIONS' +export const SET_MAP_SCALE = 'SET_MAP_SCALE' + +export function setMapPosition(x, y) { + return { + type: SET_MAP_POSITION, + x, + y, + } +} + +export function setMapDimensions(width, height) { + return { + type: SET_MAP_DIMENSIONS, + width, + height, + } +} + +export function setMapScale(scale) { + return { + type: SET_MAP_SCALE, + scale, + } +} + +export function zoomInOnCenter(zoomIn) { + return (dispatch, getState) => { + const state = getState() + + dispatch(zoomInOnPosition(zoomIn, state.map.dimensions.width / 2, state.map.dimensions.height / 2)) + } +} + +export function zoomInOnPosition(zoomIn, x, y) { + return (dispatch, getState) => { + const state = getState() + + const centerPoint = { + x: x / state.map.scale - state.map.position.x / state.map.scale, + y: y / state.map.scale - state.map.position.y / state.map.scale, + } + const newScale = zoomIn ? state.map.scale * MAP_SCALE_PER_EVENT : state.map.scale / MAP_SCALE_PER_EVENT + const boundedScale = Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) + + const newX = -(centerPoint.x - x / boundedScale) * boundedScale + const newY = -(centerPoint.y - y / boundedScale) * boundedScale + + dispatch(setMapPositionWithBoundsCheck(newX, newY)) + dispatch(setMapScale(boundedScale)) + } +} + +export function setMapPositionWithBoundsCheck(x, y) { + return (dispatch, getState) => { + const state = getState() + + const scaledMapSize = MAP_SIZE_IN_PIXELS * state.map.scale + + const updatedX = + x > 0 + ? 0 + : x < -scaledMapSize + state.map.dimensions.width + ? -scaledMapSize + state.map.dimensions.width + : x + const updatedY = + y > 0 + ? 0 + : y < -scaledMapSize + state.map.dimensions.height + ? -scaledMapSize + state.map.dimensions.height + : y + + dispatch(setMapPosition(updatedX, updatedY)) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/objects.js b/opendc-web/opendc-web-ui/src/redux/actions/objects.js new file mode 100644 index 00000000..7b648b18 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/objects.js @@ -0,0 +1,41 @@ +export const ADD_TO_STORE = 'ADD_TO_STORE' +export const ADD_PROP_TO_STORE_OBJECT = 'ADD_PROP_TO_STORE_OBJECT' +export const ADD_ID_TO_STORE_OBJECT_LIST_PROP = 'ADD_ID_TO_STORE_OBJECT_LIST_PROP' +export const REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP = 'REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP' + +export function addToStore(objectType, object) { + return { + type: ADD_TO_STORE, + objectType, + object, + } +} + +export function addPropToStoreObject(objectType, objectId, propObject) { + return { + type: ADD_PROP_TO_STORE_OBJECT, + objectType, + objectId, + propObject, + } +} + +export function addIdToStoreObjectListProp(objectType, objectId, propName, id) { + return { + type: ADD_ID_TO_STORE_OBJECT_LIST_PROP, + objectType, + objectId, + propName, + id, + } +} + +export function removeIdFromStoreObjectListProp(objectType, objectId, propName, id) { + return { + type: REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP, + objectType, + objectId, + propName, + id, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/portfolios.js b/opendc-web/opendc-web-ui/src/redux/actions/portfolios.js new file mode 100644 index 00000000..d37886d8 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/portfolios.js @@ -0,0 +1,41 @@ +export const ADD_PORTFOLIO = 'ADD_PORTFOLIO' +export const UPDATE_PORTFOLIO = 'UPDATE_PORTFOLIO' +export const DELETE_PORTFOLIO = 'DELETE_PORTFOLIO' +export const OPEN_PORTFOLIO_SUCCEEDED = 'OPEN_PORTFOLIO_SUCCEEDED' +export const SET_CURRENT_PORTFOLIO = 'SET_CURRENT_PORTFOLIO' + +export function addPortfolio(portfolio) { + return { + type: ADD_PORTFOLIO, + portfolio, + } +} + +export function updatePortfolio(portfolio) { + return { + type: UPDATE_PORTFOLIO, + portfolio, + } +} + +export function deletePortfolio(id) { + return { + type: DELETE_PORTFOLIO, + id, + } +} + +export function openPortfolioSucceeded(projectId, portfolioId) { + return { + type: OPEN_PORTFOLIO_SUCCEEDED, + projectId, + portfolioId, + } +} + +export function setCurrentPortfolio(portfolioId) { + return { + type: SET_CURRENT_PORTFOLIO, + portfolioId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js new file mode 100644 index 00000000..c112feed --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js @@ -0,0 +1,32 @@ +export const ADD_PREFAB = 'ADD_PREFAB' +export const DELETE_PREFAB = 'DELETE_PREFAB' +export const DELETE_PREFAB_SUCCEEDED = 'DELETE_PREFAB_SUCCEEDED' +export const OPEN_PREFAB_SUCCEEDED = 'OPEN_PREFAB_SUCCEEDED' + +export function addPrefab(name) { + return { + type: ADD_PREFAB, + name, + } +} + +export function deletePrefab(id) { + return { + type: DELETE_PREFAB, + id, + } +} + +export function deletePrefabSucceeded(id) { + return { + type: DELETE_PREFAB_SUCCEEDED, + id, + } +} + +export function openPrefabSucceeded(id) { + return { + type: OPEN_PREFAB_SUCCEEDED, + id, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/projects.js b/opendc-web/opendc-web-ui/src/redux/actions/projects.js new file mode 100644 index 00000000..15158164 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/projects.js @@ -0,0 +1,44 @@ +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 addProject(name) { + return (dispatch, getState) => { + const { auth } = getState() + dispatch({ + type: ADD_PROJECT, + name, + userId: auth.userId, + }) + } +} + +export function addProjectSucceeded(authorization) { + return { + type: ADD_PROJECT_SUCCEEDED, + authorization, + } +} + +export function deleteProject(id) { + return { + type: DELETE_PROJECT, + id, + } +} + +export function deleteProjectSucceeded(id) { + return { + type: DELETE_PROJECT_SUCCEEDED, + id, + } +} + +export function openProjectSucceeded(id) { + return { + type: OPEN_PROJECT_SUCCEEDED, + id, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/scenarios.js b/opendc-web/opendc-web-ui/src/redux/actions/scenarios.js new file mode 100644 index 00000000..c8a90762 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/scenarios.js @@ -0,0 +1,43 @@ +export const ADD_SCENARIO = 'ADD_SCENARIO' +export const UPDATE_SCENARIO = 'UPDATE_SCENARIO' +export const DELETE_SCENARIO = 'DELETE_SCENARIO' +export const OPEN_SCENARIO_SUCCEEDED = 'OPEN_SCENARIO_SUCCEEDED' +export const SET_CURRENT_SCENARIO = 'SET_CURRENT_SCENARIO' + +export function addScenario(scenario) { + return { + type: ADD_SCENARIO, + scenario, + } +} + +export function updateScenario(scenario) { + return { + type: UPDATE_SCENARIO, + scenario, + } +} + +export function deleteScenario(id) { + return { + type: DELETE_SCENARIO, + id, + } +} + +export function openScenarioSucceeded(projectId, portfolioId, scenarioId) { + return { + type: OPEN_SCENARIO_SUCCEEDED, + projectId, + portfolioId, + scenarioId, + } +} + +export function setCurrentScenario(portfolioId, scenarioId) { + return { + type: SET_CURRENT_SCENARIO, + portfolioId, + scenarioId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topologies.js b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js new file mode 100644 index 00000000..dcce3b7d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js @@ -0,0 +1,17 @@ +export const ADD_TOPOLOGY = 'ADD_TOPOLOGY' +export const DELETE_TOPOLOGY = 'DELETE_TOPOLOGY' + +export function addTopology(name, duplicateId) { + return { + type: ADD_TOPOLOGY, + name, + duplicateId, + } +} + +export function deleteTopology(id) { + return { + type: DELETE_TOPOLOGY, + id, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js new file mode 100644 index 00000000..72deda6f --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js @@ -0,0 +1,105 @@ +export const SET_CURRENT_TOPOLOGY = 'SET_CURRENT_TOPOLOGY' +export const START_NEW_ROOM_CONSTRUCTION = 'START_NEW_ROOM_CONSTRUCTION' +export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'START_NEW_ROOM_CONSTRUCTION_SUCCEEDED' +export const FINISH_NEW_ROOM_CONSTRUCTION = 'FINISH_NEW_ROOM_CONSTRUCTION' +export const CANCEL_NEW_ROOM_CONSTRUCTION = 'CANCEL_NEW_ROOM_CONSTRUCTION' +export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED' +export const START_ROOM_EDIT = 'START_ROOM_EDIT' +export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT' +export const ADD_TILE = 'ADD_TILE' +export const DELETE_TILE = 'DELETE_TILE' + +export function setCurrentTopology(topologyId) { + return { + type: SET_CURRENT_TOPOLOGY, + topologyId, + } +} + +export function startNewRoomConstruction() { + return { + type: START_NEW_ROOM_CONSTRUCTION, + } +} + +export function startNewRoomConstructionSucceeded(roomId) { + return { + type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + roomId, + } +} + +export function finishNewRoomConstruction() { + return (dispatch, getState) => { + const { objects, construction } = getState() + if (objects.room[construction.currentRoomInConstruction].tileIds.length === 0) { + dispatch(cancelNewRoomConstruction()) + return + } + + dispatch({ + type: FINISH_NEW_ROOM_CONSTRUCTION, + }) + } +} + +export function cancelNewRoomConstruction() { + return { + type: CANCEL_NEW_ROOM_CONSTRUCTION, + } +} + +export function cancelNewRoomConstructionSucceeded() { + return { + type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + } +} + +export function startRoomEdit() { + return (dispatch, getState) => { + const { interactionLevel } = getState() + dispatch({ + type: START_ROOM_EDIT, + roomId: interactionLevel.roomId, + }) + } +} + +export function finishRoomEdit() { + return { + type: FINISH_ROOM_EDIT, + } +} + +export function toggleTileAtLocation(positionX, positionY) { + return (dispatch, getState) => { + const { objects, construction } = getState() + + const tileIds = objects.room[construction.currentRoomInConstruction].tileIds + for (let index in tileIds) { + if ( + objects.tile[tileIds[index]].positionX === positionX && + objects.tile[tileIds[index]].positionY === positionY + ) { + dispatch(deleteTile(tileIds[index])) + return + } + } + dispatch(addTile(positionX, positionY)) + } +} + +export function addTile(positionX, positionY) { + return { + type: ADD_TILE, + positionX, + positionY, + } +} + +export function deleteTile(tileId) { + return { + type: DELETE_TILE, + tileId, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js new file mode 100644 index 00000000..17ccce5d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js @@ -0,0 +1,25 @@ +export const DELETE_MACHINE = 'DELETE_MACHINE' +export const ADD_UNIT = 'ADD_UNIT' +export const DELETE_UNIT = 'DELETE_UNIT' + +export function deleteMachine() { + return { + type: DELETE_MACHINE, + } +} + +export function addUnit(unitType, id) { + return { + type: ADD_UNIT, + unitType, + id, + } +} + +export function deleteUnit(unitType, index) { + return { + type: DELETE_UNIT, + unitType, + index, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js new file mode 100644 index 00000000..b117402e --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js @@ -0,0 +1,23 @@ +export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' +export const DELETE_RACK = 'DELETE_RACK' +export const ADD_MACHINE = 'ADD_MACHINE' + +export function editRackName(name) { + return { + type: EDIT_RACK_NAME, + name, + } +} + +export function deleteRack() { + return { + type: DELETE_RACK, + } +} + +export function addMachine(position) { + return { + type: ADD_MACHINE, + position, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js new file mode 100644 index 00000000..61eea7fe --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js @@ -0,0 +1,48 @@ +import { findTileWithPosition } from '../../../util/tile-calculations' + +export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME' +export const DELETE_ROOM = 'DELETE_ROOM' +export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION' +export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION' +export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE' + +export function editRoomName(name) { + return { + type: EDIT_ROOM_NAME, + name, + } +} + +export function startRackConstruction() { + return { + type: START_RACK_CONSTRUCTION, + } +} + +export function stopRackConstruction() { + return { + type: STOP_RACK_CONSTRUCTION, + } +} + +export function addRackToTile(positionX, positionY) { + return (dispatch, getState) => { + const { objects, interactionLevel } = getState() + const currentRoom = objects.room[interactionLevel.roomId] + const tiles = currentRoom.tileIds.map((tileId) => objects.tile[tileId]) + const tile = findTileWithPosition(tiles, positionX, positionY) + + if (tile !== null) { + dispatch({ + type: ADD_RACK_TO_TILE, + tileId: tile._id, + }) + } + } +} + +export function deleteRoom() { + return { + type: DELETE_ROOM, + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/users.js b/opendc-web/opendc-web-ui/src/redux/actions/users.js new file mode 100644 index 00000000..4868ac34 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/actions/users.js @@ -0,0 +1,37 @@ +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/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js new file mode 100644 index 00000000..c706752b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/index.js @@ -0,0 +1,60 @@ +import { useMemo } from 'react' +import { applyMiddleware, compose, createStore } from 'redux' +import { createLogger } from 'redux-logger' +import persistState from 'redux-localstorage' +import createSagaMiddleware from 'redux-saga' +import thunk from 'redux-thunk' +import { authRedirectMiddleware } from '../auth' +import rootReducer from './reducers' +import rootSaga from './sagas' +import { viewportAdjustmentMiddleware } from './middleware/viewport-adjustment' + +let store + +function initStore(initialState) { + const sagaMiddleware = createSagaMiddleware() + + const middlewares = [thunk, sagaMiddleware, authRedirectMiddleware, viewportAdjustmentMiddleware] + + if (process.env.NODE_ENV !== 'production') { + middlewares.push(createLogger()) + } + + let enhancer = applyMiddleware(...middlewares) + + if (global.localStorage) { + enhancer = compose(persistState('auth'), enhancer) + } + + const configuredStore = createStore(rootReducer, enhancer) + sagaMiddleware.run(rootSaga) + store = configuredStore + + return configuredStore +} + +export const initializeStore = (preloadedState) => { + let _store = store ?? initStore(preloadedState) + + // 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) { + return useMemo(() => initializeStore(initialState), [initialState]) +} diff --git a/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js new file mode 100644 index 00000000..6b22eb80 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js @@ -0,0 +1,73 @@ +import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../actions/map' +import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' +import { + MAP_MAX_SCALE, + MAP_MIN_SCALE, + SIDEBAR_WIDTH, + TILE_SIZE_IN_PIXELS, + VIEWPORT_PADDING, +} from '../../components/app/map/MapConstants' +import { calculateRoomListBounds } from '../../util/tile-calculations' + +export const viewportAdjustmentMiddleware = (store) => (next) => (action) => { + const state = store.getState() + + let topologyId = '-1' + let mapDimensions = {} + if (action.type === SET_CURRENT_TOPOLOGY && action.topologyId !== '-1') { + topologyId = action.topologyId + mapDimensions = state.map.dimensions + } else if (action.type === SET_MAP_DIMENSIONS && state.currentTopologyId !== '-1') { + topologyId = state.currentTopologyId + mapDimensions = { width: action.width, height: action.height } + } + + if (topologyId !== '-1') { + const roomIds = state.objects.topology[topologyId].roomIds + const rooms = roomIds.map((id) => Object.assign({}, state.objects.room[id])) + rooms.forEach((room) => (room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId]))) + + let hasNoTiles = true + for (let i in rooms) { + if (rooms[i].tiles.length > 0) { + hasNoTiles = false + break + } + } + + if (!hasNoTiles) { + const viewportParams = calculateParametersToZoomInOnRooms(rooms, mapDimensions.width, mapDimensions.height) + store.dispatch(setMapPosition(viewportParams.newX, viewportParams.newY)) + store.dispatch(setMapScale(viewportParams.newScale)) + } + } + + next(action) +} + +function calculateParametersToZoomInOnRooms(rooms, mapWidth, mapHeight) { + const bounds = calculateRoomListBounds(rooms) + const newScale = calculateNewScale(bounds, mapWidth, mapHeight) + + // Coordinates of the center of the room, relative to the global origin of the map + const roomCenterCoordinates = { + x: bounds.center.x * TILE_SIZE_IN_PIXELS * newScale, + y: bounds.center.y * TILE_SIZE_IN_PIXELS * newScale, + } + + const newX = -roomCenterCoordinates.x + mapWidth / 2 + const newY = -roomCenterCoordinates.y + mapHeight / 2 + + return { newScale, newX, newY } +} + +function calculateNewScale(bounds, mapWidth, mapHeight) { + const width = bounds.max.x - bounds.min.x + const height = bounds.max.y - bounds.min.y + + const scaleX = (mapWidth - 2 * SIDEBAR_WIDTH) / (width * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) + const scaleY = mapHeight / (height * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) + const newScale = Math.min(scaleX, scaleY) + + return Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) +} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/auth.js b/opendc-web/opendc-web-ui/src/redux/reducers/auth.js new file mode 100644 index 00000000..399a4b10 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/auth.js @@ -0,0 +1,12 @@ +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/redux/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js new file mode 100644 index 00000000..257dddd2 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js @@ -0,0 +1,52 @@ +import { combineReducers } from 'redux' +import { GO_DOWN_ONE_INTERACTION_LEVEL } from '../actions/interaction-level' +import { + CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + FINISH_NEW_ROOM_CONSTRUCTION, + FINISH_ROOM_EDIT, + SET_CURRENT_TOPOLOGY, + START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, + START_ROOM_EDIT, +} from '../actions/topology/building' +import { DELETE_ROOM, START_RACK_CONSTRUCTION, STOP_RACK_CONSTRUCTION } from '../actions/topology/room' +import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' +import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' + +export function currentRoomInConstruction(state = '-1', action) { + switch (action.type) { + case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED: + return action.roomId + case START_ROOM_EDIT: + return action.roomId + case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: + case FINISH_NEW_ROOM_CONSTRUCTION: + case OPEN_PORTFOLIO_SUCCEEDED: + case OPEN_SCENARIO_SUCCEEDED: + case FINISH_ROOM_EDIT: + case SET_CURRENT_TOPOLOGY: + case DELETE_ROOM: + return '-1' + default: + return state + } +} + +export function inRackConstructionMode(state = false, action) { + switch (action.type) { + case START_RACK_CONSTRUCTION: + return true + case STOP_RACK_CONSTRUCTION: + case OPEN_PORTFOLIO_SUCCEEDED: + case OPEN_SCENARIO_SUCCEEDED: + case SET_CURRENT_TOPOLOGY: + case GO_DOWN_ONE_INTERACTION_LEVEL: + return false + default: + return state + } +} + +export const construction = combineReducers({ + currentRoomInConstruction, + inRackConstructionMode, +}) diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js b/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js new file mode 100644 index 00000000..9b46aa60 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js @@ -0,0 +1,54 @@ +import { OPEN_PORTFOLIO_SUCCEEDED, SET_CURRENT_PORTFOLIO } from '../actions/portfolios' +import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' +import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' +import { OPEN_SCENARIO_SUCCEEDED, SET_CURRENT_SCENARIO } from '../actions/scenarios' + +export function currentTopologyId(state = '-1', action) { + switch (action.type) { + case SET_CURRENT_TOPOLOGY: + return action.topologyId + default: + return state + } +} + +export function currentProjectId(state = '-1', action) { + switch (action.type) { + case OPEN_PROJECT_SUCCEEDED: + return action.id + case OPEN_PORTFOLIO_SUCCEEDED: + case OPEN_SCENARIO_SUCCEEDED: + return action.projectId + default: + return state + } +} + +export function currentPortfolioId(state = '-1', action) { + switch (action.type) { + case OPEN_PORTFOLIO_SUCCEEDED: + case SET_CURRENT_PORTFOLIO: + case SET_CURRENT_SCENARIO: + return action.portfolioId + case OPEN_SCENARIO_SUCCEEDED: + return action.portfolioId + case OPEN_PROJECT_SUCCEEDED: + case SET_CURRENT_TOPOLOGY: + return '-1' + default: + return state + } +} +export function currentScenarioId(state = '-1', action) { + switch (action.type) { + case OPEN_SCENARIO_SUCCEEDED: + case SET_CURRENT_SCENARIO: + return action.scenarioId + case OPEN_PORTFOLIO_SUCCEEDED: + case SET_CURRENT_TOPOLOGY: + case OPEN_PROJECT_SUCCEEDED: + return '-1' + default: + return state + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js new file mode 100644 index 00000000..9dff379b --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js @@ -0,0 +1,23 @@ +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 { objects } from './objects' +import { projectList } from './project-list' + +const rootReducer = combineReducers({ + objects, + projectList, + construction, + map, + currentProjectId, + currentTopologyId, + currentPortfolioId, + currentScenarioId, + interactionLevel, + auth, +}) + +export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js new file mode 100644 index 00000000..eafcb269 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js @@ -0,0 +1,61 @@ +import { OPEN_PORTFOLIO_SUCCEEDED } from '../actions/portfolios' +import { + GO_DOWN_ONE_INTERACTION_LEVEL, + GO_FROM_BUILDING_TO_ROOM, + GO_FROM_RACK_TO_MACHINE, + GO_FROM_ROOM_TO_RACK, +} from '../actions/interaction-level' +import { OPEN_PROJECT_SUCCEEDED } from '../actions/projects' +import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building' +import { OPEN_SCENARIO_SUCCEEDED } from '../actions/scenarios' + +export function interactionLevel(state = { mode: 'BUILDING' }, action) { + switch (action.type) { + case OPEN_PORTFOLIO_SUCCEEDED: + case OPEN_SCENARIO_SUCCEEDED: + case OPEN_PROJECT_SUCCEEDED: + case SET_CURRENT_TOPOLOGY: + return { + mode: 'BUILDING', + } + case GO_FROM_BUILDING_TO_ROOM: + return { + mode: 'ROOM', + roomId: action.roomId, + } + case GO_FROM_ROOM_TO_RACK: + return { + mode: 'RACK', + roomId: state.roomId, + tileId: action.tileId, + } + case GO_FROM_RACK_TO_MACHINE: + return { + mode: 'MACHINE', + roomId: state.roomId, + tileId: state.tileId, + position: action.position, + } + case GO_DOWN_ONE_INTERACTION_LEVEL: + if (state.mode === 'ROOM') { + return { + mode: 'BUILDING', + } + } else if (state.mode === 'RACK') { + return { + mode: 'ROOM', + roomId: state.roomId, + } + } else if (state.mode === 'MACHINE') { + return { + mode: 'RACK', + roomId: state.roomId, + tileId: state.tileId, + } + } else { + return state + } + default: + return state + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/map.js b/opendc-web/opendc-web-ui/src/redux/reducers/map.js new file mode 100644 index 00000000..de712c15 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/map.js @@ -0,0 +1,35 @@ +import { combineReducers } from 'redux' +import { SET_MAP_DIMENSIONS, SET_MAP_POSITION, SET_MAP_SCALE } from '../actions/map' + +export function position(state = { x: 0, y: 0 }, action) { + switch (action.type) { + case SET_MAP_POSITION: + return { x: action.x, y: action.y } + default: + return state + } +} + +export function dimensions(state = { width: 600, height: 400 }, action) { + switch (action.type) { + case SET_MAP_DIMENSIONS: + return { width: action.width, height: action.height } + default: + return state + } +} + +export function scale(state = 1, action) { + switch (action.type) { + case SET_MAP_SCALE: + return action.scale + default: + return state + } +} + +export const map = combineReducers({ + position, + dimensions, + scale, +}) diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/objects.js b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js new file mode 100644 index 00000000..a2483b43 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js @@ -0,0 +1,64 @@ +import { combineReducers } from 'redux' +import { + ADD_ID_TO_STORE_OBJECT_LIST_PROP, + ADD_PROP_TO_STORE_OBJECT, + 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' + +export const objects = combineReducers({ + project: object('project'), + user: object('user'), + authorization: objectWithId('authorization', (object) => [object.userId, object.projectId]), + cpu: object('cpu', CPU_UNITS), + gpu: object('gpu', GPU_UNITS), + memory: object('memory', MEMORY_UNITS), + storage: object('storage', STORAGE_UNITS), + machine: object('machine'), + rack: object('rack'), + tile: object('tile'), + room: object('room'), + topology: object('topology'), + trace: object('trace'), + scheduler: object('scheduler'), + portfolio: object('portfolio'), + scenario: object('scenario'), + prefab: object('prefab'), +}) + +function object(type, defaultState = {}) { + return objectWithId(type, (object) => object._id, defaultState) +} + +function objectWithId(type, getId, defaultState = {}) { + return (state = defaultState, action) => { + if (action.objectType !== type) { + return state + } + + if (action.type === ADD_TO_STORE) { + return Object.assign({}, state, { + [getId(action.object)]: action.object, + }) + } else if (action.type === ADD_PROP_TO_STORE_OBJECT) { + return Object.assign({}, state, { + [action.objectId]: Object.assign({}, state[action.objectId], action.propObject), + }) + } else if (action.type === ADD_ID_TO_STORE_OBJECT_LIST_PROP) { + return Object.assign({}, state, { + [action.objectId]: Object.assign({}, state[action.objectId], { + [action.propName]: [...state[action.objectId][action.propName], action.id], + }), + }) + } else if (action.type === REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP) { + return Object.assign({}, state, { + [action.objectId]: Object.assign({}, state[action.objectId], { + [action.propName]: state[action.objectId][action.propName].filter((id) => id !== action.id), + }), + }) + } + + return state + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/project-list.js b/opendc-web/opendc-web-ui/src/redux/reducers/project-list.js new file mode 100644 index 00000000..ad803db0 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/reducers/project-list.js @@ -0,0 +1,18 @@ +import { combineReducers } from 'redux' +import { ADD_PROJECT_SUCCEEDED, DELETE_PROJECT_SUCCEEDED } 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 const projectList = combineReducers({ authorizationsOfCurrentUser }) diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js new file mode 100644 index 00000000..6332b2fb --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js @@ -0,0 +1,80 @@ +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_TILE, + CANCEL_NEW_ROOM_CONSTRUCTION, + DELETE_TILE, + START_NEW_ROOM_CONSTRUCTION, +} from '../actions/topology/building' +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 { + onAddMachine, + onAddRackToTile, + onAddTile, + onAddTopology, + onAddUnit, + onCancelNewRoomConstruction, + onDeleteMachine, + onDeleteRack, + onDeleteRoom, + onDeleteTile, + onDeleteTopology, + onDeleteUnit, + onEditRackName, + 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' +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(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) + + yield takeEvery(ADD_TOPOLOGY, onAddTopology) + yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) + yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) + yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction) + yield takeEvery(ADD_TILE, onAddTile) + yield takeEvery(DELETE_TILE, onDeleteTile) + yield takeEvery(EDIT_ROOM_NAME, onEditRoomName) + yield takeEvery(DELETE_ROOM, onDeleteRoom) + yield takeEvery(EDIT_RACK_NAME, onEditRackName) + yield takeEvery(DELETE_RACK, onDeleteRack) + yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile) + yield takeEvery(ADD_MACHINE, onAddMachine) + yield takeEvery(DELETE_MACHINE, onDeleteMachine) + yield takeEvery(ADD_UNIT, onAddUnit) + yield takeEvery(DELETE_UNIT, onDeleteUnit) + + yield takeEvery(ADD_PORTFOLIO, onAddPortfolio) + yield takeEvery(UPDATE_PORTFOLIO, onUpdatePortfolio) + yield takeEvery(DELETE_PORTFOLIO, onDeletePortfolio) + + yield takeEvery(ADD_SCENARIO, onAddScenario) + yield takeEvery(UPDATE_SCENARIO, onUpdateScenario) + yield takeEvery(DELETE_SCENARIO, onDeleteScenario) + + yield takeEvery(ADD_PREFAB, onAddPrefab) +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js new file mode 100644 index 00000000..82dbb935 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js @@ -0,0 +1,229 @@ +import { call, put, select } from 'redux-saga/effects' +import { addToStore } from '../actions/objects' +import { getAllSchedulers } from '../../api/schedulers' +import { getProject } from '../../api/projects' +import { getAllTraces } from '../../api/traces' +import { getUser } from '../../api/users' +import { getTopology, updateTopology } from '../../api/topologies' +import { uuid } from 'uuidv4' + +export const OBJECT_SELECTORS = { + project: (state) => state.objects.project, + user: (state) => state.objects.user, + authorization: (state) => state.objects.authorization, + portfolio: (state) => state.objects.portfolio, + scenario: (state) => state.objects.scenario, + cpu: (state) => state.objects.cpu, + gpu: (state) => state.objects.gpu, + memory: (state) => state.objects.memory, + storage: (state) => state.objects.storage, + machine: (state) => state.objects.machine, + rack: (state) => state.objects.rack, + tile: (state) => state.objects.tile, + room: (state) => state.objects.room, + topology: (state) => state.objects.topology, +} + +function* fetchAndStoreObject(objectType, id, apiCall) { + const objectStore = yield select(OBJECT_SELECTORS[objectType]) + let object = objectStore[id] + if (!object) { + object = yield apiCall + yield put(addToStore(objectType, object)) + } + return object +} + +function* fetchAndStoreObjects(objectType, apiCall) { + const objects = yield apiCall + for (let object of objects) { + yield put(addToStore(objectType, object)) + } + return objects +} + +export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) + +export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) + +export const fetchAndStoreTopology = function* (id) { + const topologyStore = yield select(OBJECT_SELECTORS['topology']) + const roomStore = yield select(OBJECT_SELECTORS['room']) + const tileStore = yield select(OBJECT_SELECTORS['tile']) + const rackStore = yield select(OBJECT_SELECTORS['rack']) + const machineStore = yield select(OBJECT_SELECTORS['machine']) + + let topology = topologyStore[id] + if (!topology) { + const fullTopology = yield call(getTopology, id) + + for (let roomIdx in fullTopology.rooms) { + const fullRoom = fullTopology.rooms[roomIdx] + + generateIdIfNotPresent(fullRoom) + + if (!roomStore[fullRoom._id]) { + for (let tileIdx in fullRoom.tiles) { + const fullTile = fullRoom.tiles[tileIdx] + + generateIdIfNotPresent(fullTile) + + if (!tileStore[fullTile._id]) { + if (fullTile.rack) { + const fullRack = fullTile.rack + + generateIdIfNotPresent(fullRack) + + if (!rackStore[fullRack._id]) { + for (let machineIdx in fullRack.machines) { + const fullMachine = fullRack.machines[machineIdx] + + generateIdIfNotPresent(fullMachine) + + if (!machineStore[fullMachine._id]) { + let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({ + _id, + rackId: fullRack._id, + position, + cpuIds: cpus.map((u) => u._id), + gpuIds: gpus.map((u) => u._id), + memoryIds: memories.map((u) => u._id), + storageIds: storages.map((u) => u._id), + }))(fullMachine) + yield put(addToStore('machine', machine)) + } + } + + const filledSlots = new Array(fullRack.capacity).fill(null) + fullRack.machines.forEach( + (machine) => (filledSlots[machine.position - 1] = machine._id) + ) + let rack = (({ _id, name, capacity, powerCapacityW }) => ({ + _id, + name, + capacity, + powerCapacityW, + machineIds: filledSlots, + }))(fullRack) + yield put(addToStore('rack', rack)) + } + } + + let tile = (({ _id, positionX, positionY, rack }) => ({ + _id, + roomId: fullRoom._id, + positionX, + positionY, + rackId: rack ? rack._id : undefined, + }))(fullTile) + yield put(addToStore('tile', tile)) + } + } + + let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom) + yield put(addToStore('room', room)) + } + } + + topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology) + yield put(addToStore('topology', topology)) + + // TODO consider pushing the IDs + } + + return topology +} + +const generateIdIfNotPresent = (obj) => { + if (!obj._id) { + obj._id = uuid() + } +} + +export const updateTopologyOnServer = function* (id) { + const topology = yield getTopologyAsObject(id, true) + + yield call(updateTopology, topology) +} + +export const getTopologyAsObject = function* (id, keepIds) { + const topologyStore = yield select(OBJECT_SELECTORS['topology']) + const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) + return { + _id: keepIds ? id : undefined, + name: topologyStore[id].name, + rooms: rooms, + } +} + +export const getAllRooms = function* (roomIds, keepIds) { + const roomStore = yield select(OBJECT_SELECTORS['room']) + + let rooms = [] + + for (let id of roomIds) { + let tiles = yield getAllRoomTiles(roomStore[id], keepIds) + rooms.push({ + _id: keepIds ? id : undefined, + name: roomStore[id].name, + tiles: tiles, + }) + } + return rooms +} + +export const getAllRoomTiles = function* (roomStore, keepIds) { + let tiles = [] + + for (let id of roomStore.tileIds) { + tiles.push(yield getTileById(id, keepIds)) + } + return tiles +} + +export const getTileById = function* (id, keepIds) { + const tileStore = yield select(OBJECT_SELECTORS['tile']) + return { + _id: keepIds ? id : undefined, + positionX: tileStore[id].positionX, + positionY: tileStore[id].positionY, + rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds), + } +} + +export const getRackById = function* (id, keepIds) { + const rackStore = yield select(OBJECT_SELECTORS['rack']) + const machineStore = yield select(OBJECT_SELECTORS['machine']) + const cpuStore = yield select(OBJECT_SELECTORS['cpu']) + const gpuStore = yield select(OBJECT_SELECTORS['gpu']) + const memoryStore = yield select(OBJECT_SELECTORS['memory']) + const storageStore = yield select(OBJECT_SELECTORS['storage']) + + return { + _id: keepIds ? rackStore[id]._id : undefined, + name: rackStore[id].name, + capacity: rackStore[id].capacity, + powerCapacityW: rackStore[id].powerCapacityW, + machines: rackStore[id].machineIds + .filter((m) => m !== null) + .map((machineId) => ({ + _id: keepIds ? machineId : undefined, + position: machineStore[machineId].position, + cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]), + gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]), + memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]), + storages: machineStore[machineId].storageIds.map((id) => storageStore[id]), + })), + } +} + +export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) + +export const fetchAndStoreAllSchedulers = function* () { + const objects = yield call(getAllSchedulers) + for (let object of objects) { + object._id = object.name + yield put(addToStore('scheduler', object)) + } + return objects +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js new file mode 100644 index 00000000..8ddf888d --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/portfolios.js @@ -0,0 +1,131 @@ +import { call, put, select, delay } from 'redux-saga/effects' +import { addPropToStoreObject, addToStore } from '../actions/objects' +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/scenarios' + +export function* onOpenPortfolioSucceeded(action) { + try { + const project = yield call(getProject, action.projectId) + yield put(addToStore('project', project)) + yield fetchAndStoreAllTopologiesOfProject(project._id) + yield fetchPortfoliosOfProject() + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + + yield watchForPortfolioResults() + } catch (error) { + console.error(error) + } +} + +export function* watchForPortfolioResults() { + try { + const currentPortfolioId = yield select((state) => state.currentPortfolioId) + let unfinishedScenarios = yield getCurrentUnfinishedScenarios() + + while (unfinishedScenarios.length > 0) { + yield delay(3000) + yield fetchPortfolioWithScenarios(currentPortfolioId) + unfinishedScenarios = yield getCurrentUnfinishedScenarios() + } + } catch (error) { + console.error(error) + } +} + +export function* getCurrentUnfinishedScenarios() { + try { + const currentPortfolioId = yield select((state) => state.currentPortfolioId) + const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) + const scenarioObjects = yield select((state) => state.objects.scenario) + const scenarios = scenarioIds.map((s) => scenarioObjects[s]) + return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') + } catch (error) { + console.error(error) + } +} + +export function* fetchPortfoliosOfProject() { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + const currentProject = yield select((state) => state.objects.project[currentProjectId]) + + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + + for (let i in currentProject.portfolioIds) { + yield fetchPortfolioWithScenarios(currentProject.portfolioIds[i]) + } + } catch (error) { + console.error(error) + } +} + +export function* fetchPortfolioWithScenarios(portfolioId) { + try { + const portfolio = yield call(getPortfolio, portfolioId) + yield put(addToStore('portfolio', portfolio)) + + for (let i in portfolio.scenarioIds) { + const scenario = yield call(getScenario, portfolio.scenarioIds[i]) + yield put(addToStore('scenario', scenario)) + } + return portfolio + } catch (error) { + console.error(error) + } +} + +export function* onAddPortfolio(action) { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + + const portfolio = yield call( + addPortfolio, + currentProjectId, + Object.assign({}, action.portfolio, { + projectId: currentProjectId, + scenarioIds: [], + }) + ) + yield put(addToStore('portfolio', portfolio)) + + const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) + yield put( + addPropToStoreObject('project', currentProjectId, { + portfolioIds: portfolioIds.concat([portfolio._id]), + }) + ) + } catch (error) { + console.error(error) + } +} + +export function* onUpdatePortfolio(action) { + try { + const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) + yield put(addToStore('portfolio', portfolio)) + } catch (error) { + console.error(error) + } +} + +export function* onDeletePortfolio(action) { + try { + yield call(deletePortfolio, action.id) + + const currentProjectId = yield select((state) => state.currentProjectId) + const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) + + yield put( + addPropToStoreObject('project', currentProjectId, { + portfolioIds: portfolioIds.filter((id) => id !== action.id), + }) + ) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js new file mode 100644 index 00000000..3158a219 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js @@ -0,0 +1,15 @@ +import { call, put, select } from 'redux-saga/effects' +import { addToStore } from '../actions/objects' +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 }) + yield put(addToStore('prefab', prefab)) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/profile.js b/opendc-web/opendc-web-ui/src/redux/sagas/profile.js new file mode 100644 index 00000000..e187b765 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/profile.js @@ -0,0 +1,12 @@ +import { call, put } from 'redux-saga/effects' +import { deleteCurrentUserSucceeded } from '../actions/users' +import { deleteUser } from '../../api/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/redux/sagas/projects.js b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js new file mode 100644 index 00000000..ecd9a7c9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/projects.js @@ -0,0 +1,48 @@ +import { call, put } from 'redux-saga/effects' +import { addToStore } from '../actions/objects' +import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' +import { addProject, deleteProject, getProject } 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) + yield put(addToStore('project', project)) + + yield fetchAndStoreAllTopologiesOfProject(action.id, true) + yield fetchPortfoliosOfProject() + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + } catch (error) { + console.error(error) + } +} + +export function* onProjectAdd(action) { + try { + const project = yield call(addProject, { 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])) + } catch (error) { + console.error(error) + } +} + +export function* onProjectDelete(action) { + try { + yield call(deleteProject, action.id) + yield put(deleteProjectSucceeded(action.id)) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js new file mode 100644 index 00000000..a5463fa6 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/scenarios.js @@ -0,0 +1,65 @@ +import { call, put, select } from 'redux-saga/effects' +import { addPropToStoreObject, addToStore } from '../actions/objects' +import { getProject } from '../../api/projects' +import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' +import { fetchAndStoreAllTopologiesOfProject } from './topology' +import { addScenario, deleteScenario, updateScenario } from '../../api/scenarios' +import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' + +export function* onOpenScenarioSucceeded(action) { + try { + const project = yield call(getProject, action.projectId) + yield put(addToStore('project', project)) + yield fetchAndStoreAllTopologiesOfProject(project._id) + yield fetchAndStoreAllSchedulers() + yield fetchAndStoreAllTraces() + yield fetchPortfolioWithScenarios(action.portfolioId) + + // TODO Fetch scenario-specific metrics + } catch (error) { + console.error(error) + } +} + +export function* onAddScenario(action) { + try { + const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) + yield put(addToStore('scenario', scenario)) + + const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) + yield put( + addPropToStoreObject('portfolio', action.scenario.portfolioId, { + scenarioIds: scenarioIds.concat([scenario._id]), + }) + ) + yield watchForPortfolioResults() + } catch (error) { + console.error(error) + } +} + +export function* onUpdateScenario(action) { + try { + const scenario = yield call(updateScenario, action.scenario._id, action.scenario) + yield put(addToStore('scenario', scenario)) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteScenario(action) { + try { + yield call(deleteScenario, action.id) + + const currentPortfolioId = yield select((state) => state.currentPortfolioId) + const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) + + yield put( + addPropToStoreObject('scenario', currentPortfolioId, { + scenarioIds: scenarioIds.filter((id) => id !== action.id), + }) + ) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js new file mode 100644 index 00000000..65f97cc9 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js @@ -0,0 +1,311 @@ +import { call, put, select } from 'redux-saga/effects' +import { goDownOneInteractionLevel } from '../actions/interaction-level' +import { + addIdToStoreObjectListProp, + addPropToStoreObject, + addToStore, + removeIdFromStoreObjectListProp, +} from '../actions/objects' +import { + cancelNewRoomConstructionSucceeded, + setCurrentTopology, + startNewRoomConstructionSucceeded, +} from '../actions/topology/building' +import { + DEFAULT_RACK_POWER_CAPACITY, + DEFAULT_RACK_SLOT_CAPACITY, + MAX_NUM_UNITS_PER_MACHINE, +} from '../../components/app/map/MapConstants' +import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' +import { uuid } from 'uuidv4' +import { addTopology, deleteTopology } from '../../api/topologies' + +export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { + try { + const project = yield select((state) => state.objects.project[projectId]) + + for (let i in project.topologyIds) { + yield fetchAndStoreTopology(project.topologyIds[i]) + } + + if (setTopology) { + yield put(setCurrentTopology(project.topologyIds[0])) + } + } catch (error) { + console.error(error) + } +} + +export function* onAddTopology(action) { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + + let topologyToBeCreated + if (action.duplicateId) { + topologyToBeCreated = yield getTopologyAsObject(action.duplicateId, false) + topologyToBeCreated = Object.assign({}, topologyToBeCreated, { + name: action.name, + }) + } else { + topologyToBeCreated = { name: action.name, rooms: [] } + } + + const topology = yield call( + addTopology, + Object.assign({}, topologyToBeCreated, { + projectId: currentProjectId, + }) + ) + yield fetchAndStoreTopology(topology._id) + + const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) + yield put( + addPropToStoreObject('project', currentProjectId, { + topologyIds: topologyIds.concat([topology._id]), + }) + ) + yield put(setCurrentTopology(topology._id)) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteTopology(action) { + try { + const currentProjectId = yield select((state) => state.currentProjectId) + const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) + const currentTopologyId = yield select((state) => state.currentTopologyId) + if (currentTopologyId === action.id) { + yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) + } + + yield call(deleteTopology, action.id) + + yield put( + addPropToStoreObject('project', currentProjectId, { + topologyIds: topologyIds.filter((id) => id !== action.id), + }) + ) + } catch (error) { + console.error(error) + } +} + +export function* onStartNewRoomConstruction() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const room = { + _id: uuid(), + name: 'Room', + topologyId, + tileIds: [], + } + yield put(addToStore('room', room)) + yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id)) + yield updateTopologyOnServer(topologyId) + yield put(startNewRoomConstructionSucceeded(room._id)) + } catch (error) { + console.error(error) + } +} + +export function* onCancelNewRoomConstruction() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.construction.currentRoomInConstruction) + yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) + // TODO remove room from store, too + yield updateTopologyOnServer(topologyId) + yield put(cancelNewRoomConstructionSucceeded()) + } catch (error) { + console.error(error) + } +} + +export function* onAddTile(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.construction.currentRoomInConstruction) + const tile = { + _id: uuid(), + roomId, + positionX: action.positionX, + positionY: action.positionY, + } + yield put(addToStore('tile', tile)) + yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id)) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteTile(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.construction.currentRoomInConstruction) + yield put(removeIdFromStoreObjectListProp('room', roomId, 'tileIds', action.tileId)) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onEditRoomName(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.interactionLevel.roomId) + const room = Object.assign({}, yield select((state) => state.objects.room[roomId])) + room.name = action.name + yield put(addPropToStoreObject('room', roomId, { name: action.name })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteRoom() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const roomId = yield select((state) => state.interactionLevel.roomId) + yield put(goDownOneInteractionLevel()) + yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onEditRackName(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId])) + rack.name = action.name + yield put(addPropToStoreObject('rack', rackId, { name: action.name })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteRack() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + yield put(goDownOneInteractionLevel()) + yield put(addPropToStoreObject('tile', tileId, { rackId: undefined })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onAddRackToTile(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const rack = { + _id: uuid(), + name: 'Rack', + capacity: DEFAULT_RACK_SLOT_CAPACITY, + powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, + } + rack.machineIds = new Array(rack.capacity).fill(null) + yield put(addToStore('rack', rack)) + yield put(addPropToStoreObject('tile', action.tileId, { rackId: rack._id })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onAddMachine(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) + const rack = yield select((state) => state.objects.rack[rackId]) + + const machine = { + _id: uuid(), + rackId, + position: action.position, + cpuIds: [], + gpuIds: [], + memoryIds: [], + storageIds: [], + } + yield put(addToStore('machine', machine)) + + const machineIds = [...rack.machineIds] + machineIds[machine.position - 1] = machine._id + yield put(addPropToStoreObject('rack', rackId, { machineIds })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteMachine() { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + const position = yield select((state) => state.interactionLevel.position) + const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rackId]) + const machineIds = [...rack.machineIds] + machineIds[position - 1] = null + yield put(goDownOneInteractionLevel()) + yield put(addPropToStoreObject('rack', rack._id, { machineIds })) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onAddUnit(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + const position = yield select((state) => state.interactionLevel.position) + const machine = yield select( + (state) => + state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] + ) + + if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) { + return + } + + const units = [...machine[action.unitType + 'Ids'], action.id] + yield put( + addPropToStoreObject('machine', machine._id, { + [action.unitType + 'Ids']: units, + }) + ) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} + +export function* onDeleteUnit(action) { + try { + const topologyId = yield select((state) => state.currentTopologyId) + const tileId = yield select((state) => state.interactionLevel.tileId) + const position = yield select((state) => state.interactionLevel.position) + const machine = yield select( + (state) => + state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] + ) + const unitIds = machine[action.unitType + 'Ids'].slice() + unitIds.splice(action.index, 1) + + yield put( + addPropToStoreObject('machine', machine._id, { + [action.unitType + 'Ids']: unitIds, + }) + ) + yield updateTopologyOnServer(topologyId) + } catch (error) { + console.error(error) + } +} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/users.js b/opendc-web/opendc-web-ui/src/redux/sagas/users.js new file mode 100644 index 00000000..88c424b5 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/redux/sagas/users.js @@ -0,0 +1,44 @@ +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/token-signin' +import { addUser } from '../../api/users' +import { saveAuthLocalStorage } from '../../auth' +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/sagas/index.js b/opendc-web/opendc-web-ui/src/sagas/index.js deleted file mode 100644 index 6332b2fb..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/index.js +++ /dev/null @@ -1,80 +0,0 @@ -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_TILE, - CANCEL_NEW_ROOM_CONSTRUCTION, - DELETE_TILE, - START_NEW_ROOM_CONSTRUCTION, -} from '../actions/topology/building' -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 { - onAddMachine, - onAddRackToTile, - onAddTile, - onAddTopology, - onAddUnit, - onCancelNewRoomConstruction, - onDeleteMachine, - onDeleteRack, - onDeleteRoom, - onDeleteTile, - onDeleteTopology, - onDeleteUnit, - onEditRackName, - 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' -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(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) - - yield takeEvery(ADD_TOPOLOGY, onAddTopology) - yield takeEvery(DELETE_TOPOLOGY, onDeleteTopology) - yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction) - yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction) - yield takeEvery(ADD_TILE, onAddTile) - yield takeEvery(DELETE_TILE, onDeleteTile) - yield takeEvery(EDIT_ROOM_NAME, onEditRoomName) - yield takeEvery(DELETE_ROOM, onDeleteRoom) - yield takeEvery(EDIT_RACK_NAME, onEditRackName) - yield takeEvery(DELETE_RACK, onDeleteRack) - yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile) - yield takeEvery(ADD_MACHINE, onAddMachine) - yield takeEvery(DELETE_MACHINE, onDeleteMachine) - yield takeEvery(ADD_UNIT, onAddUnit) - yield takeEvery(DELETE_UNIT, onDeleteUnit) - - yield takeEvery(ADD_PORTFOLIO, onAddPortfolio) - yield takeEvery(UPDATE_PORTFOLIO, onUpdatePortfolio) - yield takeEvery(DELETE_PORTFOLIO, onDeletePortfolio) - - yield takeEvery(ADD_SCENARIO, onAddScenario) - yield takeEvery(UPDATE_SCENARIO, onUpdateScenario) - yield takeEvery(DELETE_SCENARIO, onDeleteScenario) - - yield takeEvery(ADD_PREFAB, onAddPrefab) -} diff --git a/opendc-web/opendc-web-ui/src/sagas/objects.js b/opendc-web/opendc-web-ui/src/sagas/objects.js deleted file mode 100644 index 313d9976..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/objects.js +++ /dev/null @@ -1,229 +0,0 @@ -import { call, put, select } 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 { uuid } from 'uuidv4' - -export const OBJECT_SELECTORS = { - project: (state) => state.objects.project, - user: (state) => state.objects.user, - authorization: (state) => state.objects.authorization, - portfolio: (state) => state.objects.portfolio, - scenario: (state) => state.objects.scenario, - cpu: (state) => state.objects.cpu, - gpu: (state) => state.objects.gpu, - memory: (state) => state.objects.memory, - storage: (state) => state.objects.storage, - machine: (state) => state.objects.machine, - rack: (state) => state.objects.rack, - tile: (state) => state.objects.tile, - room: (state) => state.objects.room, - topology: (state) => state.objects.topology, -} - -function* fetchAndStoreObject(objectType, id, apiCall) { - const objectStore = yield select(OBJECT_SELECTORS[objectType]) - let object = objectStore[id] - if (!object) { - object = yield apiCall - yield put(addToStore(objectType, object)) - } - return object -} - -function* fetchAndStoreObjects(objectType, apiCall) { - const objects = yield apiCall - for (let object of objects) { - yield put(addToStore(objectType, object)) - } - return objects -} - -export const fetchAndStoreProject = (id) => fetchAndStoreObject('project', id, call(getProject, id)) - -export const fetchAndStoreUser = (id) => fetchAndStoreObject('user', id, call(getUser, id)) - -export const fetchAndStoreTopology = function* (id) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const roomStore = yield select(OBJECT_SELECTORS['room']) - const tileStore = yield select(OBJECT_SELECTORS['tile']) - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - - let topology = topologyStore[id] - if (!topology) { - const fullTopology = yield call(getTopology, id) - - for (let roomIdx in fullTopology.rooms) { - const fullRoom = fullTopology.rooms[roomIdx] - - generateIdIfNotPresent(fullRoom) - - if (!roomStore[fullRoom._id]) { - for (let tileIdx in fullRoom.tiles) { - const fullTile = fullRoom.tiles[tileIdx] - - generateIdIfNotPresent(fullTile) - - if (!tileStore[fullTile._id]) { - if (fullTile.rack) { - const fullRack = fullTile.rack - - generateIdIfNotPresent(fullRack) - - if (!rackStore[fullRack._id]) { - for (let machineIdx in fullRack.machines) { - const fullMachine = fullRack.machines[machineIdx] - - generateIdIfNotPresent(fullMachine) - - if (!machineStore[fullMachine._id]) { - let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({ - _id, - rackId: fullRack._id, - position, - cpuIds: cpus.map((u) => u._id), - gpuIds: gpus.map((u) => u._id), - memoryIds: memories.map((u) => u._id), - storageIds: storages.map((u) => u._id), - }))(fullMachine) - yield put(addToStore('machine', machine)) - } - } - - const filledSlots = new Array(fullRack.capacity).fill(null) - fullRack.machines.forEach( - (machine) => (filledSlots[machine.position - 1] = machine._id) - ) - let rack = (({ _id, name, capacity, powerCapacityW }) => ({ - _id, - name, - capacity, - powerCapacityW, - machineIds: filledSlots, - }))(fullRack) - yield put(addToStore('rack', rack)) - } - } - - let tile = (({ _id, positionX, positionY, rack }) => ({ - _id, - roomId: fullRoom._id, - positionX, - positionY, - rackId: rack ? rack._id : undefined, - }))(fullTile) - yield put(addToStore('tile', tile)) - } - } - - let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom) - yield put(addToStore('room', room)) - } - } - - topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology) - yield put(addToStore('topology', topology)) - - // TODO consider pushing the IDs - } - - return topology -} - -const generateIdIfNotPresent = (obj) => { - if (!obj._id) { - obj._id = uuid() - } -} - -export const updateTopologyOnServer = function* (id) { - const topology = yield getTopologyAsObject(id, true) - - yield call(updateTopology, topology) -} - -export const getTopologyAsObject = function* (id, keepIds) { - const topologyStore = yield select(OBJECT_SELECTORS['topology']) - const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds) - return { - _id: keepIds ? id : undefined, - name: topologyStore[id].name, - rooms: rooms, - } -} - -export const getAllRooms = function* (roomIds, keepIds) { - const roomStore = yield select(OBJECT_SELECTORS['room']) - - let rooms = [] - - for (let id of roomIds) { - let tiles = yield getAllRoomTiles(roomStore[id], keepIds) - rooms.push({ - _id: keepIds ? id : undefined, - name: roomStore[id].name, - tiles: tiles, - }) - } - return rooms -} - -export const getAllRoomTiles = function* (roomStore, keepIds) { - let tiles = [] - - for (let id of roomStore.tileIds) { - tiles.push(yield getTileById(id, keepIds)) - } - return tiles -} - -export const getTileById = function* (id, keepIds) { - const tileStore = yield select(OBJECT_SELECTORS['tile']) - return { - _id: keepIds ? id : undefined, - positionX: tileStore[id].positionX, - positionY: tileStore[id].positionY, - rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds), - } -} - -export const getRackById = function* (id, keepIds) { - const rackStore = yield select(OBJECT_SELECTORS['rack']) - const machineStore = yield select(OBJECT_SELECTORS['machine']) - const cpuStore = yield select(OBJECT_SELECTORS['cpu']) - const gpuStore = yield select(OBJECT_SELECTORS['gpu']) - const memoryStore = yield select(OBJECT_SELECTORS['memory']) - const storageStore = yield select(OBJECT_SELECTORS['storage']) - - return { - _id: keepIds ? rackStore[id]._id : undefined, - name: rackStore[id].name, - capacity: rackStore[id].capacity, - powerCapacityW: rackStore[id].powerCapacityW, - machines: rackStore[id].machineIds - .filter((m) => m !== null) - .map((machineId) => ({ - _id: keepIds ? machineId : undefined, - position: machineStore[machineId].position, - cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]), - gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]), - memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]), - storages: machineStore[machineId].storageIds.map((id) => storageStore[id]), - })), - } -} - -export const fetchAndStoreAllTraces = () => fetchAndStoreObjects('trace', call(getAllTraces)) - -export const fetchAndStoreAllSchedulers = function* () { - const objects = yield call(getAllSchedulers) - for (let object of objects) { - object._id = object.name - yield put(addToStore('scheduler', object)) - } - return objects -} diff --git a/opendc-web/opendc-web-ui/src/sagas/portfolios.js b/opendc-web/opendc-web-ui/src/sagas/portfolios.js deleted file mode 100644 index ed9bfd29..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/portfolios.js +++ /dev/null @@ -1,131 +0,0 @@ -import { call, put, select, delay } 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 { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { getScenario } from '../api/routes/scenarios' - -export function* onOpenPortfolioSucceeded(action) { - try { - const project = yield call(getProject, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchPortfoliosOfProject() - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - - yield watchForPortfolioResults() - } catch (error) { - console.error(error) - } -} - -export function* watchForPortfolioResults() { - try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - let unfinishedScenarios = yield getCurrentUnfinishedScenarios() - - while (unfinishedScenarios.length > 0) { - yield delay(3000) - yield fetchPortfolioWithScenarios(currentPortfolioId) - unfinishedScenarios = yield getCurrentUnfinishedScenarios() - } - } catch (error) { - console.error(error) - } -} - -export function* getCurrentUnfinishedScenarios() { - try { - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) - const scenarioObjects = yield select((state) => state.objects.scenario) - const scenarios = scenarioIds.map((s) => scenarioObjects[s]) - return scenarios.filter((s) => !s || s.simulation.state === 'QUEUED' || s.simulation.state === 'RUNNING') - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfoliosOfProject() { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - const currentProject = yield select((state) => state.objects.project[currentProjectId]) - - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - - for (let i in currentProject.portfolioIds) { - yield fetchPortfolioWithScenarios(currentProject.portfolioIds[i]) - } - } catch (error) { - console.error(error) - } -} - -export function* fetchPortfolioWithScenarios(portfolioId) { - try { - const portfolio = yield call(getPortfolio, portfolioId) - yield put(addToStore('portfolio', portfolio)) - - for (let i in portfolio.scenarioIds) { - const scenario = yield call(getScenario, portfolio.scenarioIds[i]) - yield put(addToStore('scenario', scenario)) - } - return portfolio - } catch (error) { - console.error(error) - } -} - -export function* onAddPortfolio(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - - const portfolio = yield call( - addPortfolio, - currentProjectId, - Object.assign({}, action.portfolio, { - projectId: currentProjectId, - scenarioIds: [], - }) - ) - yield put(addToStore('portfolio', portfolio)) - - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) - yield put( - addPropToStoreObject('project', currentProjectId, { - portfolioIds: portfolioIds.concat([portfolio._id]), - }) - ) - } catch (error) { - console.error(error) - } -} - -export function* onUpdatePortfolio(action) { - try { - const portfolio = yield call(updatePortfolio, action.portfolio._id, action.portfolio) - yield put(addToStore('portfolio', portfolio)) - } catch (error) { - console.error(error) - } -} - -export function* onDeletePortfolio(action) { - try { - yield call(deletePortfolio, action.id) - - const currentProjectId = yield select((state) => state.currentProjectId) - const portfolioIds = yield select((state) => state.objects.project[currentProjectId].portfolioIds) - - yield put( - addPropToStoreObject('project', currentProjectId, { - portfolioIds: portfolioIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/sagas/prefabs.js deleted file mode 100644 index 16cf3d62..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/prefabs.js +++ /dev/null @@ -1,15 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addPrefab } from '../api/routes/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 }) - yield put(addToStore('prefab', prefab)) - } catch (error) { - console.error(error) - } -} 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/projects.js b/opendc-web/opendc-web-ui/src/sagas/projects.js deleted file mode 100644 index fdeea132..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/projects.js +++ /dev/null @@ -1,48 +0,0 @@ -import { call, put } from 'redux-saga/effects' -import { addToStore } from '../actions/objects' -import { addProjectSucceeded, deleteProjectSucceeded } from '../actions/projects' -import { addProject, deleteProject, getProject } from '../api/routes/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) - yield put(addToStore('project', project)) - - yield fetchAndStoreAllTopologiesOfProject(action.id, true) - yield fetchPortfoliosOfProject() - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - } catch (error) { - console.error(error) - } -} - -export function* onProjectAdd(action) { - try { - const project = yield call(addProject, { 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])) - } catch (error) { - console.error(error) - } -} - -export function* onProjectDelete(action) { - try { - yield call(deleteProject, action.id) - yield put(deleteProjectSucceeded(action.id)) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/scenarios.js b/opendc-web/opendc-web-ui/src/sagas/scenarios.js deleted file mode 100644 index 59223610..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/scenarios.js +++ /dev/null @@ -1,65 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { addPropToStoreObject, addToStore } from '../actions/objects' -import { getProject } from '../api/routes/projects' -import { fetchAndStoreAllSchedulers, fetchAndStoreAllTraces } from './objects' -import { fetchAndStoreAllTopologiesOfProject } from './topology' -import { addScenario, deleteScenario, updateScenario } from '../api/routes/scenarios' -import { fetchPortfolioWithScenarios, watchForPortfolioResults } from './portfolios' - -export function* onOpenScenarioSucceeded(action) { - try { - const project = yield call(getProject, action.projectId) - yield put(addToStore('project', project)) - yield fetchAndStoreAllTopologiesOfProject(project._id) - yield fetchAndStoreAllSchedulers() - yield fetchAndStoreAllTraces() - yield fetchPortfolioWithScenarios(action.portfolioId) - - // TODO Fetch scenario-specific metrics - } catch (error) { - console.error(error) - } -} - -export function* onAddScenario(action) { - try { - const scenario = yield call(addScenario, action.scenario.portfolioId, action.scenario) - yield put(addToStore('scenario', scenario)) - - const scenarioIds = yield select((state) => state.objects.portfolio[action.scenario.portfolioId].scenarioIds) - yield put( - addPropToStoreObject('portfolio', action.scenario.portfolioId, { - scenarioIds: scenarioIds.concat([scenario._id]), - }) - ) - yield watchForPortfolioResults() - } catch (error) { - console.error(error) - } -} - -export function* onUpdateScenario(action) { - try { - const scenario = yield call(updateScenario, action.scenario._id, action.scenario) - yield put(addToStore('scenario', scenario)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteScenario(action) { - try { - yield call(deleteScenario, action.id) - - const currentPortfolioId = yield select((state) => state.currentPortfolioId) - const scenarioIds = yield select((state) => state.objects.portfolio[currentPortfolioId].scenarioIds) - - yield put( - addPropToStoreObject('scenario', currentPortfolioId, { - scenarioIds: scenarioIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} diff --git a/opendc-web/opendc-web-ui/src/sagas/topology.js b/opendc-web/opendc-web-ui/src/sagas/topology.js deleted file mode 100644 index bba1ebb1..00000000 --- a/opendc-web/opendc-web-ui/src/sagas/topology.js +++ /dev/null @@ -1,311 +0,0 @@ -import { call, put, select } from 'redux-saga/effects' -import { goDownOneInteractionLevel } from '../actions/interaction-level' -import { - addIdToStoreObjectListProp, - addPropToStoreObject, - addToStore, - removeIdFromStoreObjectListProp, -} from '../actions/objects' -import { - cancelNewRoomConstructionSucceeded, - setCurrentTopology, - startNewRoomConstructionSucceeded, -} from '../actions/topology/building' -import { - DEFAULT_RACK_POWER_CAPACITY, - DEFAULT_RACK_SLOT_CAPACITY, - MAX_NUM_UNITS_PER_MACHINE, -} from '../components/app/map/MapConstants' -import { fetchAndStoreTopology, getTopologyAsObject, updateTopologyOnServer } from './objects' -import { uuid } from 'uuidv4' -import { addTopology, deleteTopology } from '../api/routes/topologies' - -export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = false) { - try { - const project = yield select((state) => state.objects.project[projectId]) - - for (let i in project.topologyIds) { - yield fetchAndStoreTopology(project.topologyIds[i]) - } - - if (setTopology) { - yield put(setCurrentTopology(project.topologyIds[0])) - } - } catch (error) { - console.error(error) - } -} - -export function* onAddTopology(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - - let topologyToBeCreated - if (action.duplicateId) { - topologyToBeCreated = yield getTopologyAsObject(action.duplicateId, false) - topologyToBeCreated = Object.assign({}, topologyToBeCreated, { - name: action.name, - }) - } else { - topologyToBeCreated = { name: action.name, rooms: [] } - } - - const topology = yield call( - addTopology, - Object.assign({}, topologyToBeCreated, { - projectId: currentProjectId, - }) - ) - yield fetchAndStoreTopology(topology._id) - - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) - yield put( - addPropToStoreObject('project', currentProjectId, { - topologyIds: topologyIds.concat([topology._id]), - }) - ) - yield put(setCurrentTopology(topology._id)) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTopology(action) { - try { - const currentProjectId = yield select((state) => state.currentProjectId) - const topologyIds = yield select((state) => state.objects.project[currentProjectId].topologyIds) - const currentTopologyId = yield select((state) => state.currentTopologyId) - if (currentTopologyId === action.id) { - yield put(setCurrentTopology(topologyIds.filter((t) => t !== action.id)[0])) - } - - yield call(deleteTopology, action.id) - - yield put( - addPropToStoreObject('project', currentProjectId, { - topologyIds: topologyIds.filter((id) => id !== action.id), - }) - ) - } catch (error) { - console.error(error) - } -} - -export function* onStartNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const room = { - _id: uuid(), - name: 'Room', - topologyId, - tileIds: [], - } - yield put(addToStore('room', room)) - yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id)) - yield updateTopologyOnServer(topologyId) - yield put(startNewRoomConstructionSucceeded(room._id)) - } catch (error) { - console.error(error) - } -} - -export function* onCancelNewRoomConstruction() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) - // TODO remove room from store, too - yield updateTopologyOnServer(topologyId) - yield put(cancelNewRoomConstructionSucceeded()) - } catch (error) { - console.error(error) - } -} - -export function* onAddTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - const tile = { - _id: uuid(), - roomId, - positionX: action.positionX, - positionY: action.positionY, - } - yield put(addToStore('tile', tile)) - yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.construction.currentRoomInConstruction) - yield put(removeIdFromStoreObjectListProp('room', roomId, 'tileIds', action.tileId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRoomName(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - const room = Object.assign({}, yield select((state) => state.objects.room[roomId])) - room.name = action.name - yield put(addPropToStoreObject('room', roomId, { name: action.name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRoom() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const roomId = yield select((state) => state.interactionLevel.roomId) - yield put(goDownOneInteractionLevel()) - yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId)) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onEditRackName(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId])) - rack.name = action.name - yield put(addPropToStoreObject('rack', rackId, { name: action.name })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteRack() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('tile', tileId, { rackId: undefined })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddRackToTile(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rack = { - _id: uuid(), - name: 'Rack', - capacity: DEFAULT_RACK_SLOT_CAPACITY, - powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, - } - rack.machineIds = new Array(rack.capacity).fill(null) - yield put(addToStore('rack', rack)) - yield put(addPropToStoreObject('tile', action.tileId, { rackId: rack._id })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddMachine(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId) - const rack = yield select((state) => state.objects.rack[rackId]) - - const machine = { - _id: uuid(), - rackId, - position: action.position, - cpuIds: [], - gpuIds: [], - memoryIds: [], - storageIds: [], - } - yield put(addToStore('machine', machine)) - - const machineIds = [...rack.machineIds] - machineIds[machine.position - 1] = machine._id - yield put(addPropToStoreObject('rack', rackId, { machineIds })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteMachine() { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rackId]) - const machineIds = [...rack.machineIds] - machineIds[position - 1] = null - yield put(goDownOneInteractionLevel()) - yield put(addPropToStoreObject('rack', rack._id, { machineIds })) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onAddUnit(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const machine = yield select( - (state) => - state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] - ) - - if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) { - return - } - - const units = [...machine[action.unitType + 'Ids'], action.id] - yield put( - addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: units, - }) - ) - yield updateTopologyOnServer(topologyId) - } catch (error) { - console.error(error) - } -} - -export function* onDeleteUnit(action) { - try { - const topologyId = yield select((state) => state.currentTopologyId) - const tileId = yield select((state) => state.interactionLevel.tileId) - const position = yield select((state) => state.interactionLevel.position) - const machine = yield select( - (state) => - state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]] - ) - const unitIds = machine[action.unitType + 'Ids'].slice() - unitIds.splice(action.index, 1) - - yield put( - addPropToStoreObject('machine', machine._id, { - [action.unitType + 'Ids']: unitIds, - }) - ) - yield updateTopologyOnServer(topologyId) - } 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.js b/opendc-web/opendc-web-ui/src/shapes.js new file mode 100644 index 00000000..621c7d25 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/shapes.js @@ -0,0 +1,144 @@ +import PropTypes from 'prop-types' + +export const User = PropTypes.shape({ + _id: PropTypes.string.isRequired, + googleId: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + givenName: PropTypes.string.isRequired, + familyName: PropTypes.string.isRequired, + authorizations: PropTypes.array.isRequired, +}) + +export const Project = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + datetimeCreated: PropTypes.string.isRequired, + datetimeLastEdited: PropTypes.string.isRequired, + topologyIds: PropTypes.array.isRequired, + portfolioIds: PropTypes.array.isRequired, +}) + +export const Authorization = PropTypes.shape({ + userId: PropTypes.string.isRequired, + user: User, + projectId: PropTypes.string.isRequired, + project: Project, + authorizationLevel: PropTypes.string.isRequired, +}) + +export const ProcessingUnit = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + clockRateMhz: PropTypes.number.isRequired, + numberOfCores: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, +}) + +export const StorageUnit = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + speedMbPerS: PropTypes.number.isRequired, + sizeMb: PropTypes.number.isRequired, + energyConsumptionW: PropTypes.number.isRequired, +}) + +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(ProcessingUnit), + gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), + gpus: PropTypes.arrayOf(ProcessingUnit), + memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired), + memories: PropTypes.arrayOf(StorageUnit), + storageIds: PropTypes.arrayOf(PropTypes.string.isRequired), + storages: PropTypes.arrayOf(StorageUnit), +}) + +export const Rack = PropTypes.shape({ + _id: PropTypes.string.isRequired, + capacity: PropTypes.number.isRequired, + powerCapacityW: PropTypes.number.isRequired, + machines: PropTypes.arrayOf(Machine), +}) + +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: Rack, +}) + +export const Room = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + tiles: PropTypes.arrayOf(Tile), +}) + +export const Topology = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + rooms: PropTypes.arrayOf(Room), +}) + +export const Scheduler = PropTypes.shape({ + name: PropTypes.string.isRequired, +}) + +export const Trace = PropTypes.shape({ + _id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + type: PropTypes.string.isRequired, +}) + +export const Portfolio = PropTypes.shape({ + _id: PropTypes.string.isRequired, + projectId: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + scenarioIds: PropTypes.arrayOf(PropTypes.string).isRequired, + targets: PropTypes.shape({ + enabledMetrics: PropTypes.arrayOf(PropTypes.string).isRequired, + repeatsPerScenario: PropTypes.number.isRequired, + }).isRequired, +}) + +export const Scenario = PropTypes.shape({ + _id: PropTypes.string.isRequired, + portfolioId: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + simulation: PropTypes.shape({ + state: PropTypes.string.isRequired, + }).isRequired, + trace: PropTypes.shape({ + traceId: PropTypes.string.isRequired, + trace: Trace, + loadSamplingFraction: PropTypes.number.isRequired, + }).isRequired, + topology: PropTypes.shape({ + topologyId: PropTypes.string.isRequired, + topology: Topology, + }).isRequired, + operational: PropTypes.shape({ + failuresEnabled: PropTypes.bool.isRequired, + performanceInterferenceEnabled: PropTypes.bool.isRequired, + schedulerName: PropTypes.string.isRequired, + scheduler: Scheduler, + }).isRequired, + results: PropTypes.object, +}) + +export const WallSegment = PropTypes.shape({ + startPosX: PropTypes.number.isRequired, + startPosY: PropTypes.number.isRequired, + isHorizontal: PropTypes.bool.isRequired, + length: PropTypes.number.isRequired, +}) + +export const InteractionLevel = PropTypes.shape({ + mode: PropTypes.string.isRequired, + roomId: PropTypes.string, + rackId: PropTypes.string, +}) diff --git a/opendc-web/opendc-web-ui/src/shapes/index.js b/opendc-web/opendc-web-ui/src/shapes/index.js deleted file mode 100644 index 621c7d25..00000000 --- a/opendc-web/opendc-web-ui/src/shapes/index.js +++ /dev/null @@ -1,144 +0,0 @@ -import PropTypes from 'prop-types' - -export const User = PropTypes.shape({ - _id: PropTypes.string.isRequired, - googleId: PropTypes.string.isRequired, - email: PropTypes.string.isRequired, - givenName: PropTypes.string.isRequired, - familyName: PropTypes.string.isRequired, - authorizations: PropTypes.array.isRequired, -}) - -export const Project = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - datetimeCreated: PropTypes.string.isRequired, - datetimeLastEdited: PropTypes.string.isRequired, - topologyIds: PropTypes.array.isRequired, - portfolioIds: PropTypes.array.isRequired, -}) - -export const Authorization = PropTypes.shape({ - userId: PropTypes.string.isRequired, - user: User, - projectId: PropTypes.string.isRequired, - project: Project, - authorizationLevel: PropTypes.string.isRequired, -}) - -export const ProcessingUnit = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - clockRateMhz: PropTypes.number.isRequired, - numberOfCores: PropTypes.number.isRequired, - energyConsumptionW: PropTypes.number.isRequired, -}) - -export const StorageUnit = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - speedMbPerS: PropTypes.number.isRequired, - sizeMb: PropTypes.number.isRequired, - energyConsumptionW: PropTypes.number.isRequired, -}) - -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(ProcessingUnit), - gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired), - gpus: PropTypes.arrayOf(ProcessingUnit), - memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired), - memories: PropTypes.arrayOf(StorageUnit), - storageIds: PropTypes.arrayOf(PropTypes.string.isRequired), - storages: PropTypes.arrayOf(StorageUnit), -}) - -export const Rack = PropTypes.shape({ - _id: PropTypes.string.isRequired, - capacity: PropTypes.number.isRequired, - powerCapacityW: PropTypes.number.isRequired, - machines: PropTypes.arrayOf(Machine), -}) - -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: Rack, -}) - -export const Room = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - tiles: PropTypes.arrayOf(Tile), -}) - -export const Topology = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - rooms: PropTypes.arrayOf(Room), -}) - -export const Scheduler = PropTypes.shape({ - name: PropTypes.string.isRequired, -}) - -export const Trace = PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}) - -export const Portfolio = PropTypes.shape({ - _id: PropTypes.string.isRequired, - projectId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - scenarioIds: PropTypes.arrayOf(PropTypes.string).isRequired, - targets: PropTypes.shape({ - enabledMetrics: PropTypes.arrayOf(PropTypes.string).isRequired, - repeatsPerScenario: PropTypes.number.isRequired, - }).isRequired, -}) - -export const Scenario = PropTypes.shape({ - _id: PropTypes.string.isRequired, - portfolioId: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - simulation: PropTypes.shape({ - state: PropTypes.string.isRequired, - }).isRequired, - trace: PropTypes.shape({ - traceId: PropTypes.string.isRequired, - trace: Trace, - loadSamplingFraction: PropTypes.number.isRequired, - }).isRequired, - topology: PropTypes.shape({ - topologyId: PropTypes.string.isRequired, - topology: Topology, - }).isRequired, - operational: PropTypes.shape({ - failuresEnabled: PropTypes.bool.isRequired, - performanceInterferenceEnabled: PropTypes.bool.isRequired, - schedulerName: PropTypes.string.isRequired, - scheduler: Scheduler, - }).isRequired, - results: PropTypes.object, -}) - -export const WallSegment = PropTypes.shape({ - startPosX: PropTypes.number.isRequired, - startPosY: PropTypes.number.isRequired, - isHorizontal: PropTypes.bool.isRequired, - length: PropTypes.number.isRequired, -}) - -export const InteractionLevel = PropTypes.shape({ - mode: PropTypes.string.isRequired, - roomId: PropTypes.string, - rackId: PropTypes.string, -}) diff --git a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js b/opendc-web/opendc-web-ui/src/shortcuts/keymap.js deleted file mode 100644 index 8260ace2..00000000 --- a/opendc-web/opendc-web-ui/src/shortcuts/keymap.js +++ /dev/null @@ -1,8 +0,0 @@ -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/store/configure-store.js b/opendc-web/opendc-web-ui/src/store/configure-store.js deleted file mode 100644 index 149536a3..00000000 --- a/opendc-web/opendc-web-ui/src/store/configure-store.js +++ /dev/null @@ -1,60 +0,0 @@ -import { useMemo } from 'react' -import { applyMiddleware, compose, createStore } from 'redux' -import { createLogger } from 'redux-logger' -import persistState from 'redux-localstorage' -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 { viewportAdjustmentMiddleware } from './middlewares/viewport-adjustment' - -let store - -function initStore(initialState) { - const sagaMiddleware = createSagaMiddleware() - - const middlewares = [thunk, sagaMiddleware, authRedirectMiddleware, viewportAdjustmentMiddleware] - - if (process.env.NODE_ENV !== 'production') { - middlewares.push(createLogger()) - } - - let enhancer = applyMiddleware(...middlewares) - - if (global.localStorage) { - enhancer = compose(persistState('auth'), enhancer) - } - - const configuredStore = createStore(rootReducer, enhancer) - sagaMiddleware.run(rootSaga) - store = configuredStore - - return configuredStore -} - -export const initializeStore = (preloadedState) => { - let _store = store ?? initStore(preloadedState) - - // 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) { - return useMemo(() => initializeStore(initialState), [initialState]) -} diff --git a/opendc-web/opendc-web-ui/src/store/hooks/experiments.js b/opendc-web/opendc-web-ui/src/store/hooks/experiments.js deleted file mode 100644 index aef512e5..00000000 --- a/opendc-web/opendc-web-ui/src/store/hooks/experiments.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useSelector } from 'react-redux' - -/** - * Return the 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/store/hooks/map.js b/opendc-web/opendc-web-ui/src/store/hooks/map.js deleted file mode 100644 index 6aef6ac5..00000000 --- a/opendc-web/opendc-web-ui/src/store/hooks/map.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { 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/store/hooks/project.js b/opendc-web/opendc-web-ui/src/store/hooks/project.js deleted file mode 100644 index 0db49fdd..00000000 --- a/opendc-web/opendc-web-ui/src/store/hooks/project.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useSelector } from 'react-redux' - -/** - * Return the 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/store/hooks/topology.js b/opendc-web/opendc-web-ui/src/store/hooks/topology.js deleted file mode 100644 index d3ffb3e1..00000000 --- a/opendc-web/opendc-web-ui/src/store/hooks/topology.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useSelector } from 'react-redux' - -/** - * Return the 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/store/middlewares/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js deleted file mode 100644 index b4472c54..00000000 --- a/opendc-web/opendc-web-ui/src/store/middlewares/viewport-adjustment.js +++ /dev/null @@ -1,73 +0,0 @@ -import { SET_MAP_DIMENSIONS, setMapPosition, setMapScale } from '../../actions/map' -import { SET_CURRENT_TOPOLOGY } from '../../actions/topology/building' -import { - MAP_MAX_SCALE, - MAP_MIN_SCALE, - SIDEBAR_WIDTH, - TILE_SIZE_IN_PIXELS, - VIEWPORT_PADDING, -} from '../../components/app/map/MapConstants' -import { calculateRoomListBounds } from '../../util/tile-calculations' - -export const viewportAdjustmentMiddleware = (store) => (next) => (action) => { - const state = store.getState() - - let topologyId = '-1' - let mapDimensions = {} - if (action.type === SET_CURRENT_TOPOLOGY && action.topologyId !== '-1') { - topologyId = action.topologyId - mapDimensions = state.map.dimensions - } else if (action.type === SET_MAP_DIMENSIONS && state.currentTopologyId !== '-1') { - topologyId = state.currentTopologyId - mapDimensions = { width: action.width, height: action.height } - } - - if (topologyId !== '-1') { - const roomIds = state.objects.topology[topologyId].roomIds - const rooms = roomIds.map((id) => Object.assign({}, state.objects.room[id])) - rooms.forEach((room) => (room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId]))) - - let hasNoTiles = true - for (let i in rooms) { - if (rooms[i].tiles.length > 0) { - hasNoTiles = false - break - } - } - - if (!hasNoTiles) { - const viewportParams = calculateParametersToZoomInOnRooms(rooms, mapDimensions.width, mapDimensions.height) - store.dispatch(setMapPosition(viewportParams.newX, viewportParams.newY)) - store.dispatch(setMapScale(viewportParams.newScale)) - } - } - - next(action) -} - -function calculateParametersToZoomInOnRooms(rooms, mapWidth, mapHeight) { - const bounds = calculateRoomListBounds(rooms) - const newScale = calculateNewScale(bounds, mapWidth, mapHeight) - - // Coordinates of the center of the room, relative to the global origin of the map - const roomCenterCoordinates = { - x: bounds.center.x * TILE_SIZE_IN_PIXELS * newScale, - y: bounds.center.y * TILE_SIZE_IN_PIXELS * newScale, - } - - const newX = -roomCenterCoordinates.x + mapWidth / 2 - const newY = -roomCenterCoordinates.y + mapHeight / 2 - - return { newScale, newX, newY } -} - -function calculateNewScale(bounds, mapWidth, mapHeight) { - const width = bounds.max.x - bounds.min.x - const height = bounds.max.y - bounds.min.y - - const scaleX = (mapWidth - 2 * SIDEBAR_WIDTH) / (width * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) - const scaleY = mapHeight / (height * TILE_SIZE_IN_PIXELS + 2 * VIEWPORT_PADDING) - const newScale = Math.min(scaleX, scaleY) - - return Math.min(Math.max(MAP_MIN_SCALE, newScale), MAP_MAX_SCALE) -} diff --git a/opendc-web/opendc-web-ui/src/style-globals/_mixins.scss b/opendc-web/opendc-web-ui/src/style-globals/_mixins.scss deleted file mode 100644 index 5f103cd7..00000000 --- a/opendc-web/opendc-web-ui/src/style-globals/_mixins.scss +++ /dev/null @@ -1,5 +0,0 @@ -/* General Button Abstractions */ -@mixin clickable { - cursor: pointer; - user-select: none; -} diff --git a/opendc-web/opendc-web-ui/src/style-globals/_variables.scss b/opendc-web/opendc-web-ui/src/style-globals/_variables.scss deleted file mode 100644 index e3df6cbd..00000000 --- a/opendc-web/opendc-web-ui/src/style-globals/_variables.scss +++ /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; -- cgit v1.2.3