summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/redux
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-21 15:04:22 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-22 14:23:09 +0200
commit54f424a18cc21a52ea518d40893218a07ab55989 (patch)
treed754d2705a5daf93178f20ea2a2e39046c6dce8a /opendc-web/opendc-web-ui/src/redux
parent28a4259c43e6180723b15a8c36a9b36871420f8a (diff)
feat(ui): Extract topology construction out of Sagas
This change updates the OpenDC frontend to perform the construction of the topology directly in the reducers instead of performing the mutations in Redux Sagas as side effects. This allows us to nicely map actions to mutations in the reducers.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/redux')
-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
21 files changed, 467 insertions, 538 deletions
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
+ })
}