summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/pages/projects
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2021-07-20 10:51:39 +0200
committerGitHub <noreply@github.com>2021-07-20 10:51:39 +0200
commit51c759e74b088d405b63fdb3e374822308d21366 (patch)
tree3094cb874872d932d278d98d60f79902bf08b1a0 /opendc-web/opendc-web-ui/src/pages/projects
parentdb1d2c2f8c18850dedf34b5d690b6cd6a1d1f6b5 (diff)
parent28d6d13844db28745bc2813e87a367131f862070 (diff)
merge: Address technical dept in topology view (#162)
This pull request aims to address some of the technical debt in the topology view of the OpenDC frontend. * Add support for panning of the datacenter topology * Isolate world coordinate space (world objects do not depend on camera scale or position) * Split transpiled modules into a separate chunk to reduce deduplication * Encode state in topology actions to reduce global state * Restructure components per page * Enable more ESLint rules through `eslint:recommended` ruleset * Move page components in separate files.
Diffstat (limited to 'opendc-web/opendc-web-ui/src/pages/projects')
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js61
-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].js125
-rw-r--r--opendc-web/opendc-web-ui/src/pages/projects/index.js3
4 files changed, 81 insertions, 201 deletions
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 c6ded12b..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,24 +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,
- Button,
- Card,
- CardActions,
- CardBody,
- CardHeader,
- CardTitle,
- DescriptionList,
- DescriptionListDescription,
- DescriptionListGroup,
- DescriptionListTerm,
- Grid,
- GridItem,
PageSection,
PageSectionVariants,
Skeleton,
@@ -46,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()
@@ -81,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 55bee445..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 ScenarioTable from '../../../../components/projects/ScenarioTable'
-import NewScenario from '../../../../components/projects/NewScenario'
-import { METRIC_NAMES } from '../../../../util/available-metrics'
-import PortfolioResults from '../../../../components/app/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 5873ed11..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,31 +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 React, { useEffect, useState } from 'react'
-import { HotKeys } from 'react-hotkeys'
-import { KeymapConfiguration } from '../../../../hotkeys'
+import { useDispatch } from 'react-redux'
+import React, { useEffect, useRef, useState } from 'react'
import Head from 'next/head'
-import MapStage from '../../../../components/app/map/MapStage'
import { openProjectSucceeded } from '../../../../redux/actions/projects'
import { AppPage } from '../../../../components/AppPage'
import {
- Bullseye,
- Drawer,
- DrawerContent,
- DrawerContentBody,
- EmptyState,
- EmptyStateIcon,
- Spinner,
- Title,
+ Breadcrumb,
+ BreadcrumbItem,
+ Divider,
+ PageSection,
+ PageSectionVariants,
+ Tab,
+ TabContent,
+ Tabs,
+ TabTitleText,
+ Text,
+ TextContent,
} from '@patternfly/react-core'
-import { zoomInOnCenter } from '../../../../redux/actions/map'
-import Toolbar from '../../../../components/app/map/controls/Toolbar'
-import { useMapScale } from '../../../../data/map'
-import ScaleIndicator from '../../../../components/app/map/controls/ScaleIndicator'
-import TopologySidebar from '../../../../components/app/sidebars/topology/TopologySidebar'
-import Collapse from '../../../../components/app/map/controls/Collapse'
+import BreadcrumbLink from '../../../../components/util/BreadcrumbLink'
+import TopologyMap from '../../../../components/topologies/TopologyMap'
/**
* Page that displays a datacenter topology.
@@ -63,44 +60,70 @@ function Topology() {
}
}, [projectId, topologyId, dispatch])
- const topologyIsLoading = useSelector((state) => state.currentTopologyId === '-1')
- const scale = useMapScale()
- const interactionLevel = useSelector((state) => state.interactionLevel)
+ const [activeTab, setActiveTab] = useState('overview')
+ const overviewRef = useRef(null)
+ const floorPlanRef = useRef(null)
- const [isExpanded, setExpanded] = useState(true)
- const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} />
+ const breadcrumb = (
+ <Breadcrumb>
+ <BreadcrumbItem to="/projects" component={BreadcrumbLink}>
+ Projects
+ </BreadcrumbItem>
+ <BreadcrumbItem to={`/projects/${projectId}`} component={BreadcrumbLink}>
+ Project details
+ </BreadcrumbItem>
+ <BreadcrumbItem to={`/projects/${projectId}/topologies/${topologyId}`} component={BreadcrumbLink} isActive>
+ Topology
+ </BreadcrumbItem>
+ </Breadcrumb>
+ )
return (
- <AppPage>
+ <AppPage breadcrumb={breadcrumb}>
<Head>
<title>{project?.name ?? 'Topologies'} - OpenDC</title>
</Head>
- {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 />
- <ScaleIndicator scale={scale} />
- <Toolbar
- onZoom={(zoomIn) => dispatch(zoomInOnCenter(zoomIn))}
- onExport={() => window['exportCanvasToImage']()}
- />
- <Collapse onClick={() => setExpanded(true)} />
- </DrawerContentBody>
- </DrawerContent>
- </Drawer>
- </HotKeys>
- )}
+ <PageSection variant={PageSectionVariants.light}>
+ <TextContent>
+ <Text component="h1">Topology</Text>
+ </TextContent>
+ </PageSection>
+ <PageSection type="none" variant={PageSectionVariants.light} className="pf-c-page__main-tabs" sticky="top">
+ <Divider component="div" />
+ <Tabs
+ activeKey={activeTab}
+ onSelect={(_, tabIndex) => setActiveTab(tabIndex)}
+ className="pf-m-page-insets"
+ >
+ <Tab
+ eventKey="overview"
+ title={<TabTitleText>Overview</TabTitleText>}
+ tabContentId="overview"
+ tabContentRef={overviewRef}
+ />
+ <Tab
+ eventKey="floor-plan"
+ title={<TabTitleText>Floor Plan</TabTitleText>}
+ tabContentId="floor-plan"
+ tabContentRef={floorPlanRef}
+ />
+ </Tabs>
+ </PageSection>
+ <PageSection padding={activeTab === 'floor-plan' && { default: 'noPadding' }} isFilled>
+ <TabContent eventKey="overview" id="overview" ref={overviewRef} aria-label="Overview tab">
+ <TopologyOverview topologyId={topologyId} />
+ </TabContent>
+ <TabContent
+ eventKey="floor-plan"
+ id="floor-plan"
+ ref={floorPlanRef}
+ aria-label="Floor Plan tab"
+ className="pf-u-h-100"
+ hidden
+ >
+ <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')