summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components/app/sidebars/topology
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-04-25 16:01:14 +0200
committerFabian Mastenbroek <mail.fabianm@gmail.com>2021-04-25 16:01:14 +0200
commitcd0b45627f0d8da8c8dc4edde223f3c36e9bcfbf (patch)
tree6ae1681630a0e270c23804e6dbb3bd414ebe5d6e /opendc-web/opendc-web-ui/src/components/app/sidebars/topology
parent128a1db017545597a5c035b7960eb3fd36b5f987 (diff)
build: Migrate to flat project structure
This change updates the project structure to become flattened. Previously, the simulator, frontend and API each lived into their own directory. With this change, all modules of the project live in the top-level directory of the repository. This should improve discoverability of modules of the project.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components/app/sidebars/topology')
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js13
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js31
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js13
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js26
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js5
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js18
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js35
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js52
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js20
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js78
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js13
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js43
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js20
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass2
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js25
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass11
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js10
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js27
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js20
28 files changed, 556 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js
new file mode 100644
index 00000000..5fb0dc55
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/NameComponent.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import FontAwesome from 'react-fontawesome'
+
+const NameComponent = ({ name, onEdit }) => (
+ <h2>
+ {name}
+ <button className="btn btn-outline-secondary float-right" onClick={onEdit}>
+ <FontAwesome name="pencil" />
+ </button>
+ </h2>
+)
+
+export default NameComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js
new file mode 100644
index 00000000..f5eee36b
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/TopologySidebarComponent.js
@@ -0,0 +1,31 @@
+import React from 'react'
+import BuildingSidebarContainer from '../../../../containers/app/sidebars/topology/building/BuildingSidebarContainer'
+import MachineSidebarContainer from '../../../../containers/app/sidebars/topology/machine/MachineSidebarContainer'
+import RackSidebarContainer from '../../../../containers/app/sidebars/topology/rack/RackSidebarContainer'
+import RoomSidebarContainer from '../../../../containers/app/sidebars/topology/room/RoomSidebarContainer'
+import Sidebar from '../Sidebar'
+
+const TopologySidebarComponent = ({ interactionLevel }) => {
+ let sidebarContent
+
+ switch (interactionLevel.mode) {
+ case 'BUILDING':
+ sidebarContent = <BuildingSidebarContainer />
+ break
+ case 'ROOM':
+ sidebarContent = <RoomSidebarContainer />
+ break
+ case 'RACK':
+ sidebarContent = <RackSidebarContainer />
+ break
+ case 'MACHINE':
+ sidebarContent = <MachineSidebarContainer />
+ break
+ default:
+ sidebarContent = 'Missing Content'
+ }
+
+ return <Sidebar isRight={true}>{sidebarContent}</Sidebar>
+}
+
+export default TopologySidebarComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js
new file mode 100644
index 00000000..eea62f84
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/BuildingSidebarComponent.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import NewRoomConstructionContainer from '../../../../../containers/app/sidebars/topology/building/NewRoomConstructionContainer'
+
+const BuildingSidebarComponent = () => {
+ return (
+ <div>
+ <h2>Building</h2>
+ <NewRoomConstructionContainer />
+ </div>
+ )
+}
+
+export default BuildingSidebarComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js
new file mode 100644
index 00000000..fd552c1e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/building/NewRoomConstructionComponent.js
@@ -0,0 +1,26 @@
+import React from 'react'
+
+const NewRoomConstructionComponent = ({ onStart, onFinish, onCancel, currentRoomInConstruction }) => {
+ if (currentRoomInConstruction === '-1') {
+ return (
+ <div className="btn btn-outline-primary btn-block" onClick={onStart}>
+ <span className="fa fa-plus mr-2" />
+ Construct a new room
+ </div>
+ )
+ }
+ return (
+ <div>
+ <div className="btn btn-primary btn-block" onClick={onFinish}>
+ <span className="fa fa-check mr-2" />
+ Finalize new room
+ </div>
+ <div className="btn btn-default btn-block" onClick={onCancel}>
+ <span className="fa fa-times mr-2" />
+ Cancel construction
+ </div>
+ </div>
+ )
+}
+
+export default NewRoomConstructionComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js
new file mode 100644
index 00000000..70d522b2
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/BackToRackComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const BackToRackComponent = ({ onClick }) => (
+ <div className="btn btn-secondary btn-block" onClick={onClick}>
+ <span className="fa fa-angle-left mr-2" />
+ Back to rack
+ </div>
+)
+
+export default BackToRackComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js
new file mode 100644
index 00000000..37820316
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/DeleteMachineComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const DeleteMachineComponent = ({ onClick }) => (
+ <div className="btn btn-outline-danger btn-block" onClick={onClick}>
+ <span className="fa fa-trash mr-2" />
+ Delete this machine
+ </div>
+)
+
+export default DeleteMachineComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js
new file mode 100644
index 00000000..992383c4
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineNameComponent.js
@@ -0,0 +1,5 @@
+import React from 'react'
+
+const MachineNameComponent = ({ position }) => <h2>Machine at slot {position}</h2>
+
+export default MachineNameComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js
new file mode 100644
index 00000000..7c78cf9e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/MachineSidebarComponent.js
@@ -0,0 +1,18 @@
+import React from 'react'
+import BackToRackContainer from '../../../../../containers/app/sidebars/topology/machine/BackToRackContainer'
+import DeleteMachineContainer from '../../../../../containers/app/sidebars/topology/machine/DeleteMachineContainer'
+import MachineNameContainer from '../../../../../containers/app/sidebars/topology/machine/MachineNameContainer'
+import UnitTabsContainer from '../../../../../containers/app/sidebars/topology/machine/UnitTabsContainer'
+
+const MachineSidebarComponent = ({ machineId }) => {
+ return (
+ <div className="h-100 overflow-auto">
+ <MachineNameContainer />
+ <BackToRackContainer />
+ <DeleteMachineContainer />
+ <UnitTabsContainer />
+ </div>
+ )
+}
+
+export default MachineSidebarComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js
new file mode 100644
index 00000000..4e9dbc7e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitAddComponent.js
@@ -0,0 +1,35 @@
+import PropTypes from 'prop-types'
+import React from 'react'
+
+class UnitAddComponent extends React.Component {
+ static propTypes = {
+ units: PropTypes.array.isRequired,
+ onAdd: PropTypes.func.isRequired,
+ }
+
+ render() {
+ return (
+ <div className="form-inline">
+ <div className="form-group w-100">
+ <select className="form-control w-70 mr-1" ref={(unitSelect) => (this.unitSelect = unitSelect)}>
+ {this.props.units.map((unit) => (
+ <option value={unit._id} key={unit._id}>
+ {unit.name}
+ </option>
+ ))}
+ </select>
+ <button
+ type="submit"
+ className="btn btn-outline-primary"
+ onClick={() => this.props.onAdd(this.unitSelect.value)}
+ >
+ <span className="fa fa-plus mr-2" />
+ Add
+ </button>
+ </div>
+ </div>
+ )
+ }
+}
+
+export default UnitAddComponent
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
new file mode 100644
index 00000000..de55e506
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitComponent.js
@@ -0,0 +1,52 @@
+import React from 'react'
+import { UncontrolledPopover, PopoverHeader, PopoverBody, Button } from 'reactstrap'
+
+function UnitComponent({ index, unitType, unit, onDelete }) {
+ let unitInfo
+ if (unitType === 'cpu' || unitType === 'gpu') {
+ unitInfo = (
+ <>
+ <strong>Clockrate: </strong>
+ <code>{unit.clockRateMhz}</code>
+ <br />
+ <strong>Num. Cores: </strong>
+ <code>{unit.numberOfCores}</code>
+ <br />
+ <strong>Energy Cons.: </strong>
+ <code>{unit.energyConsumptionW} W</code>
+ <br />
+ </>
+ )
+ } else if (unitType === 'memory' || unitType === 'storage') {
+ unitInfo = (
+ <>
+ <strong>Speed:</strong>
+ <code>{unit.speedMbPerS} Mb/s</code>
+ <br />
+ <strong>Size:</strong>
+ <code>{unit.sizeMb} MB</code>
+ <br />
+ <strong>Energy Cons.:</strong>
+ <code>{unit.energyConsumptionW} W</code>
+ <br />
+ </>
+ )
+ }
+
+ return (
+ <li className="d-flex list-group-item justify-content-between align-items-center">
+ <span style={{ maxWidth: '60%' }}>{unit.name}</span>
+ <span>
+ <Button outline={true} color="info" className="mr-1 fa fa-info-circle" id={`unit-${index}`} />
+ <UncontrolledPopover trigger="focus" placement="left" target={`unit-${index}`}>
+ <PopoverHeader>Unit Information</PopoverHeader>
+ <PopoverBody>{unitInfo}</PopoverBody>
+ </UncontrolledPopover>
+
+ <span className="btn btn-outline-danger fa fa-trash" onClick={onDelete} />
+ </span>
+ </li>
+ )
+}
+
+export default UnitComponent
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
new file mode 100644
index 00000000..2ade0f6a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitListComponent.js
@@ -0,0 +1,20 @@
+import React from 'react'
+import UnitContainer from '../../../../../containers/app/sidebars/topology/machine/UnitContainer'
+
+const UnitListComponent = ({ unitType, unitIds }) => (
+ <ul className="list-group mt-1">
+ {unitIds.length !== 0 ? (
+ unitIds.map((unitId, index) => (
+ <UnitContainer unitType={unitType} unitId={unitId} index={index} key={index} />
+ ))
+ ) : (
+ <div className="alert alert-info">
+ <span>
+ <strong>No units...</strong> Add some with the menu above!
+ </span>
+ </div>
+ )}
+ </ul>
+)
+
+export default UnitListComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js
new file mode 100644
index 00000000..6599fefd
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/machine/UnitTabsComponent.js
@@ -0,0 +1,78 @@
+import React, { useState } from 'react'
+import { Nav, NavItem, NavLink, TabContent, TabPane } from 'reactstrap'
+import UnitAddContainer from '../../../../../containers/app/sidebars/topology/machine/UnitAddContainer'
+import UnitListContainer from '../../../../../containers/app/sidebars/topology/machine/UnitListContainer'
+
+const UnitTabsComponent = () => {
+ const [activeTab, setActiveTab] = useState('cpu-units')
+ const toggle = (tab) => {
+ if (activeTab !== tab) setActiveTab(tab)
+ }
+
+ return (
+ <div>
+ <Nav tabs>
+ <NavItem>
+ <NavLink
+ className={activeTab === 'cpu-units' ? 'active' : ''}
+ onClick={() => {
+ toggle('cpu-units')
+ }}
+ >
+ CPU
+ </NavLink>
+ </NavItem>
+ <NavItem>
+ <NavLink
+ className={activeTab === 'gpu-units' ? 'active' : ''}
+ onClick={() => {
+ toggle('gpu-units')
+ }}
+ >
+ GPU
+ </NavLink>
+ </NavItem>
+ <NavItem>
+ <NavLink
+ className={activeTab === 'memory-units' ? 'active' : ''}
+ onClick={() => {
+ toggle('memory-units')
+ }}
+ >
+ Memory
+ </NavLink>
+ </NavItem>
+ <NavItem>
+ <NavLink
+ className={activeTab === 'storage-units' ? 'active' : ''}
+ onClick={() => {
+ toggle('storage-units')
+ }}
+ >
+ Stor.
+ </NavLink>
+ </NavItem>
+ </Nav>
+ <TabContent activeTab={activeTab}>
+ <TabPane tabId="cpu-units">
+ <UnitAddContainer unitType="cpu" />
+ <UnitListContainer unitType="cpu" />
+ </TabPane>
+ <TabPane tabId="gpu-units">
+ <UnitAddContainer unitType="gpu" />
+ <UnitListContainer unitType="gpu" />
+ </TabPane>
+ <TabPane tabId="memory-units">
+ <UnitAddContainer unitType="memory" />
+ <UnitListContainer unitType="memory" />
+ </TabPane>
+ <TabPane tabId="storage-units">
+ <UnitAddContainer unitType="storage" />
+ <UnitListContainer unitType="storage" />
+ </TabPane>
+ </TabContent>
+ </div>
+ )
+}
+
+export default UnitTabsComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js
new file mode 100644
index 00000000..75418f9d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/AddPrefabComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const AddPrefabComponent = ({ onClick }) => (
+ <div className="btn btn-primary btn-block" onClick={onClick}>
+ <span className="fa fa-floppy-o mr-2" />
+ Save this rack to a prefab
+ </div>
+)
+
+export default AddPrefabComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js
new file mode 100644
index 00000000..c14775bf
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/BackToRoomComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const BackToRoomComponent = ({ onClick }) => (
+ <div className="btn btn-secondary btn-block mb-2" onClick={onClick}>
+ <span className="fa fa-angle-left mr-2" />
+ Back to room
+ </div>
+)
+
+export default BackToRoomComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js
new file mode 100644
index 00000000..23b0daac
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/DeleteRackComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const DeleteRackComponent = ({ onClick }) => (
+ <div className="btn btn-outline-danger btn-block" onClick={onClick}>
+ <span className="fa fa-trash mr-2" />
+ Delete this rack
+ </div>
+)
+
+export default DeleteRackComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js
new file mode 100644
index 00000000..d7e30f1d
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/EmptySlotComponent.js
@@ -0,0 +1,13 @@
+import React from 'react'
+
+const EmptySlotComponent = ({ position, onAdd }) => (
+ <li className="list-group-item d-flex justify-content-between align-items-center">
+ <span className="badge badge-default badge-info mr-1 disabled">{position}</span>
+ <button className="btn btn-outline-primary" onClick={onAdd}>
+ <span className="fa fa-plus mr-2" />
+ Add machine
+ </button>
+ </li>
+)
+
+export default EmptySlotComponent
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
new file mode 100644
index 00000000..caa3dc04
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineComponent.js
@@ -0,0 +1,43 @@
+import React from 'react'
+import Shapes from '../../../../../shapes'
+
+const UnitIcon = ({ id, type }) => (
+ <div>
+ <img
+ src={'/img/topology/' + id + '-icon.png'}
+ alt={'Machine contains ' + type + ' units'}
+ className="img-fluid ml-1"
+ style={{ maxHeight: '35px' }}
+ />
+ </div>
+)
+
+const MachineComponent = ({ position, machine, onClick }) => {
+ const hasNoUnits =
+ machine.cpuIds.length + machine.gpuIds.length + machine.memoryIds.length + machine.storageIds.length === 0
+
+ return (
+ <li
+ className="d-flex list-group-item list-group-item-action justify-content-between align-items-center"
+ onClick={onClick}
+ style={{ backgroundColor: 'white' }}
+ >
+ <span className="badge badge-default badge-info mr-1">{position}</span>
+ <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}
+ {hasNoUnits ? (
+ <span className="badge badge-default badge-warning">Machine with no units</span>
+ ) : undefined}
+ </div>
+ </li>
+ )
+}
+
+MachineComponent.propTypes = {
+ machine: Shapes.Machine,
+}
+
+export default MachineComponent
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
new file mode 100644
index 00000000..12be26bd
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.js
@@ -0,0 +1,20 @@
+import React from 'react'
+import EmptySlotContainer from '../../../../../containers/app/sidebars/topology/rack/EmptySlotContainer'
+import MachineContainer from '../../../../../containers/app/sidebars/topology/rack/MachineContainer'
+import './MachineListComponent.sass'
+
+const MachineListComponent = ({ machineIds }) => {
+ return (
+ <ul className="list-group machine-list">
+ {machineIds.map((machineId, index) => {
+ if (machineId === null) {
+ return <EmptySlotContainer key={index} position={index + 1} />
+ } else {
+ return <MachineContainer key={index} position={index + 1} machineId={machineId} />
+ }
+ })}
+ </ul>
+ )
+}
+
+export default MachineListComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass
new file mode 100644
index 00000000..11b82c93
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/MachineListComponent.sass
@@ -0,0 +1,2 @@
+.machine-list li
+ min-height: 64px
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js
new file mode 100644
index 00000000..b701909a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackNameComponent.js
@@ -0,0 +1,6 @@
+import React from 'react'
+import NameComponent from '../NameComponent'
+
+const RackNameComponent = ({ rackName, onEdit }) => <NameComponent name={rackName} onEdit={onEdit} />
+
+export default RackNameComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js
new file mode 100644
index 00000000..ca41bf57
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.js
@@ -0,0 +1,25 @@
+import React from 'react'
+import BackToRoomContainer from '../../../../../containers/app/sidebars/topology/rack/BackToRoomContainer'
+import DeleteRackContainer from '../../../../../containers/app/sidebars/topology/rack/DeleteRackContainer'
+import MachineListContainer from '../../../../../containers/app/sidebars/topology/rack/MachineListContainer'
+import RackNameContainer from '../../../../../containers/app/sidebars/topology/rack/RackNameContainer'
+import './RackSidebarComponent.sass'
+import AddPrefabContainer from '../../../../../containers/app/sidebars/topology/rack/AddPrefabContainer'
+
+const RackSidebarComponent = () => {
+ return (
+ <div className="rack-sidebar-container flex-column">
+ <div className="rack-sidebar-header-container">
+ <RackNameContainer />
+ <BackToRoomContainer />
+ <AddPrefabContainer />
+ <DeleteRackContainer />
+ </div>
+ <div className="machine-list-container mt-2">
+ <MachineListContainer />
+ </div>
+ </div>
+ )
+}
+
+export default RackSidebarComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass
new file mode 100644
index 00000000..29fec02a
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/rack/RackSidebarComponent.sass
@@ -0,0 +1,11 @@
+.rack-sidebar-container
+ display: flex
+ height: 100%
+ max-height: 100%
+
+.rack-sidebar-header-container
+ flex: 0
+
+.machine-list-container
+ flex: 1
+ overflow-y: scroll
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js
new file mode 100644
index 00000000..64c0a1f6
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/BackToBuildingComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const BackToBuildingComponent = ({ onClick }) => (
+ <div className="btn btn-secondary btn-block mb-2" onClick={onClick}>
+ <span className="fa fa-angle-left mr-2" />
+ Back to building
+ </div>
+)
+
+export default BackToBuildingComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js
new file mode 100644
index 00000000..78417359
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/DeleteRoomComponent.js
@@ -0,0 +1,10 @@
+import React from 'react'
+
+const DeleteRoomComponent = ({ onClick }) => (
+ <div className="btn btn-outline-danger btn-block" onClick={onClick}>
+ <span className="fa fa-trash mr-2" />
+ Delete this room
+ </div>
+)
+
+export default DeleteRoomComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js
new file mode 100644
index 00000000..857a646f
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/EditRoomComponent.js
@@ -0,0 +1,22 @@
+import classNames from 'classnames'
+import React from 'react'
+
+const EditRoomComponent = ({ onEdit, onFinish, isEditing, isInRackConstructionMode }) =>
+ isEditing ? (
+ <div className="btn btn-info btn-block" onClick={onFinish}>
+ <span className="fa fa-check mr-2" />
+ Finish editing room
+ </div>
+ ) : (
+ <div
+ className={classNames('btn btn-outline-info btn-block', {
+ disabled: isInRackConstructionMode,
+ })}
+ onClick={() => (isInRackConstructionMode ? undefined : onEdit())}
+ >
+ <span className="fa fa-pencil mr-2" />
+ Edit the tiles of this room
+ </div>
+ )
+
+export default EditRoomComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js
new file mode 100644
index 00000000..44566f61
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RackConstructionComponent.js
@@ -0,0 +1,27 @@
+import classNames from 'classnames'
+import React from 'react'
+
+const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => {
+ if (inRackConstructionMode) {
+ return (
+ <div className="btn btn-primary btn-block" onClick={onStop}>
+ <span className="fa fa-times mr-2" />
+ Stop rack construction
+ </div>
+ )
+ }
+
+ return (
+ <div
+ className={classNames('btn btn-outline-primary btn-block', {
+ disabled: isEditingRoom,
+ })}
+ onClick={() => (isEditingRoom ? undefined : onStart())}
+ >
+ <span className="fa fa-plus mr-2" />
+ Start rack construction
+ </div>
+ )
+}
+
+export default RackConstructionComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js
new file mode 100644
index 00000000..d637828e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomNameComponent.js
@@ -0,0 +1,6 @@
+import React from 'react'
+import NameComponent from '../NameComponent'
+
+const RoomNameComponent = ({ roomName, onEdit }) => <NameComponent name={roomName} onEdit={onEdit} />
+
+export default RoomNameComponent
diff --git a/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js
new file mode 100644
index 00000000..1bc6533e
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/app/sidebars/topology/room/RoomSidebarComponent.js
@@ -0,0 +1,20 @@
+import React from 'react'
+import BackToBuildingContainer from '../../../../../containers/app/sidebars/topology/room/BackToBuildingContainer'
+import DeleteRoomContainer from '../../../../../containers/app/sidebars/topology/room/DeleteRoomContainer'
+import EditRoomContainer from '../../../../../containers/app/sidebars/topology/room/EditRoomContainer'
+import RackConstructionContainer from '../../../../../containers/app/sidebars/topology/room/RackConstructionContainer'
+import RoomNameContainer from '../../../../../containers/app/sidebars/topology/room/RoomNameContainer'
+
+const RoomSidebarComponent = () => {
+ return (
+ <div>
+ <RoomNameContainer />
+ <BackToBuildingContainer />
+ <RackConstructionContainer />
+ <EditRoomContainer />
+ <DeleteRoomContainer />
+ </div>
+ )
+}
+
+export default RoomSidebarComponent