summaryrefslogtreecommitdiff
path: root/opendc-web
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web')
-rw-r--r--opendc-web/opendc-web-api/opendc/models/topology.py2
-rw-r--r--opendc-web/opendc-web-ui/package.json1
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js8
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js19
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js5
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js6
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js14
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js33
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js12
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js14
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js27
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_document.js2
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topologies.js8
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/building.js13
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/room.js2
-rw-r--r--opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js4
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/objects.js18
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/objects.js186
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js11
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/topology.js74
-rw-r--r--opendc-web/opendc-web-ui/src/shapes.js31
-rw-r--r--opendc-web/opendc-web-ui/src/util/topology-schema.js47
-rw-r--r--opendc-web/opendc-web-ui/yarn.lock5
38 files changed, 254 insertions, 362 deletions
diff --git a/opendc-web/opendc-web-api/opendc/models/topology.py b/opendc-web/opendc-web-api/opendc/models/topology.py
index 7d2349ab..592f82c5 100644
--- a/opendc-web/opendc-web-api/opendc/models/topology.py
+++ b/opendc-web/opendc-web-api/opendc/models/topology.py
@@ -75,7 +75,7 @@ class TopologySchema(Schema):
Schema representing a datacenter topology.
"""
_id = fields.String(dump_only=True)
- projectId = fields.String(dump_only=True)
+ projectId = fields.String()
name = fields.String(required=True)
rooms = fields.List(fields.Nested(RoomSchema), required=True)
diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json
index e9570879..1a906acd 100644
--- a/opendc-web/opendc-web-ui/package.json
+++ b/opendc-web/opendc-web-ui/package.json
@@ -32,6 +32,7 @@
"lint-staged": "~10.2.2",
"mathjs": "~7.6.0",
"next": "^11.0.1",
+ "normalizr": "^3.6.1",
"prettier": "~2.0.5",
"prop-types": "~15.7.2",
"react": "^17.0.2",
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js
index e67d54fc..42d20ff1 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/RoomGroup.js
@@ -10,7 +10,7 @@ const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick
if (currentRoomInConstruction === room._id) {
return (
<Group onClick={onClick}>
- {room.tileIds.map((tileId) => (
+ {room.tiles.map((tileId) => (
<TileContainer key={tileId} tileId={tileId} newTile={true} />
))}
</Group>
@@ -25,16 +25,16 @@ const RoomGroup = ({ room, interactionLevel, currentRoomInConstruction, onClick
interactionLevel.roomId === room._id
) {
return [
- room.tileIds
+ room.tiles
.filter((tileId) => tileId !== interactionLevel.tileId)
.map((tileId) => <TileContainer key={tileId} tileId={tileId} />),
<GrayContainer key={-1} />,
- room.tileIds
+ room.tiles
.filter((tileId) => tileId === interactionLevel.tileId)
.map((tileId) => <TileContainer key={tileId} tileId={tileId} />),
]
} else {
- return room.tileIds.map((tileId) => <TileContainer key={tileId} tileId={tileId} />)
+ return room.tiles.map((tileId) => <TileContainer key={tileId} tileId={tileId} />)
}
})()}
<WallContainer roomId={room._id} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js
index 2a108b93..ce5e4a6b 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/TileGroup.js
@@ -8,7 +8,7 @@ import RoomTile from '../elements/RoomTile'
const TileGroup = ({ tile, newTile, onClick }) => {
let tileObject
- if (tile.rackId) {
+ if (tile.rack) {
tileObject = <RackContainer tile={tile} />
} else {
tileObject = null
diff --git a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js
index 57107768..d4c6db7d 100644
--- a/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/app/map/groups/TopologyGroup.js
@@ -12,7 +12,7 @@ const TopologyGroup = ({ topology, interactionLevel }) => {
if (interactionLevel.mode === 'BUILDING') {
return (
<Group>
- {topology.roomIds.map((roomId) => (
+ {topology.rooms.map((roomId) => (
<RoomContainer key={roomId} roomId={roomId} />
))}
</Group>
@@ -21,13 +21,13 @@ const TopologyGroup = ({ topology, interactionLevel }) => {
return (
<Group>
- {topology.roomIds
+ {topology.rooms
.filter((roomId) => roomId !== interactionLevel.roomId)
.map((roomId) => (
<RoomContainer key={roomId} roomId={roomId} />
))}
{interactionLevel.mode === 'ROOM' ? <GrayContainer /> : null}
- {topology.roomIds
+ {topology.rooms
.filter((roomId) => roomId === interactionLevel.roomId)
.map((roomId) => (
<RoomContainer key={roomId} roomId={roomId} />
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js
index 03b92459..46c639bd 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js
@@ -60,7 +60,7 @@ function UnitComponent({ index, unitType, unit, onDelete }) {
UnitComponent.propTypes = {
index: PropTypes.number,
unitType: PropTypes.string,
- unit: PropTypes.oneOf([ProcessingUnit, StorageUnit]),
+ unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]),
onDelete: PropTypes.func,
}
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js
index b7da74a1..54c1a6cc 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js
@@ -1,12 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
-import UnitContainer from '../../../../../containers/app/sidebars/topology/machine/UnitContainer'
+import { ProcessingUnit, StorageUnit } from '../../../../../shapes'
+import UnitComponent from './UnitComponent'
-const UnitListComponent = ({ unitType, unitIds }) => (
+const UnitListComponent = ({ unitType, units, onDelete }) => (
<ul className="list-group mt-1">
- {unitIds.length !== 0 ? (
- unitIds.map((unitId, index) => (
- <UnitContainer unitType={unitType} unitId={unitId} index={index} key={index} />
+ {units.length !== 0 ? (
+ units.map((unit, index) => (
+ <UnitComponent
+ unitType={unitType}
+ unit={unit}
+ onDelete={() => onDelete(unit, unitType)}
+ index={index}
+ key={index}
+ />
))
) : (
<div className="alert alert-info">
@@ -19,8 +26,9 @@ const UnitListComponent = ({ unitType, unitIds }) => (
)
UnitListComponent.propTypes = {
- unitType: PropTypes.string,
- unitIds: PropTypes.array,
+ unitType: PropTypes.string.isRequired,
+ units: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, StorageUnit])).isRequired,
+ onDelete: PropTypes.func,
}
export default UnitListComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
index f91202ba..b71918da 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
@@ -23,7 +23,7 @@ UnitIcon.propTypes = {
const MachineComponent = ({ position, machine, onClick }) => {
const hasNoUnits =
- machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length + machine.storageIds.length === 0
+ machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0
return (
<ListGroupItem
@@ -36,10 +36,10 @@ const MachineComponent = ({ position, machine, onClick }) => {
{position}
</Badge>
<div className="d-inline-flex">
- {machine.cpuIds.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined}
- {machine.gpuIds.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined}
- {machine.memoryIds.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined}
- {machine.storageIds.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined}
+ {machine.cpus.length > 0 ? <UnitIcon id="cpu" type="CPU" /> : undefined}
+ {machine.gpus.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined}
+ {machine.memories.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined}
+ {machine.storages.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined}
{hasNoUnits ? <Badge color="warning">Machine with no units</Badge> : undefined}
</div>
</ListGroupItem>
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js
index d0958c28..e024a417 100644
--- a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js
@@ -1,17 +1,18 @@
import PropTypes from 'prop-types'
import React from 'react'
-import EmptySlotContainer from '../../../../../containers/app/sidebars/topology/rack/EmptySlotContainer'
-import MachineContainer from '../../../../../containers/app/sidebars/topology/rack/MachineContainer'
import { machineList } from './MachineListComponent.module.scss'
+import MachineComponent from './MachineComponent'
+import { Machine } from '../../../../../shapes'
+import EmptySlotComponent from './EmptySlotComponent'
-const MachineListComponent = ({ machineIds }) => {
+const MachineListComponent = ({ machines = [], onSelect, onAdd }) => {
return (
<ul className={`list-group ${machineList}`}>
- {machineIds.map((machineId, index) => {
- if (machineId === null) {
- return <EmptySlotContainer key={index} position={index + 1} />
+ {machines.map((machine, index) => {
+ if (machine === null) {
+ return <EmptySlotComponent key={index} onAdd={() => onAdd(index + 1)} />
} else {
- return <MachineContainer key={index} position={index + 1} machineId={machineId} />
+ return <MachineComponent key={index} onClick={() => onSelect(index + 1)} machine={machine} />
}
})}
</ul>
@@ -19,7 +20,9 @@ const MachineListComponent = ({ machineIds }) => {
}
MachineListComponent.propTypes = {
- machineIds: PropTypes.array,
+ machines: PropTypes.arrayOf(Machine),
+ onSelect: PropTypes.func.isRequired,
+ onAdd: PropTypes.func.isRequired,
}
export default MachineListComponent
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js
index 00d3152f..d22317a5 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RackEnergyFillContainer.js
@@ -5,17 +5,17 @@ import RackFillBar from '../../../components/app/map/elements/RackFillBar'
const RackSpaceFillContainer = (props) => {
const state = useSelector((state) => {
let energyConsumptionTotal = 0
- const rack = state.objects.rack[state.objects.tile[props.tileId].rackId]
- const machineIds = rack.machineIds
+ const rack = state.objects.rack[state.objects.tile[props.tileId].rack]
+ const machineIds = rack.machines
machineIds.forEach((machineId) => {
if (machineId !== null) {
const machine = state.objects.machine[machineId]
- machine.cpuIds.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW))
- machine.gpuIds.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW))
- machine.memoryIds.forEach(
+ machine.cpus.forEach((id) => (energyConsumptionTotal += state.objects.cpu[id].energyConsumptionW))
+ machine.gpus.forEach((id) => (energyConsumptionTotal += state.objects.gpu[id].energyConsumptionW))
+ machine.memories.forEach(
(id) => (energyConsumptionTotal += state.objects.memory[id].energyConsumptionW)
)
- machine.storageIds.forEach(
+ machine.storages.forEach(
(id) => (energyConsumptionTotal += state.objects.storage[id].energyConsumptionW)
)
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js
index dc5119fd..8d6f61e0 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RackSpaceFillContainer.js
@@ -4,7 +4,7 @@ import RackFillBar from '../../../components/app/map/elements/RackFillBar'
const RackSpaceFillContainer = (props) => {
const state = useSelector((state) => {
- const machineIds = state.objects.rack[state.objects.tile[props.tileId].rackId].machineIds
+ const machineIds = state.objects.rack[state.objects.tile[props.tileId].rack].machines
return {
type: 'space',
fillFraction: machineIds.filter((id) => id !== null).length / machineIds.length,
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
index 52d48317..0a9e1503 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/RoomContainer.js
@@ -1,3 +1,4 @@
+import PropTypes from 'prop-types'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level'
@@ -15,4 +16,8 @@ const RoomContainer = (props) => {
return <RoomGroup {...props} {...state} onClick={() => dispatch(goFromBuildingToRoom(props.roomId))} />
}
+RoomContainer.propTypes = {
+ roomId: PropTypes.string,
+}
+
export default RoomContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
index f97e89a1..50a2abfd 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/TileContainer.js
@@ -9,7 +9,7 @@ const TileContainer = (props) => {
const dispatch = useDispatch()
const onClick = (tile) => {
- if (tile.rackId) {
+ if (tile.rack) {
dispatch(goFromRoomToRack(tile._id))
}
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js b/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js
index 2a469860..67f36396 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/WallContainer.js
@@ -4,7 +4,7 @@ import WallGroup from '../../../components/app/map/groups/WallGroup'
const WallContainer = (props) => {
const tiles = useSelector((state) =>
- state.objects.room[props.roomId].tileIds.map((tileId) => state.objects.tile[tileId])
+ state.objects.room[props.roomId].tiles.map((tileId) => state.objects.tile[tileId])
)
return <WallGroup {...props} tiles={tiles} />
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
index 8e934a01..e9a64545 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/ObjectHoverLayer.js
@@ -16,10 +16,10 @@ const ObjectHoverLayer = (props) => {
}
const currentRoom = state.objects.room[state.interactionLevel.roomId]
- const tiles = currentRoom.tileIds.map((tileId) => state.objects.tile[tileId])
+ const tiles = currentRoom.tiles.map((tileId) => state.objects.tile[tileId])
const tile = findTileWithPosition(tiles, x, y)
- return !(tile === null || tile.rackId)
+ return !(tile === null || tile.rack)
},
}
})
diff --git a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
index 1bfadb6d..4070c766 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/map/layers/RoomHoverLayer.js
@@ -23,14 +23,14 @@ const RoomHoverLayer = (props) => {
.map((id) => Object.assign({}, state.objects.room[id]))
.filter(
(room) =>
- state.objects.topology[state.currentTopologyId].roomIds.indexOf(room._id) !== -1 &&
+ state.objects.topology[state.currentTopologyId].rooms.indexOf(room._id) !== -1 &&
room._id !== state.construction.currentRoomInConstruction
)
;[...oldRooms, newRoom].forEach((room) => {
- room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId])
+ room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId])
})
- if (newRoom.tileIds.length === 0) {
+ if (newRoom.tiles.length === 0) {
return findPositionInRooms(oldRooms, x, y) === -1
}
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
index 62761583..fd55582f 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/ScenarioListContainer.js
@@ -15,7 +15,7 @@ const ScenarioListContainer = ({ portfolioId }) => {
.map((res) => res.data)
const topologies = useTopologies(project?.topologyIds ?? [])
.filter((res) => res.data)
- .map((res) => res.data)
+ .map((res) => ({ _id: res.data._id, name: res.data.name }))
const traces = useTraces().data ?? []
const schedulers = useSchedulers().data ?? []
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
index 78db166c..55eab23a 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/project/TopologyListContainer.js
@@ -16,7 +16,7 @@ const TopologyListContainer = () => {
const { data: currentProject } = useProject(currentProjectId)
const topologies = useTopologies(currentProject?.topologyIds ?? [])
.filter((res) => res.data)
- .map((res) => res.data)
+ .map((res) => ({ _id: res.data._id, name: res.data.name }))
const currentTopologyId = useActiveTopology()?._id
const [isVisible, setVisible] = useState(false)
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
index cb7ec8f9..7553c2fe 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/MachineSidebarContainer.js
@@ -5,7 +5,7 @@ import MachineSidebarComponent from '../../../../../components/app/sidebars/topo
const MachineSidebarContainer = (props) => {
const machineId = useSelector(
(state) =>
- state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[
+ state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].machines[
state.interactionLevel.position - 1
]
)
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js
deleted file mode 100644
index acb16a21..00000000
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitContainer.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { deleteUnit } from '../../../../../redux/actions/topology/machine'
-import UnitComponent from '../../../../../components/app/sidebars/topology/machine/UnitComponent'
-
-const UnitContainer = ({ unitId, unitType }) => {
- const dispatch = useDispatch()
- const unit = useSelector((state) => state.objects[unitType][unitId])
- const onDelete = () => dispatch(deleteUnit(unitType, unitId))
-
- return <UnitComponent index={unitId} unit={unit} unitType={unitType} onDelete={onDelete} />
-}
-
-export default UnitContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js
index c5c9444d..cdd7e268 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/machine/UnitListContainer.js
@@ -1,17 +1,34 @@
+import PropTypes from 'prop-types'
import React from 'react'
-import { useSelector } from 'react-redux'
+import { useDispatch, useSelector } from 'react-redux'
import UnitListComponent from '../../../../../components/app/sidebars/topology/machine/UnitListComponent'
+import { deleteUnit } from '../../../../../redux/actions/topology/machine'
-const UnitListContainer = (props) => {
- const unitIds = useSelector(
- (state) =>
+const unitMapping = {
+ cpu: 'cpus',
+ gpu: 'gpus',
+ memory: 'memories',
+ storage: 'storages',
+}
+
+const UnitListContainer = ({ unitType, ...props }) => {
+ const dispatch = useDispatch()
+ const units = useSelector((state) => {
+ const machine =
state.objects.machine[
- state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds[
+ state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].machines[
state.interactionLevel.position - 1
]
- ][props.unitType + 'Ids']
- )
- return <UnitListComponent {...props} unitIds={unitIds} />
+ ]
+ return machine[unitMapping[unitType]].map((id) => state.objects[unitType][id])
+ })
+ const onDelete = (unit, unitType) => dispatch(deleteUnit(unitType, unit._id))
+
+ return <UnitListComponent {...props} units={units} unitType={unitType} onDelete={onDelete} />
+}
+
+UnitListContainer.propTypes = {
+ unitType: PropTypes.string.isRequired,
}
export default UnitListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
deleted file mode 100644
index 2134e411..00000000
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/EmptySlotContainer.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-import { useDispatch } from 'react-redux'
-import { addMachine } from '../../../../../redux/actions/topology/rack'
-import EmptySlotComponent from '../../../../../components/app/sidebars/topology/rack/EmptySlotComponent'
-
-const EmptySlotContainer = (props) => {
- const dispatch = useDispatch()
- const onAdd = () => dispatch(addMachine(props.position))
- return <EmptySlotComponent {...props} onAdd={onAdd} />
-}
-
-export default EmptySlotContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js
deleted file mode 100644
index 7d8e32c1..00000000
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineContainer.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react'
-import { useDispatch, useSelector } from 'react-redux'
-import { goFromRackToMachine } from '../../../../../redux/actions/interaction-level'
-import MachineComponent from '../../../../../components/app/sidebars/topology/rack/MachineComponent'
-
-const MachineContainer = (props) => {
- const machine = useSelector((state) => state.objects.machine[props.machineId])
- const dispatch = useDispatch()
- return (
- <MachineComponent {...props} onClick={() => dispatch(goFromRackToMachine(props.position))} machine={machine} />
- )
-}
-
-export default MachineContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js
index b45300fc..2118d915 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/MachineListContainer.js
@@ -1,12 +1,29 @@
-import React from 'react'
-import { useSelector } from 'react-redux'
+import React, { useMemo } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
import MachineListComponent from '../../../../../components/app/sidebars/topology/rack/MachineListComponent'
+import { goFromRackToMachine } from '../../../../../redux/actions/interaction-level'
+import { addMachine } from '../../../../../redux/actions/topology/rack'
const MachineListContainer = (props) => {
- const machineIds = useSelector(
- (state) => state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].machineIds
+ const rack = useSelector((state) => state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack])
+ const machines = useSelector((state) => rack.machines.map((id) => state.objects.machine[id]))
+ const machinesNull = useMemo(() => {
+ const res = Array(rack.capacity).fill(null)
+ for (const machine of machines) {
+ res[machine.position - 1] = machine
+ }
+ return res
+ }, [rack, machines])
+ const dispatch = useDispatch()
+
+ return (
+ <MachineListComponent
+ {...props}
+ machines={machinesNull}
+ onAdd={(index) => dispatch(addMachine(index))}
+ onSelect={(index) => dispatch(goFromRackToMachine(index))}
+ />
)
- return <MachineListComponent {...props} machineIds={machineIds} />
}
export default MachineListContainer
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
index eaa1e78e..2c39cf9f 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackNameContainer.js
@@ -7,7 +7,7 @@ import { editRackName } from '../../../../../redux/actions/topology/rack'
const RackNameContainer = () => {
const [isVisible, setVisible] = useState(false)
const rackName = useSelector(
- (state) => state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rackId].name
+ (state) => state.objects.rack[state.objects.tile[state.interactionLevel.tileId].rack].name
)
const dispatch = useDispatch()
const callback = (name) => {
diff --git a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
index b8fc3bfb..34777125 100644
--- a/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
+++ b/opendc-web/opendc-web-ui/src/containers/app/sidebars/topology/rack/RackSidebarContainer.js
@@ -3,7 +3,7 @@ import { useSelector } from 'react-redux'
import RackSidebarComponent from '../../../../../components/app/sidebars/topology/rack/RackSidebarComponent'
const RackSidebarContainer = (props) => {
- const rackId = useSelector((state) => state.objects.tile[state.interactionLevel.tileId].rackId)
+ const rackId = useSelector((state) => state.objects.tile[state.interactionLevel.tileId].rack)
return <RackSidebarComponent {...props} rackId={rackId} />
}
diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js
index 8e4680c0..51d8d3e0 100644
--- a/opendc-web/opendc-web-ui/src/pages/_document.js
+++ b/opendc-web/opendc-web-ui/src/pages/_document.js
@@ -25,7 +25,7 @@ import Document, { Html, Head, Main, NextScript } from 'next/document'
class OpenDCDocument extends Document {
render() {
return (
- <Html>
+ <Html lang="en">
<Head>
<meta charSet="utf-8" />
<meta name="theme-color" content="#00A6D6" />
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 e19a7f21..529e8663 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topologies.js
@@ -1,4 +1,5 @@
export const ADD_TOPOLOGY = 'ADD_TOPOLOGY'
+export const STORE_TOPOLOGY = 'STORE_TOPOLOGY'
export function addTopology(projectId, name, duplicateId) {
return {
@@ -8,3 +9,10 @@ export function addTopology(projectId, name, duplicateId) {
duplicateId,
}
}
+
+export function storeTopology(entities) {
+ return {
+ type: STORE_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 72deda6f..f1a7d569 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
@@ -32,7 +32,7 @@ export function startNewRoomConstructionSucceeded(roomId) {
export function finishNewRoomConstruction() {
return (dispatch, getState) => {
const { objects, construction } = getState()
- if (objects.room[construction.currentRoomInConstruction].tileIds.length === 0) {
+ if (objects.room[construction.currentRoomInConstruction].tiles.length === 0) {
dispatch(cancelNewRoomConstruction())
return
}
@@ -75,13 +75,10 @@ export function toggleTileAtLocation(positionX, positionY) {
return (dispatch, getState) => {
const { objects, construction } = getState()
- const tileIds = objects.room[construction.currentRoomInConstruction].tileIds
- for (let index in tileIds) {
- if (
- objects.tile[tileIds[index]].positionX === positionX &&
- objects.tile[tileIds[index]].positionY === positionY
- ) {
- dispatch(deleteTile(tileIds[index]))
+ const tileIds = objects.room[construction.currentRoomInConstruction].tiles
+ for (const tileId of tileIds) {
+ if (objects.tile[tileId].positionX === positionX && objects.tile[tileId].positionY === positionY) {
+ dispatch(deleteTile(tileId))
return
}
}
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 61eea7fe..80ef7c5e 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
@@ -29,7 +29,7 @@ export function addRackToTile(positionX, positionY) {
return (dispatch, getState) => {
const { objects, interactionLevel } = getState()
const currentRoom = objects.room[interactionLevel.roomId]
- const tiles = currentRoom.tileIds.map((tileId) => objects.tile[tileId])
+ const tiles = currentRoom.tiles.map((tileId) => objects.tile[tileId])
const tile = findTileWithPosition(tiles, positionX, positionY)
if (tile !== null) {
diff --git a/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js
index bee6becd..c2fc5004 100644
--- a/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js
+++ b/opendc-web/opendc-web-ui/src/redux/middleware/viewport-adjustment.js
@@ -23,9 +23,9 @@ export const viewportAdjustmentMiddleware = (store) => (next) => (action) => {
}
if (topologyId && topologyId !== '-1') {
- const roomIds = state.objects.topology[topologyId].roomIds
+ const roomIds = state.objects.topology[topologyId].rooms
const rooms = roomIds.map((id) => Object.assign({}, state.objects.room[id]))
- rooms.forEach((room) => (room.tiles = room.tileIds.map((tileId) => state.objects.tile[tileId])))
+ rooms.forEach((room) => (room.tiles = room.tiles.map((tileId) => state.objects.tile[tileId])))
let hasNoTiles = true
for (let i in rooms) {
diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/objects.js b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
index f8f577ac..11f6d353 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/objects.js
@@ -6,11 +6,9 @@ import {
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({
- project: object('project'),
- user: object('user'),
- authorization: objectWithId('authorization', (object) => [object.userId, object.projectId]),
cpu: object('cpu', CPU_UNITS),
gpu: object('gpu', GPU_UNITS),
memory: object('memory', MEMORY_UNITS),
@@ -20,8 +18,6 @@ export const objects = combineReducers({
tile: object('tile'),
room: object('room'),
topology: object('topology'),
- portfolio: object('portfolio'),
- scenario: object('scenario'),
prefab: object('prefab'),
})
@@ -31,18 +27,16 @@ function object(type, defaultState = {}) {
function objectWithId(type, getId, defaultState = {}) {
return (state = defaultState, action) => {
- if (action.objectType !== type) {
+ if (action.type === STORE_TOPOLOGY) {
+ return { ...state, ...action.entities[type] }
+ } else if (action.objectType !== type) {
return state
}
if (action.type === ADD_TO_STORE) {
- return Object.assign({}, state, {
- [getId(action.object)]: action.object,
- })
+ return { ...state, [getId(action.object)]: action.object }
} else if (action.type === ADD_PROP_TO_STORE_OBJECT) {
- return Object.assign({}, state, {
- [action.objectId]: Object.assign({}, state[action.objectId], action.propObject),
- })
+ 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], {
diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js
index 99082df0..9b4f8094 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/objects.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/objects.js
@@ -1,124 +1,27 @@
import { call, put, select, getContext } from 'redux-saga/effects'
-import { addToStore } from '../actions/objects'
import { fetchTopology, updateTopology } from '../../api/topologies'
-import { uuid } from 'uuidv4'
-
-export const OBJECT_SELECTORS = {
- user: (state) => state.objects.user,
- authorization: (state) => state.objects.authorization,
- portfolio: (state) => state.objects.portfolio,
- scenario: (state) => state.objects.scenario,
- cpu: (state) => state.objects.cpu,
- gpu: (state) => state.objects.gpu,
- memory: (state) => state.objects.memory,
- storage: (state) => state.objects.storage,
- machine: (state) => state.objects.machine,
- rack: (state) => state.objects.rack,
- tile: (state) => state.objects.tile,
- room: (state) => state.objects.room,
- topology: (state) => state.objects.topology,
-}
+import { Topology } from '../../util/topology-schema'
+import { denormalize, normalize } from 'normalizr'
+import { storeTopology } from '../actions/topologies'
/**
* Fetches and normalizes the topology with the specified identifier.
*/
export const fetchAndStoreTopology = function* (id) {
- const topologyStore = yield select(OBJECT_SELECTORS['topology'])
- const roomStore = yield select(OBJECT_SELECTORS['room'])
- const tileStore = yield select(OBJECT_SELECTORS['tile'])
- const rackStore = yield select(OBJECT_SELECTORS['rack'])
- const machineStore = yield select(OBJECT_SELECTORS['machine'])
const auth = yield getContext('auth')
- let topology = topologyStore[id]
+ let topology = yield select((state) => state.objects.topology[id])
if (!topology) {
- const fullTopology = yield call(fetchTopology, auth, id)
-
- for (let roomIdx in fullTopology.rooms) {
- const fullRoom = fullTopology.rooms[roomIdx]
-
- generateIdIfNotPresent(fullRoom)
-
- if (!roomStore[fullRoom._id]) {
- for (let tileIdx in fullRoom.tiles) {
- const fullTile = fullRoom.tiles[tileIdx]
-
- generateIdIfNotPresent(fullTile)
-
- if (!tileStore[fullTile._id]) {
- if (fullTile.rack) {
- const fullRack = fullTile.rack
-
- generateIdIfNotPresent(fullRack)
-
- if (!rackStore[fullRack._id]) {
- for (let machineIdx in fullRack.machines) {
- const fullMachine = fullRack.machines[machineIdx]
-
- generateIdIfNotPresent(fullMachine)
-
- if (!machineStore[fullMachine._id]) {
- let machine = (({ _id, position, cpus, gpus, memories, storages }) => ({
- _id,
- rackId: fullRack._id,
- position,
- cpuIds: cpus.map((u) => u._id),
- gpuIds: gpus.map((u) => u._id),
- memoryIds: memories.map((u) => u._id),
- storageIds: storages.map((u) => u._id),
- }))(fullMachine)
- yield put(addToStore('machine', machine))
- }
- }
-
- const filledSlots = new Array(fullRack.capacity).fill(null)
- fullRack.machines.forEach(
- (machine) => (filledSlots[machine.position - 1] = machine._id)
- )
- let rack = (({ _id, name, capacity, powerCapacityW }) => ({
- _id,
- name,
- capacity,
- powerCapacityW,
- machineIds: filledSlots,
- }))(fullRack)
- yield put(addToStore('rack', rack))
- }
- }
-
- let tile = (({ _id, positionX, positionY, rack }) => ({
- _id,
- roomId: fullRoom._id,
- positionX,
- positionY,
- rackId: rack ? rack._id : undefined,
- }))(fullTile)
- yield put(addToStore('tile', tile))
- }
- }
-
- let room = (({ _id, name, tiles }) => ({ _id, name, tileIds: tiles.map((t) => t._id) }))(fullRoom)
- yield put(addToStore('room', room))
- }
- }
-
- topology = (({ _id, name, rooms }) => ({ _id, name, roomIds: rooms.map((r) => r._id) }))(fullTopology)
- yield put(addToStore('topology', topology))
-
- // TODO consider pushing the IDs
+ const newTopology = yield call(fetchTopology, auth, id)
+ const { entities } = normalize(newTopology, Topology)
+ yield put(storeTopology(entities))
}
return topology
}
-const generateIdIfNotPresent = (obj) => {
- if (!obj._id) {
- obj._id = uuid()
- }
-}
-
export const updateTopologyOnServer = function* (id) {
- const topology = yield denormalizeTopology(id, true)
+ const topology = yield denormalizeTopology(id)
const auth = yield getContext('auth')
yield call(updateTopology, auth, topology)
}
@@ -126,73 +29,8 @@ export const updateTopologyOnServer = function* (id) {
/**
* Denormalizes the topology representation in order to be stored on the server.
*/
-export const denormalizeTopology = function* (id, keepIds) {
- const topologyStore = yield select(OBJECT_SELECTORS['topology'])
- const rooms = yield getAllRooms(topologyStore[id].roomIds, keepIds)
- return {
- _id: keepIds ? id : undefined,
- name: topologyStore[id].name,
- rooms: rooms,
- }
-}
-
-export const getAllRooms = function* (roomIds, keepIds) {
- const roomStore = yield select(OBJECT_SELECTORS['room'])
-
- let rooms = []
-
- for (let id of roomIds) {
- let tiles = yield getAllRoomTiles(roomStore[id], keepIds)
- rooms.push({
- _id: keepIds ? id : undefined,
- name: roomStore[id].name,
- tiles: tiles,
- })
- }
- return rooms
-}
-
-export const getAllRoomTiles = function* (roomStore, keepIds) {
- let tiles = []
-
- for (let id of roomStore.tileIds) {
- tiles.push(yield getTileById(id, keepIds))
- }
- return tiles
-}
-
-export const getTileById = function* (id, keepIds) {
- const tileStore = yield select(OBJECT_SELECTORS['tile'])
- return {
- _id: keepIds ? id : undefined,
- positionX: tileStore[id].positionX,
- positionY: tileStore[id].positionY,
- rack: !tileStore[id].rackId ? undefined : yield getRackById(tileStore[id].rackId, keepIds),
- }
-}
-
-export const getRackById = function* (id, keepIds) {
- const rackStore = yield select(OBJECT_SELECTORS['rack'])
- const machineStore = yield select(OBJECT_SELECTORS['machine'])
- const cpuStore = yield select(OBJECT_SELECTORS['cpu'])
- const gpuStore = yield select(OBJECT_SELECTORS['gpu'])
- const memoryStore = yield select(OBJECT_SELECTORS['memory'])
- const storageStore = yield select(OBJECT_SELECTORS['storage'])
-
- return {
- _id: keepIds ? rackStore[id]._id : undefined,
- name: rackStore[id].name,
- capacity: rackStore[id].capacity,
- powerCapacityW: rackStore[id].powerCapacityW,
- machines: rackStore[id].machineIds
- .filter((m) => m !== null)
- .map((machineId) => ({
- _id: keepIds ? machineId : undefined,
- position: machineStore[machineId].position,
- cpus: machineStore[machineId].cpuIds.map((id) => cpuStore[id]),
- gpus: machineStore[machineId].gpuIds.map((id) => gpuStore[id]),
- memories: machineStore[machineId].memoryIds.map((id) => memoryStore[id]),
- storages: machineStore[machineId].storageIds.map((id) => storageStore[id]),
- })),
- }
+export const denormalizeTopology = function* (id) {
+ const objects = yield select((state) => state.objects)
+ const topology = objects.topology[id]
+ return denormalize(topology, Topology, objects)
}
diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
index ec679391..91b03bf6 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/prefabs.js
@@ -1,14 +1,17 @@
import { call, put, select, getContext } from 'redux-saga/effects'
import { addToStore } from '../actions/objects'
import { addPrefab } from '../../api/prefabs'
-import { getRackById } from './objects'
+import { Rack } from '../../util/topology-schema'
+import { denormalize } from 'normalizr'
export function* onAddPrefab(action) {
try {
- const currentRackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId)
- const currentRackJson = yield getRackById(currentRackId, false)
+ const interactionLevel = yield select((state) => state.interactionLevel)
+ const objects = yield select((state) => state.objects)
+ const rack = objects.rack[objects.tile[interactionLevel.tileId].rack]
+ const prefabRack = denormalize(rack, Rack, objects)
const auth = yield getContext('auth')
- const prefab = yield call(addPrefab, auth, { name: action.name, rack: currentRackJson })
+ const prefab = yield call(() => addPrefab(auth, { name: action.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 0ed40131..5d9154fd 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
@@ -25,8 +25,8 @@ export function* fetchAndStoreAllTopologiesOfProject(projectId, setTopology = fa
const queryClient = yield getContext('queryClient')
const project = yield call(() => queryClient.fetchQuery(['projects', projectId]))
- for (let i in project.topologyIds) {
- yield fetchAndStoreTopology(project.topologyIds[i])
+ for (const id of project.topologyIds) {
+ yield fetchAndStoreTopology(id)
}
if (setTopology) {
@@ -43,8 +43,9 @@ export function* onAddTopology(action) {
let topologyToBeCreated
if (duplicateId) {
- topologyToBeCreated = yield denormalizeTopology(duplicateId, false)
+ topologyToBeCreated = yield denormalizeTopology(duplicateId)
topologyToBeCreated = { ...topologyToBeCreated, name }
+ delete topologyToBeCreated._id
} else {
topologyToBeCreated = { name: action.name, rooms: [] }
}
@@ -65,10 +66,10 @@ export function* onStartNewRoomConstruction() {
_id: uuid(),
name: 'Room',
topologyId,
- tileIds: [],
+ tiles: [],
}
yield put(addToStore('room', room))
- yield put(addIdToStoreObjectListProp('topology', topologyId, 'roomIds', room._id))
+ yield put(addIdToStoreObjectListProp('topology', topologyId, 'rooms', room._id))
yield updateTopologyOnServer(topologyId)
yield put(startNewRoomConstructionSucceeded(room._id))
} catch (error) {
@@ -80,7 +81,7 @@ export function* onCancelNewRoomConstruction() {
try {
const topologyId = yield select((state) => state.currentTopologyId)
const roomId = yield select((state) => state.construction.currentRoomInConstruction)
- yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId))
+ yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId))
// TODO remove room from store, too
yield updateTopologyOnServer(topologyId)
yield put(cancelNewRoomConstructionSucceeded())
@@ -100,7 +101,7 @@ export function* onAddTile(action) {
positionY: action.positionY,
}
yield put(addToStore('tile', tile))
- yield put(addIdToStoreObjectListProp('room', roomId, 'tileIds', tile._id))
+ yield put(addIdToStoreObjectListProp('room', roomId, 'tiles', tile._id))
yield updateTopologyOnServer(topologyId)
} catch (error) {
console.error(error)
@@ -111,7 +112,7 @@ export function* onDeleteTile(action) {
try {
const topologyId = yield select((state) => state.currentTopologyId)
const roomId = yield select((state) => state.construction.currentRoomInConstruction)
- yield put(removeIdFromStoreObjectListProp('room', roomId, 'tileIds', action.tileId))
+ yield put(removeIdFromStoreObjectListProp('room', roomId, 'tiles', action.tileId))
yield updateTopologyOnServer(topologyId)
} catch (error) {
console.error(error)
@@ -136,7 +137,7 @@ export function* onDeleteRoom() {
const topologyId = yield select((state) => state.currentTopologyId)
const roomId = yield select((state) => state.interactionLevel.roomId)
yield put(goDownOneInteractionLevel())
- yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'roomIds', roomId))
+ yield put(removeIdFromStoreObjectListProp('topology', topologyId, 'rooms', roomId))
yield updateTopologyOnServer(topologyId)
} catch (error) {
console.error(error)
@@ -146,7 +147,7 @@ export function* onDeleteRoom() {
export function* onEditRackName(action) {
try {
const topologyId = yield select((state) => state.currentTopologyId)
- const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId)
+ const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rack)
const rack = Object.assign({}, yield select((state) => state.objects.rack[rackId]))
rack.name = action.name
yield put(addPropToStoreObject('rack', rackId, { name: action.name }))
@@ -161,7 +162,7 @@ export function* onDeleteRack() {
const topologyId = yield select((state) => state.currentTopologyId)
const tileId = yield select((state) => state.interactionLevel.tileId)
yield put(goDownOneInteractionLevel())
- yield put(addPropToStoreObject('tile', tileId, { rackId: undefined }))
+ yield put(addPropToStoreObject('tile', tileId, { rack: undefined }))
yield updateTopologyOnServer(topologyId)
} catch (error) {
console.error(error)
@@ -176,10 +177,10 @@ export function* onAddRackToTile(action) {
name: 'Rack',
capacity: DEFAULT_RACK_SLOT_CAPACITY,
powerCapacityW: DEFAULT_RACK_POWER_CAPACITY,
+ machines: [],
}
- rack.machineIds = new Array(rack.capacity).fill(null)
yield put(addToStore('rack', rack))
- yield put(addPropToStoreObject('tile', action.tileId, { rackId: rack._id }))
+ yield put(addPropToStoreObject('tile', action.tileId, { rack: rack._id }))
yield updateTopologyOnServer(topologyId)
} catch (error) {
console.error(error)
@@ -189,23 +190,21 @@ export function* onAddRackToTile(action) {
export function* onAddMachine(action) {
try {
const topologyId = yield select((state) => state.currentTopologyId)
- const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rackId)
+ const rackId = yield select((state) => state.objects.tile[state.interactionLevel.tileId].rack)
const rack = yield select((state) => state.objects.rack[rackId])
const machine = {
_id: uuid(),
- rackId,
position: action.position,
- cpuIds: [],
- gpuIds: [],
- memoryIds: [],
- storageIds: [],
+ cpus: [],
+ gpus: [],
+ memories: [],
+ storages: [],
}
yield put(addToStore('machine', machine))
- const machineIds = [...rack.machineIds]
- machineIds[machine.position - 1] = machine._id
- yield put(addPropToStoreObject('rack', rackId, { machineIds }))
+ const machineIds = [...rack.machines, machine._id]
+ yield put(addPropToStoreObject('rack', rackId, { machines: machineIds }))
yield updateTopologyOnServer(topologyId)
} catch (error) {
console.error(error)
@@ -217,35 +216,41 @@ export function* onDeleteMachine() {
const topologyId = yield select((state) => state.currentTopologyId)
const tileId = yield select((state) => state.interactionLevel.tileId)
const position = yield select((state) => state.interactionLevel.position)
- const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rackId])
- const machineIds = [...rack.machineIds]
- machineIds[position - 1] = null
+ const rack = yield select((state) => state.objects.rack[state.objects.tile[tileId].rack])
yield put(goDownOneInteractionLevel())
- yield put(addPropToStoreObject('rack', rack._id, { machineIds }))
+ yield put(
+ addPropToStoreObject('rack', rack._id, { 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(action) {
try {
const topologyId = yield select((state) => state.currentTopologyId)
const tileId = yield select((state) => state.interactionLevel.tileId)
const position = yield select((state) => state.interactionLevel.position)
const machine = yield select(
- (state) =>
- state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]]
+ (state) => state.objects.machine[state.objects.rack[state.objects.tile[tileId].rack].machines[position - 1]]
)
- if (machine[action.unitType + 'Ids'].length >= MAX_NUM_UNITS_PER_MACHINE) {
+ if (machine[unitMapping[action.unitType]].length >= MAX_NUM_UNITS_PER_MACHINE) {
return
}
- const units = [...machine[action.unitType + 'Ids'], action.id]
+ const units = [...machine[unitMapping[action.unitType]], action.id]
yield put(
addPropToStoreObject('machine', machine._id, {
- [action.unitType + 'Ids']: units,
+ [unitMapping[action.unitType]]: units,
})
)
yield updateTopologyOnServer(topologyId)
@@ -260,15 +265,14 @@ export function* onDeleteUnit(action) {
const tileId = yield select((state) => state.interactionLevel.tileId)
const position = yield select((state) => state.interactionLevel.position)
const machine = yield select(
- (state) =>
- state.objects.machine[state.objects.rack[state.objects.tile[tileId].rackId].machineIds[position - 1]]
+ (state) => state.objects.machine[state.objects.rack[state.objects.tile[tileId].rack].machines[position - 1]]
)
- const unitIds = machine[action.unitType + 'Ids'].slice()
+ const unitIds = machine[unitMapping[action.unitType]].slice()
unitIds.splice(action.index, 1)
yield put(
addPropToStoreObject('machine', machine._id, {
- [action.unitType + 'Ids']: unitIds,
+ [unitMapping[action.unitType]]: unitIds,
})
)
yield updateTopologyOnServer(topologyId)
diff --git a/opendc-web/opendc-web-ui/src/shapes.js b/opendc-web/opendc-web-ui/src/shapes.js
index 6c29eab0..3c27ad11 100644
--- a/opendc-web/opendc-web-ui/src/shapes.js
+++ b/opendc-web/opendc-web-ui/src/shapes.js
@@ -40,14 +40,6 @@ export const Project = PropTypes.shape({
portfolioIds: PropTypes.array.isRequired,
})
-export const Authorization = PropTypes.shape({
- userId: PropTypes.string.isRequired,
- user: User,
- projectId: PropTypes.string.isRequired,
- project: Project,
- level: PropTypes.string.isRequired,
-})
-
export const ProcessingUnit = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
@@ -66,44 +58,37 @@ export const StorageUnit = PropTypes.shape({
export const Machine = PropTypes.shape({
_id: PropTypes.string.isRequired,
- rackId: PropTypes.string.isRequired,
position: PropTypes.number.isRequired,
- cpuIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- cpus: PropTypes.arrayOf(ProcessingUnit),
- gpuIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- gpus: PropTypes.arrayOf(ProcessingUnit),
- memoryIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- memories: PropTypes.arrayOf(StorageUnit),
- storageIds: PropTypes.arrayOf(PropTypes.string.isRequired),
- storages: PropTypes.arrayOf(StorageUnit),
+ cpus: PropTypes.arrayOf(PropTypes.string),
+ gpus: PropTypes.arrayOf(PropTypes.string),
+ memories: PropTypes.arrayOf(PropTypes.string),
+ storages: PropTypes.arrayOf(PropTypes.string),
})
export const Rack = PropTypes.shape({
_id: PropTypes.string.isRequired,
capacity: PropTypes.number.isRequired,
powerCapacityW: PropTypes.number.isRequired,
- machines: PropTypes.arrayOf(Machine),
+ machines: PropTypes.arrayOf(PropTypes.string),
})
export const Tile = PropTypes.shape({
_id: PropTypes.string.isRequired,
- roomId: PropTypes.string.isRequired,
positionX: PropTypes.number.isRequired,
positionY: PropTypes.number.isRequired,
- rackId: PropTypes.string,
- rack: Rack,
+ rack: PropTypes.string,
})
export const Room = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- tiles: PropTypes.arrayOf(Tile),
+ tiles: PropTypes.arrayOf(PropTypes.string),
})
export const Topology = PropTypes.shape({
_id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- rooms: PropTypes.arrayOf(Room),
+ rooms: PropTypes.arrayOf(PropTypes.string),
})
export const Scheduler = PropTypes.shape({
diff --git a/opendc-web/opendc-web-ui/src/util/topology-schema.js b/opendc-web/opendc-web-ui/src/util/topology-schema.js
new file mode 100644
index 00000000..9acd688b
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/util/topology-schema.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 { 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' })
+
+export const Machine = new schema.Entity(
+ 'machine',
+ {
+ cpus: [Cpu],
+ gpus: [Gpu],
+ memories: [Memory],
+ storages: [Storage],
+ },
+ { idAttribute: '_id' }
+)
+
+export const Rack = new schema.Entity('rack', { machines: [Machine] }, { idAttribute: '_id' })
+
+export const Tile = new schema.Entity('tile', { rack: Rack }, { idAttribute: '_id' })
+
+export const Room = new schema.Entity('room', { tiles: [Tile] }, { idAttribute: '_id' })
+
+export const Topology = new schema.Entity('topology', { rooms: [Room] }, { idAttribute: '_id' })
diff --git a/opendc-web/opendc-web-ui/yarn.lock b/opendc-web/opendc-web-ui/yarn.lock
index 630f38db..bf4c419e 100644
--- a/opendc-web/opendc-web-ui/yarn.lock
+++ b/opendc-web/opendc-web-ui/yarn.lock
@@ -2763,6 +2763,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+normalizr@^3.6.1:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/normalizr/-/normalizr-3.6.1.tgz#d367ab840e031ff382141b8d81ce279292ff69fe"
+ integrity sha512-8iEmqXmPtll8PwbEFrbPoDxVw7MKnNvt3PZzR2Xvq9nggEEOgBlNICPXYzyZ4w4AkHUzCU998mdatER3n2VaMA==
+
npm-run-path@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"