summaryrefslogtreecommitdiff
path: root/frontend/src/containers
diff options
context:
space:
mode:
authorGeorgios Andreadis <info@gandreadis.com>2020-06-29 15:47:09 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2020-08-24 16:08:41 +0200
commit90fae26aa4bd0e0eb3272ff6e6524060e9004fbb (patch)
treebf6943882f5fa5f3114c01fc571503c79ee1056d /frontend/src/containers
parent7032a007d4431f5a0c4c5e2d3f3bd20462d49950 (diff)
Prepare frontend repository for monorepo
This change prepares the frontend Git repository for the monorepo residing at https://github.com/atlarge-research.com/opendc. To accomodate for this, we move all files into a frontend subdirectory.
Diffstat (limited to 'frontend/src/containers')
-rw-r--r--frontend/src/containers/app/map/DatacenterContainer.js17
-rw-r--r--frontend/src/containers/app/map/GrayContainer.js13
-rw-r--r--frontend/src/containers/app/map/MapStage.js31
-rw-r--r--frontend/src/containers/app/map/RackContainer.js30
-rw-r--r--frontend/src/containers/app/map/RackEnergyFillContainer.js40
-rw-r--r--frontend/src/containers/app/map/RackSpaceFillContainer.js16
-rw-r--r--frontend/src/containers/app/map/RoomContainer.js21
-rw-r--r--frontend/src/containers/app/map/TileContainer.js43
-rw-r--r--frontend/src/containers/app/map/WallContainer.js14
-rw-r--r--frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js14
-rw-r--r--frontend/src/containers/app/map/controls/ZoomControlContainer.js21
-rw-r--r--frontend/src/containers/app/map/layers/MapLayer.js13
-rw-r--r--frontend/src/containers/app/map/layers/ObjectHoverLayer.js37
-rw-r--r--frontend/src/containers/app/map/layers/RoomHoverLayer.js55
-rw-r--r--frontend/src/containers/app/sidebars/elements/LoadBarContainer.js32
-rw-r--r--frontend/src/containers/app/sidebars/elements/LoadChartContainer.js31
-rw-r--r--frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js38
-rw-r--r--frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js12
-rw-r--r--frontend/src/containers/app/sidebars/simulation/TaskContainer.js26
-rw-r--r--frontend/src/containers/app/sidebars/simulation/TraceContainer.js25
-rw-r--r--frontend/src/containers/app/sidebars/topology/TopologySidebar.js12
-rw-r--r--frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js14
-rw-r--r--frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js27
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/MachineNameContainer.js12
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js18
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js21
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js22
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js18
-rw-r--r--frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js12
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js21
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js40
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js24
-rw-r--r--frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js13
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js15
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js26
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js26
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js21
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js14
-rw-r--r--frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js12
-rw-r--r--frontend/src/containers/app/timeline/PlayButtonContainer.js27
-rw-r--r--frontend/src/containers/app/timeline/TimelineContainer.js41
-rw-r--r--frontend/src/containers/app/timeline/TimelineControlsContainer.js36
-rw-r--r--frontend/src/containers/app/timeline/TimelineLabelsContainer.js15
-rw-r--r--frontend/src/containers/auth/Login.js65
-rw-r--r--frontend/src/containers/auth/Logout.js13
-rw-r--r--frontend/src/containers/auth/ProfileName.js14
-rw-r--r--frontend/src/containers/experiments/ExperimentListContainer.js28
-rw-r--r--frontend/src/containers/experiments/ExperimentRowContainer.js30
-rw-r--r--frontend/src/containers/experiments/NewExperimentButtonContainer.js15
-rw-r--r--frontend/src/containers/modals/DeleteMachineModal.js37
-rw-r--r--frontend/src/containers/modals/DeleteProfileModal.js37
-rw-r--r--frontend/src/containers/modals/DeleteRackModal.js37
-rw-r--r--frontend/src/containers/modals/DeleteRoomModal.js37
-rw-r--r--frontend/src/containers/modals/EditRackNameModal.js44
-rw-r--r--frontend/src/containers/modals/EditRoomNameModal.js42
-rw-r--r--frontend/src/containers/modals/NewExperimentModal.js39
-rw-r--r--frontend/src/containers/modals/NewSimulationModal.js37
-rw-r--r--frontend/src/containers/simulations/FilterLink.js19
-rw-r--r--frontend/src/containers/simulations/NewSimulationButtonContainer.js15
-rw-r--r--frontend/src/containers/simulations/SimulationActions.js22
-rw-r--r--frontend/src/containers/simulations/VisibleSimulationAuthList.js42
67 files changed, 1679 insertions, 0 deletions
diff --git a/frontend/src/containers/app/map/DatacenterContainer.js b/frontend/src/containers/app/map/DatacenterContainer.js
new file mode 100644
index 00000000..125739f3
--- /dev/null
+++ b/frontend/src/containers/app/map/DatacenterContainer.js
@@ -0,0 +1,17 @@
+import { connect } from "react-redux";
+import DatacenterGroup from "../../../components/app/map/groups/DatacenterGroup";
+
+const mapStateToProps = state => {
+ if (state.currentDatacenterId === -1) {
+ return {};
+ }
+
+ return {
+ datacenter: state.objects.datacenter[state.currentDatacenterId],
+ interactionLevel: state.interactionLevel
+ };
+};
+
+const DatacenterContainer = connect(mapStateToProps)(DatacenterGroup);
+
+export default DatacenterContainer;
diff --git a/frontend/src/containers/app/map/GrayContainer.js b/frontend/src/containers/app/map/GrayContainer.js
new file mode 100644
index 00000000..d215bf6c
--- /dev/null
+++ b/frontend/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/frontend/src/containers/app/map/MapStage.js b/frontend/src/containers/app/map/MapStage.js
new file mode 100644
index 00000000..a8467171
--- /dev/null
+++ b/frontend/src/containers/app/map/MapStage.js
@@ -0,0 +1,31 @@
+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/frontend/src/containers/app/map/RackContainer.js b/frontend/src/containers/app/map/RackContainer.js
new file mode 100644
index 00000000..365bb062
--- /dev/null
+++ b/frontend/src/containers/app/map/RackContainer.js
@@ -0,0 +1,30 @@
+import { connect } from "react-redux";
+import RackGroup from "../../../components/app/map/groups/RackGroup";
+import { getStateLoad } from "../../../util/simulation-load";
+
+const mapStateToProps = (state, ownProps) => {
+ const inSimulation = state.currentExperimentId !== -1;
+
+ let rackLoad = undefined;
+ if (inSimulation) {
+ if (
+ state.states.rack[state.currentTick] &&
+ state.states.rack[state.currentTick][ownProps.tile.objectId]
+ ) {
+ rackLoad = getStateLoad(
+ state.loadMetric,
+ state.states.rack[state.currentTick][ownProps.tile.objectId]
+ );
+ }
+ }
+
+ return {
+ interactionLevel: state.interactionLevel,
+ inSimulation,
+ rackLoad
+ };
+};
+
+const RackContainer = connect(mapStateToProps)(RackGroup);
+
+export default RackContainer;
diff --git a/frontend/src/containers/app/map/RackEnergyFillContainer.js b/frontend/src/containers/app/map/RackEnergyFillContainer.js
new file mode 100644
index 00000000..0b7921d9
--- /dev/null
+++ b/frontend/src/containers/app/map/RackEnergyFillContainer.js
@@ -0,0 +1,40 @@
+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].objectId];
+ 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/frontend/src/containers/app/map/RackSpaceFillContainer.js b/frontend/src/containers/app/map/RackSpaceFillContainer.js
new file mode 100644
index 00000000..cc4d1251
--- /dev/null
+++ b/frontend/src/containers/app/map/RackSpaceFillContainer.js
@@ -0,0 +1,16 @@
+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].objectId].machineIds;
+ return {
+ type: "space",
+ fillFraction:
+ machineIds.filter(id => id !== null).length / machineIds.length
+ };
+};
+
+const RackSpaceFillContainer = connect(mapStateToProps)(RackFillBar);
+
+export default RackSpaceFillContainer;
diff --git a/frontend/src/containers/app/map/RoomContainer.js b/frontend/src/containers/app/map/RoomContainer.js
new file mode 100644
index 00000000..b83c7fa0
--- /dev/null
+++ b/frontend/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/frontend/src/containers/app/map/TileContainer.js b/frontend/src/containers/app/map/TileContainer.js
new file mode 100644
index 00000000..9e179924
--- /dev/null
+++ b/frontend/src/containers/app/map/TileContainer.js
@@ -0,0 +1,43 @@
+import { connect } from "react-redux";
+import { goFromRoomToRack } from "../../../actions/interaction-level";
+import TileGroup from "../../../components/app/map/groups/TileGroup";
+import { getStateLoad } from "../../../util/simulation-load";
+
+const mapStateToProps = (state, ownProps) => {
+ const tile = state.objects.tile[ownProps.tileId];
+ const inSimulation = state.currentExperimentId !== -1;
+
+ let roomLoad = undefined;
+ if (inSimulation) {
+ if (
+ state.states.room[state.currentTick] &&
+ state.states.room[state.currentTick][tile.roomId]
+ ) {
+ roomLoad = getStateLoad(
+ state.loadMetric,
+ state.states.room[state.currentTick][tile.roomId]
+ );
+ }
+ }
+
+ return {
+ interactionLevel: state.interactionLevel,
+ tile,
+ inSimulation,
+ roomLoad
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: tile => {
+ if (tile.objectType) {
+ dispatch(goFromRoomToRack(tile.id));
+ }
+ }
+ };
+};
+
+const TileContainer = connect(mapStateToProps, mapDispatchToProps)(TileGroup);
+
+export default TileContainer;
diff --git a/frontend/src/containers/app/map/WallContainer.js b/frontend/src/containers/app/map/WallContainer.js
new file mode 100644
index 00000000..38192b05
--- /dev/null
+++ b/frontend/src/containers/app/map/WallContainer.js
@@ -0,0 +1,14 @@
+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/frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js b/frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js
new file mode 100644
index 00000000..f075cde5
--- /dev/null
+++ b/frontend/src/containers/app/map/controls/ScaleIndicatorContainer.js
@@ -0,0 +1,14 @@
+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/frontend/src/containers/app/map/controls/ZoomControlContainer.js b/frontend/src/containers/app/map/controls/ZoomControlContainer.js
new file mode 100644
index 00000000..50910bd6
--- /dev/null
+++ b/frontend/src/containers/app/map/controls/ZoomControlContainer.js
@@ -0,0 +1,21 @@
+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/frontend/src/containers/app/map/layers/MapLayer.js b/frontend/src/containers/app/map/layers/MapLayer.js
new file mode 100644
index 00000000..cf971350
--- /dev/null
+++ b/frontend/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/frontend/src/containers/app/map/layers/ObjectHoverLayer.js b/frontend/src/containers/app/map/layers/ObjectHoverLayer.js
new file mode 100644
index 00000000..9b28575e
--- /dev/null
+++ b/frontend/src/containers/app/map/layers/ObjectHoverLayer.js
@@ -0,0 +1,37 @@
+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.objectType);
+ }
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: (x, y) => dispatch(addRackToTile(x, y))
+ };
+};
+
+const ObjectHoverLayer = connect(mapStateToProps, mapDispatchToProps)(
+ ObjectHoverLayerComponent
+);
+
+export default ObjectHoverLayer;
diff --git a/frontend/src/containers/app/map/layers/RoomHoverLayer.js b/frontend/src/containers/app/map/layers/RoomHoverLayer.js
new file mode 100644
index 00000000..020102bf
--- /dev/null
+++ b/frontend/src/containers/app/map/layers/RoomHoverLayer.js
@@ -0,0 +1,55 @@
+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.datacenter[state.currentDatacenterId].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/frontend/src/containers/app/sidebars/elements/LoadBarContainer.js b/frontend/src/containers/app/sidebars/elements/LoadBarContainer.js
new file mode 100644
index 00000000..2e637f9a
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/elements/LoadBarContainer.js
@@ -0,0 +1,32 @@
+import { connect } from "react-redux";
+import LoadBarComponent from "../../../../components/app/sidebars/elements/LoadBarComponent";
+import { getStateLoad } from "../../../../util/simulation-load";
+
+const mapStateToProps = (state, ownProps) => {
+ let percent = 0;
+ let enabled = false;
+
+ const objectStates = state.states[ownProps.objectType];
+ if (
+ objectStates[state.currentTick] &&
+ objectStates[state.currentTick][ownProps.objectId]
+ ) {
+ percent = Math.floor(
+ 100 *
+ getStateLoad(
+ state.loadMetric,
+ objectStates[state.currentTick][ownProps.objectId]
+ )
+ );
+ enabled = true;
+ }
+
+ return {
+ percent,
+ enabled
+ };
+};
+
+const LoadBarContainer = connect(mapStateToProps)(LoadBarComponent);
+
+export default LoadBarContainer;
diff --git a/frontend/src/containers/app/sidebars/elements/LoadChartContainer.js b/frontend/src/containers/app/sidebars/elements/LoadChartContainer.js
new file mode 100644
index 00000000..57bfec38
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/elements/LoadChartContainer.js
@@ -0,0 +1,31 @@
+import { connect } from "react-redux";
+import LoadChartComponent from "../../../../components/app/sidebars/elements/LoadChartComponent";
+import { getStateLoad } from "../../../../util/simulation-load";
+
+const mapStateToProps = (state, ownProps) => {
+ const data = [];
+
+ if (state.lastSimulatedTick !== -1) {
+ const objectStates = state.states[ownProps.objectType];
+ Object.keys(objectStates).forEach(tick => {
+ if (objectStates[tick][ownProps.objectId]) {
+ data.push({
+ x: tick,
+ y: getStateLoad(
+ state.loadMetric,
+ objectStates[tick][ownProps.objectId]
+ )
+ });
+ }
+ });
+ }
+
+ return {
+ data,
+ currentTick: state.currentTick
+ };
+};
+
+const LoadChartContainer = connect(mapStateToProps)(LoadChartComponent);
+
+export default LoadChartContainer;
diff --git a/frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js b/frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js
new file mode 100644
index 00000000..25a0d9e9
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/simulation/ExperimentMetadataContainer.js
@@ -0,0 +1,38 @@
+import { connect } from "react-redux";
+import ExperimentMetadataComponent from "../../../../components/app/sidebars/simulation/ExperimentMetadataComponent";
+
+const mapStateToProps = state => {
+ if (!state.objects.experiment[state.currentExperimentId]) {
+ return {
+ experimentName: "Loading experiment",
+ pathName: "",
+ traceName: "",
+ schedulerName: ""
+ };
+ }
+
+ const path =
+ state.objects.path[
+ state.objects.experiment[state.currentExperimentId].pathId
+ ];
+ const pathName = path.name ? path.name : "Path " + path.id;
+
+ return {
+ experimentName: state.objects.experiment[state.currentExperimentId].name,
+ pathName,
+ traceName:
+ state.objects.trace[
+ state.objects.experiment[state.currentExperimentId].traceId
+ ].name,
+ schedulerName:
+ state.objects.scheduler[
+ state.objects.experiment[state.currentExperimentId].schedulerName
+ ].name
+ };
+};
+
+const ExperimentMetadataContainer = connect(mapStateToProps)(
+ ExperimentMetadataComponent
+);
+
+export default ExperimentMetadataContainer;
diff --git a/frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js b/frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js
new file mode 100644
index 00000000..0c66b582
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/simulation/LoadMetricContainer.js
@@ -0,0 +1,12 @@
+import { connect } from "react-redux";
+import LoadMetricComponent from "../../../../components/app/sidebars/simulation/LoadMetricComponent";
+
+const mapStateToProps = state => {
+ return {
+ loadMetric: state.loadMetric
+ };
+};
+
+const LoadMetricContainer = connect(mapStateToProps)(LoadMetricComponent);
+
+export default LoadMetricContainer;
diff --git a/frontend/src/containers/app/sidebars/simulation/TaskContainer.js b/frontend/src/containers/app/sidebars/simulation/TaskContainer.js
new file mode 100644
index 00000000..093d4266
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/simulation/TaskContainer.js
@@ -0,0 +1,26 @@
+import { connect } from "react-redux";
+import TaskComponent from "../../../../components/app/sidebars/simulation/TaskComponent";
+
+const mapStateToProps = (state, ownProps) => {
+ let flopsLeft = state.objects.task[ownProps.taskId].totalFlopCount;
+
+ if (
+ state.states.task[state.currentTick] &&
+ state.states.task[state.currentTick][ownProps.taskId]
+ ) {
+ flopsLeft = state.states.task[state.currentTick][ownProps.taskId].flopsLeft;
+ } else if (
+ state.objects.task[ownProps.taskId].startTick < state.currentTick
+ ) {
+ flopsLeft = 0;
+ }
+
+ return {
+ task: state.objects.task[ownProps.taskId],
+ flopsLeft
+ };
+};
+
+const TaskContainer = connect(mapStateToProps)(TaskComponent);
+
+export default TaskContainer;
diff --git a/frontend/src/containers/app/sidebars/simulation/TraceContainer.js b/frontend/src/containers/app/sidebars/simulation/TraceContainer.js
new file mode 100644
index 00000000..682b6cc9
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/simulation/TraceContainer.js
@@ -0,0 +1,25 @@
+import { connect } from "react-redux";
+import TraceComponent from "../../../../components/app/sidebars/simulation/TraceComponent";
+
+const mapStateToProps = state => {
+ if (
+ !state.objects.experiment[state.currentExperimentId] ||
+ !state.objects.trace[
+ state.objects.experiment[state.currentExperimentId].traceId
+ ].jobIds
+ ) {
+ return {
+ jobs: []
+ };
+ }
+
+ return {
+ jobs: state.objects.trace[
+ state.objects.experiment[state.currentExperimentId].traceId
+ ].jobIds.map(id => state.objects.job[id])
+ };
+};
+
+const TraceContainer = connect(mapStateToProps)(TraceComponent);
+
+export default TraceContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/TopologySidebar.js b/frontend/src/containers/app/sidebars/topology/TopologySidebar.js
new file mode 100644
index 00000000..31c902fc
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/TopologySidebar.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 TopologySidebar = connect(mapStateToProps)(TopologySidebarComponent);
+
+export default TopologySidebar;
diff --git a/frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js
new file mode 100644
index 00000000..da24b8f0
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/building/BuildingSidebarContainer.js
@@ -0,0 +1,14 @@
+import { connect } from "react-redux";
+import BuildingSidebarComponent from "../../../../../components/app/sidebars/topology/building/BuildingSidebarComponent";
+
+const mapStateToProps = state => {
+ return {
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const BuildingSidebarContainer = connect(mapStateToProps)(
+ BuildingSidebarComponent
+);
+
+export default BuildingSidebarContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js b/frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js
new file mode 100644
index 00000000..bb64cbb4
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/building/NewRoomConstructionContainer.js
@@ -0,0 +1,27 @@
+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/frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js b/frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js
new file mode 100644
index 00000000..885c533d
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/BackToRackContainer.js
@@ -0,0 +1,15 @@
+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/frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js b/frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js
new file mode 100644
index 00000000..f42c8ba7
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/DeleteMachineContainer.js
@@ -0,0 +1,15 @@
+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/frontend/src/containers/app/sidebars/topology/machine/MachineNameContainer.js b/frontend/src/containers/app/sidebars/topology/machine/MachineNameContainer.js
new file mode 100644
index 00000000..05d2bf80
--- /dev/null
+++ b/frontend/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/frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
new file mode 100644
index 00000000..7729385e
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
@@ -0,0 +1,18 @@
+import { connect } from "react-redux";
+import MachineSidebarComponent from "../../../../../components/app/sidebars/topology/machine/MachineSidebarComponent";
+
+const mapStateToProps = state => {
+ return {
+ inSimulation: state.currentExperimentId !== -1,
+ machineId:
+ state.objects.rack[
+ state.objects.tile[state.interactionLevel.tileId].objectId
+ ].machineIds[state.interactionLevel.position - 1]
+ };
+};
+
+const MachineSidebarContainer = connect(mapStateToProps)(
+ MachineSidebarComponent
+);
+
+export default MachineSidebarContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js
new file mode 100644
index 00000000..0e5a6073
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/UnitAddContainer.js
@@ -0,0 +1,21 @@
+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/frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js
new file mode 100644
index 00000000..a919e8d3
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/UnitContainer.js
@@ -0,0 +1,22 @@
+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],
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onDelete: () => dispatch(deleteUnit(ownProps.unitType, ownProps.index))
+ };
+};
+
+const UnitContainer = connect(mapStateToProps, mapDispatchToProps)(
+ UnitComponent
+);
+
+export default UnitContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js
new file mode 100644
index 00000000..6554b8f8
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/UnitListContainer.js
@@ -0,0 +1,18 @@
+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].objectId
+ ].machineIds[state.interactionLevel.position - 1]
+ ][ownProps.unitType + "Ids"],
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const UnitListContainer = connect(mapStateToProps)(UnitListComponent);
+
+export default UnitListContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js b/frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js
new file mode 100644
index 00000000..85d83877
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/machine/UnitTabsContainer.js
@@ -0,0 +1,12 @@
+import { connect } from "react-redux";
+import UnitTabsComponent from "../../../../../components/app/sidebars/topology/machine/UnitTabsComponent";
+
+const mapStateToProps = state => {
+ return {
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const UnitTabsContainer = connect(mapStateToProps)(UnitTabsComponent);
+
+export default UnitTabsContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js b/frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js
new file mode 100644
index 00000000..1b1bb2b0
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/BackToRoomContainer.js
@@ -0,0 +1,15 @@
+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/frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js b/frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js
new file mode 100644
index 00000000..a54ceb23
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/DeleteRackContainer.js
@@ -0,0 +1,15 @@
+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/frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
new file mode 100644
index 00000000..527805a2
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
@@ -0,0 +1,21 @@
+import { connect } from "react-redux";
+import { addMachine } from "../../../../../actions/topology/rack";
+import EmptySlotComponent from "../../../../../components/app/sidebars/topology/rack/EmptySlotComponent";
+
+const mapStateToProps = state => {
+ return {
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onAdd: () => dispatch(addMachine(ownProps.position))
+ };
+};
+
+const EmptySlotContainer = connect(mapStateToProps, mapDispatchToProps)(
+ EmptySlotComponent
+);
+
+export default EmptySlotContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js b/frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js
new file mode 100644
index 00000000..8cd177e7
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/MachineContainer.js
@@ -0,0 +1,40 @@
+import { connect } from "react-redux";
+import { goFromRackToMachine } from "../../../../../actions/interaction-level";
+import MachineComponent from "../../../../../components/app/sidebars/topology/rack/MachineComponent";
+import { getStateLoad } from "../../../../../util/simulation-load";
+
+const mapStateToProps = (state, ownProps) => {
+ const machine = state.objects.machine[ownProps.machineId];
+ const inSimulation = state.currentExperimentId !== -1;
+
+ let machineLoad = undefined;
+ if (inSimulation) {
+ if (
+ state.states.machine[state.currentTick] &&
+ state.states.machine[state.currentTick][machine.id]
+ ) {
+ machineLoad = getStateLoad(
+ state.loadMetric,
+ state.states.machine[state.currentTick][machine.id]
+ );
+ }
+ }
+
+ return {
+ machine,
+ inSimulation,
+ machineLoad
+ };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+ return {
+ onClick: () => dispatch(goFromRackToMachine(ownProps.position))
+ };
+};
+
+const MachineContainer = connect(mapStateToProps, mapDispatchToProps)(
+ MachineComponent
+);
+
+export default MachineContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js b/frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js
new file mode 100644
index 00000000..b19a50ae
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/MachineListContainer.js
@@ -0,0 +1,15 @@
+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].objectId
+ ].machineIds
+ };
+};
+
+const MachineListContainer = connect(mapStateToProps)(MachineListComponent);
+
+export default MachineListContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js b/frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js
new file mode 100644
index 00000000..8f364ca0
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/RackNameContainer.js
@@ -0,0 +1,24 @@
+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].objectId
+ ].name
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onEdit: () => dispatch(openEditRackNameModal())
+ };
+};
+
+const RackNameContainer = connect(mapStateToProps, mapDispatchToProps)(
+ RackNameComponent
+);
+
+export default RackNameContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
new file mode 100644
index 00000000..0a2bfdcc
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
@@ -0,0 +1,13 @@
+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].objectId,
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const RackSidebarContainer = connect(mapStateToProps)(RackSidebarComponent);
+
+export default RackSidebarContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js b/frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js
new file mode 100644
index 00000000..02288b7b
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/BackToBuildingContainer.js
@@ -0,0 +1,15 @@
+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/frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js b/frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js
new file mode 100644
index 00000000..5223061d
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/DeleteRoomContainer.js
@@ -0,0 +1,15 @@
+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/frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js b/frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js
new file mode 100644
index 00000000..81052f54
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/EditRoomContainer.js
@@ -0,0 +1,26 @@
+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/frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js b/frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js
new file mode 100644
index 00000000..c784d3ae
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/RackConstructionContainer.js
@@ -0,0 +1,26 @@
+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/frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js b/frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js
new file mode 100644
index 00000000..36125521
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/RoomNameContainer.js
@@ -0,0 +1,21 @@
+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/frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js b/frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js
new file mode 100644
index 00000000..38d5fb80
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/RoomSidebarContainer.js
@@ -0,0 +1,14 @@
+import { connect } from "react-redux";
+import RoomSidebarComponent from "../../../../../components/app/sidebars/topology/room/RoomSidebarComponent";
+
+const mapStateToProps = state => {
+ return {
+ roomId: state.interactionLevel.roomId,
+ roomType: state.objects.room[state.interactionLevel.roomId].roomType,
+ inSimulation: state.currentExperimentId !== -1
+ };
+};
+
+const RoomSidebarContainer = connect(mapStateToProps)(RoomSidebarComponent);
+
+export default RoomSidebarContainer;
diff --git a/frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js b/frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js
new file mode 100644
index 00000000..414852f1
--- /dev/null
+++ b/frontend/src/containers/app/sidebars/topology/room/RoomTypeContainer.js
@@ -0,0 +1,12 @@
+import { connect } from "react-redux";
+import RoomTypeComponent from "../../../../../components/app/sidebars/topology/room/RoomTypeComponent";
+
+const mapStateToProps = state => {
+ return {
+ roomType: state.objects.room[state.interactionLevel.roomId].roomType
+ };
+};
+
+const RoomNameContainer = connect(mapStateToProps)(RoomTypeComponent);
+
+export default RoomNameContainer;
diff --git a/frontend/src/containers/app/timeline/PlayButtonContainer.js b/frontend/src/containers/app/timeline/PlayButtonContainer.js
new file mode 100644
index 00000000..4e3c3d81
--- /dev/null
+++ b/frontend/src/containers/app/timeline/PlayButtonContainer.js
@@ -0,0 +1,27 @@
+import { connect } from "react-redux";
+import {
+ pauseSimulation,
+ playSimulation
+} from "../../../actions/simulation/playback";
+import PlayButtonComponent from "../../../components/app/timeline/PlayButtonComponent";
+
+const mapStateToProps = state => {
+ return {
+ isPlaying: state.isPlaying,
+ currentTick: state.currentTick,
+ lastSimulatedTick: state.lastSimulatedTick
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onPlay: () => dispatch(playSimulation()),
+ onPause: () => dispatch(pauseSimulation())
+ };
+};
+
+const PlayButtonContainer = connect(mapStateToProps, mapDispatchToProps)(
+ PlayButtonComponent
+);
+
+export default PlayButtonContainer;
diff --git a/frontend/src/containers/app/timeline/TimelineContainer.js b/frontend/src/containers/app/timeline/TimelineContainer.js
new file mode 100644
index 00000000..74d37d58
--- /dev/null
+++ b/frontend/src/containers/app/timeline/TimelineContainer.js
@@ -0,0 +1,41 @@
+import { connect } from "react-redux";
+import { pauseSimulation } from "../../../actions/simulation/playback";
+import { incrementTick } from "../../../actions/simulation/tick";
+import { setCurrentDatacenter } from "../../../actions/topology/building";
+import TimelineComponent from "../../../components/app/timeline/TimelineComponent";
+
+const mapStateToProps = state => {
+ let sections = [];
+ if (state.currentExperimentId !== -1) {
+ const sectionIds =
+ state.objects.path[
+ state.objects.experiment[state.currentExperimentId].pathId
+ ].sectionIds;
+
+ if (sectionIds) {
+ sections = sectionIds.map(sectionId => state.objects.section[sectionId]);
+ }
+ }
+
+ return {
+ isPlaying: state.isPlaying,
+ currentTick: state.currentTick,
+ lastSimulatedTick: state.lastSimulatedTick,
+ currentDatacenterId: state.currentDatacenterId,
+ sections
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ incrementTick: () => dispatch(incrementTick()),
+ pauseSimulation: () => dispatch(pauseSimulation()),
+ setCurrentDatacenter: id => dispatch(setCurrentDatacenter(id))
+ };
+};
+
+const TimelineContainer = connect(mapStateToProps, mapDispatchToProps)(
+ TimelineComponent
+);
+
+export default TimelineContainer;
diff --git a/frontend/src/containers/app/timeline/TimelineControlsContainer.js b/frontend/src/containers/app/timeline/TimelineControlsContainer.js
new file mode 100644
index 00000000..ac851b2e
--- /dev/null
+++ b/frontend/src/containers/app/timeline/TimelineControlsContainer.js
@@ -0,0 +1,36 @@
+import { connect } from "react-redux";
+import { goToTick } from "../../../actions/simulation/tick";
+import TimelineControlsComponent from "../../../components/app/timeline/TimelineControlsComponent";
+
+const mapStateToProps = state => {
+ let sectionTicks = [];
+ if (state.currentExperimentId !== -1) {
+ const sectionIds =
+ state.objects.path[
+ state.objects.experiment[state.currentExperimentId].pathId
+ ].sectionIds;
+ if (sectionIds) {
+ sectionTicks = sectionIds
+ .filter(sectionId => state.objects.section[sectionId].startTick !== 0)
+ .map(sectionId => state.objects.section[sectionId].startTick);
+ }
+ }
+
+ return {
+ currentTick: state.currentTick,
+ lastSimulatedTick: state.lastSimulatedTick,
+ sectionTicks
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ goToTick: tick => dispatch(goToTick(tick))
+ };
+};
+
+const TimelineControlsContainer = connect(mapStateToProps, mapDispatchToProps)(
+ TimelineControlsComponent
+);
+
+export default TimelineControlsContainer;
diff --git a/frontend/src/containers/app/timeline/TimelineLabelsContainer.js b/frontend/src/containers/app/timeline/TimelineLabelsContainer.js
new file mode 100644
index 00000000..9d7f268d
--- /dev/null
+++ b/frontend/src/containers/app/timeline/TimelineLabelsContainer.js
@@ -0,0 +1,15 @@
+import { connect } from "react-redux";
+import TimelineLabelsComponent from "../../../components/app/timeline/TimelineLabelsComponent";
+
+const mapStateToProps = state => {
+ return {
+ currentTick: state.currentTick,
+ lastSimulatedTick: state.lastSimulatedTick
+ };
+};
+
+const TimelineLabelsContainer = connect(mapStateToProps)(
+ TimelineLabelsComponent
+);
+
+export default TimelineLabelsContainer;
diff --git a/frontend/src/containers/auth/Login.js b/frontend/src/containers/auth/Login.js
new file mode 100644
index 00000000..15af8e62
--- /dev/null
+++ b/frontend/src/containers/auth/Login.js
@@ -0,0 +1,65 @@
+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>
+ )}
+ />
+ );
+ }
+}
+
+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/frontend/src/containers/auth/Logout.js b/frontend/src/containers/auth/Logout.js
new file mode 100644
index 00000000..918932f6
--- /dev/null
+++ b/frontend/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/frontend/src/containers/auth/ProfileName.js b/frontend/src/containers/auth/ProfileName.js
new file mode 100644
index 00000000..21941bd2
--- /dev/null
+++ b/frontend/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/frontend/src/containers/experiments/ExperimentListContainer.js b/frontend/src/containers/experiments/ExperimentListContainer.js
new file mode 100644
index 00000000..53bb1dad
--- /dev/null
+++ b/frontend/src/containers/experiments/ExperimentListContainer.js
@@ -0,0 +1,28 @@
+import { connect } from "react-redux";
+import ExperimentListComponent from "../../components/experiments/ExperimentListComponent";
+
+const mapStateToProps = state => {
+ if (
+ state.currentSimulationId === -1 ||
+ !("experimentIds" in state.objects.simulation[state.currentSimulationId])
+ ) {
+ return {
+ loading: true,
+ experimentIds: []
+ };
+ }
+
+ const experimentIds =
+ state.objects.simulation[state.currentSimulationId].experimentIds;
+ if (experimentIds) {
+ return {
+ experimentIds
+ };
+ }
+};
+
+const ExperimentListContainer = connect(mapStateToProps)(
+ ExperimentListComponent
+);
+
+export default ExperimentListContainer;
diff --git a/frontend/src/containers/experiments/ExperimentRowContainer.js b/frontend/src/containers/experiments/ExperimentRowContainer.js
new file mode 100644
index 00000000..96ebc3db
--- /dev/null
+++ b/frontend/src/containers/experiments/ExperimentRowContainer.js
@@ -0,0 +1,30 @@
+import { connect } from "react-redux";
+import { deleteExperiment } from "../../actions/experiments";
+import ExperimentRowComponent from "../../components/experiments/ExperimentRowComponent";
+
+const mapStateToProps = (state, ownProps) => {
+ const experiment = Object.assign(
+ {},
+ state.objects.experiment[ownProps.experimentId]
+ );
+ experiment.trace = state.objects.trace[experiment.traceId];
+ experiment.scheduler = state.objects.scheduler[experiment.schedulerName];
+ experiment.path = state.objects.path[experiment.pathId];
+
+ return {
+ experiment,
+ simulationId: state.currentSimulationId
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onDelete: id => dispatch(deleteExperiment(id))
+ };
+};
+
+const ExperimentRowContainer = connect(mapStateToProps, mapDispatchToProps)(
+ ExperimentRowComponent
+);
+
+export default ExperimentRowContainer;
diff --git a/frontend/src/containers/experiments/NewExperimentButtonContainer.js b/frontend/src/containers/experiments/NewExperimentButtonContainer.js
new file mode 100644
index 00000000..60eb92a6
--- /dev/null
+++ b/frontend/src/containers/experiments/NewExperimentButtonContainer.js
@@ -0,0 +1,15 @@
+import { connect } from "react-redux";
+import { openNewExperimentModal } from "../../actions/modals/experiments";
+import NewExperimentButtonComponent from "../../components/experiments/NewExperimentButtonComponent";
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: () => dispatch(openNewExperimentModal())
+ };
+};
+
+const NewExperimentButtonContainer = connect(undefined, mapDispatchToProps)(
+ NewExperimentButtonComponent
+);
+
+export default NewExperimentButtonContainer;
diff --git a/frontend/src/containers/modals/DeleteMachineModal.js b/frontend/src/containers/modals/DeleteMachineModal.js
new file mode 100644
index 00000000..eba37833
--- /dev/null
+++ b/frontend/src/containers/modals/DeleteMachineModal.js
@@ -0,0 +1,37 @@
+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/frontend/src/containers/modals/DeleteProfileModal.js b/frontend/src/containers/modals/DeleteProfileModal.js
new file mode 100644
index 00000000..674e9408
--- /dev/null
+++ b/frontend/src/containers/modals/DeleteProfileModal.js
@@ -0,0 +1,37 @@
+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/frontend/src/containers/modals/DeleteRackModal.js b/frontend/src/containers/modals/DeleteRackModal.js
new file mode 100644
index 00000000..41bacb37
--- /dev/null
+++ b/frontend/src/containers/modals/DeleteRackModal.js
@@ -0,0 +1,37 @@
+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/frontend/src/containers/modals/DeleteRoomModal.js b/frontend/src/containers/modals/DeleteRoomModal.js
new file mode 100644
index 00000000..339ff22c
--- /dev/null
+++ b/frontend/src/containers/modals/DeleteRoomModal.js
@@ -0,0 +1,37 @@
+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/frontend/src/containers/modals/EditRackNameModal.js b/frontend/src/containers/modals/EditRackNameModal.js
new file mode 100644
index 00000000..748e847b
--- /dev/null
+++ b/frontend/src/containers/modals/EditRackNameModal.js
@@ -0,0 +1,44 @@
+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].objectId
+ ].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/frontend/src/containers/modals/EditRoomNameModal.js b/frontend/src/containers/modals/EditRoomNameModal.js
new file mode 100644
index 00000000..be6c547c
--- /dev/null
+++ b/frontend/src/containers/modals/EditRoomNameModal.js
@@ -0,0 +1,42 @@
+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/frontend/src/containers/modals/NewExperimentModal.js b/frontend/src/containers/modals/NewExperimentModal.js
new file mode 100644
index 00000000..c703c39a
--- /dev/null
+++ b/frontend/src/containers/modals/NewExperimentModal.js
@@ -0,0 +1,39 @@
+import { connect } from "react-redux";
+import { addExperiment } from "../../actions/experiments";
+import { closeNewExperimentModal } from "../../actions/modals/experiments";
+import NewExperimentModalComponent from "../../components/modals/custom-components/NewExperimentModalComponent";
+
+const mapStateToProps = state => {
+ return {
+ show: state.modals.newExperimentModalVisible,
+ paths: Object.values(state.objects.path).filter(
+ path => path.simulationId === state.currentSimulationId
+ ),
+ traces: Object.values(state.objects.trace),
+ schedulers: Object.values(state.objects.scheduler)
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ callback: (name, pathId, traceId, schedulerName) => {
+ if (name) {
+ dispatch(
+ addExperiment({
+ name,
+ pathId,
+ traceId,
+ schedulerName
+ })
+ );
+ }
+ dispatch(closeNewExperimentModal());
+ }
+ };
+};
+
+const NewExperimentModal = connect(mapStateToProps, mapDispatchToProps)(
+ NewExperimentModalComponent
+);
+
+export default NewExperimentModal;
diff --git a/frontend/src/containers/modals/NewSimulationModal.js b/frontend/src/containers/modals/NewSimulationModal.js
new file mode 100644
index 00000000..80789cd2
--- /dev/null
+++ b/frontend/src/containers/modals/NewSimulationModal.js
@@ -0,0 +1,37 @@
+import React from "react";
+import { connect } from "react-redux";
+import { closeNewSimulationModal } from "../../actions/modals/simulations";
+import { addSimulation } from "../../actions/simulations";
+import TextInputModal from "../../components/modals/TextInputModal";
+
+const NewSimulationModalComponent = ({ visible, callback }) => (
+ <TextInputModal
+ title="New Simulation"
+ label="Simulation title"
+ show={visible}
+ callback={callback}
+ />
+);
+
+const mapStateToProps = state => {
+ return {
+ visible: state.modals.newSimulationModalVisible
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ callback: text => {
+ if (text) {
+ dispatch(addSimulation(text));
+ }
+ dispatch(closeNewSimulationModal());
+ }
+ };
+};
+
+const NewSimulationModal = connect(mapStateToProps, mapDispatchToProps)(
+ NewSimulationModalComponent
+);
+
+export default NewSimulationModal;
diff --git a/frontend/src/containers/simulations/FilterLink.js b/frontend/src/containers/simulations/FilterLink.js
new file mode 100644
index 00000000..2c5f4ed5
--- /dev/null
+++ b/frontend/src/containers/simulations/FilterLink.js
@@ -0,0 +1,19 @@
+import { connect } from "react-redux";
+import { setAuthVisibilityFilter } from "../../actions/simulations";
+import FilterButton from "../../components/simulations/FilterButton";
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ active: state.simulationList.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/frontend/src/containers/simulations/NewSimulationButtonContainer.js b/frontend/src/containers/simulations/NewSimulationButtonContainer.js
new file mode 100644
index 00000000..3ea04d24
--- /dev/null
+++ b/frontend/src/containers/simulations/NewSimulationButtonContainer.js
@@ -0,0 +1,15 @@
+import { connect } from "react-redux";
+import { openNewSimulationModal } from "../../actions/modals/simulations";
+import NewSimulationButtonComponent from "../../components/simulations/NewSimulationButtonComponent";
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onClick: () => dispatch(openNewSimulationModal())
+ };
+};
+
+const NewSimulationButtonContainer = connect(undefined, mapDispatchToProps)(
+ NewSimulationButtonComponent
+);
+
+export default NewSimulationButtonContainer;
diff --git a/frontend/src/containers/simulations/SimulationActions.js b/frontend/src/containers/simulations/SimulationActions.js
new file mode 100644
index 00000000..32243eff
--- /dev/null
+++ b/frontend/src/containers/simulations/SimulationActions.js
@@ -0,0 +1,22 @@
+import { connect } from "react-redux";
+import { deleteSimulation } from "../../actions/simulations";
+import SimulationActionButtons from "../../components/simulations/SimulationActionButtons";
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ simulationId: ownProps.simulationId
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onViewUsers: id => {}, // TODO implement user viewing
+ onDelete: id => dispatch(deleteSimulation(id))
+ };
+};
+
+const SimulationActions = connect(mapStateToProps, mapDispatchToProps)(
+ SimulationActionButtons
+);
+
+export default SimulationActions;
diff --git a/frontend/src/containers/simulations/VisibleSimulationAuthList.js b/frontend/src/containers/simulations/VisibleSimulationAuthList.js
new file mode 100644
index 00000000..ffc74d9e
--- /dev/null
+++ b/frontend/src/containers/simulations/VisibleSimulationAuthList.js
@@ -0,0 +1,42 @@
+import { connect } from "react-redux";
+import SimulationList from "../../components/simulations/SimulationAuthList";
+
+const getVisibleSimulationAuths = (simulationAuths, filter) => {
+ switch (filter) {
+ case "SHOW_ALL":
+ return simulationAuths;
+ case "SHOW_OWN":
+ return simulationAuths.filter(
+ simulationAuth => simulationAuth.authorizationLevel === "OWN"
+ );
+ case "SHOW_SHARED":
+ return simulationAuths.filter(
+ simulationAuth => simulationAuth.authorizationLevel !== "OWN"
+ );
+ default:
+ return simulationAuths;
+ }
+};
+
+const mapStateToProps = state => {
+ const denormalizedAuthorizations = state.simulationList.authorizationsOfCurrentUser.map(
+ authorizationIds => {
+ const authorization = state.objects.authorization[authorizationIds];
+ authorization.user = state.objects.user[authorization.userId];
+ authorization.simulation =
+ state.objects.simulation[authorization.simulationId];
+ return authorization;
+ }
+ );
+
+ return {
+ authorizations: getVisibleSimulationAuths(
+ denormalizedAuthorizations,
+ state.simulationList.authVisibilityFilter
+ )
+ };
+};
+
+const VisibleSimulationAuthList = connect(mapStateToProps)(SimulationList);
+
+export default VisibleSimulationAuthList;