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/components/portfolios/PortfolioOverview.js121
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js (renamed from opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js)2
-rw-r--r--opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js (renamed from opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js)28
-rw-r--r--opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js98
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js76
-rw-r--r--opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js77
-rw-r--r--opendc-web/opendc-web-ui/src/data/topology.js7
-rw-r--r--opendc-web/opendc-web-ui/src/pages/_app.js3
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js60
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js93
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js53
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/index.js3
12 files changed, 419 insertions, 202 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js
new file mode 100644
index 00000000..580b0a29
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js
@@ -0,0 +1,121 @@
+/*
+ * 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 PropTypes from 'prop-types'
+import {
+ Card,
+ CardActions,
+ CardBody,
+ CardHeader,
+ CardTitle,
+ Chip,
+ ChipGroup,
+ DescriptionList,
+ DescriptionListDescription,
+ DescriptionListGroup,
+ DescriptionListTerm,
+ Grid,
+ GridItem,
+ Skeleton,
+} from '@patternfly/react-core'
+import React from 'react'
+import { usePortfolio } from '../../data/project'
+import { METRIC_NAMES } from '../../util/available-metrics'
+import NewScenario from './NewScenario'
+import ScenarioTable from './ScenarioTable'
+
+function PortfolioOverview({ portfolioId }) {
+ const { data: portfolio } = usePortfolio(portfolioId)
+
+ return (
+ <Grid hasGutter>
+ <GridItem md={2}>
+ <Card>
+ <CardTitle>Details</CardTitle>
+ <CardBody>
+ <DescriptionList>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Scenarios</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.scenarioIds.length ?? <Skeleton screenreaderText="Loading portfolio" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Metrics</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.targets?.enabledMetrics ? (
+ portfolio.targets.enabledMetrics.length > 0 ? (
+ <ChipGroup>
+ {portfolio.targets.enabledMetrics.map((metric) => (
+ <Chip isReadOnly key={metric}>
+ {METRIC_NAMES[metric]}
+ </Chip>
+ ))}
+ </ChipGroup>
+ ) : (
+ 'No metrics enabled'
+ )
+ ) : (
+ <Skeleton screenreaderText="Loading portfolio" />
+ )}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm>
+ <DescriptionListDescription>
+ {portfolio?.targets?.repeatsPerScenario ?? (
+ <Skeleton screenreaderText="Loading portfolio" />
+ )}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem md={6}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <NewScenario portfolioId={portfolioId} />
+ </CardActions>
+ <CardTitle>Scenarios</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <ScenarioTable portfolioId={portfolioId} />
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ )
+}
+
+PortfolioOverview.propTypes = {
+ portfolioId: PropTypes.string,
+}
+
+export default PortfolioOverview
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js
index 09348e60..dbfa928f 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResultInfo.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js
@@ -23,7 +23,7 @@
import PropTypes from 'prop-types'
import { Tooltip } from '@patternfly/react-core'
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons'
-import { METRIC_DESCRIPTIONS } from '../../../util/available-metrics'
+import { METRIC_DESCRIPTIONS } from '../../util/available-metrics'
function PortfolioResultInfo({ metric }) {
return (
diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
index f1883e68..00023d9e 100644
--- a/opendc-web/opendc-web-ui/src/components/portfolios/results/PortfolioResults.js
+++ b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js
@@ -1,7 +1,29 @@
+/*
+ * 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 React from 'react'
import PropTypes from 'prop-types'
import { Bar, CartesianGrid, ComposedChart, ErrorBar, ResponsiveContainer, Scatter, XAxis, YAxis } from 'recharts'
-import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../../util/available-metrics'
+import { AVAILABLE_METRICS, METRIC_NAMES, METRIC_UNITS } from '../../util/available-metrics'
import { mean, std } from 'mathjs'
import approx from 'approximate-number'
import {
@@ -20,9 +42,9 @@ import {
Title,
} from '@patternfly/react-core'
import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons'
-import { usePortfolioScenarios } from '../../../data/project'
+import { usePortfolioScenarios } from '../../data/project'
import PortfolioResultInfo from './PortfolioResultInfo'
-import NewScenario from '../NewScenario'
+import NewScenario from './NewScenario'
const PortfolioResults = ({ portfolioId }) => {
const { status, data: scenarios = [] } = usePortfolioScenarios(portfolioId)
diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js
new file mode 100644
index 00000000..65b8f5a0
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js
@@ -0,0 +1,98 @@
+/*
+ * 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 PropTypes from 'prop-types'
+import {
+ Card,
+ CardActions,
+ CardBody,
+ CardHeader,
+ CardTitle,
+ DescriptionList,
+ DescriptionListDescription,
+ DescriptionListGroup,
+ DescriptionListTerm,
+ Grid,
+ GridItem,
+ Skeleton,
+} from '@patternfly/react-core'
+import NewTopology from './NewTopology'
+import TopologyTable from './TopologyTable'
+import NewPortfolio from './NewPortfolio'
+import PortfolioTable from './PortfolioTable'
+import { useProject } from '../../data/project'
+
+function ProjectOverview({ projectId }) {
+ const { data: project } = useProject(projectId)
+
+ return (
+ <Grid hasGutter>
+ <GridItem md={2}>
+ <Card>
+ <CardTitle>Details</CardTitle>
+ <CardBody>
+ <DescriptionList>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>
+ {project?.name ?? <Skeleton screenreaderText="Loading project" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem md={5}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <NewTopology projectId={projectId} />
+ </CardActions>
+ <CardTitle>Topologies</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <TopologyTable projectId={projectId} />
+ </CardBody>
+ </Card>
+ </GridItem>
+ <GridItem md={5}>
+ <Card>
+ <CardHeader>
+ <CardActions>
+ <NewPortfolio projectId={projectId} />
+ </CardActions>
+ <CardTitle>Portfolios</CardTitle>
+ </CardHeader>
+ <CardBody>
+ <PortfolioTable projectId={projectId} />
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ )
+}
+
+ProjectOverview.propTypes = {
+ projectId: PropTypes.string,
+}
+
+export default ProjectOverview
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js
new file mode 100644
index 00000000..c16f554c
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js
@@ -0,0 +1,76 @@
+/*
+ * 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 React, { useState } from 'react'
+import {
+ Bullseye,
+ Drawer,
+ DrawerContent,
+ DrawerContentBody,
+ EmptyState,
+ EmptyStateIcon,
+ Spinner,
+ Title,
+} from '@patternfly/react-core'
+import { configure, HotKeys } from 'react-hotkeys'
+import { KeymapConfiguration } from '../../hotkeys'
+import MapStage from './map/MapStage'
+import Collapse from './map/controls/Collapse'
+import { useSelector } from 'react-redux'
+import TopologySidebar from './sidebar/TopologySidebar'
+
+function TopologyMap() {
+ const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1')
+ const interactionLevel = useSelector((state) => state.interactionLevel)
+
+ const [isExpanded, setExpanded] = useState(true)
+ const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} />
+
+ // Make sure that holding down a key will generate repeated events
+ configure({
+ ignoreRepeatedEventsWhenKeyHeldDown: false,
+ })
+
+ return topologyIsLoading ? (
+ <Bullseye>
+ <EmptyState>
+ <EmptyStateIcon variant="container" component={Spinner} />
+ <Title size="lg" headingLevel="h4">
+ Loading Topology
+ </Title>
+ </EmptyState>
+ </Bullseye>
+ ) : (
+ <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="full-height">
+ <Drawer isExpanded={isExpanded}>
+ <DrawerContent panelContent={panelContent}>
+ <DrawerContentBody>
+ <MapStage />
+ <Collapse onClick={() => setExpanded(true)} />
+ </DrawerContentBody>
+ </DrawerContent>
+ </Drawer>
+ </HotKeys>
+ )
+}
+
+export default TopologyMap
diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js
new file mode 100644
index 00000000..f773dcd1
--- /dev/null
+++ b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js
@@ -0,0 +1,77 @@
+/*
+ * 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 PropTypes from 'prop-types'
+import {
+ Card,
+ CardBody,
+ CardTitle,
+ DescriptionList,
+ DescriptionListDescription,
+ DescriptionListGroup,
+ DescriptionListTerm,
+ Grid,
+ GridItem,
+ Skeleton,
+} from '@patternfly/react-core'
+import { useTopology } from '../../data/topology'
+import { parseAndFormatDateTime } from '../../util/date-time'
+
+function TopologyOverview({ topologyId }) {
+ const { data: topology } = useTopology(topologyId)
+
+ return (
+ <Grid hasGutter>
+ <GridItem md={2}>
+ <Card>
+ <CardTitle>Details</CardTitle>
+ <CardBody>
+ <DescriptionList>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Name</DescriptionListTerm>
+ <DescriptionListDescription>
+ {topology?.name ?? <Skeleton screenreaderText="Loading topology" />}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ <DescriptionListGroup>
+ <DescriptionListTerm>Last edited</DescriptionListTerm>
+ <DescriptionListDescription>
+ {topology ? (
+ parseAndFormatDateTime(topology.datetimeLastEdited)
+ ) : (
+ <Skeleton screenreaderText="Loading topology" />
+ )}
+ </DescriptionListDescription>
+ </DescriptionListGroup>
+ </DescriptionList>
+ </CardBody>
+ </Card>
+ </GridItem>
+ </Grid>
+ )
+}
+
+TopologyOverview.propTypes = {
+ topologyId: PropTypes.string,
+}
+
+export default TopologyOverview
diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js
index 14bd7562..bd4d1e4d 100644
--- a/opendc-web/opendc-web-ui/src/data/topology.js
+++ b/opendc-web/opendc-web-ui/src/data/topology.js
@@ -71,6 +71,13 @@ export function useActiveTopology() {
}
/**
+ * Return the current active topology.
+ */
+export function useTopology(topologyId, options = {}) {
+ return useQuery(['topologies', topologyId], { enabled: !!topologyId, ...options })
+}
+
+/**
* Return the topologies of the specified project.
*/
export function useProjectTopologies(projectId, options = {}) {
diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js
index 028dab10..d5f3b329 100644
--- a/opendc-web/opendc-web-ui/src/pages/_app.js
+++ b/opendc-web/opendc-web-ui/src/pages/_app.js
@@ -24,7 +24,7 @@ import PropTypes from 'prop-types'
import Head from 'next/head'
import { Provider } from 'react-redux'
import { useStore } from '../redux'
-import { AuthProvider, useAuth } from '../auth'
+import { AuthProvider, useAuth, useRequireAuth } from '../auth'
import * as Sentry from '@sentry/react'
import { Integrations } from '@sentry/tracing'
import { QueryClient, QueryClientProvider } from 'react-query'
@@ -48,6 +48,7 @@ import '../style/index.scss'
// This setup is necessary to forward the Auth0 context to the Redux context
const Inner = ({ Component, pageProps }) => {
+ useRequireAuth()
const auth = useAuth()
const queryClient = useMemo(() => {
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 8b0ed8e0..5c17303f 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
@@ -21,23 +21,13 @@
*/
import { useRouter } from 'next/router'
+import ProjectOverview from '../../../components/projects/ProjectOverview'
import { useProject } from '../../../data/project'
import { AppPage } from '../../../components/AppPage'
import Head from 'next/head'
import {
Breadcrumb,
BreadcrumbItem,
- Card,
- CardActions,
- CardBody,
- CardHeader,
- CardTitle,
- DescriptionList,
- DescriptionListDescription,
- DescriptionListGroup,
- DescriptionListTerm,
- Grid,
- GridItem,
PageSection,
PageSectionVariants,
Skeleton,
@@ -45,10 +35,6 @@ import {
TextContent,
} from '@patternfly/react-core'
import BreadcrumbLink from '../../../components/util/BreadcrumbLink'
-import PortfolioTable from '../../../components/projects/PortfolioTable'
-import TopologyTable from '../../../components/projects/TopologyTable'
-import NewTopology from '../../../components/projects/NewTopology'
-import NewPortfolio from '../../../components/projects/NewPortfolio'
function Project() {
const router = useRouter()
@@ -80,49 +66,7 @@ function Project() {
</TextContent>
</PageSection>
<PageSection isFilled>
- <Grid hasGutter>
- <GridItem md={2}>
- <Card>
- <CardTitle>Details</CardTitle>
- <CardBody>
- <DescriptionList>
- <DescriptionListGroup>
- <DescriptionListTerm>Name</DescriptionListTerm>
- <DescriptionListDescription>
- {project?.name ?? <Skeleton screenreaderText="Loading project" />}
- </DescriptionListDescription>
- </DescriptionListGroup>
- </DescriptionList>
- </CardBody>
- </Card>
- </GridItem>
- <GridItem md={5}>
- <Card>
- <CardHeader>
- <CardActions>
- <NewTopology projectId={projectId} />
- </CardActions>
- <CardTitle>Topologies</CardTitle>
- </CardHeader>
- <CardBody>
- <TopologyTable projectId={projectId} />
- </CardBody>
- </Card>
- </GridItem>
- <GridItem md={5}>
- <Card>
- <CardHeader>
- <CardActions>
- <NewPortfolio projectId={projectId} />
- </CardActions>
- <CardTitle>Portfolios</CardTitle>
- </CardHeader>
- <CardBody>
- <PortfolioTable projectId={projectId} />
- </CardBody>
- </Card>
- </GridItem>
- </Grid>
+ <ProjectOverview projectId={projectId} />
</PageSection>
</AppPage>
)
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 53cc9c73..28b03c37 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
@@ -23,27 +23,12 @@
import { useRouter } from 'next/router'
import Head from 'next/head'
import React, { useRef } from 'react'
-import { usePortfolio, useProject } from '../../../../data/project'
import {
Breadcrumb,
BreadcrumbItem,
- Card,
- CardActions,
- CardBody,
- CardHeader,
- CardTitle,
- Chip,
- ChipGroup,
- DescriptionList,
- DescriptionListDescription,
- DescriptionListGroup,
- DescriptionListTerm,
Divider,
- Grid,
- GridItem,
PageSection,
PageSectionVariants,
- Skeleton,
Tab,
TabContent,
Tabs,
@@ -53,10 +38,8 @@ import {
} from '@patternfly/react-core'
import { AppPage } from '../../../../components/AppPage'
import BreadcrumbLink from '../../../../components/util/BreadcrumbLink'
-import { METRIC_NAMES } from '../../../../util/available-metrics'
-import NewScenario from '../../../../components/portfolios/NewScenario'
-import ScenarioTable from '../../../../components/portfolios/ScenarioTable'
-import PortfolioResults from '../../../../components/portfolios/results/PortfolioResults'
+import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview'
+import PortfolioResults from '../../../../components/portfolios/PortfolioResults'
/**
* Page that displays the results in a portfolio.
@@ -65,9 +48,6 @@ function Portfolio() {
const router = useRouter()
const { project: projectId, portfolio: portfolioId } = router.query
- const { data: project } = useProject(projectId)
- const { data: portfolio } = usePortfolio(portfolioId)
-
const overviewRef = useRef(null)
const resultsRef = useRef(null)
@@ -88,7 +68,7 @@ function Portfolio() {
return (
<AppPage breadcrumb={breadcrumb}>
<Head>
- <title>{project?.name ?? 'Portfolios'} - OpenDC</title>
+ <title>Portfolio - OpenDC</title>
</Head>
<PageSection variant={PageSectionVariants.light}>
<TextContent>
@@ -114,72 +94,7 @@ function Portfolio() {
</PageSection>
<PageSection isFilled>
<TabContent eventKey={0} id="overview" ref={overviewRef} aria-label="Overview tab">
- <Grid hasGutter>
- <GridItem md={2}>
- <Card>
- <CardTitle>Details</CardTitle>
- <CardBody>
- <DescriptionList>
- <DescriptionListGroup>
- <DescriptionListTerm>Name</DescriptionListTerm>
- <DescriptionListDescription>
- {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />}
- </DescriptionListDescription>
- </DescriptionListGroup>
- <DescriptionListGroup>
- <DescriptionListTerm>Scenarios</DescriptionListTerm>
- <DescriptionListDescription>
- {portfolio?.scenarioIds.length ?? (
- <Skeleton screenreaderText="Loading portfolio" />
- )}
- </DescriptionListDescription>
- </DescriptionListGroup>
- <DescriptionListGroup>
- <DescriptionListTerm>Metrics</DescriptionListTerm>
- <DescriptionListDescription>
- {portfolio?.targets?.enabledMetrics ? (
- portfolio.targets.enabledMetrics.length > 0 ? (
- <ChipGroup>
- {portfolio.targets.enabledMetrics.map((metric) => (
- <Chip isReadOnly key={metric}>
- {METRIC_NAMES[metric]}
- </Chip>
- ))}
- </ChipGroup>
- ) : (
- 'No metrics enabled'
- )
- ) : (
- <Skeleton screenreaderText="Loading portfolio" />
- )}
- </DescriptionListDescription>
- </DescriptionListGroup>
- <DescriptionListGroup>
- <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm>
- <DescriptionListDescription>
- {portfolio?.targets?.repeatsPerScenario ?? (
- <Skeleton screenreaderText="Loading portfolio" />
- )}
- </DescriptionListDescription>
- </DescriptionListGroup>
- </DescriptionList>
- </CardBody>
- </Card>
- </GridItem>
- <GridItem md={6}>
- <Card>
- <CardHeader>
- <CardActions>
- <NewScenario portfolioId={portfolioId} />
- </CardActions>
- <CardTitle>Scenarios</CardTitle>
- </CardHeader>
- <CardBody>
- <ScenarioTable portfolioId={portfolioId} />
- </CardBody>
- </Card>
- </GridItem>
- </Grid>
+ <PortfolioOverview portfolioId={portfolioId} />
</TabContent>
<TabContent eventKey={1} id="results" ref={resultsRef} aria-label="Results tab" hidden>
<PortfolioResults portfolioId={portfolioId} />
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 a1d6ac7e..f95b18ed 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
@@ -21,39 +21,28 @@
*/
import { useRouter } from 'next/router'
+import TopologyOverview from '../../../../components/topologies/TopologyOverview'
import { useProject } from '../../../../data/project'
-import { useDispatch, useSelector } from 'react-redux'
+import { useDispatch } from 'react-redux'
import React, { useEffect, useRef, useState } from 'react'
-import { configure, HotKeys } from 'react-hotkeys'
-import { KeymapConfiguration } from '../../../../hotkeys'
import Head from 'next/head'
import { openProjectSucceeded } from '../../../../redux/actions/projects'
import { AppPage } from '../../../../components/AppPage'
import {
Breadcrumb,
BreadcrumbItem,
- Bullseye,
Divider,
- Drawer,
- DrawerContent,
- DrawerContentBody,
- EmptyState,
- EmptyStateIcon,
PageSection,
PageSectionVariants,
- Spinner,
Tab,
TabContent,
Tabs,
TabTitleText,
Text,
TextContent,
- Title,
} from '@patternfly/react-core'
import BreadcrumbLink from '../../../../components/util/BreadcrumbLink'
-import MapStage from '../../../../components/topologies/map/MapStage'
-import Collapse from '../../../../components/topologies/map/controls/Collapse'
-import TopologySidebar from '../../../../components/topologies/sidebar/TopologySidebar'
+import TopologyMap from '../../../../components/topologies/TopologyMap'
/**
* Page that displays a datacenter topology.
@@ -75,11 +64,6 @@ function Topology() {
const overviewRef = useRef(null)
const floorPlanRef = useRef(null)
- const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1')
- const interactionLevel = useSelector((state) => state.interactionLevel)
-
- const [isExpanded, setExpanded] = useState(true)
-
const breadcrumb = (
<Breadcrumb>
<BreadcrumbItem to="/projects" component={BreadcrumbLink}>
@@ -94,13 +78,6 @@ function Topology() {
</Breadcrumb>
)
- const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} />
-
- // Make sure that holding down a key will generate repeated events
- configure({
- ignoreRepeatedEventsWhenKeyHeldDown: false,
- })
-
return (
<AppPage breadcrumb={breadcrumb}>
<Head>
@@ -134,7 +111,7 @@ function Topology() {
</PageSection>
<PageSection padding={activeTab === 'floor-plan' && { default: 'noPadding' }} isFilled>
<TabContent eventKey="overview" id="overview" ref={overviewRef} aria-label="Overview tab">
- Test
+ <TopologyOverview topologyId={topologyId} />
</TabContent>
<TabContent
eventKey="floor-plan"
@@ -144,27 +121,7 @@ function Topology() {
className="pf-u-h-100"
hidden
>
- {topologyIsLoading ? (
- <Bullseye>
- <EmptyState>
- <EmptyStateIcon variant="container" component={Spinner} />
- <Title size="lg" headingLevel="h4">
- Loading Topology
- </Title>
- </EmptyState>
- </Bullseye>
- ) : (
- <HotKeys keyMap={KeymapConfiguration} allowChanges={true} className="full-height">
- <Drawer isExpanded={isExpanded}>
- <DrawerContent panelContent={panelContent}>
- <DrawerContentBody>
- <MapStage />
- <Collapse onClick={() => setExpanded(true)} />
- </DrawerContentBody>
- </DrawerContent>
- </Drawer>
- </HotKeys>
- )}
+ <TopologyMap />
</TabContent>
</PageSection>
</AppPage>
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 9dcc9aea..eb77701e 100644
--- a/opendc-web/opendc-web-ui/src/pages/projects/index.js
+++ b/opendc-web/opendc-web-ui/src/pages/projects/index.js
@@ -23,7 +23,7 @@
import React, { useMemo, useState } from 'react'
import Head from 'next/head'
import ProjectFilterPanel from '../../components/projects/FilterPanel'
-import { useAuth, useRequireAuth } from '../../auth'
+import { useAuth } from '../../auth'
import { AppPage } from '../../components/AppPage'
import { PageSection, PageSectionVariants, Text, TextContent } from '@patternfly/react-core'
import { useProjects } from '../../data/project'
@@ -49,7 +49,6 @@ const getVisibleProjects = (projects, filter, userId) => {
}
function Projects() {
- useRequireAuth()
const { user } = useAuth()
const { status, data: projects } = useProjects()
const [filter, setFilter] = useState('SHOW_ALL')