summaryrefslogtreecommitdiff
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/containers/FilterToolbar.jsx17
-rw-r--r--web/src/pages/Builds.jsx23
-rw-r--r--web/src/pages/Buildsets.jsx23
-rw-r--r--web/src/pages/Labels.jsx57
-rw-r--r--web/src/pages/Nodes.jsx143
-rw-r--r--web/src/pages/Projects.jsx93
6 files changed, 234 insertions, 122 deletions
diff --git a/web/src/containers/FilterToolbar.jsx b/web/src/containers/FilterToolbar.jsx
index 328d60d85..bcf74c062 100644
--- a/web/src/containers/FilterToolbar.jsx
+++ b/web/src/containers/FilterToolbar.jsx
@@ -13,6 +13,7 @@
// under the License.
import React, { useState } from 'react'
+import { useDispatch } from 'react-redux'
import PropTypes from 'prop-types'
import {
Button,
@@ -32,12 +33,14 @@ import {
} from '@patternfly/react-core'
import { FilterIcon, SearchIcon } from '@patternfly/react-icons'
+import { addNotification } from '../actions/notifications'
import { FilterSelect } from './filters/Select'
import { FilterTernarySelect } from './filters/TernarySelect'
import { FilterCheckbox } from './filters/Checkbox'
function FilterToolbar(props) {
+ const dispatch = useDispatch()
const [isCategoryDropdownOpen, setIsCategoryDropdownOpen] = useState(false)
const [currentCategory, setCurrentCategory] = useState(
props.filterCategories[0].title
@@ -58,15 +61,22 @@ function FilterToolbar(props) {
}
function handleInputSend(event, category) {
- const { onFilterChange, filters } = props
+ const { onFilterChange, filters, filterInputValidation } = props
// In case the event comes from a key press, only accept "Enter"
if (event.key && event.key !== 'Enter') {
return
}
- // Ignore empty values
- if (!inputValue) {
+ const validationResult = filterInputValidation(category.key, inputValue)
+ if (!validationResult.success) {
+ dispatch(addNotification(
+ {
+ text: validationResult.message,
+ type: 'error',
+ status: '',
+ url: '',
+ }))
return
}
@@ -250,6 +260,7 @@ FilterToolbar.propTypes = {
onFilterChange: PropTypes.func.isRequired,
filters: PropTypes.object.isRequired,
filterCategories: PropTypes.array.isRequired,
+ filterInputValidation: PropTypes.func.isRequired,
}
function getChipsFromFilters(filters, category) {
diff --git a/web/src/pages/Builds.jsx b/web/src/pages/Builds.jsx
index b89bcd678..f1c449ec0 100644
--- a/web/src/pages/Builds.jsx
+++ b/web/src/pages/Builds.jsx
@@ -195,6 +195,28 @@ class BuildsPage extends React.Component {
this.updateData(filters)
}
}
+
+ filterInputValidation = (filterKey, filterValue) => {
+ // Input value should not be empty for all cases
+ if (!filterValue) {
+ return {
+ success: false,
+ message: 'Input should not be empty'
+ }
+ }
+
+ // For change filter, it must be an integer
+ if (filterKey === 'change' && isNaN(filterValue)) {
+ return {
+ success: false,
+ message: 'Change must be an integer (do not include revision)'
+ }
+ }
+
+ return {
+ success: true
+ }
+ }
handleFilterChange = (newFilters) => {
const { location, history } = this.props
@@ -261,6 +283,7 @@ class BuildsPage extends React.Component {
filterCategories={this.filterCategories}
onFilterChange={this.handleFilterChange}
filters={filters}
+ filterInputValidation={this.filterInputValidation}
/>
<Pagination
toggleTemplate={({ firstIndex, lastIndex, itemCount }) => (
diff --git a/web/src/pages/Buildsets.jsx b/web/src/pages/Buildsets.jsx
index 98d86d640..938309034 100644
--- a/web/src/pages/Buildsets.jsx
+++ b/web/src/pages/Buildsets.jsx
@@ -148,6 +148,28 @@ class BuildsetsPage extends React.Component {
}
}
+ filterInputValidation = (filterKey, filterValue) => {
+ // Input value should not be empty for all cases
+ if (!filterValue) {
+ return {
+ success: false,
+ message: 'Input should not be empty'
+ }
+ }
+
+ // For change filter, it must be an integer
+ if (filterKey === 'change' && isNaN(filterValue)) {
+ return {
+ success: false,
+ message: 'Change must be an integer (do not include revision)'
+ }
+ }
+
+ return {
+ success: true
+ }
+ }
+
handleFilterChange = (newFilters) => {
const { location, history } = this.props
const { filters, itemCount } = this.state
@@ -213,6 +235,7 @@ class BuildsetsPage extends React.Component {
filterCategories={this.filterCategories}
onFilterChange={this.handleFilterChange}
filters={filters}
+ filterInputValidation={this.filterInputValidation}
/>
<Pagination
toggleTemplate={({ firstIndex, lastIndex, itemCount }) => (
diff --git a/web/src/pages/Labels.jsx b/web/src/pages/Labels.jsx
index 10decaa73..1c6e50db5 100644
--- a/web/src/pages/Labels.jsx
+++ b/web/src/pages/Labels.jsx
@@ -15,8 +15,15 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
-import { Table } from 'patternfly-react'
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
+import {
+ Table,
+ TableVariant,
+ TableHeader,
+ TableBody,
+} from '@patternfly/react-table'
+import { TagIcon } from '@patternfly/react-icons'
+import { IconProperty } from '../Misc'
import { fetchLabelsIfNeeded } from '../actions/labels'
import { Fetchable, Fetching } from '../containers/Fetching'
@@ -54,18 +61,22 @@ class LabelsPage extends React.Component {
return <Fetching />
}
- const headerFormat = value => <Table.Heading>{value}</Table.Heading>
- const cellFormat = value => <Table.Cell>{value}</Table.Cell>
- const columns = []
- const myColumns = ['name']
- myColumns.forEach(column => {
- let formatter = cellFormat
- let prop = column
- columns.push({
- header: {label: column, formatters: [headerFormat]},
- property: prop,
- cell: {formatters: [formatter]}
- })
+ const columns = [
+ {
+ title: (
+ <IconProperty icon={<TagIcon />} value="Name" />
+ ),
+ dataLabel: 'name'
+ }
+ ]
+ let rows = []
+ labels.forEach((label) => {
+ let r = {
+ cells: [
+ {title: label.name, props: {column: 'Name'}}
+ ],
+ }
+ rows.push(r)
})
return (
<PageSection variant={PageSectionVariants.light}>
@@ -75,18 +86,16 @@ class LabelsPage extends React.Component {
fetchCallback={this.updateData}
/>
</PageSection>
- <Table.PfProvider
- striped
- bordered
- hover
- columns={columns}
+ <Table
+ aria-label="Labels Table"
+ variant={TableVariant.compact}
+ cells={columns}
+ rows={rows}
+ className="zuul-table"
>
- <Table.Header/>
- <Table.Body
- rows={labels}
- rowKey="name"
- />
- </Table.PfProvider>
+ <TableHeader />
+ <TableBody />
+ </Table>
</PageSection>
)
}
diff --git a/web/src/pages/Nodes.jsx b/web/src/pages/Nodes.jsx
index a7081ac59..b13878087 100644
--- a/web/src/pages/Nodes.jsx
+++ b/web/src/pages/Nodes.jsx
@@ -15,9 +15,29 @@
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
-import { Table } from 'patternfly-react'
+import {
+ Table,
+ TableVariant,
+ TableHeader,
+ TableBody,
+} from '@patternfly/react-table'
import * as moment from 'moment'
-import { PageSection, PageSectionVariants } from '@patternfly/react-core'
+import {
+ PageSection,
+ PageSectionVariants,
+ ClipboardCopy,
+} from '@patternfly/react-core'
+import {
+ BuildIcon,
+ ClusterIcon,
+ ConnectedIcon,
+ OutlinedCalendarAltIcon,
+ TagIcon,
+ RunningIcon,
+ PencilAltIcon,
+ ZoneIcon,
+} from '@patternfly/react-icons'
+import { IconProperty } from '../Misc'
import { fetchNodesIfNeeded } from '../actions/nodes'
import { Fetchable } from '../containers/Fetching'
@@ -51,43 +71,69 @@ class NodesPage extends React.Component {
const { remoteData } = this.props
const nodes = remoteData.nodes
- const headerFormat = value => <Table.Heading>{value}</Table.Heading>
- const cellFormat = value => <Table.Cell>{value}</Table.Cell>
- const cellLabelsFormat = value => <Table.Cell>{value.join(',')}</Table.Cell>
- const cellPreFormat = value => (
- <Table.Cell style={{fontFamily: 'Menlo,Monaco,Consolas,monospace'}}>
- {value}
- </Table.Cell>)
- const cellAgeFormat = value => (
- <Table.Cell style={{fontFamily: 'Menlo,Monaco,Consolas,monospace'}}>
- {moment.unix(value).fromNow()}
- </Table.Cell>)
-
- const columns = []
- const myColumns = [
- 'id', 'labels', 'connection', 'server', 'provider', 'state',
- 'age', 'comment'
- ]
- myColumns.forEach(column => {
- let formatter = cellFormat
- let prop = column
- if (column === 'labels') {
- prop = 'type'
- formatter = cellLabelsFormat
- } else if (column === 'connection') {
- prop = 'connection_type'
- } else if (column === 'server') {
- prop = 'external_id'
- formatter = cellPreFormat
- } else if (column === 'age') {
- prop = 'state_time'
- formatter = cellAgeFormat
+ const columns = [
+ {
+ title: (
+ <IconProperty icon={<BuildIcon />} value="ID" />
+ ),
+ dataLabel: 'id',
+ },
+ {
+ title: (
+ <IconProperty icon={<TagIcon />} value="Labels" />
+ ),
+ dataLabel: 'labels',
+ },
+ {
+ title: (
+ <IconProperty icon={<ConnectedIcon />} value="Connection" />
+ ),
+ dataLabel: 'connection',
+ },
+ {
+ title: (
+ <IconProperty icon={<ClusterIcon />} value="Server" />
+ ),
+ dataLabel: 'server',
+ },
+ {
+ title: (
+ <IconProperty icon={<ZoneIcon />} value="Provider" />
+ ),
+ dataLabel: 'provider',
+ },
+ {
+ title: (
+ <IconProperty icon={<RunningIcon />} value="State" />
+ ),
+ dataLabel: 'state',
+ },
+ {
+ title: (
+ <IconProperty icon={<OutlinedCalendarAltIcon />} value="Age" />
+ ),
+ dataLabel: 'age',
+ },
+ {
+ title: (
+ <IconProperty icon={<PencilAltIcon />} value="Comment" />
+ ),
+ dataLabel: 'comment',
}
- columns.push({
- header: {label: column, formatters: [headerFormat]},
- property: prop,
- cell: {formatters: [formatter]}
- })
+ ]
+ let rows = []
+ nodes.forEach((node) => {
+ let r = [
+ {title: node.id, props: {column: 'ID'}},
+ {title: node.type.join(','), props: {column: 'Label' }},
+ {title: node.connection_type, props: {column: 'Connection'}},
+ {title: <ClipboardCopy hoverTip="Copy" clickTip="Copied" variant="inline-compact">{node.external_id}</ClipboardCopy>, props: {column: 'Server'}},
+ {title: node.provider, props: {column: 'Provider'}},
+ {title: node.state, props: {column: 'State'}},
+ {title: moment.unix(node.state_time).fromNow(), props: {column: 'Age'}},
+ {title: node.comment, props: {column: 'Comment'}},
+ ]
+ rows.push({cells: r})
})
return (
<PageSection variant={PageSectionVariants.light}>
@@ -97,18 +143,17 @@ class NodesPage extends React.Component {
fetchCallback={this.updateData}
/>
</PageSection>
- <Table.PfProvider
- striped
- bordered
- hover
- columns={columns}
+
+ <Table
+ aria-label="Nodes Table"
+ variant={TableVariant.compact}
+ cells={columns}
+ rows={rows}
+ className="zuul-table"
>
- <Table.Header/>
- <Table.Body
- rows={nodes}
- rowKey="id"
- />
- </Table.PfProvider>
+ <TableHeader />
+ <TableBody />
+ </Table>
</PageSection>
)
}
diff --git a/web/src/pages/Projects.jsx b/web/src/pages/Projects.jsx
index 13ee81bd8..598133384 100644
--- a/web/src/pages/Projects.jsx
+++ b/web/src/pages/Projects.jsx
@@ -16,8 +16,18 @@ import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
-import { Table } from 'patternfly-react'
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
+import {
+ Table,
+ TableVariant,
+ TableHeader,
+ TableBody,
+} from '@patternfly/react-table'
+import {
+ CubeIcon,
+ ConnectedIcon,
+} from '@patternfly/react-icons'
+import { IconProperty } from '../Misc'
import { fetchProjectsIfNeeded } from '../actions/projects'
import { Fetchable, Fetching } from '../containers/Fetching'
@@ -59,42 +69,35 @@ class ProjectsPage extends React.Component {
return <Fetching />
}
- const headerFormat = value => <Table.Heading>{value}</Table.Heading>
- const cellFormat = (value) => (
- <Table.Cell>{value}</Table.Cell>)
- const cellProjectFormat = (value, row) => (
- <Table.Cell>
- <Link to={this.props.tenant.linkPrefix + '/project/' + row.rowData.canonical_name}>
- {value}
- </Link>
- </Table.Cell>)
- const cellBuildFormat = (value) => (
- <Table.Cell>
- <Link to={this.props.tenant.linkPrefix + '/builds?project=' + value}>
- builds
- </Link>
- </Table.Cell>)
- const columns = []
- const myColumns = ['name', 'connection', 'type', 'last builds']
- myColumns.forEach(column => {
- let formatter = cellFormat
- let prop = column
- if (column === 'name') {
- formatter = cellProjectFormat
+ const columns = [
+ {
+ title: <IconProperty icon={<CubeIcon />} value="Name" />,
+ dataLabel: 'name',
+ },
+ {
+ title: <IconProperty icon={<ConnectedIcon />} value="Connection" />,
+ dataLabel: 'connection',
+ },
+ {
+ title: 'Type',
+ dataLabel: 'type',
+ },
+ {
+ title: 'Last builds',
+ dataLabel: 'last-builds',
}
- if (column === 'connection') {
- prop = 'connection_name'
+ ]
+ let rows = []
+ projects.forEach((project) => {
+ let r = {
+ cells: [
+ {title: <Link to={this.props.tenant.linkPrefix + '/project/' + project.canonical_name}>{project.name}</Link>, props: {column: 'Name'}},
+ {title: project.connection_name, props: {column: 'Connection'}},
+ {title: project.type, props: {column: 'Type'}},
+ {title: <Link to={this.props.tenant.linkPrefix + '/builds?project=' + project.name}>Builds</Link>, props: {column: 'Last builds'}},
+ ]
}
- if (column === 'last builds') {
- prop = 'name'
- formatter = cellBuildFormat
- }
- columns.push({
- header: {label: column,
- formatters: [headerFormat]},
- property: prop,
- cell: {formatters: [formatter]}
- })
+ rows.push(r)
})
return (
<PageSection variant={PageSectionVariants.light}>
@@ -104,18 +107,16 @@ class ProjectsPage extends React.Component {
fetchCallback={this.updateData}
/>
</PageSection>
- <Table.PfProvider
- striped
- bordered
- hover
- columns={columns}
+ <Table
+ aria-label="Projects Table"
+ variant={TableVariant.compact}
+ cells={columns}
+ rows={rows}
+ className="zuul-table"
>
- <Table.Header/>
- <Table.Body
- rows={projects}
- rowKey="name"
- />
- </Table.PfProvider>
+ <TableHeader />
+ <TableBody />
+ </Table>
</PageSection>
)
}