summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-ui')
-rw-r--r--opendc-web/opendc-web-ui/src/api/index.js2
-rw-r--r--opendc-web/opendc-web-ui/src/api/portfolios.js18
-rw-r--r--opendc-web/opendc-web-ui/src/api/prefabs.js39
-rw-r--r--opendc-web/opendc-web-ui/src/api/projects.js6
-rw-r--r--opendc-web/opendc-web-ui/src/api/scenarios.js20
-rw-r--r--opendc-web/opendc-web-ui/src/api/topologies.js19
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppNavigation.js30
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/ContextSelector.js21
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss1
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js24
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js17
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/TopologySelector.js23
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js20
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js48
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js23
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js19
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js31
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewProject.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewTopology.js16
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js22
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js27
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js11
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js19
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js9
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js15
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js1
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js6
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js7
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js2
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js4
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js4
-rw-r--r--opendc-web/opendc-web-ui/src/data/experiments.js8
-rw-r--r--opendc-web/opendc-web-ui/src/data/project.js114
-rw-r--r--opendc-web/opendc-web-ui/src/data/topology.js69
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_app.js14
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_document.js13
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js6
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js25
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js36
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/index.js16
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/building.js8
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/index.js3
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js2
-rw-r--r--opendc-web/opendc-web-ui/src/redux/actions/topology/room.js6
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js2
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js4
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js4
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js4
-rw-r--r--opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js4
-rw-r--r--opendc-web/opendc-web-ui/src/redux/sagas/topology.js9
-rw-r--r--opendc-web/opendc-web-ui/src/shapes.js146
-rw-r--r--opendc-web/opendc-web-ui/src/util/authorizations.js12
-rw-r--r--opendc-web/opendc-web-ui/src/util/topology-schema.js18
-rw-r--r--opendc-web/opendc-web-ui/src/util/unit-specifications.js26
64 files changed, 537 insertions, 552 deletions
diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js
index 680d49ce..1a9877d0 100644
--- a/opendc-web/opendc-web-ui/src/api/index.js
+++ b/opendc-web/opendc-web-ui/src/api/index.js
@@ -47,5 +47,5 @@ export async function request(auth, path, method = 'GET', body) {
throw response.message
}
- return json.data
+ return json
}
diff --git a/opendc-web/opendc-web-ui/src/api/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js
index 82ac0ced..d818876f 100644
--- a/opendc-web/opendc-web-ui/src/api/portfolios.js
+++ b/opendc-web/opendc-web-ui/src/api/portfolios.js
@@ -22,22 +22,18 @@
import { request } from './index'
-export function fetchPortfolio(auth, portfolioId) {
- return request(auth, `portfolios/${portfolioId}`)
+export function fetchPortfolio(auth, projectId, number) {
+ return request(auth, `projects/${projectId}/portfolios/${number}`)
}
-export function fetchPortfoliosOfProject(auth, projectId) {
+export function fetchPortfolios(auth, projectId) {
return request(auth, `projects/${projectId}/portfolios`)
}
-export function addPortfolio(auth, portfolio) {
- return request(auth, `projects/${portfolio.projectId}/portfolios`, 'POST', { portfolio })
+export function addPortfolio(auth, projectId, portfolio) {
+ return request(auth, `projects/${projectId}/portfolios`, 'POST', portfolio)
}
-export function updatePortfolio(auth, portfolioId, portfolio) {
- return request(auth, `portfolios/${portfolioId}`, 'PUT', { portfolio })
-}
-
-export function deletePortfolio(auth, portfolioId) {
- return request(auth, `portfolios/${portfolioId}`, 'DELETE')
+export function deletePortfolio(auth, projectId, number) {
+ return request(auth, `projects/${projectId}/portfolios/${number}`, 'DELETE')
}
diff --git a/opendc-web/opendc-web-ui/src/api/prefabs.js b/opendc-web/opendc-web-ui/src/api/prefabs.js
deleted file mode 100644
index eb9aa23c..00000000
--- a/opendc-web/opendc-web-ui/src/api/prefabs.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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 { request } from './index'
-
-export function getPrefab(auth, prefabId) {
- return request(auth, `prefabs/${prefabId}`)
-}
-
-export function addPrefab(auth, prefab) {
- return request(auth, 'prefabs/', 'POST', { prefab })
-}
-
-export function updatePrefab(auth, prefab) {
- return request(auth, `prefabs/${prefab._id}`, 'PUT', { prefab })
-}
-
-export function deletePrefab(auth, prefabId) {
- return request(auth, `prefabs/${prefabId}`, 'DELETE')
-}
diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js
index 4123b371..e7e095da 100644
--- a/opendc-web/opendc-web-ui/src/api/projects.js
+++ b/opendc-web/opendc-web-ui/src/api/projects.js
@@ -31,11 +31,7 @@ export function fetchProject(auth, projectId) {
}
export function addProject(auth, project) {
- return request(auth, 'projects/', 'POST', { project })
-}
-
-export function updateProject(auth, project) {
- return request(auth, `projects/${project._id}`, 'PUT', { project })
+ return request(auth, 'projects/', 'POST', project)
}
export function deleteProject(auth, projectId) {
diff --git a/opendc-web/opendc-web-ui/src/api/scenarios.js b/opendc-web/opendc-web-ui/src/api/scenarios.js
index 88516caa..7eeb8f28 100644
--- a/opendc-web/opendc-web-ui/src/api/scenarios.js
+++ b/opendc-web/opendc-web-ui/src/api/scenarios.js
@@ -22,22 +22,18 @@
import { request } from './index'
-export function fetchScenario(auth, scenarioId) {
- return request(auth, `scenarios/${scenarioId}`)
+export function fetchScenario(auth, projectId, scenarioId) {
+ return request(auth, `projects/${projectId}/scenarios/${scenarioId}`)
}
-export function fetchScenariosOfPortfolio(auth, portfolioId) {
- return request(auth, `portfolios/${portfolioId}/scenarios`)
+export function fetchScenariosOfPortfolio(auth, projectId, portfolioId) {
+ return request(auth, `projects/${projectId}/portfolios/${portfolioId}/scenarios`)
}
-export function addScenario(auth, scenario) {
- return request(auth, `portfolios/${scenario.portfolioId}/scenarios`, 'POST', { scenario })
+export function addScenario(auth, projectId, portfolioId, scenario) {
+ return request(auth, `projects/${projectId}/portfolios/${portfolioId}/scenarios`, 'POST', scenario)
}
-export function updateScenario(auth, scenarioId, scenario) {
- return request(auth, `scenarios/${scenarioId}`, 'PUT', { scenario })
-}
-
-export function deleteScenario(auth, scenarioId) {
- return request(auth, `scenarios/${scenarioId}`, 'DELETE')
+export function deleteScenario(auth, projectId, scenarioId) {
+ return request(auth, `projects/${projectId}/scenarios/${scenarioId}`, 'DELETE')
}
diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js
index bd4e3bc4..0509c6d0 100644
--- a/opendc-web/opendc-web-ui/src/api/topologies.js
+++ b/opendc-web/opendc-web-ui/src/api/topologies.js
@@ -22,24 +22,23 @@
import { request } from './index'
-export function fetchTopology(auth, topologyId) {
- return request(auth, `topologies/${topologyId}`)
+export function fetchTopology(auth, projectId, number) {
+ return request(auth, `projects/${projectId}/topologies/${number}`)
}
-export function fetchTopologiesOfProject(auth, projectId) {
+export function fetchTopologies(auth, projectId) {
return request(auth, `projects/${projectId}/topologies`)
}
-export function addTopology(auth, topology) {
- return request(auth, `projects/${topology.projectId}/topologies`, 'POST', { topology })
+export function addTopology(auth, projectId, topology) {
+ return request(auth, `projects/${projectId}/topologies`, 'POST', topology)
}
export function updateTopology(auth, topology) {
- // eslint-disable-next-line no-unused-vars
- const { _id, ...data } = topology
- return request(auth, `topologies/${topology._id}`, 'PUT', { topology: data })
+ const { project, number, rooms } = topology
+ return request(auth, `projects/${project.id}/topologies/${number}`, 'PUT', { rooms })
}
-export function deleteTopology(auth, topologyId) {
- return request(auth, `topologies/${topologyId}`, 'DELETE')
+export function deleteTopology(auth, projectId, number) {
+ return request(auth, `projects/${projectId}/topologies/${number}`, 'DELETE')
}
diff --git a/opendc-web/opendc-web-ui/src/components/AppNavigation.js b/opendc-web/opendc-web-ui/src/components/AppNavigation.js
index 178c3ec0..77c683a2 100644
--- a/opendc-web/opendc-web-ui/src/components/AppNavigation.js
+++ b/opendc-web/opendc-web-ui/src/components/AppNavigation.js
@@ -23,15 +23,9 @@
import { Nav, NavItem, NavList } from '@patternfly/react-core'
import { useRouter } from 'next/router'
import NavItemLink from './util/NavItemLink'
-import { useProject } from '../data/project'
export function AppNavigation() {
- const { pathname, query } = useRouter()
- const { project: projectId } = query
- const { data: project } = useProject(projectId)
-
- const nextTopologyId = project?.topologyIds?.[0]
- const nextPortfolioId = project?.portfolioIds?.[0]
+ const { pathname } = useRouter()
return (
<Nav variant="horizontal">
@@ -45,28 +39,6 @@ export function AppNavigation() {
>
Projects
</NavItem>
- {pathname.startsWith('/projects/[project]') && (
- <>
- <NavItem
- id="topologies"
- to={nextTopologyId ? `/projects/${projectId}/topologies/${nextTopologyId}` : '/projects'}
- itemId={1}
- component={NavItemLink}
- isActive={pathname === '/projects/[project]/topologies/[topology]'}
- >
- Topologies
- </NavItem>
- <NavItem
- id="portfolios"
- to={nextPortfolioId ? `/projects/${projectId}/portfolios/${nextPortfolioId}` : '/projects'}
- itemId={2}
- component={NavItemLink}
- isActive={pathname === '/projects/[project]/portfolios/[portfolio]'}
- >
- Portfolios
- </NavItem>
- </>
- )}
</NavList>
</Nav>
)
diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js
index 3712cfa0..a99b60c0 100644
--- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js
+++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js
@@ -22,13 +22,11 @@
import PropTypes from 'prop-types'
import { ContextSelector as PFContextSelector, ContextSelectorItem } from '@patternfly/react-core'
-import { useMemo, useState, useReducer } from 'react'
+import { useMemo, useState } from 'react'
import { contextSelector } from './ContextSelector.module.scss'
-function ContextSelector({ activeItem, items, onSelect, label }) {
- const [isOpen, toggle] = useReducer((isOpen) => !isOpen, false)
+function ContextSelector({ activeItem, items, onSelect, onToggle, isOpen, label }) {
const [searchValue, setSearchValue] = useState('')
-
const filteredItems = useMemo(
() => items.filter(({ name }) => name.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1) || items,
[items, searchValue]
@@ -36,23 +34,22 @@ function ContextSelector({ activeItem, items, onSelect, label }) {
return (
<PFContextSelector
- menuAppendTo={global.document?.body}
className={contextSelector}
toggleText={activeItem ? `${label}: ${activeItem.name}` : label}
onSearchInputChange={(value) => setSearchValue(value)}
searchInputValue={searchValue}
isOpen={isOpen}
- onToggle={toggle}
+ onToggle={(_, isOpen) => onToggle(isOpen)}
onSelect={(event) => {
- const targetId = event.target.value
- const target = items.find((item) => item._id === targetId)
+ const targetId = +event.target.value
+ const target = items.find((item) => item.id === targetId)
- toggle()
onSelect(target)
+ onToggle(!isOpen)
}}
>
{filteredItems.map((item) => (
- <ContextSelectorItem key={item._id} value={item._id}>
+ <ContextSelectorItem key={item.id} value={item.id}>
{item.name}
</ContextSelectorItem>
))}
@@ -61,7 +58,7 @@ function ContextSelector({ activeItem, items, onSelect, label }) {
}
const Item = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.any.isRequired,
name: PropTypes.string.isRequired,
})
@@ -69,6 +66,8 @@ ContextSelector.propTypes = {
activeItem: Item,
items: PropTypes.arrayOf(Item).isRequired,
onSelect: PropTypes.func.isRequired,
+ onToggle: PropTypes.func.isRequired,
+ isOpen: PropTypes.bool,
label: PropTypes.string,
}
diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss
index fefba41f..0aa63ee6 100644
--- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss
+++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss
@@ -21,7 +21,6 @@
*/
.contextSelector {
- width: auto;
margin-right: 20px;
--pf-c-context-selector__toggle--PaddingTop: var(--pf-global--spacer--sm);
diff --git a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js
index 694681ac..c4f2d50e 100644
--- a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js
+++ b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js
@@ -21,27 +21,31 @@
*/
import { useRouter } from 'next/router'
-import { useMemo } from 'react'
-import { useProjectPortfolios } from '../../data/project'
+import { useState } from 'react'
+import { usePortfolios } from '../../data/project'
+import { Portfolio } from '../../shapes'
import ContextSelector from './ContextSelector'
-function PortfolioSelector() {
+function PortfolioSelector({ activePortfolio }) {
const router = useRouter()
- const { project, portfolio: activePortfolioId } = router.query
- const { data: portfolios = [] } = useProjectPortfolios(project)
- const activePortfolio = useMemo(() => portfolios.find((portfolio) => portfolio._id === activePortfolioId), [
- activePortfolioId,
- portfolios,
- ])
+
+ const [isOpen, setOpen] = useState(false)
+ const { data: portfolios = [] } = usePortfolios(activePortfolio?.project?.id, { enabled: isOpen })
return (
<ContextSelector
label="Portfolio"
activeItem={activePortfolio}
items={portfolios}
- onSelect={(portfolio) => router.push(`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`)}
+ onSelect={(portfolio) => router.push(`/projects/${portfolio.project.id}/portfolios/${portfolio.number}`)}
+ onToggle={setOpen}
+ isOpen={isOpen}
/>
)
}
+PortfolioSelector.propTypes = {
+ activePortfolio: Portfolio,
+}
+
export default PortfolioSelector
diff --git a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js
index 753632ab..7721e04c 100644
--- a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js
+++ b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js
@@ -20,29 +20,32 @@
* SOFTWARE.
*/
-import PropTypes from 'prop-types'
import { useRouter } from 'next/router'
-import { useMemo } from 'react'
+import { useState } from 'react'
import { useProjects } from '../../data/project'
+import { Project } from '../../shapes'
import ContextSelector from './ContextSelector'
-function ProjectSelector({ projectId }) {
+function ProjectSelector({ activeProject }) {
const router = useRouter()
- const { data: projects = [] } = useProjects()
- const activeProject = useMemo(() => projects.find((project) => project._id === projectId), [projectId, projects])
+
+ const [isOpen, setOpen] = useState(false)
+ const { data: projects = [] } = useProjects({ enabled: isOpen })
return (
<ContextSelector
label="Project"
activeItem={activeProject}
items={projects}
- onSelect={(project) => router.push(`/projects/${project._id}`)}
+ onSelect={(project) => router.push(`/projects/${project.id}`)}
+ onToggle={setOpen}
+ isOpen={isOpen}
/>
)
}
ProjectSelector.propTypes = {
- projectId: PropTypes.string,
+ activeProject: Project,
}
export default ProjectSelector
diff --git a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js
index d5e51c6c..9cae4cbf 100644
--- a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js
+++ b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js
@@ -20,33 +20,32 @@
* SOFTWARE.
*/
-import PropTypes from 'prop-types'
import { useRouter } from 'next/router'
-import { useMemo } from 'react'
-import { useProjectTopologies } from '../../data/topology'
+import { useState } from 'react'
+import { useTopologies } from '../../data/topology'
+import { Topology } from '../../shapes'
import ContextSelector from './ContextSelector'
-function TopologySelector({ projectId, topologyId }) {
+function TopologySelector({ activeTopology }) {
const router = useRouter()
- const { data: topologies = [] } = useProjectTopologies(projectId)
- const activeTopology = useMemo(() => topologies.find((topology) => topology._id === topologyId), [
- topologyId,
- topologies,
- ])
+
+ const [isOpen, setOpen] = useState(false)
+ const { data: topologies = [] } = useTopologies(activeTopology?.project?.id, { enabled: isOpen })
return (
<ContextSelector
label="Topology"
activeItem={activeTopology}
items={topologies}
- onSelect={(topology) => router.push(`/projects/${topology.projectId}/topologies/${topology._id}`)}
+ onSelect={(topology) => router.push(`/projects/${topology.project.id}/topologies/${topology.number}`)}
+ onToggle={setOpen}
+ isOpen={isOpen}
/>
)
}
TopologySelector.propTypes = {
- projectId: PropTypes.string,
- topologyId: PropTypes.string,
+ activeTopology: Topology,
}
export default TopologySelector
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js
index 856282a7..fd9a72d2 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js
@@ -24,21 +24,15 @@ import PropTypes from 'prop-types'
import { PlusIcon } from '@patternfly/react-icons'
import { Button } from '@patternfly/react-core'
import { useState } from 'react'
-import { useMutation } from 'react-query'
+import { useNewScenario } from '../../data/project'
import NewScenarioModal from './NewScenarioModal'
-function NewScenario({ portfolioId }) {
+function NewScenario({ projectId, portfolioId }) {
const [isVisible, setVisible] = useState(false)
- const { mutate: addScenario } = useMutation('addScenario')
+ const { mutate: addScenario } = useNewScenario()
- const onSubmit = (name, portfolioId, trace, topology, operational) => {
- addScenario({
- portfolioId,
- name,
- trace,
- topology,
- operational,
- })
+ const onSubmit = (projectId, portfolioNumber, data) => {
+ addScenario({ projectId, portfolioNumber, data })
setVisible(false)
}
@@ -48,6 +42,7 @@ function NewScenario({ portfolioId }) {
New Scenario
</Button>
<NewScenarioModal
+ projectId={projectId}
portfolioId={portfolioId}
isOpen={isVisible}
onSubmit={onSubmit}
@@ -58,7 +53,8 @@ function NewScenario({ portfolioId }) {
}
NewScenario.propTypes = {
- portfolioId: PropTypes.string,
+ projectId: PropTypes.number,
+ portfolioId: PropTypes.number,
}
export default NewScenario
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js
index 7f620c8c..ed35c163 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js
@@ -12,14 +12,14 @@ import {
TextInput,
} from '@patternfly/react-core'
import { useSchedulers, useTraces } from '../../data/experiments'
-import { useProjectTopologies } from '../../data/topology'
+import { useTopologies } from '../../data/topology'
import { usePortfolio } from '../../data/project'
-const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => {
- const { data: portfolio } = usePortfolio(portfolioId)
- const { data: topologies = [] } = useProjectTopologies(portfolio?.projectId)
- const { data: traces = [] } = useTraces()
- const { data: schedulers = [] } = useSchedulers()
+function NewScenarioModal({ projectId, portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) {
+ const { data: portfolio } = usePortfolio(projectId, portfolioId)
+ const { data: topologies = [] } = useTopologies(projectId, { enabled: isOpen })
+ const { data: traces = [] } = useTraces({ enabled: isOpen })
+ const { data: schedulers = [] } = useSchedulers({ enabled: isOpen })
// eslint-disable-next-line no-unused-vars
const [isSubmitted, setSubmitted] = useState(false)
@@ -51,22 +51,19 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
const name = nameInput.current.value
- onSubmitUpstream(
+ onSubmitUpstream(portfolio.project.id, portfolio.number, {
name,
- portfolio._id,
- {
- traceId: trace || traces[0]._id,
- loadSamplingFraction: traceLoad / 100,
+ workload: {
+ trace: trace || traces[0].id,
+ samplingFraction: traceLoad / 100,
},
- {
- topologyId: topology || topologies[0]._id,
+ topology: topology || topologies[0].number,
+ phenomena: {
+ failures: failuresEnabled,
+ interference: opPhenEnabled,
},
- {
- failuresEnabled,
- performanceInterferenceEnabled: opPhenEnabled,
- schedulerName: scheduler || schedulers[0].name,
- }
- )
+ schedulerName: scheduler || schedulers[0],
+ })
resetState()
return true
@@ -84,8 +81,8 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
id="name"
name="name"
type="text"
- isDisabled={portfolio?.scenarioIds?.length === 0}
- defaultValue={portfolio?.scenarioIds?.length === 0 ? 'Base scenario' : ''}
+ isDisabled={portfolio?.scenarios?.length === 0}
+ defaultValue={portfolio?.scenarios?.length === 0 ? 'Base scenario' : ''}
ref={nameInput}
/>
</FormGroup>
@@ -93,7 +90,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
<FormGroup label="Trace" fieldId="trace" isRequired>
<FormSelect id="trace" name="trace" value={trace} onChange={setTrace}>
{traces.map((trace) => (
- <FormSelectOption value={trace._id} key={trace._id} label={trace.name} />
+ <FormSelectOption value={trace.id} key={trace.id} label={trace.name} />
))}
</FormSelect>
</FormGroup>
@@ -115,7 +112,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
<FormGroup label="Topology" fieldId="topology" isRequired>
<FormSelect id="topology" name="topology" value={topology} onChange={setTopology}>
{topologies.map((topology) => (
- <FormSelectOption value={topology._id} key={topology._id} label={topology.name} />
+ <FormSelectOption value={topology.number} key={topology.number} label={topology.name} />
))}
</FormSelect>
</FormGroup>
@@ -123,7 +120,7 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
<FormGroup label="Scheduler" fieldId="scheduler" isRequired>
<FormSelect id="scheduler" name="scheduler" value={scheduler} onChange={setScheduler}>
{schedulers.map((scheduler) => (
- <FormSelectOption value={scheduler.name} key={scheduler.name} label={scheduler.name} />
+ <FormSelectOption value={scheduler} key={scheduler} label={scheduler} />
))}
</FormSelect>
</FormGroup>
@@ -150,7 +147,8 @@ const NewScenarioModal = ({ portfolioId, isOpen, onSubmit: onSubmitUpstream, onC
}
NewScenarioModal.propTypes = {
- portfolioId: PropTypes.string,
+ projectId: PropTypes.number,
+ portfolioId: PropTypes.number,
isOpen: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js
index 580b0a29..e561b655 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js
@@ -43,8 +43,8 @@ import { METRIC_NAMES } from '../../util/available-metrics'
import NewScenario from './NewScenario'
import ScenarioTable from './ScenarioTable'
-function PortfolioOverview({ portfolioId }) {
- const { data: portfolio } = usePortfolio(portfolioId)
+function PortfolioOverview({ projectId, portfolioId }) {
+ const { status, data: portfolio } = usePortfolio(projectId, portfolioId)
return (
<Grid hasGutter>
@@ -62,16 +62,16 @@ function PortfolioOverview({ portfolioId }) {
<DescriptionListGroup>
<DescriptionListTerm>Scenarios</DescriptionListTerm>
<DescriptionListDescription>
- {portfolio?.scenarioIds.length ?? <Skeleton screenreaderText="Loading portfolio" />}
+ {portfolio?.scenarios?.length ?? <Skeleton screenreaderText="Loading portfolio" />}
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup>
<DescriptionListTerm>Metrics</DescriptionListTerm>
<DescriptionListDescription>
- {portfolio?.targets?.enabledMetrics ? (
- portfolio.targets.enabledMetrics.length > 0 ? (
+ {portfolio ? (
+ portfolio.targets.metrics.length > 0 ? (
<ChipGroup>
- {portfolio.targets.enabledMetrics.map((metric) => (
+ {portfolio.targets.metrics.map((metric) => (
<Chip isReadOnly key={metric}>
{METRIC_NAMES[metric]}
</Chip>
@@ -88,9 +88,7 @@ function PortfolioOverview({ portfolioId }) {
<DescriptionListGroup>
<DescriptionListTerm>Repeats per Scenario</DescriptionListTerm>
<DescriptionListDescription>
- {portfolio?.targets?.repeatsPerScenario ?? (
- <Skeleton screenreaderText="Loading portfolio" />
- )}
+ {portfolio?.targets?.repeats ?? <Skeleton screenreaderText="Loading portfolio" />}
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
@@ -101,12 +99,12 @@ function PortfolioOverview({ portfolioId }) {
<Card>
<CardHeader>
<CardActions>
- <NewScenario portfolioId={portfolioId} />
+ <NewScenario projectId={projectId} portfolioId={portfolioId} />
</CardActions>
<CardTitle>Scenarios</CardTitle>
</CardHeader>
<CardBody>
- <ScenarioTable portfolioId={portfolioId} />
+ <ScenarioTable portfolio={portfolio} status={status} />
</CardBody>
</Card>
</GridItem>
@@ -115,7 +113,8 @@ function PortfolioOverview({ portfolioId }) {
}
PortfolioOverview.propTypes = {
- portfolioId: PropTypes.string,
+ projectId: PropTypes.number,
+ portfolioId: PropTypes.number,
}
export default PortfolioOverview
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
index 00023d9e..f63f0c7f 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
@@ -42,12 +42,14 @@ import {
Title,
} from '@patternfly/react-core'
import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons'
-import { usePortfolioScenarios } from '../../data/project'
+import { usePortfolio } from '../../data/project'
import PortfolioResultInfo from './PortfolioResultInfo'
import NewScenario from './NewScenario'
-const PortfolioResults = ({ portfolioId }) => {
- const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId)
+const PortfolioResults = ({ projectId, portfolioId }) => {
+ const { status, data: scenarios = [] } = usePortfolio(projectId, portfolioId, {
+ select: (portfolio) => portfolio.scenarios,
+ })
if (status === 'loading') {
return (
@@ -86,7 +88,7 @@ const PortfolioResults = ({ portfolioId }) => {
No results are currently available for this portfolio. Run a scenario to obtain simulation
results.
</EmptyStateBody>
- <NewScenario portfolioId={portfolioId} />
+ <NewScenario projectId={projectId} portfolioId={portfolioId} />
</EmptyState>
</Bullseye>
)
@@ -96,11 +98,11 @@ const PortfolioResults = ({ portfolioId }) => {
AVAILABLE_METRICS.forEach((metric) => {
dataPerMetric[metric] = scenarios
- .filter((scenario) => scenario.results)
+ .filter((scenario) => scenario.job?.results)
.map((scenario) => ({
name: scenario.name,
- value: mean(scenario.results[metric]),
- errorX: std(scenario.results[metric]),
+ value: mean(scenario.job.results[metric]),
+ errorX: std(scenario.job.results[metric]),
}))
})
@@ -150,7 +152,8 @@ const PortfolioResults = ({ portfolioId }) => {
}
PortfolioResults.propTypes = {
- portfolioId: PropTypes.string,
+ projectId: PropTypes.number,
+ portfolioId: PropTypes.number,
}
export default PortfolioResults
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js
index 66691580..99d83f64 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js
@@ -20,13 +20,13 @@
* SOFTWARE.
*/
-import PropTypes from 'prop-types'
import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons'
+import { JobState } from '../../shapes'
function ScenarioState({ state }) {
switch (state) {
+ case 'PENDING':
case 'CLAIMED':
- case 'QUEUED':
return (
<span>
<ClockIcon color="blue" /> Queued
@@ -56,7 +56,7 @@ function ScenarioState({ state }) {
}
ScenarioState.propTypes = {
- state: PropTypes.oneOf(['QUEUED', 'CLAIMED', 'RUNNING', 'FINISHED', 'FAILED']),
+ state: JobState.isRequired,
}
export default ScenarioState
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js
index 9966e3ba..68647957 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js
@@ -20,44 +20,38 @@
* SOFTWARE.
*/
-import PropTypes from 'prop-types'
import Link from 'next/link'
import { Table, TableBody, TableHeader } from '@patternfly/react-table'
import React from 'react'
+import { Portfolio, Status } from '../../shapes'
import TableEmptyState from '../util/TableEmptyState'
import ScenarioState from './ScenarioState'
-import { usePortfolio, usePortfolioScenarios } from '../../data/project'
-import { useProjectTopologies } from '../../data/topology'
-import { useMutation } from 'react-query'
+import { useDeleteScenario } from '../../data/project'
-const ScenarioTable = ({ portfolioId }) => {
- const { data: portfolio } = usePortfolio(portfolioId)
- const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId)
- const { data: topologies } = useProjectTopologies(portfolio?.projectId, {
- select: (topologies) => new Map(topologies.map((topology) => [topology._id, topology])),
- })
-
- const { mutate: deleteScenario } = useMutation('deleteScenario')
+function ScenarioTable({ portfolio, status }) {
+ const { mutate: deleteScenario } = useDeleteScenario()
+ const projectId = portfolio?.project?.id
+ const scenarios = portfolio?.scenarios ?? []
const columns = ['Name', 'Topology', 'Trace', 'State']
const rows =
scenarios.length > 0
? scenarios.map((scenario) => {
- const topology = topologies?.get(scenario.topology.topologyId)
+ const topology = scenario.topology
return [
scenario.name,
{
title: topology ? (
- <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}>
+ <Link href={`/projects/${projectId}/topologies/${topology.number}`}>
<a>{topology.name}</a>
</Link>
) : (
'Unknown Topology'
),
},
- scenario.trace.traceId,
- { title: <ScenarioState state={scenario.simulation.state} /> },
+ `${scenario.workload.trace.name} (${scenario.workload.samplingFraction * 100}%)`,
+ { title: <ScenarioState state={scenario.job.state} /> },
]
})
: [
@@ -82,7 +76,7 @@ const ScenarioTable = ({ portfolioId }) => {
const actionResolver = (_, { rowIndex }) => [
{
title: 'Delete Scenario',
- onClick: (_, rowId) => deleteScenario(scenarios[rowId]._id),
+ onClick: (_, rowId) => deleteScenario({ projectId: projectId, number: scenarios[rowId].number }),
isDisabled: rowIndex === 0,
},
]
@@ -102,7 +96,8 @@ const ScenarioTable = ({ portfolioId }) => {
}
ScenarioTable.propTypes = {
- portfolioId: PropTypes.string,
+ portfolio: Portfolio,
+ status: Status.isRequired,
}
export default ScenarioTable
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js
index 87ea059d..aebcc3c9 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js
@@ -24,12 +24,12 @@ import PropTypes from 'prop-types'
import { PlusIcon } from '@patternfly/react-icons'
import { Button } from '@patternfly/react-core'
import { useState } from 'react'
-import { useMutation } from 'react-query'
+import { useNewPortfolio } from '../../data/project'
import NewPortfolioModal from './NewPortfolioModal'
function NewPortfolio({ projectId }) {
const [isVisible, setVisible] = useState(false)
- const { mutate: addPortfolio } = useMutation('addPortfolio')
+ const { mutate: addPortfolio } = useNewPortfolio()
const onSubmit = (name, targets) => {
addPortfolio({ projectId, name, targets })
@@ -47,7 +47,7 @@ function NewPortfolio({ projectId }) {
}
NewPortfolio.propTypes = {
- projectId: PropTypes.string,
+ projectId: PropTypes.number,
}
export default NewPortfolio
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js
index 4276d7d4..ba4bc819 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js
@@ -67,7 +67,7 @@ const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUps
setErrors({ name: true })
return false
} else {
- onSubmitUpstream(name, { enabledMetrics: selectedMetrics, repeatsPerScenario: repeats })
+ onSubmitUpstream(name, { metrics: selectedMetrics, repeats })
}
clearState()
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js
index 984264dc..bfa7c01a 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/NewProject.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewProject.js
@@ -1,7 +1,7 @@
import React, { useState } from 'react'
import { Button } from '@patternfly/react-core'
-import { useMutation } from 'react-query'
import { PlusIcon } from '@patternfly/react-icons'
+import { useNewProject } from '../../data/project'
import { buttonContainer } from './NewProject.module.scss'
import TextInputModal from '../util/modals/TextInputModal'
@@ -10,7 +10,7 @@ import TextInputModal from '../util/modals/TextInputModal'
*/
const NewProject = () => {
const [isVisible, setVisible] = useState(false)
- const { mutate: addProject } = useMutation('addProject')
+ const { mutate: addProject } = useNewProject()
const onSubmit = (name) => {
if (name) {
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js
index 77c57d26..4c569c56 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js
@@ -21,25 +21,17 @@
*/
import PropTypes from 'prop-types'
-import produce from 'immer'
import { PlusIcon } from '@patternfly/react-icons'
import { Button } from '@patternfly/react-core'
import { useState } from 'react'
-import { useMutation } from "react-query";
-import { useProjectTopologies } from "../../data/topology";
+import { useNewTopology } from '../../data/topology'
import NewTopologyModal from './NewTopologyModal'
function NewTopology({ projectId }) {
const [isVisible, setVisible] = useState(false)
- const { data: topologies = [] } = useProjectTopologies(projectId)
- const { mutate: addTopology } = useMutation('addTopology')
+ const { mutate: addTopology } = useNewTopology()
- const onSubmit = (name, duplicateId) => {
- const candidate = topologies.find((topology) => topology._id === duplicateId) || { projectId, rooms: [] }
- const topology = produce(candidate, (draft) => {
- delete draft._id
- draft.name = name
- })
+ const onSubmit = (topology) => {
addTopology(topology)
setVisible(false)
}
@@ -59,7 +51,7 @@ function NewTopology({ projectId }) {
}
NewTopology.propTypes = {
- projectId: PropTypes.string,
+ projectId: PropTypes.number,
}
export default NewTopology
diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js
index a495f73e..be4256e3 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js
@@ -20,10 +20,11 @@
* SOFTWARE.
*/
+import produce from 'immer'
import PropTypes from 'prop-types'
import React, { useRef, useState } from 'react'
import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core'
-import { useProjectTopologies } from '../../data/topology'
+import { useTopologies } from '../../data/topology'
import Modal from '../util/modals/Modal'
const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => {
@@ -32,10 +33,12 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan
const [originTopology, setOriginTopology] = useState(-1)
const [errors, setErrors] = useState({})
- const { data: topologies = [] } = useProjectTopologies(projectId)
+ const { data: topologies = [] } = useTopologies(projectId, { enabled: isOpen })
const clearState = () => {
- nameInput.current.value = ''
+ if (nameInput.current) {
+ nameInput.current.value = ''
+ }
setSubmitted(false)
setOriginTopology(-1)
setErrors({})
@@ -53,10 +56,13 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan
if (!name) {
setErrors({ name: true })
return false
- } else if (originTopology === -1) {
- onSubmitUpstream(name)
} else {
- onSubmitUpstream(name, originTopology)
+ const candidate = topologies.find((topology) => topology.id === originTopology) || { projectId, rooms: [] }
+ const topology = produce(candidate, (draft) => {
+ delete draft.id
+ draft.name = name
+ })
+ onSubmitUpstream(topology)
}
clearState()
@@ -84,7 +90,7 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan
<FormSelect id="origin" name="origin" value={originTopology} onChange={setOriginTopology}>
<FormSelectOption value={-1} key={-1} label="None - start from scratch" />
{topologies.map((topology) => (
- <FormSelectOption value={topology._id} key={topology._id} label={topology.name} />
+ <FormSelectOption value={topology.id} key={topology.id} label={topology.name} />
))}
</FormSelect>
</FormGroup>
@@ -94,7 +100,7 @@ const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCan
}
NewTopologyModal.propTypes = {
- projectId: PropTypes.string,
+ projectId: PropTypes.number,
isOpen: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js
index 45e399ed..aa679843 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js
@@ -25,12 +25,11 @@ import Link from 'next/link'
import { Table, TableBody, TableHeader } from '@patternfly/react-table'
import React from 'react'
import TableEmptyState from '../util/TableEmptyState'
-import { useProjectPortfolios } from '../../data/project'
-import { useMutation } from 'react-query'
+import { usePortfolios, useDeletePortfolio } from '../../data/project'
const PortfolioTable = ({ projectId }) => {
- const { status, data: portfolios = [] } = useProjectPortfolios(projectId)
- const { mutate: deletePortfolio } = useMutation('deletePortfolio')
+ const { status, data: portfolios = [] } = usePortfolios(projectId)
+ const { mutate: deletePortfolio } = useDeletePortfolio()
const columns = ['Name', 'Scenarios', 'Metrics', 'Repeats']
const rows =
@@ -38,20 +37,12 @@ const PortfolioTable = ({ projectId }) => {
? portfolios.map((portfolio) => [
{
title: (
- <Link href={`/projects/${portfolio.projectId}/portfolios/${portfolio._id}`}>
- {portfolio.name}
- </Link>
+ <Link href={`/projects/${projectId}/portfolios/${portfolio.number}`}>{portfolio.name}</Link>
),
},
-
- portfolio.scenarioIds.length === 1 ? '1 scenario' : `${portfolio.scenarioIds.length} scenarios`,
-
- portfolio.targets.enabledMetrics.length === 1
- ? '1 metric'
- : `${portfolio.targets.enabledMetrics.length} metrics`,
- portfolio.targets.repeatsPerScenario === 1
- ? '1 repeat'
- : `${portfolio.targets.repeatsPerScenario} repeats`,
+ portfolio.scenarios.length === 1 ? '1 scenario' : `${portfolio.scenarios.length} scenarios`,
+ portfolio.targets.metrics.length === 1 ? '1 metric' : `${portfolio.targets.metrics.length} metrics`,
+ portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`,
])
: [
{
@@ -77,7 +68,7 @@ const PortfolioTable = ({ projectId }) => {
? [
{
title: 'Delete Portfolio',
- onClick: (_, rowId) => deletePortfolio(portfolios[rowId]._id),
+ onClick: (_, rowId) => deletePortfolio({ projectId, number: portfolios[rowId].number }),
},
]
: []
@@ -91,7 +82,7 @@ const PortfolioTable = ({ projectId }) => {
}
PortfolioTable.propTypes = {
- projectId: PropTypes.string,
+ projectId: PropTypes.number,
}
export default PortfolioTable
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js
index 65b8f5a0..3e1656f6 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js
@@ -92,7 +92,7 @@ function ProjectOverview({ projectId }) {
}
ProjectOverview.propTypes = {
- projectId: PropTypes.string,
+ projectId: PropTypes.number,
}
export default ProjectOverview
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js
index a7290259..6921578c 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectTable.js
@@ -5,26 +5,23 @@ import { Project, Status } from '../../shapes'
import { Table, TableBody, TableHeader } from '@patternfly/react-table'
import { parseAndFormatDateTime } from '../../util/date-time'
import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP } from '../../util/authorizations'
-import { useAuth } from '../../auth'
import TableEmptyState from '../util/TableEmptyState'
const ProjectTable = ({ status, projects, onDelete, isFiltering }) => {
- const { user } = useAuth()
const columns = ['Project name', 'Last edited', 'Access Rights']
const rows =
projects.length > 0
? projects.map((project) => {
- const { level } = project.authorizations.find((auth) => auth.userId === user.sub)
- const Icon = AUTH_ICON_MAP[level]
+ const Icon = AUTH_ICON_MAP[project.role]
return [
{
- title: <Link href={`/projects/${project._id}`}>{project.name}</Link>,
+ title: <Link href={`/projects/${project.id}`}>{project.name}</Link>,
},
- parseAndFormatDateTime(project.datetimeLastEdited),
+ parseAndFormatDateTime(project.updatedAt),
{
title: (
<>
- <Icon className="pf-u-mr-md" key="auth" /> {AUTH_DESCRIPTION_MAP[level]}
+ <Icon className="pf-u-mr-md" key="auth" /> {AUTH_DESCRIPTION_MAP[project.role]}
</>
),
},
diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js
index 80099ece..ced5304a 100644
--- a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js
+++ b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js
@@ -26,26 +26,21 @@ import { Table, TableBody, TableHeader } from '@patternfly/react-table'
import React from 'react'
import TableEmptyState from '../util/TableEmptyState'
import { parseAndFormatDateTime } from '../../util/date-time'
-import { useMutation } from 'react-query'
-import { useProjectTopologies } from '../../data/topology'
+import { useTopologies, useDeleteTopology } from '../../data/topology'
const TopologyTable = ({ projectId }) => {
- const { status, data: topologies = [] } = useProjectTopologies(projectId)
- const { mutate: deleteTopology } = useMutation('deleteTopology')
+ const { status, data: topologies = [] } = useTopologies(projectId)
+ const { mutate: deleteTopology } = useDeleteTopology()
const columns = ['Name', 'Rooms', 'Last Edited']
const rows =
topologies.length > 0
? topologies.map((topology) => [
{
- title: (
- <Link href={`/projects/${topology.projectId}/topologies/${topology._id}`}>
- {topology.name}
- </Link>
- ),
+ title: <Link href={`/projects/${projectId}/topologies/${topology.number}`}>{topology.name}</Link>,
},
topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`,
- parseAndFormatDateTime(topology.datetimeLastEdited),
+ parseAndFormatDateTime(topology.updatedAt),
])
: [
{
@@ -69,7 +64,7 @@ const TopologyTable = ({ projectId }) => {
const actionResolver = (_, { rowIndex }) => [
{
title: 'Delete Topology',
- onClick: (_, rowId) => deleteTopology(topologies[rowId]._id),
+ onClick: (_, rowId) => deleteTopology({ projectId, number: topologies[rowId].number }),
isDisabled: rowIndex === 0,
},
]
@@ -89,7 +84,7 @@ const TopologyTable = ({ projectId }) => {
}
TopologyTable.propTypes = {
- projectId: PropTypes.string,
+ projectId: PropTypes.number,
}
export default TopologyTable
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js
index 9bf369e9..49e5f095 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js
@@ -7,11 +7,11 @@ import { Table, TableBody, TableHeader } from '@patternfly/react-table'
import { deleteRoom } from '../../redux/actions/topology/room'
import TableEmptyState from '../util/TableEmptyState'
-function RoomTable({ topologyId, onSelect }) {
+function RoomTable({ projectId, topologyId, onSelect }) {
const dispatch = useDispatch()
- const { status, data: topology } = useTopology(topologyId)
+ const { status, data: topology } = useTopology(projectId, topologyId)
- const onDelete = (room) => dispatch(deleteRoom(room._id))
+ const onDelete = (room) => dispatch(deleteRoom(room.id))
const columns = ['Name', 'Tiles', 'Racks']
const rows =
@@ -62,7 +62,8 @@ function RoomTable({ topologyId, onSelect }) {
}
RoomTable.propTypes = {
- topologyId: PropTypes.string,
+ projectId: PropTypes.number,
+ topologyId: PropTypes.number,
onSelect: PropTypes.func,
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js
index 213a4868..f8ee4990 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js
@@ -38,8 +38,8 @@ import { useTopology } from '../../data/topology'
import { parseAndFormatDateTime } from '../../util/date-time'
import RoomTable from './RoomTable'
-function TopologyOverview({ topologyId, onSelect }) {
- const { data: topology } = useTopology(topologyId)
+function TopologyOverview({ projectId, topologyNumber, onSelect }) {
+ const { data: topology } = useTopology(projectId, topologyNumber)
return (
<Grid hasGutter>
<GridItem md={2}>
@@ -57,7 +57,7 @@ function TopologyOverview({ topologyId, onSelect }) {
<DescriptionListTerm>Last edited</DescriptionListTerm>
<DescriptionListDescription>
{topology ? (
- parseAndFormatDateTime(topology.datetimeLastEdited)
+ parseAndFormatDateTime(topology.updatedAt)
) : (
<Skeleton screenreaderText="Loading topology" />
)}
@@ -71,7 +71,11 @@ function TopologyOverview({ topologyId, onSelect }) {
<Card>
<CardTitle>Rooms</CardTitle>
<CardBody>
- <RoomTable topologyId={topologyId} onSelect={(room) => onSelect('room', room)} />
+ <RoomTable
+ projectId={projectId}
+ topologyId={topologyNumber}
+ onSelect={(room) => onSelect('room', room)}
+ />
</CardBody>
</Card>
</GridItem>
@@ -80,7 +84,8 @@ function TopologyOverview({ topologyId, onSelect }) {
}
TopologyOverview.propTypes = {
- topologyId: PropTypes.string,
+ projectId: PropTypes.number,
+ topologyNumber: PropTypes.number,
onSelect: PropTypes.func,
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
index 411a5ca7..21be3c79 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js
@@ -33,7 +33,7 @@ function TileContainer({ tileId, ...props }) {
const dispatch = useDispatch()
const onClick = (tile) => {
if (tile.rack) {
- dispatch(goFromRoomToRack(tile._id))
+ dispatch(goFromRoomToRack(tile.id))
}
}
return <TileGroup {...props} onClick={onClick} tile={tile} interactionLevel={interactionLevel} />
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js
index 7d304b6b..fdae53f2 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js
@@ -21,6 +21,7 @@ function ImageComponent({ src, x, y, width, height, opacity }) {
}
}, [src])
+ // eslint-disable-next-line jsx-a11y/alt-text
return <Image image={image} x={x} y={y} width={width} height={height} opacity={opacity} />
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js
index 46030135..dad2d62d 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js
@@ -11,8 +11,8 @@ function RackGroup({ tile }) {
<Group>
<TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} />
<Group>
- <RackSpaceFillContainer tileId={tile._id} positionX={tile.positionX} positionY={tile.positionY} />
- <RackEnergyFillContainer tileId={tile._id} positionX={tile.positionX} positionY={tile.positionY} />
+ <RackSpaceFillContainer tileId={tile.id} positionX={tile.positionX} positionY={tile.positionY} />
+ <RackEnergyFillContainer tileId={tile.id} positionX={tile.positionX} positionY={tile.positionY} />
</Group>
</Group>
)
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js
index a42e7bb7..3f8b3089 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js
@@ -7,7 +7,7 @@ import TileContainer from '../TileContainer'
import WallContainer from '../WallContainer'
function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) {
- if (currentRoomInConstruction === room._id) {
+ if (currentRoomInConstruction === room.id) {
return (
<Group onClick={onClick}>
{room.tiles.map((tileId) => (
@@ -22,7 +22,7 @@ function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick
{(() => {
if (
(interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') &&
- interactionLevel.roomId === room._id
+ interactionLevel.roomId === room.id
) {
return [
room.tiles
@@ -37,7 +37,7 @@ function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick
return room.tiles.map((tileId) => <TileContainer key={tileId} tileId={tileId} />)
}
})()}
- <WallContainer roomId={room._id} />
+ <WallContainer roomId={room.id} />
</Group>
)
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js
index 5e351691..727f4e25 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js
@@ -40,8 +40,8 @@ function RoomHoverLayer() {
.map((id) => ({ ...state.topology.rooms[id] }))
.filter(
(room) =>
- state.topology.root.rooms.indexOf(room._id) !== -1 &&
- room._id !== state.construction.currentRoomInConstruction
+ state.topology.root.rooms.indexOf(room.id) !== -1 &&
+ room.id !== state.construction.currentRoomInConstruction
)
;[...oldRooms, newRoom].forEach((room) => {
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
index 9268f615..6f89e10b 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js
@@ -17,7 +17,7 @@ function MachineSidebar({ tileId, position }) {
const rack = topology.racks[topology.tiles[tileId].rack]
return topology.machines[rack.machines[position - 1]]
})
- const machineId = machine._id
+ const machineId = machine.id
return (
<div>
<TextContent>
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js
index 88591208..4507b409 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js
@@ -22,7 +22,7 @@ function UnitAddComponent({ units, onAdd }) {
selections={selected}
>
{units.map((unit) => (
- <SelectOption value={unit._id} key={unit._id}>
+ <SelectOption value={unit.id} key={unit.id}>
{unit.name}
</SelectOption>
))}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
index 6dcc414f..25e750c4 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js
@@ -33,7 +33,7 @@ function UnitListContainer({ machineId, unitType }) {
return machine[unitType].map((id) => state.topology[unitType][id])
})
- const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit._id))
+ const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit.id))
return <UnitListComponent units={units} unitType={unitType} onDelete={onDelete} />
}
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
index e944c2e8..6a0c3ff3 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js
@@ -22,14 +22,11 @@
import PropTypes from 'prop-types'
import React from 'react'
-import { useDispatch } from 'react-redux'
import { Button } from '@patternfly/react-core'
import { SaveIcon } from '@patternfly/react-icons'
-import { addPrefab } from '../../../../api/prefabs'
-function AddPrefab({ tileId }) {
- const dispatch = useDispatch()
- const onClick = () => dispatch(addPrefab('name', tileId))
+function AddPrefab() {
+ const onClick = () => {} // TODO
return (
<Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm">
Save this rack to a prefab
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
index 619bb4e2..e1914730 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js
@@ -43,7 +43,7 @@ function MachineListContainer({ tileId, ...props }) {
<MachineListComponent
{...props}
machines={machinesNull}
- onAdd={(index) => dispatch(addMachine(rack._id, index))}
+ onAdd={(index) => dispatch(addMachine(rack.id, index))}
onSelect={(index) => dispatch(goFromRackToMachine(index))}
/>
)
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
index 30f38cce..c3422318 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js
@@ -5,11 +5,11 @@ import NameComponent from '../NameComponent'
import { editRackName } from '../../../../redux/actions/topology/rack'
const RackNameContainer = ({ tileId }) => {
- const { name: rackName, _id } = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack])
+ const { name: rackName, id } = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack])
const dispatch = useDispatch()
const callback = (name) => {
if (name) {
- dispatch(editRackName(_id, name))
+ dispatch(editRackName(id, name))
}
}
return <NameComponent name={rackName} onEdit={callback} />
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
index fb52d826..72d45bea 100644
--- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
+++ b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js
@@ -27,11 +27,11 @@ import NameComponent from '../NameComponent'
import { editRoomName } from '../../../../redux/actions/topology/room'
function RoomName({ roomId }) {
- const { name: roomName, _id } = useSelector((state) => state.topology.rooms[roomId])
+ const { name: roomName, id } = useSelector((state) => state.topology.rooms[roomId])
const dispatch = useDispatch()
const callback = (name) => {
if (name) {
- dispatch(editRoomName(_id, name))
+ dispatch(editRoomName(id, name))
}
}
return <NameComponent name={roomName} onEdit={callback} />
diff --git a/opendc-web/opendc-web-ui/src/data/experiments.js b/opendc-web/opendc-web-ui/src/data/experiments.js
index a76ea53f..ca8912a2 100644
--- a/opendc-web/opendc-web-ui/src/data/experiments.js
+++ b/opendc-web/opendc-web-ui/src/data/experiments.js
@@ -35,13 +35,13 @@ export function configureExperimentClient(queryClient, auth) {
/**
* Return the available traces to experiment with.
*/
-export function useTraces() {
- return useQuery('traces')
+export function useTraces(options) {
+ return useQuery('traces', options)
}
/**
* Return the available schedulers to experiment with.
*/
-export function useSchedulers() {
- return useQuery('schedulers')
+export function useSchedulers(options) {
+ return useQuery('schedulers', options)
}
diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js
index 9dcd8532..b1db3da5 100644
--- a/opendc-web/opendc-web-ui/src/data/project.js
+++ b/opendc-web/opendc-web-ui/src/data/project.js
@@ -20,9 +20,9 @@
* SOFTWARE.
*/
-import { useQuery } from 'react-query'
+import { useQuery, useMutation } from 'react-query'
import { addProject, deleteProject, fetchProject, fetchProjects } from '../api/projects'
-import { addPortfolio, deletePortfolio, fetchPortfolio, fetchPortfoliosOfProject } from '../api/portfolios'
+import { addPortfolio, deletePortfolio, fetchPortfolio, fetchPortfolios } from '../api/portfolios'
import { addScenario, deleteScenario, fetchScenario, fetchScenariosOfPortfolio } from '../api/scenarios'
/**
@@ -37,79 +37,60 @@ export function configureProjectClient(queryClient, auth) {
mutationFn: (data) => addProject(auth, data),
onSuccess: async (result) => {
queryClient.setQueryData('projects', (old = []) => [...old, result])
- queryClient.setQueryData(['projects', result._id], result)
+ queryClient.setQueryData(['projects', result.id], result)
},
})
queryClient.setMutationDefaults('deleteProject', {
mutationFn: (id) => deleteProject(auth, id),
onSuccess: async (result) => {
- queryClient.setQueryData('projects', (old = []) => old.filter((project) => project._id !== result._id))
- queryClient.removeQueries(['projects', result._id])
+ queryClient.setQueryData('projects', (old = []) => old.filter((project) => project.id !== result.id))
+ queryClient.removeQueries(['projects', result.id])
},
})
queryClient.setQueryDefaults('portfolios', {
- queryFn: ({ queryKey }) => fetchPortfolio(auth, queryKey[1]),
- })
- queryClient.setQueryDefaults('project-portfolios', {
- queryFn: ({ queryKey }) => fetchPortfoliosOfProject(auth, queryKey[1]),
+ queryFn: ({ queryKey }) =>
+ queryKey.length === 2 ? fetchPortfolios(auth, queryKey[1]) : fetchPortfolio(auth, queryKey[1], queryKey[2]),
})
queryClient.setMutationDefaults('addPortfolio', {
- mutationFn: (data) => addPortfolio(auth, data),
+ mutationFn: ({ projectId, ...data }) => addPortfolio(auth, projectId, data),
onSuccess: async (result) => {
- queryClient.setQueryData(['projects', result.projectId], (old) => ({
- ...old,
- portfolioIds: [...old.portfolioIds, result._id],
- }))
- queryClient.setQueryData(['project-portfolios', result.projectId], (old = []) => [...old, result])
- queryClient.setQueryData(['portfolios', result._id], result)
+ queryClient.setQueryData(['portfolios', result.project.id], (old = []) => [...old, result])
+ queryClient.setQueryData(['portfolios', result.project.id, result.number], result)
},
})
queryClient.setMutationDefaults('deletePortfolio', {
- mutationFn: (id) => deletePortfolio(auth, id),
+ mutationFn: ({ projectId, number }) => deletePortfolio(auth, projectId, number),
onSuccess: async (result) => {
- queryClient.setQueryData(['projects', result.projectId], (old) => ({
- ...old,
- portfolioIds: old.portfolioIds.filter((id) => id !== result._id),
- }))
- queryClient.setQueryData(['project-portfolios', result.projectId], (old = []) =>
- old.filter((portfolio) => portfolio._id !== result._id)
+ queryClient.setQueryData(['portfolios', result.project.id], (old = []) =>
+ old.filter((portfolio) => portfolio.id !== result.id)
)
- queryClient.removeQueries(['portfolios', result._id])
+ queryClient.removeQueries(['portfolios', result.project.id, result.number])
},
})
queryClient.setQueryDefaults('scenarios', {
- queryFn: ({ queryKey }) => fetchScenario(auth, queryKey[1]),
- })
- queryClient.setQueryDefaults('portfolio-scenarios', {
- queryFn: ({ queryKey }) => fetchScenariosOfPortfolio(auth, queryKey[1]),
+ queryFn: ({ queryKey }) => fetchScenario(auth, queryKey[1], queryKey[2]),
})
queryClient.setMutationDefaults('addScenario', {
- mutationFn: (data) => addScenario(auth, data),
+ mutationFn: ({ projectId, portfolioNumber, data }) => addScenario(auth, projectId, portfolioNumber, data),
onSuccess: async (result) => {
// Register updated scenario in cache
- queryClient.setQueryData(['scenarios', result._id], result)
- queryClient.setQueryData(['portfolio-scenarios', result.portfolioId], (old = []) => [...old, result])
-
- // Add scenario id to portfolio
- queryClient.setQueryData(['portfolios', result.portfolioId], (old) => ({
+ queryClient.setQueryData(['scenarios', result.project.id, result.id], result)
+ queryClient.setQueryData(['portfolios', result.project.id, result.portfolio.number], (old) => ({
...old,
- scenarioIds: [...old.scenarioIds, result._id],
+ scenarios: [...old.scenarios, result],
}))
},
})
queryClient.setMutationDefaults('deleteScenario', {
- mutationFn: (id) => deleteScenario(auth, id),
+ mutationFn: ({ projectId, number }) => deleteScenario(auth, projectId, number),
onSuccess: async (result) => {
- queryClient.setQueryData(['portfolios', result.portfolioId], (old) => ({
+ queryClient.removeQueries(['scenarios', result.project.id, result.id])
+ queryClient.setQueryData(['portfolios', result.project.id, result.portfolio.number], (old) => ({
...old,
- scenarioIds: old.scenarioIds.filter((id) => id !== result._id),
+ scenarios: old?.scenarios?.filter((scenario) => scenario.id !== result.id),
}))
- queryClient.setQueryData(['portfolio-scenarios', result.portfolioId], (old = []) =>
- old.filter((scenario) => scenario._id !== result._id)
- )
- queryClient.removeQueries(['scenarios', result._id])
},
})
}
@@ -129,22 +110,57 @@ export function useProject(projectId, options = {}) {
}
/**
+ * Create a mutation for a new project.
+ */
+export function useNewProject() {
+ return useMutation('addProject')
+}
+
+/**
+ * Create a mutation for deleting a project.
+ */
+export function useDeleteProject() {
+ return useMutation('deleteProject')
+}
+
+/**
* Return the portfolio with the specified identifier.
*/
-export function usePortfolio(portfolioId, options = {}) {
- return useQuery(['portfolios', portfolioId], { enabled: !!portfolioId, ...options })
+export function usePortfolio(projectId, portfolioId, options = {}) {
+ return useQuery(['portfolios', projectId, portfolioId], { enabled: !!(projectId && portfolioId), ...options })
}
/**
* Return the portfolios of the specified project.
*/
-export function useProjectPortfolios(projectId, options = {}) {
- return useQuery(['project-portfolios', projectId], { enabled: !!projectId, ...options })
+export function usePortfolios(projectId, options = {}) {
+ return useQuery(['portfolios', projectId], { enabled: !!projectId, ...options })
+}
+
+/**
+ * Create a mutation for a new portfolio.
+ */
+export function useNewPortfolio() {
+ return useMutation('addPortfolio')
+}
+
+/**
+ * Create a mutation for deleting a portfolio.
+ */
+export function useDeletePortfolio() {
+ return useMutation('deletePortfolio')
+}
+
+/**
+ * Create a mutation for a new scenario.
+ */
+export function useNewScenario() {
+ return useMutation('addScenario')
}
/**
- * Return the scenarios of the specified portfolio.
+ * Create a mutation for deleting a scenario.
*/
-export function usePortfolioScenarios(portfolioId, options = {}) {
- return useQuery(['portfolio-scenarios', portfolioId], { enabled: !!portfolioId, ...options })
+export function useDeleteScenario() {
+ return useMutation('deleteScenario')
}
diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js
index e068ed8e..cf098c56 100644
--- a/opendc-web/opendc-web-ui/src/data/topology.js
+++ b/opendc-web/opendc-web-ui/src/data/topology.js
@@ -20,58 +20,69 @@
* SOFTWARE.
*/
-import { useQuery } from 'react-query'
-import { addTopology, deleteTopology, fetchTopologiesOfProject, fetchTopology, updateTopology } from '../api/topologies'
+import { useQuery, useMutation } from 'react-query'
+import { addTopology, deleteTopology, fetchTopologies, fetchTopology, updateTopology } from '../api/topologies'
/**
* Configure the query defaults for the topology endpoints.
*/
export function configureTopologyClient(queryClient, auth) {
- queryClient.setQueryDefaults('topologies', { queryFn: ({ queryKey }) => fetchTopology(auth, queryKey[1]) })
- queryClient.setQueryDefaults('project-topologies', {
- queryFn: ({ queryKey }) => fetchTopologiesOfProject(auth, queryKey[1]),
+ queryClient.setQueryDefaults('topologies', {
+ queryFn: ({ queryKey }) =>
+ queryKey.length === 2 ? fetchTopologies(auth, queryKey[1]) : fetchTopology(auth, queryKey[1], queryKey[2]),
})
queryClient.setMutationDefaults('addTopology', {
- mutationFn: (data) => addTopology(auth, data),
- onSuccess: async (result) => {
- queryClient.setQueryData(['projects', result.projectId], (old) => ({
- ...old,
- topologyIds: [...old.topologyIds, result._id],
- }))
- queryClient.setQueryData(['project-topologies', result.projectId], (old = []) => [...old, result])
- queryClient.setQueryData(['topologies', result._id], result)
+ mutationFn: ({ projectId, ...data }) => addTopology(auth, projectId, data),
+ onSuccess: (result) => {
+ queryClient.setQueryData(['topologies', result.project.id], (old = []) => [...old, result])
+ queryClient.setQueryData(['topologies', result.project.id, result.number], result)
},
})
queryClient.setMutationDefaults('updateTopology', {
mutationFn: (data) => updateTopology(auth, data),
- onSuccess: (result) => queryClient.setQueryData(['topologies', result._id], result),
+ onSuccess: (result) => {
+ queryClient.setQueryData(['topologies', result.project.id], (old = []) =>
+ old.map((topology) => (topology.id === result.id ? result : topology))
+ )
+ queryClient.setQueryData(['topologies', result.project.id, result.number], result)
+ },
})
queryClient.setMutationDefaults('deleteTopology', {
- mutationFn: (id) => deleteTopology(auth, id),
- onSuccess: async (result) => {
- queryClient.setQueryData(['projects', result.projectId], (old) => ({
- ...old,
- topologyIds: old.topologyIds.filter((id) => id !== result._id),
- }))
- queryClient.setQueryData(['project-topologies', result.projectId], (old = []) =>
- old.filter((topology) => topology._id !== result._id)
+ mutationFn: ({ projectId, id }) => deleteTopology(auth, projectId, id),
+ onSuccess: (result) => {
+ queryClient.setQueryData(['topologies', result.project.id], (old = []) =>
+ old.filter((topology) => topology.id !== result.id)
)
- queryClient.removeQueries(['topologies', result._id])
+ queryClient.removeQueries(['topologies', result.project.id, result.number])
},
})
}
/**
- * Return the current active topology.
+ * Fetch the topology with the specified identifier for the specified project.
+ */
+export function useTopology(projectId, topologyId, options = {}) {
+ return useQuery(['topologies', projectId, topologyId], { enabled: !!(projectId && topologyId), ...options })
+}
+
+/**
+ * Fetch all topologies of the specified project.
+ */
+export function useTopologies(projectId, options = {}) {
+ return useQuery(['topologies', projectId], { enabled: !!projectId, ...options })
+}
+
+/**
+ * Create a mutation for a new topology.
*/
-export function useTopology(topologyId, options = {}) {
- return useQuery(['topologies', topologyId], { enabled: !!topologyId, ...options })
+export function useNewTopology() {
+ return useMutation('addTopology')
}
/**
- * Return the topologies of the specified project.
+ * Create a mutation for deleting a topology.
*/
-export function useProjectTopologies(projectId, options = {}) {
- return useQuery(['project-topologies', projectId], { enabled: !!projectId, ...options })
+export function useDeleteTopology() {
+ return useMutation('deleteTopology')
}
diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js
index 900ff405..4861f5c1 100644
--- a/opendc-web/opendc-web-ui/src/pages/_app.js
+++ b/opendc-web/opendc-web-ui/src/pages/_app.js
@@ -22,6 +22,7 @@
import PropTypes from 'prop-types'
import Head from 'next/head'
+import Script from 'next/script'
import { Provider } from 'react-redux'
import { useNewQueryClient } from '../data/query'
import { useStore } from '../redux'
@@ -91,6 +92,19 @@ export default function App(props) {
<Inner {...props} />
</AuthProvider>
</Sentry.ErrorBoundary>
+ {/* Google Analytics */}
+ <Script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3" />
+ <Script
+ id="gtag"
+ dangerouslySetInnerHTML={{
+ __html: `
+ window.dataLayer = window.dataLayer || [];
+ function gtag(){dataLayer.push(arguments);}
+ gtag('js', new Date());
+ gtag('config', 'UA-84285092-3');
+ `,
+ }}
+ />
</>
)
}
diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js
index 51d8d3e0..011bf4da 100644
--- a/opendc-web/opendc-web-ui/src/pages/_document.js
+++ b/opendc-web/opendc-web-ui/src/pages/_document.js
@@ -69,19 +69,6 @@ class OpenDCDocument extends Document {
href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap"
rel="stylesheet"
/>
-
- {/* Google Analytics */}
- <script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3" />
- <script
- dangerouslySetInnerHTML={{
- __html: `
- window.dataLayer = window.dataLayer || [];
- function gtag(){dataLayer.push(arguments);}
- gtag('js', new Date());
- gtag('config', 'UA-84285092-3');
- `,
- }}
- />
</Head>
<body>
<Main />
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js
index c07a2c31..39fcb4f3 100644
--- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js
@@ -40,9 +40,9 @@ import BreadcrumbLink from '../../../components/util/BreadcrumbLink'
function Project() {
const router = useRouter()
- const { project: projectId } = router.query
+ const projectId = +router.query['project']
- const { data: project } = useProject(projectId)
+ const { data: project } = useProject(+projectId)
const breadcrumb = (
<Breadcrumb>
@@ -57,7 +57,7 @@ function Project() {
const contextSelectors = (
<ContextSelectionSection>
- <ProjectSelector projectId={projectId} />
+ <ProjectSelector activeProject={project} />
</ContextSelectionSection>
)
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js
index d1533d98..68345d0b 100644
--- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js
@@ -20,6 +20,7 @@
* SOFTWARE.
*/
+import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import Head from 'next/head'
import React, { useRef } from 'react'
@@ -42,18 +43,24 @@ import PortfolioSelector from '../../../../components/context/PortfolioSelector'
import ProjectSelector from '../../../../components/context/ProjectSelector'
import BreadcrumbLink from '../../../../components/util/BreadcrumbLink'
import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview'
-import PortfolioResults from '../../../../components/portfolios/PortfolioResults'
+import { usePortfolio } from '../../../../data/project'
+
+const PortfolioResults = dynamic(() => import('../../../../components/portfolios/PortfolioResults'))
/**
* Page that displays the results in a portfolio.
*/
function Portfolio() {
const router = useRouter()
- const { project: projectId, portfolio: portfolioId } = router.query
+ const projectId = +router.query['project']
+ const portfolioNumber = +router.query['portfolio']
const overviewRef = useRef(null)
const resultsRef = useRef(null)
+ const { data: portfolio } = usePortfolio(projectId, portfolioNumber)
+ const project = portfolio?.project
+
const breadcrumb = (
<Breadcrumb>
<BreadcrumbItem to="/projects" component={BreadcrumbLink}>
@@ -62,7 +69,11 @@ function Portfolio() {
<BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}>
Project details
</BreadcrumbItem>
- <BreadcrumbItem to={`/projects/${projectId}/portfolios/${portfolioId}`} component={BreadcrumbLink} isActive>
+ <BreadcrumbItem
+ to={`/projects/${projectId}/portfolios/${portfolioNumber}`}
+ component={BreadcrumbLink}
+ isActive
+ >
Portfolio
</BreadcrumbItem>
</Breadcrumb>
@@ -70,8 +81,8 @@ function Portfolio() {
const contextSelectors = (
<ContextSelectionSection>
- <ProjectSelector projectId={projectId} />
- <PortfolioSelector projectId={projectId} portfolioId={portfolioId} />
+ <ProjectSelector activeProject={project} />
+ <PortfolioSelector activePortfolio={portfolio} />
</ContextSelectionSection>
)
@@ -104,10 +115,10 @@ function Portfolio() {
</PageSection>
<PageSection isFilled>
<TabContent eventKey={0} id="overview" ref={overviewRef} aria-label="Overview tab">
- <PortfolioOverview portfolioId={portfolioId} />
+ <PortfolioOverview projectId={projectId} portfolioId={portfolioNumber} />
</TabContent>
<TabContent eventKey={1} id="results" ref={resultsRef} aria-label="Results tab" hidden>
- <PortfolioResults portfolioId={portfolioId} />
+ <PortfolioResults projectId={projectId} portfolioId={portfolioNumber} />
</TabContent>
</PageSection>
</AppPage>
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js
index f7188d9f..6297b8c3 100644
--- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js
@@ -26,7 +26,6 @@ import ContextSelectionSection from '../../../../components/context/ContextSelec
import ProjectSelector from '../../../../components/context/ProjectSelector'
import TopologySelector from '../../../../components/context/TopologySelector'
import TopologyOverview from '../../../../components/topologies/TopologyOverview'
-import { useProject } from '../../../../data/project'
import { useDispatch } from 'react-redux'
import React, { useEffect, useState } from 'react'
import Head from 'next/head'
@@ -45,6 +44,7 @@ import {
TextContent,
} from '@patternfly/react-core'
import BreadcrumbLink from '../../../../components/util/BreadcrumbLink'
+import { useTopology } from '../../../../data/topology'
import { goToRoom } from '../../../../redux/actions/interaction-level'
import { openTopology } from '../../../../redux/actions/topology'
@@ -55,16 +55,18 @@ const TopologyMap = dynamic(() => import('../../../../components/topologies/Topo
*/
function Topology() {
const router = useRouter()
- const { project: projectId, topology: topologyId } = router.query
+ const projectId = +router.query['project']
+ const topologyNumber = +router.query['topology']
- const { data: project } = useProject(projectId)
+ const { data: topology } = useTopology(projectId, topologyNumber)
+ const project = topology?.project
const dispatch = useDispatch()
useEffect(() => {
- if (topologyId) {
- dispatch(openTopology(topologyId))
+ if (topologyNumber) {
+ dispatch(openTopology(projectId, topologyNumber))
}
- }, [topologyId, dispatch])
+ }, [projectId, topologyNumber, dispatch])
const [activeTab, setActiveTab] = useState('overview')
@@ -76,7 +78,11 @@ function Topology() {
<BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}>
Project details
</BreadcrumbItem>
- <BreadcrumbItem to={`/projects/${projectId}/topologies/${topologyId}`} component={BreadcrumbLink} isActive>
+ <BreadcrumbItem
+ to={`/projects/${projectId}/topologies/${topologyNumber}`}
+ component={BreadcrumbLink}
+ isActive
+ >
Topology
</BreadcrumbItem>
</Breadcrumb>
@@ -84,8 +90,8 @@ function Topology() {
const contextSelectors = (
<ContextSelectionSection>
- <ProjectSelector projectId={projectId} />
- <TopologySelector projectId={projectId} topologyId={topologyId} />
+ <ProjectSelector activeProject={project} />
+ <TopologySelector activeTopology={topology} />
</ContextSelectionSection>
)
@@ -117,16 +123,22 @@ function Topology() {
<PageSection padding={activeTab === 'floor-plan' && { default: 'noPadding' }} isFilled>
<TabContent id="overview" aria-label="Overview tab" hidden={activeTab !== 'overview'}>
<TopologyOverview
- topologyId={topologyId}
+ projectId={projectId}
+ topologyNumber={topologyNumber}
onSelect={(type, obj) => {
if (type === 'room') {
- dispatch(goToRoom(obj._id))
+ dispatch(goToRoom(obj.id))
setActiveTab('floor-plan')
}
}}
/>
</TabContent>
- <TabContent id="floor-plan" aria-label="Floor Plan tab" className="pf-u-h-100" hidden={activeTab !== 'floor-plan'}>
+ <TabContent
+ id="floor-plan"
+ aria-label="Floor Plan tab"
+ className="pf-u-h-100"
+ hidden={activeTab !== 'floor-plan'}
+ >
<TopologyMap />
</TabContent>
</PageSection>
diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js
index eb77701e..bb1fbd69 100644
--- a/opendc-web/opendc-web-ui/src/pages/projects/index.js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js
@@ -26,9 +26,8 @@ import ProjectFilterPanel from '../../components/projects/FilterPanel'
import { useAuth } from '../../auth'
import { AppPage } from '../../components/AppPage'
import { PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core'
-import { useProjects } from '../../data/project'
+import { useProjects, useDeleteProject } from '../../data/project'
import ProjectTable from '../../components/projects/ProjectTable'
-import { useMutation } from 'react-query'
import NewProject from '../../components/projects/NewProject'
const getVisibleProjects = (projects, filter, userId) => {
@@ -52,13 +51,12 @@ function Projects() {
const { user } = useAuth()
const { status, data: projects } = useProjects()
const [filter, setFilter] = useState('SHOW_ALL')
- const visibleProjects = useMemo(() => getVisibleProjects(projects ?? [], filter, user?.sub), [
- projects,
- filter,
- user?.sub,
- ])
+ const visibleProjects = useMemo(
+ () => getVisibleProjects(projects ?? [], filter, user?.sub),
+ [projects, filter, user?.sub]
+ )
- const { mutate: deleteProject } = useMutation('deleteProject')
+ const { mutate: deleteProject } = useDeleteProject()
return (
<AppPage>
@@ -76,7 +74,7 @@ function Projects() {
status={status}
isFiltering={filter !== 'SHOW_ALL'}
projects={visibleProjects}
- onDelete={(project) => deleteProject(project._id)}
+ onDelete={(project) => deleteProject(project.id)}
/>
<NewProject />
</PageSection>
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 939c24a4..e430da2e 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
@@ -14,16 +14,16 @@ export const DELETE_TILE = 'DELETE_TILE'
export function startNewRoomConstruction() {
return (dispatch, getState) => {
const { topology } = getState()
- const topologyId = topology.root._id
+ const topologyId = topology.root.id
const room = {
- _id: uuid(),
+ id: uuid(),
name: 'Room',
topologyId,
tiles: [],
}
dispatch(addRoom(topologyId, room))
- dispatch(startNewRoomConstructionSucceeded(room._id))
+ dispatch(startNewRoomConstructionSucceeded(room.id))
}
}
@@ -97,7 +97,7 @@ export function addTile(roomId, positionX, positionY) {
return {
type: ADD_TILE,
tile: {
- _id: uuid(),
+ id: uuid(),
roomId,
positionX,
positionY,
diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js
index 94a712c4..d48af37a 100644
--- a/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js
+++ b/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js
@@ -23,9 +23,10 @@
export const OPEN_TOPOLOGY = 'OPEN_TOPOLOGY'
export const STORE_TOPOLOGY = 'STORE_TOPOLOGY'
-export function openTopology(id) {
+export function openTopology(projectId, id) {
return {
type: OPEN_TOPOLOGY,
+ projectId,
id,
}
}
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 c319d966..308acaa6 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
@@ -24,7 +24,7 @@ export function addMachine(rackId, position) {
return {
type: ADD_MACHINE,
machine: {
- _id: uuid(),
+ id: uuid(),
rackId,
position,
cpus: [],
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 bd447db5..fd2d8cdc 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
@@ -16,7 +16,7 @@ export function addRoom(topologyId, room) {
return {
type: ADD_ROOM,
room: {
- _id: uuid(),
+ id: uuid(),
topologyId,
...room,
},
@@ -54,9 +54,9 @@ export function addRackToTile(positionX, positionY) {
dispatch({
type: ADD_RACK_TO_TILE,
rack: {
- _id: uuid(),
+ id: uuid(),
name: 'Rack',
- tileId: tile._id,
+ 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/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js
index 47af53cf..1789257b 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js
@@ -10,7 +10,7 @@ function machine(state = {}, action, { racks }) {
case ADD_MACHINE:
return produce(state, (draft) => {
const { machine } = action
- draft[machine._id] = machine
+ draft[machine.id] = machine
})
case DELETE_MACHINE:
return produce(state, (draft) => {
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
index 155837cb..ca79348a 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js
@@ -33,7 +33,7 @@ function rack(state = {}, action, { machines }) {
case ADD_RACK_TO_TILE:
return produce(state, (draft) => {
const { rack } = action
- draft[rack._id] = rack
+ draft[rack.id] = rack
})
case EDIT_RACK_NAME:
return produce(state, (draft) => {
@@ -48,7 +48,7 @@ function rack(state = {}, action, { machines }) {
case ADD_MACHINE:
return produce(state, (draft) => {
const { machine } = action
- draft[machine.rackId].machines.push(machine._id)
+ draft[machine.rackId].machines.push(machine.id)
})
case DELETE_MACHINE:
return produce(state, (draft) => {
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
index d6cc51c1..c05c8bfa 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js
@@ -32,7 +32,7 @@ function room(state = {}, action, { tiles }) {
case ADD_ROOM:
return produce(state, (draft) => {
const { room } = action
- draft[room._id] = room
+ draft[room.id] = room
})
case DELETE_ROOM:
return produce(state, (draft) => {
@@ -47,7 +47,7 @@ function room(state = {}, action, { tiles }) {
case ADD_TILE:
return produce(state, (draft) => {
const { tile } = action
- draft[tile.roomId].tiles.push(tile._id)
+ draft[tile.roomId].tiles.push(tile.id)
})
case DELETE_TILE:
return produce(state, (draft) => {
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
index 6dbccb66..8e5ecd6e 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js
@@ -33,7 +33,7 @@ function tile(state = {}, action, { racks }) {
case ADD_TILE:
return produce(state, (draft) => {
const { tile } = action
- draft[tile._id] = tile
+ draft[tile.id] = tile
})
case DELETE_TILE:
return produce(state, (draft) => {
@@ -43,7 +43,7 @@ function tile(state = {}, action, { racks }) {
case ADD_RACK_TO_TILE:
return produce(state, (draft) => {
const { rack } = action
- draft[rack.tileId].rack = rack._id
+ draft[rack.tileId].rack = rack.id
})
case DELETE_RACK:
return produce(state, (draft) => {
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
index cd9b5efd..dff0a69e 100644
--- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js
+++ b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js
@@ -21,7 +21,7 @@
*/
import produce from 'immer'
-import { STORE_TOPOLOGY } from "../../actions/topology";
+import { STORE_TOPOLOGY } from '../../actions/topology'
import { ADD_ROOM, DELETE_ROOM } from '../../actions/topology/room'
function topology(state = undefined, action) {
@@ -31,7 +31,7 @@ function topology(state = undefined, action) {
case ADD_ROOM:
return produce(state, (draft) => {
const { room } = action
- draft.rooms.push(room._id)
+ draft.rooms.push(room.id)
})
case DELETE_ROOM:
return produce(state, (draft) => {
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 4c8ff5da..15147bcf 100644
--- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
+++ b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js
@@ -32,14 +32,15 @@ export function* updateServer() {
* Watch the topology on the server for changes.
*/
export function* watchServer() {
- let { id } = yield take(OPEN_TOPOLOGY)
+ let { projectId, id } = yield take(OPEN_TOPOLOGY)
while (true) {
- const channel = yield queryObserver(id)
+ const channel = yield queryObserver(projectId, id)
while (true) {
const [action, response] = yield race([take(OPEN_TOPOLOGY), take(channel)])
if (action) {
+ projectId = action.projectId
id = action.id
break
}
@@ -57,9 +58,9 @@ export function* watchServer() {
/**
* Observe changes for the topology with the specified identifier.
*/
-function* queryObserver(id) {
+function* queryObserver(projectId, id) {
const queryClient = yield getContext('queryClient')
- const observer = new QueryObserver(queryClient, { queryKey: ['topologies', id] })
+ const observer = new QueryObserver(queryClient, { queryKey: ['topologies', projectId, id] })
return eventChannel((emitter) => {
const unsubscribe = observer.subscribe((result) => {
diff --git a/opendc-web/opendc-web-ui/src/shapes.js b/opendc-web/opendc-web-ui/src/shapes.js
index abdf146e..6c93f458 100644
--- a/opendc-web/opendc-web-ui/src/shapes.js
+++ b/opendc-web/opendc-web-ui/src/shapes.js
@@ -22,26 +22,18 @@
import PropTypes from 'prop-types'
-export const User = PropTypes.shape({
- _id: PropTypes.string.isRequired,
- googleId: PropTypes.string.isRequired,
- email: PropTypes.string.isRequired,
- givenName: PropTypes.string.isRequired,
- familyName: PropTypes.string.isRequired,
- authorizations: PropTypes.array.isRequired,
-})
+export const ProjectRole = PropTypes.oneOf(['VIEWER', 'EDITOR', 'OWNER'])
export const Project = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
- datetimeCreated: PropTypes.string.isRequired,
- datetimeLastEdited: PropTypes.string.isRequired,
- topologyIds: PropTypes.array.isRequired,
- portfolioIds: PropTypes.array.isRequired,
+ createdAt: PropTypes.string.isRequired,
+ updatedAt: PropTypes.string.isRequired,
+ role: ProjectRole,
})
export const ProcessingUnit = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
clockRateMhz: PropTypes.number.isRequired,
numberOfCores: PropTypes.number.isRequired,
@@ -49,7 +41,7 @@ export const ProcessingUnit = PropTypes.shape({
})
export const StorageUnit = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
speedMbPerS: PropTypes.number.isRequired,
sizeMb: PropTypes.number.isRequired,
@@ -57,38 +49,45 @@ export const StorageUnit = PropTypes.shape({
})
export const Machine = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
position: PropTypes.number.isRequired,
- cpus: PropTypes.arrayOf(PropTypes.string),
- gpus: PropTypes.arrayOf(PropTypes.string),
- memories: PropTypes.arrayOf(PropTypes.string),
- storages: PropTypes.arrayOf(PropTypes.string),
+ cpus: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, PropTypes.string])),
+ gpus: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, PropTypes.string])),
+ memories: PropTypes.arrayOf(PropTypes.oneOfType([StorageUnit, PropTypes.string])),
+ storages: PropTypes.arrayOf(PropTypes.oneOfType([StorageUnit, PropTypes.string])),
})
export const Rack = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
capacity: PropTypes.number.isRequired,
powerCapacityW: PropTypes.number.isRequired,
- machines: PropTypes.arrayOf(PropTypes.string),
+ machines: PropTypes.arrayOf(PropTypes.oneOfType([Machine, PropTypes.string])),
})
export const Tile = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
positionX: PropTypes.number.isRequired,
positionY: PropTypes.number.isRequired,
- rack: PropTypes.string,
+ rack: PropTypes.oneOfType([Rack, PropTypes.string]),
})
export const Room = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
- tiles: PropTypes.arrayOf(PropTypes.string),
+ tiles: PropTypes.arrayOf(PropTypes.oneOfType([Tile, PropTypes.string])),
})
export const Topology = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.number.isRequired,
+ number: PropTypes.number.isRequired,
+ project: Project.isRequired,
name: PropTypes.string.isRequired,
- rooms: PropTypes.arrayOf(PropTypes.string),
+ rooms: PropTypes.arrayOf(PropTypes.oneOfType([Room, PropTypes.string])),
+})
+
+export const Phenomena = PropTypes.shape({
+ failures: PropTypes.bool.isRequired,
+ interference: PropTypes.bool.isRequired,
})
export const Scheduler = PropTypes.shape({
@@ -96,47 +95,82 @@ export const Scheduler = PropTypes.shape({
})
export const Trace = PropTypes.shape({
- _id: PropTypes.string.isRequired,
+ id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
})
-export const Portfolio = PropTypes.shape({
- _id: PropTypes.string.isRequired,
- projectId: PropTypes.string.isRequired,
+export const Workload = PropTypes.shape({
+ trace: Trace.isRequired,
+ samplingFraction: PropTypes.number.isRequired,
+})
+
+export const Targets = PropTypes.shape({
+ repeats: PropTypes.number.isRequired,
+ metrics: PropTypes.arrayOf(PropTypes.string).isRequired,
+})
+
+export const TopologySummary = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ number: PropTypes.number.isRequired,
+ project: Project.isRequired,
+ name: PropTypes.string.isRequired,
+})
+
+export const PortfolioSummary = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ number: PropTypes.number.isRequired,
+ project: Project.isRequired,
name: PropTypes.string.isRequired,
- scenarioIds: PropTypes.arrayOf(PropTypes.string).isRequired,
targets: PropTypes.shape({
- enabledMetrics: PropTypes.arrayOf(PropTypes.string).isRequired,
- repeatsPerScenario: PropTypes.number.isRequired,
+ repeats: PropTypes.number.isRequired,
+ metrics: PropTypes.arrayOf(PropTypes.string).isRequired,
}).isRequired,
})
-export const Scenario = PropTypes.shape({
- _id: PropTypes.string.isRequired,
- portfolioId: PropTypes.string.isRequired,
+export const ScenarioSummary = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ number: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
- simulation: PropTypes.shape({
- state: PropTypes.string.isRequired,
- }).isRequired,
- trace: PropTypes.shape({
- traceId: PropTypes.string.isRequired,
- trace: Trace,
- loadSamplingFraction: PropTypes.number.isRequired,
- }).isRequired,
- topology: PropTypes.shape({
- topologyId: PropTypes.string.isRequired,
- topology: Topology,
- }).isRequired,
- operational: PropTypes.shape({
- failuresEnabled: PropTypes.bool.isRequired,
- performanceInterferenceEnabled: PropTypes.bool.isRequired,
- schedulerName: PropTypes.string.isRequired,
- scheduler: Scheduler,
- }).isRequired,
+ workload: Workload.isRequired,
+ topology: TopologySummary.isRequired,
+ phenomena: Phenomena.isRequired,
+ schedulerName: PropTypes.string.isRequired,
results: PropTypes.object,
})
+export const JobState = PropTypes.oneOf(['PENDING', 'CLAIMED', 'RUNNING', 'FAILED', 'FINISHED'])
+
+export const Job = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ state: JobState.isRequired,
+ createdAt: PropTypes.string.isRequired,
+ updatedAt: PropTypes.string.isRequired,
+ results: PropTypes.object,
+})
+
+export const Scenario = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ number: PropTypes.number.isRequired,
+ project: Project.isRequired,
+ portfolio: PortfolioSummary.isRequired,
+ name: PropTypes.string.isRequired,
+ workload: Workload.isRequired,
+ topology: TopologySummary.isRequired,
+ phenomena: Phenomena.isRequired,
+ schedulerName: PropTypes.string.isRequired,
+ job: Job.isRequired,
+})
+
+export const Portfolio = PropTypes.shape({
+ id: PropTypes.number.isRequired,
+ number: PropTypes.number.isRequired,
+ name: PropTypes.string.isRequired,
+ project: Project.isRequired,
+ targets: Targets.isRequired,
+ scenarios: PropTypes.arrayOf(ScenarioSummary).isRequired,
+})
+
export const WallSegment = PropTypes.shape({
startPosX: PropTypes.number.isRequired,
startPosY: PropTypes.number.isRequired,
diff --git a/opendc-web/opendc-web-ui/src/util/authorizations.js b/opendc-web/opendc-web-ui/src/util/authorizations.js
index ce5d34b6..fffcefeb 100644
--- a/opendc-web/opendc-web-ui/src/util/authorizations.js
+++ b/opendc-web/opendc-web-ui/src/util/authorizations.js
@@ -3,13 +3,13 @@ import EditIcon from '@patternfly/react-icons/dist/js/icons/edit-icon'
import EyeIcon from '@patternfly/react-icons/dist/js/icons/eye-icon'
export const AUTH_ICON_MAP = {
- OWN: HomeIcon,
- EDIT: EditIcon,
- VIEW: EyeIcon,
+ OWNER: HomeIcon,
+ EDITOR: EditIcon,
+ VIEWER: EyeIcon,
}
export const AUTH_DESCRIPTION_MAP = {
- OWN: 'Own',
- EDIT: 'Can Edit',
- VIEW: 'Can View',
+ OWNER: 'Own',
+ EDITOR: 'Can Edit',
+ VIEWER: 'Can View',
}
diff --git a/opendc-web/opendc-web-ui/src/util/topology-schema.js b/opendc-web/opendc-web-ui/src/util/topology-schema.js
index 7779ccfe..ff672dd6 100644
--- a/opendc-web/opendc-web-ui/src/util/topology-schema.js
+++ b/opendc-web/opendc-web-ui/src/util/topology-schema.js
@@ -22,10 +22,10 @@
import { schema } from 'normalizr'
-const Cpu = new schema.Entity('cpus', {}, { idAttribute: '_id' })
-const Gpu = new schema.Entity('gpus', {}, { idAttribute: '_id' })
-const Memory = new schema.Entity('memories', {}, { idAttribute: '_id' })
-const Storage = new schema.Entity('storages', {}, { idAttribute: '_id' })
+const Cpu = new schema.Entity('cpus', {}, { idAttribute: 'id' })
+const Gpu = new schema.Entity('gpus', {}, { idAttribute: 'id' })
+const Memory = new schema.Entity('memories', {}, { idAttribute: 'id' })
+const Storage = new schema.Entity('storages', {}, { idAttribute: 'id' })
export const Machine = new schema.Entity(
'machines',
@@ -35,13 +35,13 @@ export const Machine = new schema.Entity(
memories: [Memory],
storages: [Storage],
},
- { idAttribute: '_id' }
+ { idAttribute: 'id' }
)
-export const Rack = new schema.Entity('racks', { machines: [Machine] }, { idAttribute: '_id' })
+export const Rack = new schema.Entity('racks', { machines: [Machine] }, { idAttribute: 'id' })
-export const Tile = new schema.Entity('tiles', { rack: Rack }, { idAttribute: '_id' })
+export const Tile = new schema.Entity('tiles', { rack: Rack }, { idAttribute: 'id' })
-export const Room = new schema.Entity('rooms', { tiles: [Tile] }, { idAttribute: '_id' })
+export const Room = new schema.Entity('rooms', { tiles: [Tile] }, { idAttribute: 'id' })
-export const Topology = new schema.Entity('topologies', { rooms: [Room] }, { idAttribute: '_id' })
+export const Topology = new schema.Entity('topologies', { rooms: [Room] }, { idAttribute: 'id' })
diff --git a/opendc-web/opendc-web-ui/src/util/unit-specifications.js b/opendc-web/opendc-web-ui/src/util/unit-specifications.js
index 28479edd..3e3671cd 100644
--- a/opendc-web/opendc-web-ui/src/util/unit-specifications.js
+++ b/opendc-web/opendc-web-ui/src/util/unit-specifications.js
@@ -1,34 +1,34 @@
export const CPU_UNITS = {
'cpu-1': {
- _id: 'cpu-1',
+ id: 'cpu-1',
name: 'Intel i7 v6 6700k',
clockRateMhz: 4100,
numberOfCores: 4,
energyConsumptionW: 70,
},
'cpu-2': {
- _id: 'cpu-2',
+ id: 'cpu-2',
name: 'Intel i5 v6 6700k',
clockRateMhz: 3500,
numberOfCores: 2,
energyConsumptionW: 50,
},
'cpu-3': {
- _id: 'cpu-3',
+ id: 'cpu-3',
name: 'Intel® Xeon® E-2224G',
clockRateMhz: 3500,
numberOfCores: 4,
energyConsumptionW: 71,
},
'cpu-4': {
- _id: 'cpu-4',
+ id: 'cpu-4',
name: 'Intel® Xeon® E-2244G',
clockRateMhz: 3800,
numberOfCores: 8,
energyConsumptionW: 71,
},
'cpu-5': {
- _id: 'cpu-5',
+ id: 'cpu-5',
name: 'Intel® Xeon® E-2246G',
clockRateMhz: 3600,
numberOfCores: 12,
@@ -38,14 +38,14 @@ export const CPU_UNITS = {
export const GPU_UNITS = {
'gpu-1': {
- _id: 'gpu-1',
+ id: 'gpu-1',
name: 'NVIDIA GTX 4 1080',
clockRateMhz: 1200,
numberOfCores: 200,
energyConsumptionW: 250,
},
'gpu-2': {
- _id: 'gpu-2',
+ id: 'gpu-2',
name: 'NVIDIA Tesla V100',
clockRateMhz: 1200,
numberOfCores: 5120,
@@ -55,28 +55,28 @@ export const GPU_UNITS = {
export const MEMORY_UNITS = {
'memory-1': {
- _id: 'memory-1',
+ id: 'memory-1',
name: 'Samsung PC DRAM K4A4G045WD',
speedMbPerS: 16000,
sizeMb: 4000,
energyConsumptionW: 10,
},
'memory-2': {
- _id: 'memory-2',
+ id: 'memory-2',
name: 'Samsung PC DRAM M393A2K43BB1-CRC',
speedMbPerS: 2400,
sizeMb: 16000,
energyConsumptionW: 10,
},
'memory-3': {
- _id: 'memory-3',
+ id: 'memory-3',
name: 'Crucial MTA18ASF4G72PDZ-3G2E1',
speedMbPerS: 3200,
sizeMb: 32000,
energyConsumptionW: 10,
},
'memory-4': {
- _id: 'memory-4',
+ id: 'memory-4',
name: 'Crucial MTA9ASF2G72PZ-3G2E1',
speedMbPerS: 3200,
sizeMb: 16000,
@@ -86,14 +86,14 @@ export const MEMORY_UNITS = {
export const STORAGE_UNITS = {
'storage-1': {
- _id: 'storage-1',
+ id: 'storage-1',
name: 'Samsung EVO 2016 SATA III',
speedMbPerS: 6000,
sizeMb: 250000,
energyConsumptionW: 10,
},
'storage-2': {
- _id: 'storage-2',
+ id: 'storage-2',
name: 'Western Digital MTA9ASF2G72PZ-3G2E1',
speedMbPerS: 6000,
sizeMb: 4000000,