summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/containers
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-04-25 16:01:14 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-04-25 16:01:14 +0200
commitcd0b45627f0d8da8c8dc4edde223f3c36e9bcfbf (patch)
tree6ae1681630a0e270c23804e6dbb3bd414ebe5d6e /opendc-web/opendc-web-ui/src/containers
parent128a1db017545597a5c035b7960eb3fd36b5f987 (diff)
build: Migrate to flat project structure
This change updates the project structure to become flattened. Previously, the simulator, frontend and API each lived into their own directory. With this change, all modules of the project live in the top-level directory of the repository. This should improve discoverability of modules of the project.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/containers')
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js22
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js26
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js14
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js21
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js26
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js17
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js33
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js46
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js28
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js45
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js10
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js41
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js46
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js25
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js15
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js20
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js17
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js21
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js21
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/Login.js62
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/Logout.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js14
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js35
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js35
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js35
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js35
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js40
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js38
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js30
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js30
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js50
-rw-r--r--opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js42
-rw-r--r--opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js13
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js20
-rw-r--r--opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js32
62 files changed, 1380 insertions, 0 deletions
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
new file mode 100644
index 00000000..9e4a6969
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/GrayContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { goDownOneInteractionLevel } from '../../../actions/interaction-level'
+import GrayLayer from '../../../components/app/map/elements/GrayLayer'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(goDownOneInteractionLevel()),
+ }
+}
+
+const GrayContainer = connect(undefined, mapDispatchToProps)(GrayLayer)
+
+export default 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
new file mode 100644
index 00000000..23c920b6
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/MapStage.js
@@ -0,0 +1,22 @@
+import { connect } from 'react-redux'
+import { setMapDimensions, setMapPositionWithBoundsCheck, zoomInOnPosition } from '../../../actions/map'
+import MapStageComponent from '../../../components/app/map/MapStageComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ mapPosition: state.map.position,
+ mapDimensions: state.map.dimensions,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ zoomInOnPosition: (zoomIn, x, y) => dispatch(zoomInOnPosition(zoomIn, x, y)),
+ setMapPositionWithBoundsCheck: (x, y) => dispatch(setMapPositionWithBoundsCheck(x, y)),
+ setMapDimensions: (width, height) => dispatch(setMapDimensions(width, height)),
+ }
+}
+
+const MapStage = connect(mapStateToProps, mapDispatchToProps)(MapStageComponent)
+
+export default MapStage
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js
new file mode 100644
index 00000000..40077608
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RackContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import RackGroup from '../../../components/app/map/groups/RackGroup'
+
+const mapStateToProps = (state) => {
+ return {
+ interactionLevel: state.interactionLevel,
+ }
+}
+
+const RackContainer = connect(mapStateToProps)(RackGroup)
+
+export default RackContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js
new file mode 100644
index 00000000..53746271
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js
@@ -0,0 +1,26 @@
+import { connect } from 'react-redux'
+import RackFillBar from '../../../components/app/map/elements/RackFillBar'
+
+const mapStateToProps = (state, ownProps) => {
+ let energyConsumptionTotal = 0
+ const rack = state.objects.rack[state.objects.tile[ownProps.tileId].rackId]
+ const machineIds = rack.machineIds
+ machineIds.forEach((machineId) => {
+ if (machineId !== null) {
+ const machine = state.objects.machine[machineId]
+ machine.cpuIds.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW))
+ machine.gpuIds.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW))
+ machine.memoryIds.forEach((id) => (energyConsumptionTotal += state.objects.memory[id].energyConsumptionW))
+ machine.storageIds.forEach((id) => (energyConsumptionTotal += state.objects.storage[id].energyConsumptionW))
+ }
+ })
+
+ return {
+ type: 'energy',
+ fillFraction: Math.min(1, energyConsumptionTotal / rack.powerCapacityW),
+ }
+}
+
+const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar)
+
+export default RackSpaceFillContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js
new file mode 100644
index 00000000..0509a5a5
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js
@@ -0,0 +1,14 @@
+import { connect } from 'react-redux'
+import RackFillBar from '../../../components/app/map/elements/RackFillBar'
+
+const mapStateToProps = (state, ownProps) => {
+ const machineIds = state.objects.rack[state.objects.tile[ownProps.tileId].rackId].machineIds
+ return {
+ type: 'space',
+ fillFraction: machineIds.filter((id) => id !== null).length / machineIds.length,
+ }
+}
+
+const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar)
+
+export default RackSpaceFillContainer
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
new file mode 100644
index 00000000..91bf4e5d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import { goFromBuildingToRoom } from '../../../actions/interaction-level'
+import RoomGroup from '../../../components/app/map/groups/RoomGroup'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ interactionLevel: state.interactionLevel,
+ currentRoomInConstruction: state.construction.currentRoomInConstruction,
+ room: state.objects.room[ownProps.roomId],
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onClick: () => dispatch(goFromBuildingToRoom(ownProps.roomId)),
+ }
+}
+
+const RoomContainer = connect(mapStateToProps, mapDispatchToProps)(RoomGroup)
+
+export default RoomContainer
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
new file mode 100644
index 00000000..04d6c8d6
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
@@ -0,0 +1,26 @@
+import { connect } from 'react-redux'
+import { goFromRoomToRack } from '../../../actions/interaction-level'
+import TileGroup from '../../../components/app/map/groups/TileGroup'
+
+const mapStateToProps = (state, ownProps) => {
+ const tile = state.objects.tile[ownProps.tileId]
+
+ return {
+ interactionLevel: state.interactionLevel,
+ tile,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: (tile) => {
+ if (tile.rackId) {
+ dispatch(goFromRoomToRack(tile._id))
+ }
+ },
+ }
+}
+
+const TileContainer = connect(mapStateToProps, mapDispatchToProps)(TileGroup)
+
+export default TileContainer
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
new file mode 100644
index 00000000..de43a151
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/TopologyContainer.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux'
+import TopologyGroup from '../../../components/app/map/groups/TopologyGroup'
+
+const mapStateToProps = (state) => {
+ if (state.currentTopologyId === '-1') {
+ return {}
+ }
+
+ return {
+ topology: state.objects.topology[state.currentTopologyId],
+ interactionLevel: state.interactionLevel,
+ }
+}
+
+const TopologyContainer = connect(mapStateToProps)(TopologyGroup)
+
+export default TopologyContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js
new file mode 100644
index 00000000..67f8a242
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import WallGroup from '../../../components/app/map/groups/WallGroup'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ tiles: state.objects.room[ownProps.roomId].tileIds.map((tileId) => state.objects.tile[tileId]),
+ }
+}
+
+const WallContainer = connect(mapStateToProps)(WallGroup)
+
+export default WallContainer
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
new file mode 100644
index 00000000..fa3b9d22
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ScaleIndicatorContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import ScaleIndicatorComponent from '../../../../components/app/map/controls/ScaleIndicatorComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ scale: state.map.scale,
+ }
+}
+
+const ScaleIndicatorContainer = connect(mapStateToProps)(ScaleIndicatorComponent)
+
+export default ScaleIndicatorContainer
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
new file mode 100644
index 00000000..ddc68cc7
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/controls/ZoomControlContainer.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { zoomInOnCenter } from '../../../../actions/map'
+import ZoomControlComponent from '../../../../components/app/map/controls/ZoomControlComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ mapScale: state.map.scale,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ zoomInOnCenter: (zoomIn) => dispatch(zoomInOnCenter(zoomIn)),
+ }
+}
+
+const ZoomControlContainer = connect(mapStateToProps, mapDispatchToProps)(ZoomControlComponent)
+
+export default ZoomControlContainer
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
new file mode 100644
index 00000000..8596cb9c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/MapLayer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import MapLayerComponent from '../../../../components/app/map/layers/MapLayerComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ mapPosition: state.map.position,
+ mapScale: state.map.scale,
+ }
+}
+
+const MapLayer = connect(mapStateToProps)(MapLayerComponent)
+
+export default MapLayer
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
new file mode 100644
index 00000000..a4927862
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
@@ -0,0 +1,33 @@
+import { connect } from 'react-redux'
+import { addRackToTile } from '../../../../actions/topology/room'
+import ObjectHoverLayerComponent from '../../../../components/app/map/layers/ObjectHoverLayerComponent'
+import { findTileWithPosition } from '../../../../util/tile-calculations'
+
+const mapStateToProps = (state) => {
+ return {
+ mapPosition: state.map.position,
+ mapScale: state.map.scale,
+ isEnabled: () => state.construction.inRackConstructionMode,
+ isValid: (x, y) => {
+ if (state.interactionLevel.mode !== 'ROOM') {
+ return false
+ }
+
+ const currentRoom = state.objects.room[state.interactionLevel.roomId]
+ const tiles = currentRoom.tileIds.map((tileId) => state.objects.tile[tileId])
+ const tile = findTileWithPosition(tiles, x, y)
+
+ return !(tile === null || tile.rackId)
+ },
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: (x, y) => dispatch(addRackToTile(x, y)),
+ }
+}
+
+const ObjectHoverLayer = connect(mapStateToProps, mapDispatchToProps)(ObjectHoverLayerComponent)
+
+export default ObjectHoverLayer
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
new file mode 100644
index 00000000..66404f9e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
@@ -0,0 +1,46 @@
+import { connect } from 'react-redux'
+import { toggleTileAtLocation } from '../../../../actions/topology/building'
+import RoomHoverLayerComponent from '../../../../components/app/map/layers/RoomHoverLayerComponent'
+import {
+ deriveValidNextTilePositions,
+ findPositionInPositions,
+ findPositionInRooms,
+} from '../../../../util/tile-calculations'
+
+const mapStateToProps = (state) => {
+ return {
+ mapPosition: state.map.position,
+ mapScale: state.map.scale,
+ isEnabled: () => state.construction.currentRoomInConstruction !== '-1',
+ isValid: (x, y) => {
+ const newRoom = Object.assign({}, state.objects.room[state.construction.currentRoomInConstruction])
+ const oldRooms = Object.keys(state.objects.room)
+ .map((id) => Object.assign({}, state.objects.room[id]))
+ .filter(
+ (room) =>
+ state.objects.topology[state.currentTopologyId].roomIds.indexOf(room._id) !== -1 &&
+ room._id !== state.construction.currentRoomInConstruction
+ )
+
+ ;[...oldRooms, newRoom].forEach((room) => {
+ room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId])
+ })
+ if (newRoom.tileIds.length === 0) {
+ return findPositionInRooms(oldRooms, x, y) === -1
+ }
+
+ const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles)
+ return findPositionInPositions(validNextPositions, x, y) !== -1
+ },
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: (x, y) => dispatch(toggleTileAtLocation(x, y)),
+ }
+}
+
+const RoomHoverLayer = connect(mapStateToProps, mapDispatchToProps)(RoomHoverLayerComponent)
+
+export default RoomHoverLayer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js b/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js
new file mode 100644
index 00000000..4b430e54
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/results/PortfolioResultsContainer.js
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux'
+import PortfolioResultsComponent from '../../../components/app/results/PortfolioResultsComponent'
+
+const mapStateToProps = (state) => {
+ if (
+ state.currentPortfolioId === '-1' ||
+ !state.objects.portfolio[state.currentPortfolioId] ||
+ state.objects.portfolio[state.currentPortfolioId].scenarioIds
+ .map((scenarioId) => state.objects.scenario[scenarioId])
+ .some((s) => s === undefined)
+ ) {
+ return {
+ portfolio: undefined,
+ scenarios: [],
+ }
+ }
+
+ return {
+ portfolio: state.objects.portfolio[state.currentPortfolioId],
+ scenarios: state.objects.portfolio[state.currentPortfolioId].scenarioIds.map(
+ (scenarioId) => state.objects.scenario[scenarioId]
+ ),
+ }
+}
+
+const PortfolioResultsContainer = connect(mapStateToProps)(PortfolioResultsComponent)
+
+export default PortfolioResultsContainer
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
new file mode 100644
index 00000000..b32c8b1d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/PortfolioListContainer.js
@@ -0,0 +1,45 @@
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import PortfolioListComponent from '../../../../components/app/sidebars/project/PortfolioListComponent'
+import { deletePortfolio, setCurrentPortfolio } from '../../../../actions/portfolios'
+import { openNewPortfolioModal } from '../../../../actions/modals/portfolios'
+import { getState } from '../../../../util/state-utils'
+import { setCurrentTopology } from '../../../../actions/topology/building'
+
+const mapStateToProps = (state) => {
+ let portfolios = state.objects.project[state.currentProjectId]
+ ? state.objects.project[state.currentProjectId].portfolioIds.map((t) => state.objects.portfolio[t])
+ : []
+ if (portfolios.filter((t) => !t).length > 0) {
+ portfolios = []
+ }
+
+ return {
+ currentProjectId: state.currentProjectId,
+ currentPortfolioId: state.currentPortfolioId,
+ portfolios,
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onNewPortfolio: () => {
+ dispatch(openNewPortfolioModal())
+ },
+ onChoosePortfolio: (portfolioId) => {
+ dispatch(setCurrentPortfolio(portfolioId))
+ },
+ onDeletePortfolio: async (id) => {
+ if (id) {
+ const state = await getState(dispatch)
+ dispatch(deletePortfolio(id))
+ dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0]))
+ ownProps.history.push(`/projects/${state.currentProjectId}`)
+ }
+ },
+ }
+}
+
+const PortfolioListContainer = withRouter(connect(mapStateToProps, mapDispatchToProps)(PortfolioListComponent))
+
+export default PortfolioListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js
new file mode 100644
index 00000000..49001099
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ProjectSidebarContainer.js
@@ -0,0 +1,10 @@
+import React from 'react'
+import { withRouter } from 'react-router-dom'
+import ProjectSidebarComponent from '../../../../components/app/sidebars/project/ProjectSidebarComponent'
+import { isCollapsible } from '../../../../util/sidebar-space'
+
+const ProjectSidebarContainer = withRouter(({ location, ...props }) => (
+ <ProjectSidebarComponent collapsible={isCollapsible(location)} {...props} />
+))
+
+export default ProjectSidebarContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
new file mode 100644
index 00000000..415e2792
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
@@ -0,0 +1,41 @@
+import { connect } from 'react-redux'
+import ScenarioListComponent from '../../../../components/app/sidebars/project/ScenarioListComponent'
+import { openNewScenarioModal } from '../../../../actions/modals/scenarios'
+import { deleteScenario, setCurrentScenario } from '../../../../actions/scenarios'
+import { setCurrentPortfolio } from '../../../../actions/portfolios'
+
+const mapStateToProps = (state, ownProps) => {
+ let scenarios = state.objects.portfolio[ownProps.portfolioId]
+ ? state.objects.portfolio[ownProps.portfolioId].scenarioIds.map((t) => state.objects.scenario[t])
+ : []
+ if (scenarios.filter((t) => !t).length > 0) {
+ scenarios = []
+ }
+
+ return {
+ currentProjectId: state.currentProjectId,
+ currentScenarioId: state.currentScenarioId,
+ scenarios,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onNewScenario: (currentPortfolioId) => {
+ dispatch(setCurrentPortfolio(currentPortfolioId))
+ dispatch(openNewScenarioModal())
+ },
+ onChooseScenario: (portfolioId, scenarioId) => {
+ dispatch(setCurrentScenario(portfolioId, scenarioId))
+ },
+ onDeleteScenario: (id) => {
+ if (id) {
+ dispatch(deleteScenario(id))
+ }
+ },
+ }
+}
+
+const ScenarioListContainer = connect(mapStateToProps, mapDispatchToProps)(ScenarioListComponent)
+
+export default ScenarioListContainer
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
new file mode 100644
index 00000000..e1de18f9
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
@@ -0,0 +1,46 @@
+import { connect } from 'react-redux'
+import TopologyListComponent from '../../../../components/app/sidebars/project/TopologyListComponent'
+import { setCurrentTopology } from '../../../../actions/topology/building'
+import { openNewTopologyModal } from '../../../../actions/modals/topology'
+import { withRouter } from 'react-router-dom'
+import { getState } from '../../../../util/state-utils'
+import { deleteTopology } from '../../../../actions/topologies'
+
+const mapStateToProps = (state) => {
+ let topologies = state.objects.project[state.currentProjectId]
+ ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t])
+ : []
+ if (topologies.filter((t) => !t).length > 0) {
+ topologies = []
+ }
+
+ return {
+ currentTopologyId: state.currentTopologyId,
+ topologies,
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onChooseTopology: async (id) => {
+ dispatch(setCurrentTopology(id))
+ const state = await getState(dispatch)
+ ownProps.history.push(`/projects/${state.currentProjectId}`)
+ },
+ onNewTopology: () => {
+ dispatch(openNewTopologyModal())
+ },
+ onDeleteTopology: async (id) => {
+ if (id) {
+ const state = await getState(dispatch)
+ dispatch(deleteTopology(id))
+ dispatch(setCurrentTopology(state.objects.project[state.currentProjectId].topologyIds[0]))
+ ownProps.history.push(`/projects/${state.currentProjectId}`)
+ }
+ },
+ }
+}
+
+const TopologyListContainer = withRouter(connect(mapStateToProps, mapDispatchToProps)(TopologyListComponent))
+
+export default TopologyListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js
new file mode 100644
index 00000000..fe7c02fd
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/TopologySidebarContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import TopologySidebarComponent from '../../../../components/app/sidebars/topology/TopologySidebarComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ interactionLevel: state.interactionLevel,
+ }
+}
+
+const TopologySidebarContainer = connect(mapStateToProps)(TopologySidebarComponent)
+
+export default TopologySidebarContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js
new file mode 100644
index 00000000..a0b52e56
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js
@@ -0,0 +1,5 @@
+import BuildingSidebarComponent from '../../../../../components/app/sidebars/topology/building/BuildingSidebarComponent'
+
+const BuildingSidebarContainer = BuildingSidebarComponent
+
+export default BuildingSidebarContainer
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
new file mode 100644
index 00000000..ea9e9e60
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js
@@ -0,0 +1,25 @@
+import { connect } from 'react-redux'
+import {
+ cancelNewRoomConstruction,
+ finishNewRoomConstruction,
+ startNewRoomConstruction,
+} from '../../../../../actions/topology/building'
+import StartNewRoomConstructionComponent from '../../../../../components/app/sidebars/topology/building/NewRoomConstructionComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ currentRoomInConstruction: state.construction.currentRoomInConstruction,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onStart: () => dispatch(startNewRoomConstruction()),
+ onFinish: () => dispatch(finishNewRoomConstruction()),
+ onCancel: () => dispatch(cancelNewRoomConstruction()),
+ }
+}
+
+const NewRoomConstructionButton = connect(mapStateToProps, mapDispatchToProps)(StartNewRoomConstructionComponent)
+
+export default NewRoomConstructionButton
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
new file mode 100644
index 00000000..24287ab0
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/BackToRackContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level'
+import BackToRackComponent from '../../../../../components/app/sidebars/topology/machine/BackToRackComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(goDownOneInteractionLevel()),
+ }
+}
+
+const BackToRackContainer = connect(undefined, mapDispatchToProps)(BackToRackComponent)
+
+export default BackToRackContainer
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
new file mode 100644
index 00000000..65e683e6
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { openDeleteMachineModal } from '../../../../../actions/modals/topology'
+import DeleteMachineComponent from '../../../../../components/app/sidebars/topology/machine/DeleteMachineComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(openDeleteMachineModal()),
+ }
+}
+
+const DeleteMachineContainer = connect(undefined, mapDispatchToProps)(DeleteMachineComponent)
+
+export default DeleteMachineContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js
new file mode 100644
index 00000000..1cf35b05
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineNameContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import MachineNameComponent from '../../../../../components/app/sidebars/topology/machine/MachineNameComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ position: state.interactionLevel.position,
+ }
+}
+
+const MachineNameContainer = connect(mapStateToProps)(MachineNameComponent)
+
+export default MachineNameContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
new file mode 100644
index 00000000..b04e3118
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux'
+import MachineSidebarComponent from '../../../../../components/app/sidebars/topology/machine/MachineSidebarComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ machineId:
+ state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[
+ state.interactionLevel.position - 1
+ ],
+ }
+}
+
+const MachineSidebarContainer = connect(mapStateToProps)(MachineSidebarComponent)
+
+export default MachineSidebarContainer
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
new file mode 100644
index 00000000..29e48016
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitAddContainer.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { addUnit } from '../../../../../actions/topology/machine'
+import UnitAddComponent from '../../../../../components/app/sidebars/topology/machine/UnitAddComponent'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ units: Object.values(state.objects[ownProps.unitType]),
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onAdd: (id) => dispatch(addUnit(ownProps.unitType, id)),
+ }
+}
+
+const UnitAddContainer = connect(mapStateToProps, mapDispatchToProps)(UnitAddComponent)
+
+export default UnitAddContainer
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
new file mode 100644
index 00000000..f334f9f2
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux'
+import { deleteUnit } from '../../../../../actions/topology/machine'
+import UnitComponent from '../../../../../components/app/sidebars/topology/machine/UnitComponent'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ unit: state.objects[ownProps.unitType][ownProps.unitId],
+ index: ownProps.unitId,
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onDelete: () => dispatch(deleteUnit(ownProps.unitType, ownProps.index)),
+ }
+}
+
+const UnitContainer = connect(mapStateToProps, mapDispatchToProps)(UnitComponent)
+
+export default UnitContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js
new file mode 100644
index 00000000..f382ff74
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux'
+import UnitListComponent from '../../../../../components/app/sidebars/topology/machine/UnitListComponent'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ unitIds:
+ state.objects.machine[
+ state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[
+ state.interactionLevel.position - 1
+ ]
+ ][ownProps.unitType + 'Ids'],
+ }
+}
+
+const UnitListContainer = connect(mapStateToProps)(UnitListComponent)
+
+export default UnitListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js
new file mode 100644
index 00000000..00fe4067
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js
@@ -0,0 +1,5 @@
+import UnitTabsComponent from '../../../../../components/app/sidebars/topology/machine/UnitTabsComponent'
+
+const UnitTabsContainer = UnitTabsComponent
+
+export default UnitTabsContainer
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
new file mode 100644
index 00000000..c941e745
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/AddPrefabContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { addPrefab } from '../../../../../actions/prefabs'
+import AddPrefabComponent from '../../../../../components/app/sidebars/topology/rack/AddPrefabComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(addPrefab('name')),
+ }
+}
+
+const AddPrefabContainer = connect(undefined, mapDispatchToProps)(AddPrefabComponent)
+
+export default AddPrefabContainer
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
new file mode 100644
index 00000000..58c3b082
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level'
+import BackToRoomComponent from '../../../../../components/app/sidebars/topology/rack/BackToRoomComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(goDownOneInteractionLevel()),
+ }
+}
+
+const BackToRoomContainer = connect(undefined, mapDispatchToProps)(BackToRoomComponent)
+
+export default BackToRoomContainer
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
new file mode 100644
index 00000000..8229a359
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { openDeleteRackModal } from '../../../../../actions/modals/topology'
+import DeleteRackComponent from '../../../../../components/app/sidebars/topology/rack/DeleteRackComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(openDeleteRackModal()),
+ }
+}
+
+const DeleteRackContainer = connect(undefined, mapDispatchToProps)(DeleteRackComponent)
+
+export default DeleteRackContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
new file mode 100644
index 00000000..cf341da9
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { addMachine } from '../../../../../actions/topology/rack'
+import EmptySlotComponent from '../../../../../components/app/sidebars/topology/rack/EmptySlotComponent'
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onAdd: () => dispatch(addMachine(ownProps.position)),
+ }
+}
+
+const EmptySlotContainer = connect(undefined, mapDispatchToProps)(EmptySlotComponent)
+
+export default EmptySlotContainer
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
new file mode 100644
index 00000000..fe12827d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { goFromRackToMachine } from '../../../../../actions/interaction-level'
+import MachineComponent from '../../../../../components/app/sidebars/topology/rack/MachineComponent'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ machine: state.objects.machine[ownProps.machineId],
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onClick: () => dispatch(goFromRackToMachine(ownProps.position)),
+ }
+}
+
+const MachineContainer = connect(mapStateToProps, mapDispatchToProps)(MachineComponent)
+
+export default MachineContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js
new file mode 100644
index 00000000..bc5a285a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import MachineListComponent from '../../../../../components/app/sidebars/topology/rack/MachineListComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ machineIds: state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds,
+ }
+}
+
+const MachineListContainer = connect(mapStateToProps)(MachineListComponent)
+
+export default MachineListContainer
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
new file mode 100644
index 00000000..504dbc61
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { openEditRackNameModal } from '../../../../../actions/modals/topology'
+import RackNameComponent from '../../../../../components/app/sidebars/topology/rack/RackNameComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ rackName: state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onEdit: () => dispatch(openEditRackNameModal()),
+ }
+}
+
+const RackNameContainer = connect(mapStateToProps, mapDispatchToProps)(RackNameComponent)
+
+export default RackNameContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
new file mode 100644
index 00000000..453d7e41
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import RackSidebarComponent from '../../../../../components/app/sidebars/topology/rack/RackSidebarComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ rackId: state.objects.tile[state.interactionLevel.tileId].rackId,
+ }
+}
+
+const RackSidebarContainer = connect(mapStateToProps)(RackSidebarComponent)
+
+export default RackSidebarContainer
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
new file mode 100644
index 00000000..4c1ab99d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { goDownOneInteractionLevel } from '../../../../../actions/interaction-level'
+import BackToBuildingComponent from '../../../../../components/app/sidebars/topology/room/BackToBuildingComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(goDownOneInteractionLevel()),
+ }
+}
+
+const BackToBuildingContainer = connect(undefined, mapDispatchToProps)(BackToBuildingComponent)
+
+export default 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
new file mode 100644
index 00000000..636fa5c5
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { openDeleteRoomModal } from '../../../../../actions/modals/topology'
+import DeleteRoomComponent from '../../../../../components/app/sidebars/topology/room/DeleteRoomComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(openDeleteRoomModal()),
+ }
+}
+
+const DeleteRoomContainer = connect(undefined, mapDispatchToProps)(DeleteRoomComponent)
+
+export default DeleteRoomContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js
new file mode 100644
index 00000000..d17a45d1
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/EditRoomContainer.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import { finishRoomEdit, startRoomEdit } from '../../../../../actions/topology/building'
+import EditRoomComponent from '../../../../../components/app/sidebars/topology/room/EditRoomComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ isEditing: state.construction.currentRoomInConstruction !== '-1',
+ isInRackConstructionMode: state.construction.inRackConstructionMode,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onEdit: () => dispatch(startRoomEdit()),
+ onFinish: () => dispatch(finishRoomEdit()),
+ }
+}
+
+const EditRoomContainer = connect(mapStateToProps, mapDispatchToProps)(EditRoomComponent)
+
+export default 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
new file mode 100644
index 00000000..cd8319de
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RackConstructionContainer.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import { startRackConstruction, stopRackConstruction } from '../../../../../actions/topology/room'
+import RackConstructionComponent from '../../../../../components/app/sidebars/topology/room/RackConstructionComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ inRackConstructionMode: state.construction.inRackConstructionMode,
+ isEditingRoom: state.construction.currentRoomInConstruction !== '-1',
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onStart: () => dispatch(startRackConstruction()),
+ onStop: () => dispatch(stopRackConstruction()),
+ }
+}
+
+const RackConstructionContainer = connect(mapStateToProps, mapDispatchToProps)(RackConstructionComponent)
+
+export default RackConstructionContainer
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
new file mode 100644
index 00000000..cab16016
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomNameContainer.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { openEditRoomNameModal } from '../../../../../actions/modals/topology'
+import RoomNameComponent from '../../../../../components/app/sidebars/topology/room/RoomNameComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ roomName: state.objects.room[state.interactionLevel.roomId].name,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onEdit: () => dispatch(openEditRoomNameModal()),
+ }
+}
+
+const RoomNameContainer = connect(mapStateToProps, mapDispatchToProps)(RoomNameComponent)
+
+export default RoomNameContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js
new file mode 100644
index 00000000..8c3ca8ab
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import RoomSidebarComponent from '../../../../../components/app/sidebars/topology/room/RoomSidebarComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ roomId: state.interactionLevel.roomId,
+ }
+}
+
+const RoomSidebarContainer = connect(mapStateToProps)(RoomSidebarComponent)
+
+export default RoomSidebarContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Login.js b/opendc-web/opendc-web-ui/src/containers/auth/Login.js
new file mode 100644
index 00000000..2f9726bf
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/auth/Login.js
@@ -0,0 +1,62 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+import GoogleLogin from 'react-google-login'
+import { connect } from 'react-redux'
+import { logIn } from '../../actions/auth'
+
+class LoginContainer extends React.Component {
+ static propTypes = {
+ visible: PropTypes.bool.isRequired,
+ onLogin: PropTypes.func.isRequired,
+ }
+
+ onAuthResponse(response) {
+ this.props.onLogin({
+ email: response.getBasicProfile().getEmail(),
+ givenName: response.getBasicProfile().getGivenName(),
+ familyName: response.getBasicProfile().getFamilyName(),
+ googleId: response.googleId,
+ authToken: response.getAuthResponse().id_token,
+ expiresAt: response.getAuthResponse().expires_at,
+ })
+ }
+
+ onAuthFailure(error) {
+ console.error(error)
+ }
+
+ render() {
+ if (!this.props.visible) {
+ return <span />
+ }
+
+ return (
+ <GoogleLogin
+ clientId={process.env.REACT_APP_OAUTH_CLIENT_ID}
+ onSuccess={this.onAuthResponse.bind(this)}
+ onFailure={this.onAuthFailure.bind(this)}
+ render={(renderProps) => (
+ <span onClick={renderProps.onClick} className="login btn btn-primary">
+ <span className="fa fa-google" /> Login with Google
+ </span>
+ )}
+ ></GoogleLogin>
+ )
+ }
+}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ visible: ownProps.visible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onLogin: (payload) => dispatch(logIn(payload)),
+ }
+}
+
+const Login = connect(mapStateToProps, mapDispatchToProps)(LoginContainer)
+
+export default Login
diff --git a/opendc-web/opendc-web-ui/src/containers/auth/Logout.js b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js
new file mode 100644
index 00000000..22400381
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/auth/Logout.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { logOut } from '../../actions/auth'
+import LogoutButton from '../../components/navigation/LogoutButton'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onLogout: () => dispatch(logOut()),
+ }
+}
+
+const Logout = connect(undefined, mapDispatchToProps)(LogoutButton)
+
+export default Logout
diff --git a/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js
new file mode 100644
index 00000000..06da75ab
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/auth/ProfileName.js
@@ -0,0 +1,14 @@
+import React from 'react'
+import { connect } from 'react-redux'
+
+const mapStateToProps = (state) => {
+ return {
+ text: state.auth.givenName + ' ' + state.auth.familyName,
+ }
+}
+
+const SpanElement = ({ text }) => <span>{text}</span>
+
+const ProfileName = connect(mapStateToProps)(SpanElement)
+
+export default ProfileName
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js
new file mode 100644
index 00000000..f30febdb
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/DeleteMachineModal.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeDeleteMachineModal } from '../../actions/modals/topology'
+import { deleteMachine } from '../../actions/topology/machine'
+import ConfirmationModal from '../../components/modals/ConfirmationModal'
+
+const DeleteMachineModalComponent = ({ visible, callback }) => (
+ <ConfirmationModal
+ title="Delete this machine"
+ message="Are you sure you want to delete this machine?"
+ show={visible}
+ callback={callback}
+ />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.deleteMachineModalVisible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteMachine())
+ }
+ dispatch(closeDeleteMachineModal())
+ },
+ }
+}
+
+const DeleteMachineModal = connect(mapStateToProps, mapDispatchToProps)(DeleteMachineModalComponent)
+
+export default DeleteMachineModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js
new file mode 100644
index 00000000..e7c4014d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/DeleteProfileModal.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeDeleteProfileModal } from '../../actions/modals/profile'
+import { deleteCurrentUser } from '../../actions/users'
+import ConfirmationModal from '../../components/modals/ConfirmationModal'
+
+const DeleteProfileModalComponent = ({ visible, callback }) => (
+ <ConfirmationModal
+ title="Delete my account"
+ message="Are you sure you want to delete your OpenDC account?"
+ show={visible}
+ callback={callback}
+ />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.deleteProfileModalVisible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteCurrentUser())
+ }
+ dispatch(closeDeleteProfileModal())
+ },
+ }
+}
+
+const DeleteProfileModal = connect(mapStateToProps, mapDispatchToProps)(DeleteProfileModalComponent)
+
+export default DeleteProfileModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js
new file mode 100644
index 00000000..0cb22a7e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRackModal.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeDeleteRackModal } from '../../actions/modals/topology'
+import { deleteRack } from '../../actions/topology/rack'
+import ConfirmationModal from '../../components/modals/ConfirmationModal'
+
+const DeleteRackModalComponent = ({ visible, callback }) => (
+ <ConfirmationModal
+ title="Delete this rack"
+ message="Are you sure you want to delete this rack?"
+ show={visible}
+ callback={callback}
+ />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.deleteRackModalVisible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteRack())
+ }
+ dispatch(closeDeleteRackModal())
+ },
+ }
+}
+
+const DeleteRackModal = connect(mapStateToProps, mapDispatchToProps)(DeleteRackModalComponent)
+
+export default DeleteRackModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js
new file mode 100644
index 00000000..1f6eef92
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/DeleteRoomModal.js
@@ -0,0 +1,35 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeDeleteRoomModal } from '../../actions/modals/topology'
+import { deleteRoom } from '../../actions/topology/room'
+import ConfirmationModal from '../../components/modals/ConfirmationModal'
+
+const DeleteRoomModalComponent = ({ visible, callback }) => (
+ <ConfirmationModal
+ title="Delete this room"
+ message="Are you sure you want to delete this room?"
+ show={visible}
+ callback={callback}
+ />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.deleteRoomModalVisible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteRoom())
+ }
+ dispatch(closeDeleteRoomModal())
+ },
+ }
+}
+
+const DeleteRoomModal = connect(mapStateToProps, mapDispatchToProps)(DeleteRoomModalComponent)
+
+export default DeleteRoomModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js
new file mode 100644
index 00000000..9128f449
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/EditRackNameModal.js
@@ -0,0 +1,40 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeEditRackNameModal } from '../../actions/modals/topology'
+import { editRackName } from '../../actions/topology/rack'
+import TextInputModal from '../../components/modals/TextInputModal'
+
+const EditRackNameModalComponent = ({ visible, previousName, callback }) => (
+ <TextInputModal
+ title="Edit rack name"
+ label="Rack name"
+ show={visible}
+ initialValue={previousName}
+ callback={callback}
+ />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.editRackNameModalVisible,
+ previousName:
+ state.interactionLevel.mode === 'RACK'
+ ? state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name
+ : '',
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (name) => {
+ if (name) {
+ dispatch(editRackName(name))
+ }
+ dispatch(closeEditRackNameModal())
+ },
+ }
+}
+
+const EditRackNameModal = connect(mapStateToProps, mapDispatchToProps)(EditRackNameModalComponent)
+
+export default EditRackNameModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js b/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js
new file mode 100644
index 00000000..8032a5d1
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/EditRoomNameModal.js
@@ -0,0 +1,38 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeEditRoomNameModal } from '../../actions/modals/topology'
+import { editRoomName } from '../../actions/topology/room'
+import TextInputModal from '../../components/modals/TextInputModal'
+
+const EditRoomNameModalComponent = ({ visible, previousName, callback }) => (
+ <TextInputModal
+ title="Edit room name"
+ label="Room name"
+ show={visible}
+ initialValue={previousName}
+ callback={callback}
+ />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.editRoomNameModalVisible,
+ previousName:
+ state.interactionLevel.mode === 'ROOM' ? state.objects.room[state.interactionLevel.roomId].name : '',
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (name) => {
+ if (name) {
+ dispatch(editRoomName(name))
+ }
+ dispatch(closeEditRoomNameModal())
+ },
+ }
+}
+
+const EditRoomNameModal = connect(mapStateToProps, mapDispatchToProps)(EditRoomNameModalComponent)
+
+export default EditRoomNameModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js
new file mode 100644
index 00000000..6cf12d8e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/NewPortfolioModal.js
@@ -0,0 +1,30 @@
+import { connect } from 'react-redux'
+import NewPortfolioModalComponent from '../../components/modals/custom-components/NewPortfolioModalComponent'
+import { addPortfolio } from '../../actions/portfolios'
+import { closeNewPortfolioModal } from '../../actions/modals/portfolios'
+
+const mapStateToProps = (state) => {
+ return {
+ show: state.modals.newPortfolioModalVisible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (name, targets) => {
+ if (name) {
+ dispatch(
+ addPortfolio({
+ name,
+ targets,
+ })
+ )
+ }
+ dispatch(closeNewPortfolioModal())
+ },
+ }
+}
+
+const NewPortfolioModal = connect(mapStateToProps, mapDispatchToProps)(NewPortfolioModalComponent)
+
+export default NewPortfolioModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js
new file mode 100644
index 00000000..d306dc45
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/NewProjectModal.js
@@ -0,0 +1,30 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { closeNewProjectModal } from '../../actions/modals/projects'
+import { addProject } from '../../actions/projects'
+import TextInputModal from '../../components/modals/TextInputModal'
+
+const NewProjectModalComponent = ({ visible, callback }) => (
+ <TextInputModal title="New Project" label="Project title" show={visible} callback={callback} />
+)
+
+const mapStateToProps = (state) => {
+ return {
+ visible: state.modals.newProjectModalVisible,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (text) => {
+ if (text) {
+ dispatch(addProject(text))
+ }
+ dispatch(closeNewProjectModal())
+ },
+ }
+}
+
+const NewProjectModal = connect(mapStateToProps, mapDispatchToProps)(NewProjectModalComponent)
+
+export default NewProjectModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js
new file mode 100644
index 00000000..7d774fa4
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/NewScenarioModal.js
@@ -0,0 +1,50 @@
+import { connect } from 'react-redux'
+import NewScenarioModalComponent from '../../components/modals/custom-components/NewScenarioModalComponent'
+import { addScenario } from '../../actions/scenarios'
+import { closeNewScenarioModal } from '../../actions/modals/scenarios'
+
+const mapStateToProps = (state) => {
+ let topologies =
+ state.currentProjectId !== '-1'
+ ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t])
+ : []
+ if (topologies.filter((t) => !t).length > 0) {
+ topologies = []
+ }
+
+ return {
+ show: state.modals.newScenarioModalVisible,
+ currentPortfolioId: state.currentPortfolioId,
+ currentPortfolioScenarioIds:
+ state.currentPortfolioId !== '-1' && state.objects.portfolio[state.currentPortfolioId]
+ ? state.objects.portfolio[state.currentPortfolioId].scenarioIds
+ : [],
+ traces: Object.values(state.objects.trace),
+ topologies,
+ schedulers: Object.values(state.objects.scheduler),
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ callback: (name, portfolioId, trace, topology, operational) => {
+ if (name) {
+ dispatch(
+ addScenario({
+ portfolioId,
+ name,
+ trace,
+ topology,
+ operational,
+ })
+ )
+ }
+
+ dispatch(closeNewScenarioModal())
+ },
+ }
+}
+
+const NewScenarioModal = connect(mapStateToProps, mapDispatchToProps)(NewScenarioModalComponent)
+
+export default NewScenarioModal
diff --git a/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js
new file mode 100644
index 00000000..0acf6cf2
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/modals/NewTopologyModal.js
@@ -0,0 +1,42 @@
+import { connect } from 'react-redux'
+import NewTopologyModalComponent from '../../components/modals/custom-components/NewTopologyModalComponent'
+import { closeNewTopologyModal } from '../../actions/modals/topology'
+import { addTopology } from '../../actions/topologies'
+
+const mapStateToProps = (state) => {
+ let topologies = state.objects.project[state.currentProjectId]
+ ? state.objects.project[state.currentProjectId].topologyIds.map((t) => state.objects.topology[t])
+ : []
+ if (topologies.filter((t) => !t).length > 0) {
+ topologies = []
+ }
+
+ return {
+ show: state.modals.changeTopologyModalVisible,
+ topologies,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onCreateTopology: (name) => {
+ if (name) {
+ dispatch(addTopology(name, undefined))
+ }
+ dispatch(closeNewTopologyModal())
+ },
+ onDuplicateTopology: (name, id) => {
+ if (name) {
+ dispatch(addTopology(name, id))
+ }
+ dispatch(closeNewTopologyModal())
+ },
+ onCancel: () => {
+ dispatch(closeNewTopologyModal())
+ },
+ }
+}
+
+const NewTopologyModal = connect(mapStateToProps, mapDispatchToProps)(NewTopologyModalComponent)
+
+export default NewTopologyModal
diff --git a/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js
new file mode 100644
index 00000000..845d54e1
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/navigation/AppNavbarContainer.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux'
+import AppNavbarComponent from '../../components/navigation/AppNavbarComponent'
+
+const mapStateToProps = (state) => {
+ return {
+ project: state.currentProjectId !== '-1' ? state.objects.project[state.currentProjectId] : undefined,
+ }
+}
+
+const AppNavbarContainer = connect(mapStateToProps)(AppNavbarComponent)
+
+export default AppNavbarContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js b/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js
new file mode 100644
index 00000000..dfd6affe
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/projects/FilterLink.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux'
+import { setAuthVisibilityFilter } from '../../actions/projects'
+import FilterButton from '../../components/projects/FilterButton'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ active: state.projectList.authVisibilityFilter === ownProps.filter,
+ }
+}
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onClick: () => dispatch(setAuthVisibilityFilter(ownProps.filter)),
+ }
+}
+
+const FilterLink = connect(mapStateToProps, mapDispatchToProps)(FilterButton)
+
+export default FilterLink
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js
new file mode 100644
index 00000000..ffd4a4a3
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/projects/NewProjectButtonContainer.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux'
+import { openNewProjectModal } from '../../actions/modals/projects'
+import NewProjectButtonComponent from '../../components/projects/NewProjectButtonComponent'
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onClick: () => dispatch(openNewProjectModal()),
+ }
+}
+
+const NewProjectButtonContainer = connect(undefined, mapDispatchToProps)(NewProjectButtonComponent)
+
+export default NewProjectButtonContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js
new file mode 100644
index 00000000..8bcbb7fd
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/projects/ProjectActions.js
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux'
+import { deleteProject } from '../../actions/projects'
+import ProjectActionButtons from '../../components/projects/ProjectActionButtons'
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ projectId: ownProps.projectId,
+ }
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ onViewUsers: (id) => {}, // TODO implement user viewing
+ onDelete: (id) => dispatch(deleteProject(id)),
+ }
+}
+
+const ProjectActions = connect(mapStateToProps, mapDispatchToProps)(ProjectActionButtons)
+
+export default ProjectActions
diff --git a/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js
new file mode 100644
index 00000000..f0010540
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/containers/projects/VisibleProjectAuthList.js
@@ -0,0 +1,32 @@
+import { connect } from 'react-redux'
+import ProjectList from '../../components/projects/ProjectAuthList'
+
+const getVisibleProjectAuths = (projectAuths, filter) => {
+ switch (filter) {
+ case 'SHOW_ALL':
+ return projectAuths
+ case 'SHOW_OWN':
+ return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel === 'OWN')
+ case 'SHOW_SHARED':
+ return projectAuths.filter((projectAuth) => projectAuth.authorizationLevel !== 'OWN')
+ default:
+ return projectAuths
+ }
+}
+
+const mapStateToProps = (state) => {
+ const denormalizedAuthorizations = state.projectList.authorizationsOfCurrentUser.map((authorizationIds) => {
+ const authorization = state.objects.authorization[authorizationIds]
+ authorization.user = state.objects.user[authorization.userId]
+ authorization.project = state.objects.project[authorization.projectId]
+ return authorization
+ })
+
+ return {
+ authorizations: getVisibleProjectAuths(denormalizedAuthorizations, state.projectList.authVisibilityFilter),
+ }
+}
+
+const VisibleProjectAuthList = connect(mapStateToProps)(ProjectList)
+
+export default VisibleProjectAuthList