summaryrefslogtreecommitdiff
path: root/opendc-web
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web')
-rw-r--r--opendc-web/opendc-web-ui/package.json1
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js3
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js13
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js8
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js11
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js16
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js2
-rw-r--r--opendc-web/opendc-web-ui/src/data/topology.js8
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/objects.js41
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/prefabs.js33
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topologies.js3
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/building.js56
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js13
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js16
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/room.js32
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js3
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js10
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/index.js6
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js23
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/objects.js56
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js (renamed from opendc-web/opendc-web-ui/src/redux/sagas/query.js)35
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js47
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js66
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js65
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js59
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js47
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/index.js52
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js18
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/topology.js324
-rw-r--r--opendc-web/opendc-web-ui/src/util/topology-schema.js18
-rw-r--r--opendc-web/opendc-web-ui/yarn.lock5
45 files changed, 539 insertions, 614 deletions
diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json
index 84db4277..5a32806c 100644
--- a/opendc-web/opendc-web-ui/package.json
+++ b/opendc-web/opendc-web-ui/package.json
@@ -30,6 +30,7 @@
"approximate-number": "~2.0.0",
"classnames": "~2.2.5",
"husky": "~4.2.5",
+ "immer": "^9.0.5",
"konva": "~7.2.5",
"lint-staged": "~10.2.2",
"mathjs": "~7.6.0",
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js
index c16f554c..2f27749f 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js
@@ -39,7 +39,7 @@ import { useSelector } from 'react-redux'
import TopologySidebar from './sidebar/TopologySidebar'
function TopologyMap() {
- const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1')
+ const topologyIsLoading = useSelector((state) => !state.topology.root)
const interactionLevel = useSelector((state) => state.interactionLevel)
const [isExpanded, setExpanded] = useState(true)
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js
index 5d19b3ad..d8735cf1 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js
@@ -1,8 +1,8 @@
-import React, { useRef, useState } from 'react'
+import React, { useRef, useState, useContext } from 'react'
import { HotKeys } from 'react-hotkeys'
import { Stage } from 'react-konva'
import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants'
-import { Provider, useStore } from 'react-redux'
+import { ReactReduxContext } from 'react-redux'
import useResizeObserver from 'use-resize-observer'
import { mapContainer } from './MapStage.module.scss'
import MapLayer from './layers/MapLayer'
@@ -12,7 +12,7 @@ import ScaleIndicator from './controls/ScaleIndicator'
import Toolbar from './controls/Toolbar'
function MapStage() {
- const store = useStore()
+ const reduxContext = useContext(ReactReduxContext)
const { ref, width = 100, height = 100 } = useResizeObserver()
const stageRef = useRef(null)
const [[x, y], setPos] = useState([0, 0])
@@ -68,11 +68,11 @@ function MapStage() {
x={x}
y={y}
>
- <Provider store={store}>
+ <ReactReduxContext.Provider value={reduxContext}>
<MapLayer />
<RoomHoverLayer />
<ObjectHoverLayer />
- </Provider>
+ </ReactReduxContext.Provider>
</Stage>
<ScaleIndicator scale={scale} />
<Toolbar onZoom={onZoomButton} onExport={onExport} />
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js
index c35cbde7..be1f3e45 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js
@@ -6,18 +6,18 @@ import RackFillBar from './elements/RackFillBar'
function RackSpaceFillContainer({ tileId, ...props }) {
const fillFraction = useSelector((state) => {
let energyConsumptionTotal = 0
- const rack = state.objects.rack[state.objects.tile[tileId].rack]
+ const rack = state.topology.racks[state.topology.tiles[tileId].rack]
const machineIds = rack.machines
machineIds.forEach((machineId) => {
if (machineId !== null) {
- const machine = state.objects.machine[machineId]
- machine.cpus.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW))
- machine.gpus.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW))
+ const machine = state.topology.machines[machineId]
+ machine.cpus.forEach((id) => (energyConsumptionTotal += state.topology.cpus[id].energyConsumptionW))
+ machine.gpus.forEach((id) => (energyConsumptionTotal += state.topology.gpus[id].energyConsumptionW))
machine.memories.forEach(
- (id) => (energyConsumptionTotal += state.objects.memory[id].energyConsumptionW)
+ (id) => (energyConsumptionTotal += state.topology.memories[id].energyConsumptionW)
)
machine.storages.forEach(
- (id) => (energyConsumptionTotal += state.objects.storage[id].energyConsumptionW)
+ (id) => (energyConsumptionTotal += state.topology.storages[id].energyConsumptionW)
)
}
})
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js
index a6766f33..0c15d54b 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js
@@ -26,7 +26,7 @@ import { useSelector } from 'react-redux'
import RackFillBar from './elements/RackFillBar'
function RackSpaceFillContainer({ tileId, ...props }) {
- const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+ const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack])
return <RackFillBar {...props} type="space" fillFraction={rack.machines.length / rack.capacity} />
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js
index 93ba9c93..65189891 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js
@@ -31,7 +31,7 @@ function RoomContainer({ roomId, ...props }) {
return {
interactionLevel: state.interactionLevel,
currentRoomInConstruction: state.construction.currentRoomInConstruction,
- room: state.objects.room[roomId],
+ room: state.topology.rooms[roomId],
}
})
const dispatch = useDispatch()
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
index 149e26a1..411a5ca7 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
@@ -28,7 +28,7 @@ import TileGroup from './groups/TileGroup'
function TileContainer({ tileId, ...props }) {
const interactionLevel = useSelector((state) => state.interactionLevel)
- const tile = useSelector((state) => state.objects.tile[tileId])
+ const tile = useSelector((state) => state.topology.tiles[tileId])
const dispatch = useDispatch()
const onClick = (tile) => {
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js
index eaebabd5..cc0d46b3 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js
@@ -22,11 +22,10 @@
import React from 'react'
import { useSelector } from 'react-redux'
-import { useActiveTopology } from '../../../data/topology'
import TopologyGroup from './groups/TopologyGroup'
function TopologyContainer() {
- const topology = useActiveTopology()
+ const topology = useSelector((state) => state.topology.root)
const interactionLevel = useSelector((state) => state.interactionLevel)
return <TopologyGroup topology={topology} interactionLevel={interactionLevel} />
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js
index 77f553dd..143f70c2 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js
@@ -26,7 +26,9 @@ import { useSelector } from 'react-redux'
import WallGroup from './groups/WallGroup'
function WallContainer({ roomId, ...props }) {
- const tiles = useSelector((state) => state.objects.room[roomId].tiles.map((tileId) => state.objects.tile[tileId]))
+ const tiles = useSelector((state) => {
+ return state.topology.rooms[roomId].tiles.map((tileId) => state.topology.tiles[tileId])
+ })
return <WallGroup {...props} tiles={tiles} />
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js
index 47d9c992..1f00de36 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js
@@ -34,8 +34,8 @@ function ObjectHoverLayer() {
return false
}
- const currentRoom = state.objects.room[state.interactionLevel.roomId]
- const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId])
+ const currentRoom = state.topology.rooms[state.interactionLevel.roomId]
+ const tiles = currentRoom.tiles.map((tileId) => state.topology.tiles[tileId])
const tile = findTileWithPosition(tiles, x, y)
return !(tile === null || tile.rack)
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js
index 59f83b2b..5e351691 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js
@@ -35,17 +35,17 @@ function RoomHoverLayer() {
const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y))
const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1')
const isValid = useSelector((state) => (x, y) => {
- const newRoom = { ...state.objects.room[state.construction.currentRoomInConstruction] }
- const oldRooms = Object.keys(state.objects.room)
- .map((id) => ({ ...state.objects.room[id] }))
+ const newRoom = { ...state.topology.rooms[state.construction.currentRoomInConstruction] }
+ const oldRooms = Object.keys(state.topology.rooms)
+ .map((id) => ({ ...state.topology.rooms[id] }))
.filter(
(room) =>
- state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 &&
+ state.topology.root.rooms.indexOf(room._id) !== -1 &&
room._id !== state.construction.currentRoomInConstruction
)
;[...oldRooms, newRoom].forEach((room) => {
- room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId])
+ room.tiles = room.tiles.map((tileId) => state.topology.tiles[tileId])
})
if (newRoom.tiles.length === 0) {
return findPositionInRooms(oldRooms, x, y) === -1
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js
index 00ce4603..a4b9457b 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js
@@ -20,21 +20,20 @@
* SOFTWARE.
*/
+import PropTypes from 'prop-types'
import React, { useState } from 'react'
-import { useDispatch, useSelector } from 'react-redux'
+import { useDispatch } from 'react-redux'
import { Button } from '@patternfly/react-core'
import { TrashIcon } from '@patternfly/react-icons'
import ConfirmationModal from '../../../util/modals/ConfirmationModal'
import { deleteMachine } from '../../../../redux/actions/topology/machine'
-function DeleteMachine() {
+function DeleteMachine({ machineId }) {
const dispatch = useDispatch()
const [isVisible, setVisible] = useState(false)
- const rackId = useSelector((state) => state.objects.tile[state.interactionLevel.tileId].rack)
- const position = useSelector((state) => state.interactionLevel.position)
const callback = (isConfirmed) => {
if (isConfirmed) {
- dispatch(deleteMachine(rackId, position))
+ dispatch(deleteMachine(machineId))
}
setVisible(false)
}
@@ -53,4 +52,8 @@ function DeleteMachine() {
)
}
+DeleteMachine.propTypes = {
+ machineId: PropTypes.string.isRequired,
+}
+
export default DeleteMachine
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
index 0c3dea98..9268f615 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
@@ -13,9 +13,9 @@ import {
import { useSelector } from 'react-redux'
function MachineSidebar({ tileId, position }) {
- const machine = useSelector(({ objects }) => {
- const rack = objects.rack[objects.tile[tileId].rack]
- return objects.machine[rack.machines[position - 1]]
+ const machine = useSelector(({ topology }) => {
+ const rack = topology.racks[topology.tiles[tileId].rack]
+ return topology.machines[rack.machines[position - 1]]
})
const machineId = machine._id
return (
@@ -30,7 +30,7 @@ function MachineSidebar({ tileId, position }) {
</TextList>
<Title headingLevel="h2">Actions</Title>
- <DeleteMachine />
+ <DeleteMachine machineId={machineId} />
<Title headingLevel="h2">Units</Title>
</TextContent>
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js
index fc805b95..6b136120 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js
@@ -27,7 +27,7 @@ import UnitAddComponent from './UnitAddComponent'
import { addUnit } from '../../../../redux/actions/topology/machine'
function UnitAddContainer({ machineId, unitType }) {
- const units = useSelector((state) => Object.values(state.objects[unitType]))
+ const units = useSelector((state) => Object.values(state.topology[unitType]))
const dispatch = useDispatch()
const onAdd = (id) => dispatch(addUnit(machineId, unitType, id))
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
index 901fa45b..6dcc414f 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
@@ -26,18 +26,11 @@ import { useDispatch, useSelector } from 'react-redux'
import UnitListComponent from './UnitListComponent'
import { deleteUnit } from '../../../../redux/actions/topology/machine'
-const unitMapping = {
- cpu: 'cpus',
- gpu: 'gpus',
- memory: 'memories',
- storage: 'storages',
-}
-
function UnitListContainer({ machineId, unitType }) {
const dispatch = useDispatch()
const units = useSelector((state) => {
- const machine = state.objects.machine[machineId]
- return machine[unitMapping[unitType]].map((id) => state.objects[unitType][id])
+ const machine = state.topology.machines[machineId]
+ return machine[unitType].map((id) => state.topology[unitType][id])
})
const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id))
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js
index 6d10d2df..b800e9d4 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js
@@ -10,20 +10,20 @@ function UnitTabsComponent({ machineId }) {
return (
<Tabs activeKey={activeTab} onSelect={(_, tab) => setActiveTab(tab)}>
<Tab eventKey="cpu-units" title={<TabTitleText>CPU</TabTitleText>}>
- <UnitAddContainer machineId={machineId} unitType="cpu" />
- <UnitListContainer machineId={machineId} unitType="cpu" />
+ <UnitAddContainer machineId={machineId} unitType="cpus" />
+ <UnitListContainer machineId={machineId} unitType="cpus" />
</Tab>
<Tab eventKey="gpu-units" title={<TabTitleText>GPU</TabTitleText>}>
- <UnitAddContainer machineId={machineId} unitType="gpu" />
- <UnitListContainer machineId={machineId} unitType="gpu" />
+ <UnitAddContainer machineId={machineId} unitType="gpus" />
+ <UnitListContainer machineId={machineId} unitType="gpus" />
</Tab>
<Tab eventKey="memory-units" title={<TabTitleText>Memory</TabTitleText>}>
- <UnitAddContainer machineId={machineId} unitType="memory" />
- <UnitListContainer machineId={machineId} unitType="memory" />
+ <UnitAddContainer machineId={machineId} unitType="memories" />
+ <UnitListContainer machineId={machineId} unitType="memories" />
</Tab>
<Tab eventKey="storage-units" title={<TabTitleText>Storage</TabTitleText>}>
- <UnitAddContainer machineId={machineId} unitType="storage" />
- <UnitListContainer machineId={machineId} unitType="storage" />
+ <UnitAddContainer machineId={machineId} unitType="storages" />
+ <UnitListContainer machineId={machineId} unitType="storages" />
</Tab>
</Tabs>
)
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js
index 80c6349a..0583a7a4 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js
@@ -22,7 +22,7 @@
import PropTypes from 'prop-types'
import React, { useState } from 'react'
-import { useDispatch } from 'react-redux'
+import { useDispatch, useSelector } from 'react-redux'
import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon'
import { Button } from '@patternfly/react-core'
import ConfirmationModal from '../../../util/modals/ConfirmationModal'
@@ -31,9 +31,10 @@ import { deleteRack } from '../../../../redux/actions/topology/rack'
function DeleteRackContainer({ tileId }) {
const dispatch = useDispatch()
const [isVisible, setVisible] = useState(false)
+ const rackId = useSelector((state) => state.topology.tiles[tileId].rack)
const callback = (isConfirmed) => {
if (isConfirmed) {
- dispatch(deleteRack(tileId))
+ dispatch(deleteRack(tileId, rackId))
}
setVisible(false)
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
index 6fbff949..619bb4e2 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
@@ -28,8 +28,8 @@ import { goFromRackToMachine } from '../../../../redux/actions/interaction-level
import { addMachine } from '../../../../redux/actions/topology/rack'
function MachineListContainer({ tileId, ...props }) {
- const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
- const machines = useSelector((state) => rack.machines.map((id) => state.objects.machine[id]))
+ const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack])
+ const machines = useSelector((state) => rack.machines.map((id) => state.topology.machines[id]))
const machinesNull = useMemo(() => {
const res = Array(rack.capacity).fill(null)
for (const machine of machines) {
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
index 09d73af7..30f38cce 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
@@ -5,7 +5,7 @@ import NameComponent from '../NameComponent'
import { editRackName } from '../../../../redux/actions/topology/rack'
const RackNameContainer = ({ tileId }) => {
- const { name: rackName, _id } = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+ const { name: rackName, _id } = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack])
const dispatch = useDispatch()
const callback = (name) => {
if (name) {
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
index 3c9f152a..8f6ff135 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js
@@ -17,7 +17,7 @@ import {
import { useSelector } from 'react-redux'
function RackSidebar({ tileId }) {
- const rack = useSelector((state) => state.objects.rack[state.objects.tile[tileId].rack])
+ const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack])
return (
<div className={sidebarContainer}>
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
index e8d8b33c..fb52d826 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
@@ -27,7 +27,7 @@ import NameComponent from '../NameComponent'
import { editRoomName } from '../../../../redux/actions/topology/room'
function RoomName({ roomId }) {
- const { name: roomName, _id } = useSelector((state) => state.objects.room[roomId])
+ const { name: roomName, _id } = useSelector((state) => state.topology.rooms[roomId])
const dispatch = useDispatch()
const callback = (name) => {
if (name) {
diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js
index 83abb6aa..e068ed8e 100644
--- a/opendc-web/opendc-web-ui/src/data/topology.js
+++ b/opendc-web/opendc-web-ui/src/data/topology.js
@@ -20,7 +20,6 @@
* SOFTWARE.
*/
-import { useSelector } from 'react-redux'
import { useQuery } from 'react-query'
import { addTopology, deleteTopology, fetchTopologiesOfProject, fetchTopology, updateTopology } from '../api/topologies'
@@ -66,13 +65,6 @@ export function configureTopologyClient(queryClient, auth) {
/**
* Return the current active topology.
*/
-export function useActiveTopology() {
- return useSelector((state) => state.currentTopologyId !== '-1' && state.objects.topology[state.currentTopologyId])
-}
-
-/**
- * Return the current active topology.
- */
export function useTopology(topologyId, options = {}) {
return useQuery(['topologies', topologyId], { enabled: !!topologyId, ...options })
}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/objects.js b/opendc-web/opendc-web-ui/src/redux/actions/objects.js
deleted file mode 100644
index 7b648b18..00000000
--- a/opendc-web/opendc-web-ui/src/redux/actions/objects.js
+++ /dev/null
@@ -1,41 +0,0 @@
-export const ADD_TO_STORE = 'ADD_TO_STORE'
-export const ADD_PROP_TO_STORE_OBJECT = 'ADD_PROP_TO_STORE_OBJECT'
-export const ADD_ID_TO_STORE_OBJECT_LIST_PROP = 'ADD_ID_TO_STORE_OBJECT_LIST_PROP'
-export const REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP = 'REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP'
-
-export function addToStore(objectType, object) {
- return {
- type: ADD_TO_STORE,
- objectType,
- object,
- }
-}
-
-export function addPropToStoreObject(objectType, objectId, propObject) {
- return {
- type: ADD_PROP_TO_STORE_OBJECT,
- objectType,
- objectId,
- propObject,
- }
-}
-
-export function addIdToStoreObjectListProp(objectType, objectId, propName, id) {
- return {
- type: ADD_ID_TO_STORE_OBJECT_LIST_PROP,
- objectType,
- objectId,
- propName,
- id,
- }
-}
-
-export function removeIdFromStoreObjectListProp(objectType, objectId, propName, id) {
- return {
- type: REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP,
- objectType,
- objectId,
- propName,
- id,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js b/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js
deleted file mode 100644
index 0ef7795f..00000000
--- a/opendc-web/opendc-web-ui/src/redux/actions/prefabs.js
+++ /dev/null
@@ -1,33 +0,0 @@
-export const ADD_PREFAB = 'ADD_PREFAB'
-export const DELETE_PREFAB = 'DELETE_PREFAB'
-export const DELETE_PREFAB_SUCCEEDED = 'DELETE_PREFAB_SUCCEEDED'
-export const OPEN_PREFAB_SUCCEEDED = 'OPEN_PREFAB_SUCCEEDED'
-
-export function addPrefab(name, tileId) {
- return {
- type: ADD_PREFAB,
- name,
- tileId,
- }
-}
-
-export function deletePrefab(id) {
- return {
- type: DELETE_PREFAB,
- id,
- }
-}
-
-export function deletePrefabSucceeded(id) {
- return {
- type: DELETE_PREFAB_SUCCEEDED,
- id,
- }
-}
-
-export function openPrefabSucceeded(id) {
- return {
- type: OPEN_PREFAB_SUCCEEDED,
- id,
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topologies.js b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
index 4888c4da..fc697cc2 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
@@ -18,9 +18,10 @@ export function addTopology(projectId, name, duplicateId) {
}
}
-export function storeTopology(entities) {
+export function storeTopology(topology, entities) {
return {
type: STORE_TOPOLOGY,
+ topology,
entities,
}
}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js
index 49425318..939c24a4 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js
@@ -1,4 +1,6 @@
-export const SET_CURRENT_TOPOLOGY = 'SET_CURRENT_TOPOLOGY'
+import { uuid } from 'uuidv4'
+import { addRoom, deleteRoom } from './room'
+
export const START_NEW_ROOM_CONSTRUCTION = 'START_NEW_ROOM_CONSTRUCTION'
export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'START_NEW_ROOM_CONSTRUCTION_SUCCEEDED'
export const FINISH_NEW_ROOM_CONSTRUCTION = 'FINISH_NEW_ROOM_CONSTRUCTION'
@@ -9,16 +11,19 @@ export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT'
export const ADD_TILE = 'ADD_TILE'
export const DELETE_TILE = 'DELETE_TILE'
-export function setCurrentTopology(topologyId) {
- return {
- type: SET_CURRENT_TOPOLOGY,
- topologyId,
- }
-}
-
export function startNewRoomConstruction() {
- return {
- type: START_NEW_ROOM_CONSTRUCTION,
+ return (dispatch, getState) => {
+ const { topology } = getState()
+ const topologyId = topology.root._id
+ const room = {
+ _id: uuid(),
+ name: 'Room',
+ topologyId,
+ tiles: [],
+ }
+
+ dispatch(addRoom(topologyId, room))
+ dispatch(startNewRoomConstructionSucceeded(room._id))
}
}
@@ -31,8 +36,8 @@ export function startNewRoomConstructionSucceeded(roomId) {
export function finishNewRoomConstruction() {
return (dispatch, getState) => {
- const { objects, construction } = getState()
- if (objects.room[construction.currentRoomInConstruction].tiles.length === 0) {
+ const { topology, construction } = getState()
+ if (topology.rooms[construction.currentRoomInConstruction].tiles.length === 0) {
dispatch(cancelNewRoomConstruction())
return
}
@@ -44,8 +49,11 @@ export function finishNewRoomConstruction() {
}
export function cancelNewRoomConstruction() {
- return {
- type: CANCEL_NEW_ROOM_CONSTRUCTION,
+ return (dispatch, getState) => {
+ const { construction } = getState()
+ const roomId = construction.currentRoomInConstruction
+ dispatch(deleteRoom(roomId))
+ dispatch(cancelNewRoomConstructionSucceeded())
}
}
@@ -70,24 +78,30 @@ export function finishRoomEdit() {
export function toggleTileAtLocation(positionX, positionY) {
return (dispatch, getState) => {
- const { objects, construction } = getState()
+ const { topology, construction } = getState()
- const tileIds = objects.room[construction.currentRoomInConstruction].tiles
+ const roomId = construction.currentRoomInConstruction
+ const tileIds = topology.rooms[roomId].tiles
for (const tileId of tileIds) {
- if (objects.tile[tileId].positionX === positionX && objects.tile[tileId].positionY === positionY) {
+ if (topology.tiles[tileId].positionX === positionX && topology.tiles[tileId].positionY === positionY) {
dispatch(deleteTile(tileId))
return
}
}
- dispatch(addTile(positionX, positionY))
+
+ dispatch(addTile(roomId, positionX, positionY))
}
}
-export function addTile(positionX, positionY) {
+export function addTile(roomId, positionX, positionY) {
return {
type: ADD_TILE,
- positionX,
- positionY,
+ tile: {
+ _id: uuid(),
+ roomId,
+ positionX,
+ positionY,
+ },
}
}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js
index 170b7648..93320884 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js
@@ -2,28 +2,27 @@ export const DELETE_MACHINE = 'DELETE_MACHINE'
export const ADD_UNIT = 'ADD_UNIT'
export const DELETE_UNIT = 'DELETE_UNIT'
-export function deleteMachine(rackId, position) {
+export function deleteMachine(machineId) {
return {
type: DELETE_MACHINE,
- rackId,
- position,
+ machineId,
}
}
-export function addUnit(machineId, unitType, id) {
+export function addUnit(machineId, unitType, unitId) {
return {
type: ADD_UNIT,
machineId,
unitType,
- id,
+ unitId,
}
}
-export function deleteUnit(machineId, unitType, index) {
+export function deleteUnit(machineId, unitType, unitId) {
return {
type: DELETE_UNIT,
machineId,
unitType,
- index,
+ unitId,
}
}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js
index 228e3ae9..c319d966 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js
@@ -1,3 +1,5 @@
+import { uuid } from 'uuidv4'
+
export const EDIT_RACK_NAME = 'EDIT_RACK_NAME'
export const DELETE_RACK = 'DELETE_RACK'
export const ADD_MACHINE = 'ADD_MACHINE'
@@ -10,9 +12,10 @@ export function editRackName(rackId, name) {
}
}
-export function deleteRack(tileId) {
+export function deleteRack(tileId, rackId) {
return {
type: DELETE_RACK,
+ rackId,
tileId,
}
}
@@ -20,7 +23,14 @@ export function deleteRack(tileId) {
export function addMachine(rackId, position) {
return {
type: ADD_MACHINE,
- position,
- rackId,
+ machine: {
+ _id: uuid(),
+ rackId,
+ position,
+ cpus: [],
+ gpus: [],
+ memories: [],
+ storages: [],
+ },
}
}
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js
index e584af89..bd447db5 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js
@@ -1,11 +1,28 @@
+import { uuid } from 'uuidv4'
+import {
+ DEFAULT_RACK_SLOT_CAPACITY,
+ DEFAULT_RACK_POWER_CAPACITY,
+} from '../../../components/topologies/map/MapConstants'
import { findTileWithPosition } from '../../../util/tile-calculations'
+export const ADD_ROOM = 'ADD_ROOM'
export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME'
export const DELETE_ROOM = 'DELETE_ROOM'
export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION'
export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION'
export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE'
+export function addRoom(topologyId, room) {
+ return {
+ type: ADD_ROOM,
+ room: {
+ _id: uuid(),
+ topologyId,
+ ...room,
+ },
+ }
+}
+
export function editRoomName(roomId, name) {
return {
type: EDIT_ROOM_NAME,
@@ -28,15 +45,22 @@ export function stopRackConstruction() {
export function addRackToTile(positionX, positionY) {
return (dispatch, getState) => {
- const { objects, interactionLevel } = getState()
- const currentRoom = objects.room[interactionLevel.roomId]
- const tiles = currentRoom.tiles.map((tileId) => objects.tile[tileId])
+ const { topology, interactionLevel } = getState()
+ const currentRoom = topology.rooms[interactionLevel.roomId]
+ const tiles = currentRoom.tiles.map((tileId) => topology.tiles[tileId])
const tile = findTileWithPosition(tiles, positionX, positionY)
if (tile !== null) {
dispatch({
type: ADD_RACK_TO_TILE,
- tileId: tile._id,
+ rack: {
+ _id: uuid(),
+ name: 'Rack',
+ tileId: tile._id,
+ capacity: DEFAULT_RACK_SLOT_CAPACITY,
+ powerCapacityW: DEFAULT_RACK_POWER_CAPACITY,
+ machines: [],
+ },
})
}
}
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js
index 5bac7fea..d0aac5ae 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js
@@ -4,7 +4,6 @@ import {
CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED,
FINISH_NEW_ROOM_CONSTRUCTION,
FINISH_ROOM_EDIT,
- SET_CURRENT_TOPOLOGY,
START_NEW_ROOM_CONSTRUCTION_SUCCEEDED,
START_ROOM_EDIT,
} from '../actions/topology/building'
@@ -19,7 +18,6 @@ export function currentRoomInConstruction(state = '-1', action) {
case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED:
case FINISH_NEW_ROOM_CONSTRUCTION:
case FINISH_ROOM_EDIT:
- case SET_CURRENT_TOPOLOGY:
case DELETE_ROOM:
return '-1'
default:
@@ -32,7 +30,6 @@ export function inRackConstructionMode(state = false, action) {
case START_RACK_CONSTRUCTION:
return true
case STOP_RACK_CONSTRUCTION:
- case SET_CURRENT_TOPOLOGY:
case GO_DOWN_ONE_INTERACTION_LEVEL:
return false
default:
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js b/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js
deleted file mode 100644
index c0baf567..00000000
--- a/opendc-web/opendc-web-ui/src/redux/reducers/current-ids.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building'
-
-export function currentTopologyId(state = '-1', action) {
- switch (action.type) {
- case SET_CURRENT_TOPOLOGY:
- return action.topologyId
- default:
- return state
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js
index 2f1359d6..7ffb1211 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/index.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/index.js
@@ -1,13 +1,11 @@
import { combineReducers } from 'redux'
import { construction } from './construction-mode'
-import { currentTopologyId } from './current-ids'
import { interactionLevel } from './interaction-level'
-import { objects } from './objects'
+import topology from './topology'
const rootReducer = combineReducers({
- objects,
+ topology,
construction,
- currentTopologyId,
interactionLevel,
})
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js
index 9f23949f..b30c68b9 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js
@@ -4,14 +4,12 @@ import {
GO_FROM_RACK_TO_MACHINE,
GO_FROM_ROOM_TO_RACK,
} from '../actions/interaction-level'
-import { SET_CURRENT_TOPOLOGY } from '../actions/topology/building'
+import { DELETE_MACHINE } from '../actions/topology/machine'
+import { DELETE_RACK } from '../actions/topology/rack'
+import { DELETE_ROOM } from '../actions/topology/room'
export function interactionLevel(state = { mode: 'BUILDING' }, action) {
switch (action.type) {
- case SET_CURRENT_TOPOLOGY:
- return {
- mode: 'BUILDING',
- }
case GO_FROM_BUILDING_TO_ROOM:
return {
mode: 'ROOM',
@@ -49,6 +47,21 @@ export function interactionLevel(state = { mode: 'BUILDING' }, action) {
} else {
return state
}
+ case DELETE_MACHINE:
+ return {
+ mode: 'RACK',
+ roomId: state.roomId,
+ tileId: state.tileId,
+ }
+ case DELETE_RACK:
+ return {
+ mode: 'ROOM',
+ roomId: state.roomId,
+ }
+ case DELETE_ROOM:
+ return {
+ mode: 'BUILDING',
+ }
default:
return state
}
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/objects.js b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
deleted file mode 100644
index 11f6d353..00000000
--- a/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { combineReducers } from 'redux'
-import {
- ADD_ID_TO_STORE_OBJECT_LIST_PROP,
- ADD_PROP_TO_STORE_OBJECT,
- ADD_TO_STORE,
- REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP,
-} from '../actions/objects'
-import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../../util/unit-specifications'
-import { STORE_TOPOLOGY } from '../actions/topologies'
-
-export const objects = combineReducers({
- cpu: object('cpu', CPU_UNITS),
- gpu: object('gpu', GPU_UNITS),
- memory: object('memory', MEMORY_UNITS),
- storage: object('storage', STORAGE_UNITS),
- machine: object('machine'),
- rack: object('rack'),
- tile: object('tile'),
- room: object('room'),
- topology: object('topology'),
- prefab: object('prefab'),
-})
-
-function object(type, defaultState = {}) {
- return objectWithId(type, (object) => object._id, defaultState)
-}
-
-function objectWithId(type, getId, defaultState = {}) {
- return (state = defaultState, action) => {
- if (action.type === STORE_TOPOLOGY) {
- return { ...state, ...action.entities[type] }
- } else if (action.objectType !== type) {
- return state
- }
-
- if (action.type === ADD_TO_STORE) {
- return { ...state, [getId(action.object)]: action.object }
- } else if (action.type === ADD_PROP_TO_STORE_OBJECT) {
- return { ...state, [action.objectId]: { ...state[action.objectId], ...action.propObject } }
- } else if (action.type === ADD_ID_TO_STORE_OBJECT_LIST_PROP) {
- return Object.assign({}, state, {
- [action.objectId]: Object.assign({}, state[action.objectId], {
- [action.propName]: [...state[action.objectId][action.propName], action.id],
- }),
- })
- } else if (action.type === REMOVE_ID_FROM_STORE_OBJECT_LIST_PROP) {
- return Object.assign({}, state, {
- [action.objectId]: Object.assign({}, state[action.objectId], {
- [action.propName]: state[action.objectId][action.propName].filter((id) => id !== action.id),
- }),
- })
- }
-
- return state
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/query.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js
index 787006c7..b1c7d29e 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/query.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js
@@ -20,22 +20,25 @@
* SOFTWARE.
*/
-import { MutationObserver } from 'react-query'
-import { getContext, call } from 'redux-saga/effects'
+import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../../../util/unit-specifications'
+import machine from './machine'
+import rack from './rack'
+import room from './room'
+import tile from './tile'
+import topology from './topology'
-/**
- * Fetch the query with the specified key.
- */
-export function* fetchQuery(key, options) {
- const queryClient = yield getContext('queryClient')
- return yield call([queryClient, queryClient.fetchQuery], key, options)
+function objects(state = {}, action) {
+ return {
+ cpus: CPU_UNITS,
+ gpus: GPU_UNITS,
+ memories: MEMORY_UNITS,
+ storages: STORAGE_UNITS,
+ machines: machine(state.machines, action, state),
+ racks: rack(state.racks, action, state),
+ tiles: tile(state.tiles, action, state),
+ rooms: room(state.rooms, action, state),
+ root: topology(state.root, action, state),
+ }
}
-/**
- * Perform a mutation with the specified key.
- */
-export function* mutate(key, object, options) {
- const queryClient = yield getContext('queryClient')
- const mutationObserver = new MutationObserver(queryClient, { mutationKey: key })
- return yield call([mutationObserver, mutationObserver.mutate], object, options)
-}
+export default objects
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js
new file mode 100644
index 00000000..41773014
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js
@@ -0,0 +1,47 @@
+import produce from 'immer'
+import { STORE_TOPOLOGY } from '../../actions/topologies'
+import { DELETE_MACHINE, ADD_UNIT, DELETE_UNIT } from '../../actions/topology/machine'
+import { ADD_MACHINE, DELETE_RACK } from '../../actions/topology/rack'
+
+function machine(state = {}, action, { racks }) {
+ switch (action.type) {
+ case STORE_TOPOLOGY:
+ return action.entities.machines || {}
+ case ADD_MACHINE:
+ return produce(state, (draft) => {
+ const { machine } = action
+ draft[machine._id] = machine
+ })
+ case DELETE_MACHINE:
+ return produce(state, (draft) => {
+ const { machineId } = action
+ delete draft[machineId]
+ })
+ case ADD_UNIT:
+ return produce(state, (draft) => {
+ const { machineId, unitType, unitId } = action
+ draft[machineId][unitType].push(unitId)
+ })
+ case DELETE_UNIT:
+ return produce(state, (draft) => {
+ const { machineId, unitType, unitId } = action
+ const units = draft[machineId][unitType]
+ const index = units.indexOf(unitId)
+ units.splice(index, 1)
+ })
+ case DELETE_RACK:
+ return produce(state, (draft) => {
+ const { rackId } = action
+ const rack = racks[rackId]
+
+ for (const id of rack.machines) {
+ const machine = draft[id]
+ machine.rackId = undefined
+ }
+ })
+ default:
+ return state
+ }
+}
+
+export default machine
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js
new file mode 100644
index 00000000..9cc37124
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import produce from 'immer'
+import { STORE_TOPOLOGY } from '../../actions/topologies'
+import { DELETE_MACHINE } from '../../actions/topology/machine'
+import { DELETE_RACK, EDIT_RACK_NAME, ADD_MACHINE } from '../../actions/topology/rack'
+import { ADD_RACK_TO_TILE } from '../../actions/topology/room'
+
+function rack(state = {}, action, { machines }) {
+ switch (action.type) {
+ case STORE_TOPOLOGY:
+ return action.entities.racks || {}
+ case ADD_RACK_TO_TILE:
+ return produce(state, (draft) => {
+ const { rack } = action
+ draft[rack._id] = rack
+ })
+ case EDIT_RACK_NAME:
+ return produce(state, (draft) => {
+ const { rackId, name } = action
+ draft[rackId].name = name
+ })
+ case DELETE_RACK:
+ return produce(state, (draft) => {
+ const { rackId } = action
+ delete draft[rackId]
+ })
+ case ADD_MACHINE:
+ return produce(state, (draft) => {
+ const { machine } = action
+ draft[machine.rackId].machines.push(machine._id)
+ })
+ case DELETE_MACHINE:
+ return produce(state, (draft) => {
+ const { machineId } = action
+ const machine = machines[machineId]
+ const rack = draft[machine.rackId]
+ const index = rack.machines.indexOf(machineId)
+ rack.machines.splice(index, 1)
+ })
+ default:
+ return state
+ }
+}
+
+export default rack
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js
new file mode 100644
index 00000000..b61c9d82
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import produce from 'immer'
+import { STORE_TOPOLOGY } from '../../actions/topologies'
+import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building'
+import { DELETE_ROOM, EDIT_ROOM_NAME, ADD_ROOM } from '../../actions/topology/room'
+
+function room(state = {}, action, { tiles }) {
+ switch (action.type) {
+ case STORE_TOPOLOGY:
+ return action.entities.rooms || {}
+ case ADD_ROOM:
+ return produce(state, (draft) => {
+ const { room } = action
+ draft[room._id] = room
+ })
+ case DELETE_ROOM:
+ return produce(state, (draft) => {
+ const { roomId } = action
+ delete draft[roomId]
+ })
+ case EDIT_ROOM_NAME:
+ return produce(state, (draft) => {
+ const { roomId, name } = action
+ draft[roomId].name = name
+ })
+ case ADD_TILE:
+ return produce(state, (draft) => {
+ const { tile } = action
+ draft[tile.roomId].tiles.push(tile._id)
+ })
+ case DELETE_TILE:
+ return produce(state, (draft) => {
+ const { tileId } = action
+ const tile = tiles[tileId]
+ const room = draft[tile.roomId]
+ const index = room.tiles.indexOf(tileId)
+ room.tiles.splice(index, 1)
+ })
+ default:
+ return state
+ }
+}
+
+export default room
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js
new file mode 100644
index 00000000..e0c5dd33
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import produce from 'immer'
+import { STORE_TOPOLOGY } from '../../actions/topologies'
+import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building'
+import { DELETE_RACK } from '../../actions/topology/rack'
+import { ADD_RACK_TO_TILE } from '../../actions/topology/room'
+
+function tile(state = {}, action, { racks }) {
+ switch (action.type) {
+ case STORE_TOPOLOGY:
+ return action.entities.tiles || {}
+ case ADD_TILE:
+ return produce(state, (draft) => {
+ const { tile } = action
+ draft[tile._id] = tile
+ })
+ case DELETE_TILE:
+ return produce(state, (draft) => {
+ const { tileId } = action
+ delete draft[tileId]
+ })
+ case ADD_RACK_TO_TILE:
+ return produce(state, (draft) => {
+ const { rack } = action
+ draft[rack.tileId].rack = rack._id
+ })
+ case DELETE_RACK:
+ return produce(state, (draft) => {
+ const { rackId } = action
+ const rack = racks[rackId]
+ draft[rack.tileId].rack = undefined
+ })
+ default:
+ return state
+ }
+}
+
+export default tile
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js
new file mode 100644
index 00000000..da0e6988
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021 AtLarge Research
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import produce from 'immer'
+import { STORE_TOPOLOGY } from '../../actions/topologies'
+import { ADD_ROOM, DELETE_ROOM } from '../../actions/topology/room'
+
+function topology(state = undefined, action) {
+ switch (action.type) {
+ case STORE_TOPOLOGY:
+ return action.topology
+ case ADD_ROOM:
+ return produce(state, (draft) => {
+ const { room } = action
+ draft.rooms.push(room._id)
+ })
+ case DELETE_ROOM:
+ return produce(state, (draft) => {
+ const { roomId } = action
+ const index = draft.rooms.indexOf(roomId)
+ draft.rooms.splice(index, 1)
+ })
+ default:
+ return state
+ }
+}
+
+export default topology
diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js
index 9ddc564d..0fabdb6d 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/index.js
@@ -1,51 +1,7 @@
-import { takeEvery } from 'redux-saga/effects'
-import {
- ADD_TILE,
- CANCEL_NEW_ROOM_CONSTRUCTION,
- DELETE_TILE,
- START_NEW_ROOM_CONSTRUCTION,
-} from '../actions/topology/building'
-import { ADD_UNIT, DELETE_MACHINE, DELETE_UNIT } from '../actions/topology/machine'
-import { ADD_MACHINE, DELETE_RACK, EDIT_RACK_NAME } from '../actions/topology/rack'
-import { ADD_RACK_TO_TILE, DELETE_ROOM, EDIT_ROOM_NAME } from '../actions/topology/room'
-import {
- onAddMachine,
- onAddRackToTile,
- onAddTile,
- onAddTopology,
- onAddUnit,
- onCancelNewRoomConstruction,
- onDeleteMachine,
- onDeleteRack,
- onDeleteRoom,
- onDeleteTile,
- onDeleteUnit,
- onEditRackName,
- onEditRoomName,
- onStartNewRoomConstruction,
- onOpenTopology,
-} from './topology'
-import { ADD_TOPOLOGY, OPEN_TOPOLOGY } from '../actions/topologies'
-import { onAddPrefab } from './prefabs'
-import { ADD_PREFAB } from '../actions/prefabs'
+import { fork } from 'redux-saga/effects'
+import { watchServer, updateServer } from './topology'
export default function* rootSaga() {
- yield takeEvery(OPEN_TOPOLOGY, onOpenTopology)
-
- yield takeEvery(ADD_TOPOLOGY, onAddTopology)
- yield takeEvery(START_NEW_ROOM_CONSTRUCTION, onStartNewRoomConstruction)
- yield takeEvery(CANCEL_NEW_ROOM_CONSTRUCTION, onCancelNewRoomConstruction)
- yield takeEvery(ADD_TILE, onAddTile)
- yield takeEvery(DELETE_TILE, onDeleteTile)
- yield takeEvery(EDIT_ROOM_NAME, onEditRoomName)
- yield takeEvery(DELETE_ROOM, onDeleteRoom)
- yield takeEvery(EDIT_RACK_NAME, onEditRackName)
- yield takeEvery(DELETE_RACK, onDeleteRack)
- yield takeEvery(ADD_RACK_TO_TILE, onAddRackToTile)
- yield takeEvery(ADD_MACHINE, onAddMachine)
- yield takeEvery(DELETE_MACHINE, onDeleteMachine)
- yield takeEvery(ADD_UNIT, onAddUnit)
- yield takeEvery(DELETE_UNIT, onDeleteUnit)
-
- yield takeEvery(ADD_PREFAB, onAddPrefab)
+ yield fork(watchServer)
+ yield fork(updateServer)
}
diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
deleted file mode 100644
index f717d878..00000000
--- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { call, put, select, getContext } from 'redux-saga/effects'
-import { addToStore } from '../actions/objects'
-import { addPrefab } from '../../api/prefabs'
-import { Rack } from '../../util/topology-schema'
-import { denormalize } from 'normalizr'
-
-export function* onAddPrefab({ name, tileId }) {
- try {
- const objects = yield select((state) => state.objects)
- const rack = objects.rack[objects.tile[tileId].rack]
- const prefabRack = denormalize(rack, Rack, objects)
- const auth = yield getContext('auth')
- const prefab = yield call(() => addPrefab(auth, { name, rack: prefabRack }))
- yield put(addToStore('prefab', prefab))
- } catch (error) {
- console.error(error)
- }
-}
diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
index fb6f7f0d..f40cff28 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
@@ -1,287 +1,75 @@
+import { QueryObserver, MutationObserver } from 'react-query'
import { normalize, denormalize } from 'normalizr'
-import { put, select } from 'redux-saga/effects'
+import { select, put, take, race, getContext, call } from 'redux-saga/effects'
+import { eventChannel } from 'redux-saga'
import { Topology } from '../../util/topology-schema'
-import { goDownOneInteractionLevel } from '../actions/interaction-level'
-import {
- addIdToStoreObjectListProp,
- addPropToStoreObject,
- addToStore,
- removeIdFromStoreObjectListProp,
-} from '../actions/objects'
-import { storeTopology } from '../actions/topologies'
-import {
- cancelNewRoomConstructionSucceeded,
- setCurrentTopology,
- startNewRoomConstructionSucceeded,
-} from '../actions/topology/building'
-import {
- DEFAULT_RACK_POWER_CAPACITY,
- DEFAULT_RACK_SLOT_CAPACITY,
- MAX_NUM_UNITS_PER_MACHINE,
-} from '../../components/topologies/map/MapConstants'
-import { uuid } from 'uuidv4'
-import { fetchQuery, mutate } from './query'
+import { storeTopology, OPEN_TOPOLOGY } from '../actions/topologies'
/**
- * Fetches and normalizes the topology with the specified identifier.
+ * Update the topology on the server.
*/
-function* fetchAndStoreTopology(id) {
- let topology = yield select((state) => state.objects.topology[id])
- if (!topology) {
- const newTopology = yield fetchQuery(['topologies', id])
- const { entities } = normalize(newTopology, Topology)
- yield put(storeTopology(entities))
- }
-
- return topology
-}
-
-/**
- * Synchronize the topology with the specified identifier with the server.
- */
-function* updateTopologyOnServer(id) {
- const topology = yield denormalizeTopology(id)
- yield mutate('updateTopology', topology)
-}
-
-/**
- * Denormalizes the topology representation in order to be stored on the server.
- */
-function* denormalizeTopology(id) {
- const objects = yield select((state) => state.objects)
- const topology = objects.topology[id]
- return denormalize(topology, Topology, objects)
-}
-
-export function* onOpenTopology({ id }) {
- try {
- yield fetchAndStoreTopology(id)
- yield put(setCurrentTopology(id))
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onAddTopology({ projectId, duplicateId, name }) {
- try {
- let topologyToBeCreated
- if (duplicateId) {
- topologyToBeCreated = yield denormalizeTopology(duplicateId)
- topologyToBeCreated = { ...topologyToBeCreated, name }
- delete topologyToBeCreated._id
- } else {
- topologyToBeCreated = { name, rooms: [] }
- }
-
- const topology = yield mutate('addTopology', { ...topologyToBeCreated, projectId })
- yield fetchAndStoreTopology(topology._id)
- yield put(setCurrentTopology(topology._id))
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onStartNewRoomConstruction() {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const room = {
- _id: uuid(),
- name: 'Room',
- topologyId,
- tiles: [],
- }
- yield put(addToStore('room', room))
- yield put(addIdToStoreObjectListProp('topology', topologyId, 'rooms', room._id))
- yield updateTopologyOnServer(topologyId)
- yield put(startNewRoomConstructionSucceeded(room._id))
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onCancelNewRoomConstruction() {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const roomId = yield select((state) => state.construction.currentRoomInConstruction)
- yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId))
- // TODO remove room from store, too
- yield updateTopologyOnServer(topologyId)
- yield put(cancelNewRoomConstructionSucceeded())
- } catch (error) {
- console.error(error)
- }
-}
+export function* updateServer() {
+ const queryClient = yield getContext('queryClient')
+ const mutationObserver = new MutationObserver(queryClient, { mutationKey: 'updateTopology' })
+
+ while (true) {
+ yield take(
+ (action) =>
+ action.type.startsWith('EDIT') || action.type.startsWith('ADD') || action.type.startsWith('DELETE')
+ )
+ const topology = yield select((state) => state.topology)
-export function* onAddTile(action) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const roomId = yield select((state) => state.construction.currentRoomInConstruction)
- const tile = {
- _id: uuid(),
- roomId,
- positionX: action.positionX,
- positionY: action.positionY,
+ if (!topology.root) {
+ continue
}
- yield put(addToStore('tile', tile))
- yield put(addIdToStoreObjectListProp('room', roomId, 'tiles', tile._id))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onDeleteTile(action) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const roomId = yield select((state) => state.construction.currentRoomInConstruction)
- yield put(removeIdFromStoreObjectListProp('room', roomId, 'tiles', action.tileId))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-export function* onEditRoomName({ roomId, name }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- yield put(addPropToStoreObject('room', roomId, { name }))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
+ const denormalizedTopology = denormalize(topology.root, Topology, topology)
+ yield call([mutationObserver, mutationObserver.mutate], denormalizedTopology)
}
}
-export function* onDeleteRoom({ roomId }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- yield put(goDownOneInteractionLevel())
- yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onEditRackName({ rackId, name }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- yield put(addPropToStoreObject('rack', rackId, { name }))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onDeleteRack({ tileId }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- yield put(goDownOneInteractionLevel())
- yield put(addPropToStoreObject('tile', tileId, { rack: undefined }))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onAddRackToTile({ tileId }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const rack = {
- _id: uuid(),
- name: 'Rack',
- tileId,
- capacity: DEFAULT_RACK_SLOT_CAPACITY,
- powerCapacityW: DEFAULT_RACK_POWER_CAPACITY,
- machines: [],
- }
- yield put(addToStore('rack', rack))
- yield put(addPropToStoreObject('tile', tileId, { rack: rack._id }))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onAddMachine({ rackId, position }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const rack = yield select((state) => state.objects.rack[rackId])
-
- const machine = {
- _id: uuid(),
- rackId,
- position,
- cpus: [],
- gpus: [],
- memories: [],
- storages: [],
+/**
+ * Watch the topology on the server for changes.
+ */
+export function* watchServer() {
+ let { id } = yield take(OPEN_TOPOLOGY)
+ while (true) {
+ const channel = yield queryObserver(id)
+
+ while (true) {
+ const [action, response] = yield race([take(OPEN_TOPOLOGY), take(channel)])
+
+ if (action) {
+ id = action.id
+ break
+ }
+
+ const { isFetched, data } = response
+ // Only update the topology on the client-side when a new topology was fetched
+ if (isFetched) {
+ const { result: topologyId, entities } = normalize(data, Topology)
+ yield put(storeTopology(entities.topologies[topologyId], entities))
+ }
}
- yield put(addToStore('machine', machine))
-
- const machineIds = [...rack.machines, machine._id]
- yield put(addPropToStoreObject('rack', rackId, { machines: machineIds }))
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
-
-export function* onDeleteMachine({ rackId, position }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const rack = yield select((state) => state.objects.rack[rackId])
- yield put(goDownOneInteractionLevel())
- yield put(
- addPropToStoreObject('rack', rackId, { machines: rack.machines.filter((_, idx) => idx !== position - 1) })
- )
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
}
}
-const unitMapping = {
- cpu: 'cpus',
- gpu: 'gpus',
- memory: 'memories',
- storage: 'storages',
-}
-
-export function* onAddUnit({ machineId, unitType, id }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const machine = yield select((state) => state.objects.machine[machineId])
-
- if (machine[unitMapping[unitType]].length >= MAX_NUM_UNITS_PER_MACHINE) {
- return
- }
+/**
+ * Observe changes for the topology with the specified identifier.
+ */
+function* queryObserver(id) {
+ const queryClient = yield getContext('queryClient')
+ const observer = new QueryObserver(queryClient, { queryKey: ['topologies', id] })
- const units = [...machine[unitMapping[unitType]], id]
- yield put(
- addPropToStoreObject('machine', machine._id, {
- [unitMapping[unitType]]: units,
- })
- )
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
-}
+ return eventChannel((emitter) => {
+ const unsubscribe = observer.subscribe((result) => {
+ emitter(result)
+ })
-export function* onDeleteUnit({ machineId, unitType, index }) {
- try {
- const topologyId = yield select((state) => state.currentTopologyId)
- const machine = yield select((state) => state.objects.machine[machineId])
- const unitIds = machine[unitMapping[unitType]].slice()
- unitIds.splice(index, 1)
+ // Update result to make sure we did not miss any query updates
+ // between creating the observer and subscribing to it.
+ observer.updateResult()
- yield put(
- addPropToStoreObject('machine', machine._id, {
- [unitMapping[unitType]]: unitIds,
- })
- )
- yield updateTopologyOnServer(topologyId)
- } catch (error) {
- console.error(error)
- }
+ return unsubscribe
+ })
}
diff --git a/opendc-web/opendc-web-ui/src/util/topology-schema.js b/opendc-web/opendc-web-ui/src/util/topology-schema.js
index 9acd688b..7779ccfe 100644
--- a/opendc-web/opendc-web-ui/src/util/topology-schema.js
+++ b/opendc-web/opendc-web-ui/src/util/topology-schema.js
@@ -22,13 +22,13 @@
import { schema } from 'normalizr'
-const Cpu = new schema.Entity('cpu', {}, { idAttribute: '_id' })
-const Gpu = new schema.Entity('gpu', {}, { idAttribute: '_id' })
-const Memory = new schema.Entity('memory', {}, { idAttribute: '_id' })
-const Storage = new schema.Entity('storage', {}, { idAttribute: '_id' })
+const Cpu = new schema.Entity('cpus', {}, { idAttribute: '_id' })
+const Gpu = new schema.Entity('gpus', {}, { idAttribute: '_id' })
+const Memory = new schema.Entity('memories', {}, { idAttribute: '_id' })
+const Storage = new schema.Entity('storages', {}, { idAttribute: '_id' })
export const Machine = new schema.Entity(
- 'machine',
+ 'machines',
{
cpus: [Cpu],
gpus: [Gpu],
@@ -38,10 +38,10 @@ export const Machine = new schema.Entity(
{ idAttribute: '_id' }
)
-export const Rack = new schema.Entity('rack', { machines: [Machine] }, { idAttribute: '_id' })
+export const Rack = new schema.Entity('racks', { machines: [Machine] }, { idAttribute: '_id' })
-export const Tile = new schema.Entity('tile', { rack: Rack }, { idAttribute: '_id' })
+export const Tile = new schema.Entity('tiles', { rack: Rack }, { idAttribute: '_id' })
-export const Room = new schema.Entity('room', { tiles: [Tile] }, { idAttribute: '_id' })
+export const Room = new schema.Entity('rooms', { tiles: [Tile] }, { idAttribute: '_id' })
-export const Topology = new schema.Entity('topology', { rooms: [Room] }, { idAttribute: '_id' })
+export const Topology = new schema.Entity('topologies', { rooms: [Room] }, { idAttribute: '_id' })
diff --git a/opendc-web/opendc-web-ui/yarn.lock b/opendc-web/opendc-web-ui/yarn.lock
index 78122b5d..062766dd 100644
--- a/opendc-web/opendc-web-ui/yarn.lock
+++ b/opendc-web/opendc-web-ui/yarn.lock
@@ -2120,6 +2120,11 @@ image-size@1.0.0:
dependencies:
queue "6.0.2"
+immer@^9.0.5:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.5.tgz#a7154f34fe7064f15f00554cc94c66cc0bf453ec"
+ integrity sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ==
+
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"