Implement new status page
The current status page shows a lot of information about the different pipelines, queues and even the individual queue items and their jobs. This information can become quite overwhelming - especially for larger tenants with a lot of pipelines and longer (gate) queues. For operators, this makes it hard to get a clear picture of what's going on and for the individual developers it's hard to find their individual changes (or queues). The new status page tries to provide a more "operator-oriented" view by showing the most imporant information about a tenant in a very concised manner. The idea is to focus only on the relevant information to get an answer to the following questions: 1. What is the current state of the tenant? 2. Which items are failing? 3. Which pipelines/queues are succeeding (or failing)? 4. Which queues are piling up items? To achieve this, the new status page focuses on higher-level information like pipelines and queues, but doesn't show more detailed ones like the individual queue items and their jobs. Also, the queues are much more prominent in this new status view. To compensate the missing information, the next change implements an additional "pipeline details view". The general idea behind this rework is to provide both a "operator-oriented" view and a "developer-oriented" view without mixing both into a single view (which is kind of what the current status view tries to do). Instead of showing all information on a single page, the new approach splits that information into different scopes: 1. status / pipeline overview 2. pipeline and queue details 3. individual change Based on those scopes, the pages that handle those focus only on the relevant information, but don't show too many details about the other scopes. The changes in this stack currently cover 1. and 2., whereby 3. is already existing in form of the "single change panel/view". The change doesn't touch the current StatusPage, but creates a new one for easier review. The old status page will be cleaned up in a follow-up commit. Change-Id: I255e84ba562bb87da6bdbd44b51adbcc73136c47
This commit is contained in:
parent
01e9472306
commit
719ba50787
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2020 BMW Group
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { Tooltip } from '@patternfly/react-core'
|
||||
import {
|
||||
BundleIcon,
|
||||
CheckIcon,
|
||||
CodeBranchIcon,
|
||||
ExclamationIcon,
|
||||
FlaskIcon,
|
||||
OutlinedClockIcon,
|
||||
SortAmountDownIcon,
|
||||
StreamIcon,
|
||||
TimesIcon,
|
||||
} from '@patternfly/react-icons'
|
||||
|
||||
const QUEUE_ITEM_ICON_CONFIGS = {
|
||||
SUCCESS: {
|
||||
icon: CheckIcon,
|
||||
color: 'var(--pf-global--success-color--100)',
|
||||
variant: 'success',
|
||||
},
|
||||
FAILURE: {
|
||||
icon: TimesIcon,
|
||||
color: 'var(--pf-global--danger-color--100)',
|
||||
variant: 'danger',
|
||||
},
|
||||
MERGE_CONFLICT: {
|
||||
icon: ExclamationIcon,
|
||||
color: 'var(--pf-global--warning-color--100)',
|
||||
variant: 'warning',
|
||||
},
|
||||
QUEUED: {
|
||||
icon: OutlinedClockIcon,
|
||||
color: 'var(--pf-global--info-color--100)',
|
||||
variant: 'info',
|
||||
},
|
||||
WAITING: {
|
||||
icon: OutlinedClockIcon,
|
||||
color: 'var(--pf-global--disabled-color--100)',
|
||||
variant: 'pending',
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
Note: the documentation links are unused at the moment, but kept for
|
||||
convenience. We might figure a way to use these at some point.
|
||||
*/
|
||||
const PIPELINE_ICON_CONFIGS = {
|
||||
dependent: {
|
||||
icon: CodeBranchIcon,
|
||||
help_title: 'Dependent Pipeline',
|
||||
help: 'A dependent pipeline ensures that every change is tested exactly in the order it is going to be merged into the repository.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.dependent',
|
||||
},
|
||||
independent: {
|
||||
icon: FlaskIcon,
|
||||
help_title: 'Independent Pipeline',
|
||||
help: 'An independent pipeline treats every change as independent of other changes in it.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.independent',
|
||||
},
|
||||
serial: {
|
||||
icon: SortAmountDownIcon,
|
||||
help_title: 'Serial Pipeline',
|
||||
help: 'A serial pipeline supports shared queues, but only one item in each shared queue is processed at a time.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.serial',
|
||||
},
|
||||
supercedent: {
|
||||
icon: BundleIcon,
|
||||
help_title: 'Supercedent Pipeline',
|
||||
help: 'A supercedent pipeline groups items by project and ref, and processes only one item per grouping at a time. Only two items (currently processing and latest) can be queued per grouping.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.supercedent',
|
||||
},
|
||||
unknown: {
|
||||
icon: StreamIcon,
|
||||
help_title: '?',
|
||||
help: 'Unknown pipeline type',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html'
|
||||
},
|
||||
}
|
||||
|
||||
const DEFAULT_PIPELINE_ICON_CONFIG = PIPELINE_ICON_CONFIGS['unknown']
|
||||
|
||||
const getQueueItemIconConfig = (item) => {
|
||||
if (item.failing_reasons && item.failing_reasons.length > 0) {
|
||||
let reasons = item.failing_reasons.join(', ')
|
||||
if (reasons.match(/merge conflict/)) {
|
||||
return QUEUE_ITEM_ICON_CONFIGS['MERGE_CONFLICT']
|
||||
}
|
||||
return QUEUE_ITEM_ICON_CONFIGS['FAILURE']
|
||||
}
|
||||
|
||||
if (item.active !== true) {
|
||||
return QUEUE_ITEM_ICON_CONFIGS['QUEUED']
|
||||
}
|
||||
|
||||
if (item.live !== true) {
|
||||
return QUEUE_ITEM_ICON_CONFIGS['WAITING']
|
||||
}
|
||||
|
||||
return QUEUE_ITEM_ICON_CONFIGS['SUCCESS']
|
||||
}
|
||||
|
||||
function PipelineIcon({ pipelineType, size = 'sm' }) {
|
||||
const iconConfig = PIPELINE_ICON_CONFIGS[pipelineType] || DEFAULT_PIPELINE_ICON_CONFIG
|
||||
const Icon = iconConfig.icon
|
||||
|
||||
// Define the verticalAlign based on the size
|
||||
let verticalAlign = '-0.2em'
|
||||
|
||||
if (size === 'md') {
|
||||
verticalAlign = '-0.35em'
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
content={<div><strong>{iconConfig.help_title}</strong><p>{iconConfig.help}</p></div>}
|
||||
>
|
||||
<Icon
|
||||
size={size}
|
||||
style={{
|
||||
marginRight: 'var(--pf-global--spacer--sm)',
|
||||
verticalAlign: verticalAlign,
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
PipelineIcon.propTypes = {
|
||||
pipelineType: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
}
|
||||
|
||||
export { getQueueItemIconConfig, PipelineIcon }
|
|
@ -18,53 +18,7 @@ import { Badge } from 'patternfly-react'
|
|||
import { Title, Tooltip } from '@patternfly/react-core'
|
||||
|
||||
import ChangeQueue from './ChangeQueue'
|
||||
|
||||
import {
|
||||
CodeBranchIcon,
|
||||
FlaskIcon,
|
||||
SortAmountDownIcon,
|
||||
BundleIcon,
|
||||
StreamIcon,
|
||||
} from '@patternfly/react-icons'
|
||||
|
||||
/*
|
||||
Note: the documentation links are unused at the moment, but kept for convenience. We might figure a way to
|
||||
use these at some point.
|
||||
*/
|
||||
const PIPELINE_ICONS = {
|
||||
dependent: {
|
||||
icon: CodeBranchIcon,
|
||||
help_title: 'Dependent Pipeline',
|
||||
help: 'A dependent pipeline ensures that every change is tested exactly in the order it is going to be merged into the repository.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.dependent',
|
||||
},
|
||||
independent: {
|
||||
icon: FlaskIcon,
|
||||
help_title: 'Independent Pipeline',
|
||||
help: 'An independent pipeline treats every change as independent of other changes in it.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.independent',
|
||||
},
|
||||
serial: {
|
||||
icon: SortAmountDownIcon,
|
||||
help_title: 'Serial Pipeline',
|
||||
help: 'A serial pipeline supports shared queues, but only one item in each shared queue is processed at a time.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.serial',
|
||||
},
|
||||
supercedent: {
|
||||
icon: BundleIcon,
|
||||
help_title: 'Supercedent Pipeline',
|
||||
help: 'A supercedent pipeline groups items by project and ref, and processes only one item per grouping at a time. Only two items (currently processing and latest) can be queued per grouping.',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html#value-pipeline.manager.supercedent',
|
||||
},
|
||||
unknown: {
|
||||
icon: StreamIcon,
|
||||
help_title: '?',
|
||||
help: 'Unknown pipeline type',
|
||||
doc_url: 'https://zuul-ci.org/docs/zuul/reference/pipeline_def.html'
|
||||
},
|
||||
}
|
||||
|
||||
const DEFAULT_PIPELINE_ICON = PIPELINE_ICONS['unknown']
|
||||
import { PipelineIcon } from './Misc'
|
||||
|
||||
function getRefs(item) {
|
||||
// Backwards compat
|
||||
|
@ -170,18 +124,9 @@ class Pipeline extends React.Component {
|
|||
const { pipeline } = this.props
|
||||
let pipeline_type = pipeline.manager || 'unknown'
|
||||
|
||||
const pl_config = PIPELINE_ICONS[pipeline_type] || DEFAULT_PIPELINE_ICON
|
||||
const Icon = pl_config.icon
|
||||
return (
|
||||
<>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
content={<div><strong>{pl_config.help_title}</strong><p>{pl_config.help}</p></div>}
|
||||
>
|
||||
<Icon />
|
||||
</Tooltip>
|
||||
|
||||
{pipeline.name}
|
||||
<PipelineIcon pipelineType={pipeline_type} /> {pipeline.name}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2024 BMW Group
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
CardBody,
|
||||
Flex,
|
||||
FlexItem,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core'
|
||||
|
||||
import { SquareIcon } from '@patternfly/react-icons'
|
||||
|
||||
import { PipelineIcon, getQueueItemIconConfig } from './Misc'
|
||||
|
||||
function QueueItemSquare({ item }) {
|
||||
const iconConfig = getQueueItemIconConfig(item)
|
||||
return (
|
||||
<Button
|
||||
variant="plain"
|
||||
className={`zuul-item-square zuul-item-square-${iconConfig.variant}`}
|
||||
>
|
||||
<SquareIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
QueueItemSquare.propTypes = {
|
||||
item: PropTypes.object,
|
||||
}
|
||||
|
||||
|
||||
function QueueSummary({ pipeline, pipelineType }) {
|
||||
// Dependent pipelines usually come with named queues, so we will
|
||||
// visualize each queue individually. For other pipeline types, we
|
||||
// will consolidate all heads as a single queue to simplify the
|
||||
// visualization (e.g. independent pipelines like check where each
|
||||
// change/item is enqueued in it's own queue by design).
|
||||
if (['dependent'].indexOf(pipelineType) > -1) {
|
||||
return (
|
||||
pipeline.change_queues.map((queue) => (
|
||||
<Flex key={`${queue.name}${queue.branch}`}>
|
||||
<FlexItem>
|
||||
<Card isPlain className="zuul-compact-card">
|
||||
<CardTitle>
|
||||
{queue.name} ({queue.branch})
|
||||
</CardTitle>
|
||||
<CardBody style={{ paddingBottom: '0' }}>
|
||||
{queue.heads.map((head) => (
|
||||
head.map((item) => <QueueItemSquare item={item} key={item.id} />)
|
||||
))}
|
||||
</CardBody>
|
||||
</Card>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
))
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Flex
|
||||
display={{ default: 'inlineFlex' }}
|
||||
spaceItems={{ default: 'spaceItemsNone' }}
|
||||
>
|
||||
{pipeline.change_queues.map((queue) => (
|
||||
queue.heads.map((head) => (
|
||||
head.map((item) => (
|
||||
<FlexItem key={item.id}>
|
||||
<QueueItemSquare item={item} />
|
||||
</FlexItem>
|
||||
))
|
||||
))
|
||||
))}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
QueueSummary.propTypes = {
|
||||
pipeline: PropTypes.object,
|
||||
pipelineType: PropTypes.string,
|
||||
}
|
||||
|
||||
function PipelineSummary({ pipeline }) {
|
||||
|
||||
const countItems = (pipeline) => {
|
||||
let count = 0
|
||||
pipeline.change_queues.map(queue => (
|
||||
queue.heads.map(head => (
|
||||
head.map(() => (
|
||||
count++
|
||||
))
|
||||
))
|
||||
))
|
||||
return count
|
||||
}
|
||||
|
||||
const pipelineType = pipeline.manager || 'unknown'
|
||||
const itemCount = countItems(pipeline)
|
||||
|
||||
return (
|
||||
<Card className="zuul-pipeline-summary zuul-compact-card">
|
||||
<CardTitle
|
||||
style={pipelineType !== 'dependent' ? { paddingBottom: '8px' } : {}}
|
||||
>
|
||||
<PipelineIcon pipelineType={pipelineType} />
|
||||
{pipeline.name}
|
||||
<Tooltip
|
||||
content={
|
||||
itemCount === 1
|
||||
? <div>{itemCount} item enqueued</div>
|
||||
: <div>{itemCount} items enqueued</div>
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
isRead
|
||||
style={{ marginLeft: 'var(--pf-global--spacer--sm)', verticalAlign: '0.1em' }}
|
||||
>
|
||||
{itemCount}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</CardTitle>
|
||||
<CardBody>
|
||||
<QueueSummary pipeline={pipeline} pipelineType={pipelineType} />
|
||||
</CardBody>
|
||||
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
PipelineSummary.propTypes = {
|
||||
pipeline: PropTypes.object,
|
||||
}
|
||||
|
||||
export default PipelineSummary
|
|
@ -182,6 +182,76 @@ a.refresh {
|
|||
}
|
||||
|
||||
/* Status page */
|
||||
|
||||
/* Use the same hover effect like for selectable cards, but without
|
||||
actually selecting them */
|
||||
.zuul-pipeline-summary:hover {
|
||||
box-shadow: var(--pf-c-card--m-selectable--active--BoxShadow);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Override PF4 padding values on compact cards to make them even more
|
||||
compact. */
|
||||
.pf-c-card.zuul-compact-card .pf-c-card__header {
|
||||
padding: 8px 16px 0 16px;
|
||||
}
|
||||
|
||||
.pf-c-card.zuul-compact-card .pf-c-card__title {
|
||||
padding: 16px 16px 0 16px;
|
||||
}
|
||||
|
||||
.pf-c-card.zuul-compact-card .pf-c-card__body {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
/* TODO (felix): Could we put every title within a compact header and
|
||||
remove this css rule? */
|
||||
.pf-c-card.zuul-compact-card .pf-c-card__header .pf-c-card__title {
|
||||
padding: 0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.zuul-item-square {
|
||||
opacity: 0.7;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.zuul-item-square-success {
|
||||
color: var(--pf-global--success-color--100) !important;
|
||||
}
|
||||
.zuul-item-square-success svg:hover {
|
||||
opacity: 1;
|
||||
color: var(--pf-global--palette--green-600) !important;
|
||||
}
|
||||
.zuul-item-square-danger {
|
||||
color: var(--pf-global--danger-color--100) !important;
|
||||
}
|
||||
.zuul-item-square-danger svg:hover {
|
||||
opacity: 1;
|
||||
color: var(--pf-global--danger-color--200) !important;
|
||||
}
|
||||
.zuul-item-square-info {
|
||||
color: var(--pf-global--info-color--100) !important;
|
||||
}
|
||||
.zuul-item-square-info svg:hover {
|
||||
opacity: 1;
|
||||
color: var(--pf-global--palette--blue-500) !important;
|
||||
}
|
||||
.zuul-item-square-warning {
|
||||
color: var(--pf-global--warning-color--100) !important;
|
||||
}
|
||||
.zuul-item-square-warning svg:hover {
|
||||
opacity: 1;
|
||||
color: var(--pf-global--palette--orange-400) !important;
|
||||
}
|
||||
.zuul-item-square-pending {
|
||||
color: var(--pf-global--palette--black-500) !important;
|
||||
}
|
||||
.zuul-item-square-pending svg:hover {
|
||||
opacity: 1;
|
||||
color: var(--pf-global--palette--black-700) !important;
|
||||
}
|
||||
|
||||
.zuul-pipeline-header h3 {
|
||||
font-weight: var(--pf-global--FontWeight--bold);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
// Copyright 2024 BMW Group
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
import React, { useEffect } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {
|
||||
Gallery,
|
||||
GalleryItem,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
} from '@patternfly/react-core'
|
||||
|
||||
import PipelineSummary from '../containers/status/PipelineSummary'
|
||||
|
||||
import { fetchStatusIfNeeded } from '../actions/status'
|
||||
|
||||
function PipelineOverviewPage({ pipelines, tenant, darkMode, fetchStatusIfNeeded }) {
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Zuul Status'
|
||||
if (tenant.name) {
|
||||
fetchStatusIfNeeded(tenant)
|
||||
}
|
||||
}, [tenant, fetchStatusIfNeeded])
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageSection variant={darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
|
||||
<Gallery
|
||||
hasGutter
|
||||
minWidths={{
|
||||
default: '450px',
|
||||
}}
|
||||
>
|
||||
{pipelines.map(pipeline => (
|
||||
<GalleryItem key={pipeline.name}>
|
||||
<PipelineSummary pipeline={pipeline} />
|
||||
</GalleryItem>
|
||||
))}
|
||||
|
||||
</Gallery>
|
||||
|
||||
</PageSection>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
PipelineOverviewPage.propTypes = {
|
||||
pipelines: PropTypes.array,
|
||||
tenant: PropTypes.object,
|
||||
preferences: PropTypes.object,
|
||||
darkMode: PropTypes.bool,
|
||||
fetchStatusIfNeeded: PropTypes.func,
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
let pipelines = []
|
||||
if (state.status.status) {
|
||||
// TODO (felix): Here we could filter out the pipeline data from
|
||||
// the status.json and if necessary "reformat" it to only contain
|
||||
// the relevant information for this component (e.g. drop all
|
||||
// job related information which isn't shown).
|
||||
pipelines = state.status.status.pipelines
|
||||
}
|
||||
// TODO (felix): Here we could also order the pipelines by any
|
||||
// criteria (e.g. the pipeline_type) in case we want that. Currently
|
||||
// they are ordered in the way they are defined in the zuul config.
|
||||
// The sorting could also be done via the filter toolbar.
|
||||
return {
|
||||
pipelines,
|
||||
tenant: state.tenant,
|
||||
darkMode: state.preferences.darkMode,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = { fetchStatusIfNeeded }
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(withRouter(PipelineOverviewPage))
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import ComponentsPage from './pages/Components'
|
||||
import FreezeJobPage from './pages/FreezeJob'
|
||||
import StatusPage from './pages/Status'
|
||||
import ChangeStatusPage from './pages/ChangeStatus'
|
||||
import ProjectPage from './pages/Project'
|
||||
import ProjectsPage from './pages/Projects'
|
||||
|
@ -34,6 +33,7 @@ import ConfigErrorsPage from './pages/ConfigErrors'
|
|||
import TenantsPage from './pages/Tenants'
|
||||
import StreamPage from './pages/Stream'
|
||||
import OpenApiPage from './pages/OpenApi'
|
||||
import PipelineOverviewPage from './pages/PipelineOverview'
|
||||
|
||||
// The Route object are created in the App component.
|
||||
// Object with a title are created in the menu.
|
||||
|
@ -43,7 +43,7 @@ const routes = () => [
|
|||
{
|
||||
title: 'Status',
|
||||
to: '/status',
|
||||
component: StatusPage
|
||||
component: PipelineOverviewPage,
|
||||
},
|
||||
{
|
||||
title: 'Projects',
|
||||
|
|
Loading…
Reference in New Issue