diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2021-07-20 10:51:39 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-07-20 10:51:39 +0200 |
| commit | 51c759e74b088d405b63fdb3e374822308d21366 (patch) | |
| tree | 3094cb874872d932d278d98d60f79902bf08b1a0 /opendc-web/opendc-web-ui/src/pages/projects | |
| parent | db1d2c2f8c18850dedf34b5d690b6cd6a1d1f6b5 (diff) | |
| parent | 28d6d13844db28745bc2813e87a367131f862070 (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')
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') |
