summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthieu Huin <mhuin@redhat.com>2021-09-24 21:03:28 +0200
committerMatthieu Huin <mhuin@redhat.com>2023-02-02 15:41:18 +0100
commitd3f5b9890e9b45dd5dab08dc787ef82a3ac59dec (patch)
tree092467b8fed5e68e553580c19cb3dd19de68e7b2
parentc0985cff39e0ec2b9f7252e7d4a29e9979cda99f (diff)
downloadzuul-d3f5b9890e9b45dd5dab08dc787ef82a3ac59dec.tar.gz
GUI: Add tenant dropdown to top menu
On a non whitelabeled setup, allow a user to jump from one tenant to another without having to go back to the tenants page. On a whitelabeled setup, make the tenant item non-clickable (the click doesn't do anything anyway). Change-Id: I94d27445c65ed5c3f8d02fae9d47d426528d2332
-rw-r--r--web/src/App.jsx105
-rw-r--r--web/src/App.test.jsx5
-rw-r--r--web/src/containers/timezone/SelectTz.jsx22
-rw-r--r--web/src/index.css15
4 files changed, 131 insertions, 16 deletions
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 6a5c6e010..9a0caf551 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -32,6 +32,8 @@ import {
ButtonVariant,
Dropdown,
DropdownItem,
+ DropdownToggle,
+ DropdownSeparator,
KebabToggle,
Modal,
Nav,
@@ -54,6 +56,7 @@ import {
import {
BellIcon,
BookIcon,
+ ChevronDownIcon,
CodeIcon,
ServiceIcon,
UsersIcon,
@@ -67,6 +70,7 @@ import ConfigModal from './containers/config/Config'
import logo from './images/logo.svg'
import { clearNotification } from './actions/notifications'
import { fetchConfigErrorsAction, clearConfigErrorsAction } from './actions/configErrors'
+import { fetchTenantsIfNeeded } from './actions/tenants'
import { routes } from './routes'
import { setTenantAction } from './actions/tenant'
import { configureAuthFromTenant, configureAuthFromInfo } from './actions/auth'
@@ -81,6 +85,7 @@ class App extends React.Component {
configErrorsReady: PropTypes.bool,
info: PropTypes.object,
tenant: PropTypes.object,
+ tenants: PropTypes.object,
timezone: PropTypes.string,
location: PropTypes.object,
history: PropTypes.object,
@@ -93,6 +98,7 @@ class App extends React.Component {
state = {
showErrors: false,
+ isTenantDropdownOpen: false,
}
renderMenu() {
@@ -199,6 +205,7 @@ class App extends React.Component {
} else if (!info.tenant) {
// Multi tenant, look for tenant name in url
whiteLabel = false
+ this.props.dispatch(fetchTenantsIfNeeded())
const match = matchPath(
this.props.location.pathname, { path: '/t/:tenant' })
@@ -368,6 +375,91 @@ class App extends React.Component {
)
}
+ renderTenantDropdown() {
+ const { tenant, tenants } = this.props
+ const { isTenantDropdownOpen } = this.state
+
+ if (tenant.whiteLabel) {
+ return (
+ <PageHeaderToolsItem>
+ <strong>Tenant</strong> {tenant.name}
+ </PageHeaderToolsItem>
+ )
+ } else {
+ const tenantLink = (_tenant) => {
+ const currentPath = this.props.location.pathname
+ let suffix
+ switch (currentPath) {
+ case '/t/' + tenant.name + '/projects':
+ suffix = '/projects'
+ break
+ case '/t/' + tenant.name + '/jobs':
+ suffix = '/jobs'
+ break
+ case '/t/' + tenant.name + '/labels':
+ suffix = '/labels'
+ break
+ case '/t/' + tenant.name + '/nodes':
+ suffix = '/nodes'
+ break
+ case '/t/' + tenant.name + '/autoholds':
+ suffix = '/autoholds'
+ break
+ case '/t/' + tenant.name + '/builds':
+ suffix = '/builds'
+ break
+ case '/t/' + tenant.name + '/buildsets':
+ suffix = '/buildsets'
+ break
+ case '/t/' + tenant.name + '/status':
+ default:
+ // all other paths point to tenant-specific resources that would most likely result in a 404
+ suffix = '/status'
+ break
+ }
+ return <Link to={'/t/' + _tenant.name + suffix}>{_tenant.name}</Link>
+ }
+
+ const options = tenants.tenants.filter(
+ (_tenant) => (_tenant.name !== tenant.name)
+ ).map(
+ (_tenant, idx) => {
+ return (
+ <DropdownItem key={'tenant-dropdown-' + idx} component={tenantLink(_tenant)} />
+ )
+ })
+ options.push(
+ <DropdownSeparator key="tenant-dropdown-separator" />,
+ <DropdownItem
+ key="tenant-dropdown-tenants_page"
+ component={<Link to={tenant.defaultRoute}>Go to tenants page</Link>} />
+ )
+
+ return (tenants.isFetching ?
+ <PageHeaderToolsItem>
+ Loading tenants ...
+ </PageHeaderToolsItem> :
+ <>
+ <PageHeaderToolsItem>
+ <Dropdown
+ isOpen={isTenantDropdownOpen}
+ toggle={
+ <DropdownToggle
+ className={`zuul-menu-dropdown-toggle${isTenantDropdownOpen ? '-expanded' : ''}`}
+ id="tenant-dropdown-toggle-id"
+ onToggle={(isOpen) => { this.setState({ isTenantDropdownOpen: isOpen }) }}
+ toggleIndicator={ChevronDownIcon}
+ >
+ <strong>Tenant</strong> {tenant.name}
+ </DropdownToggle>}
+ onSelect={() => { this.setState({ isTenantDropdownOpen: !isTenantDropdownOpen }) }}
+ dropdownItems={options}
+ />
+ </PageHeaderToolsItem>
+ </>)
+ }
+ }
+
render() {
const { isKebabDropdownOpen } = this.state
const { notifications, configErrors, tenant, info, auth } = this.props
@@ -406,7 +498,7 @@ class App extends React.Component {
key="tenant"
onClick={event => this.handleTenantLink(event)}
>
- <UsersIcon /> Tenant
+ <UsersIcon /> Tenants
</DropdownItem>
)
}
@@ -445,15 +537,7 @@ class App extends React.Component {
</Button>
</a>
</PageHeaderToolsItem>
- {tenant.name && (
- <PageHeaderToolsItem>
- <Link to={tenant.defaultRoute}>
- <Button variant={ButtonVariant.plain}>
- <strong>Tenant</strong> {tenant.name}
- </Button>
- </Link>
- </PageHeaderToolsItem>
- )}
+ {tenant.name && (this.renderTenantDropdown())}
</PageHeaderToolsGroup>
<PageHeaderToolsGroup>
{/* this kebab dropdown replaces the icon buttons and is hidden for
@@ -521,6 +605,7 @@ export default withRouter(connect(
configErrorsReady: state.configErrors.ready,
info: state.info,
tenant: state.tenant,
+ tenants: state.tenants,
timezone: state.timezone,
user: state.user,
auth: state.auth,
diff --git a/web/src/App.test.jsx b/web/src/App.test.jsx
index a1d0234d9..519980c7a 100644
--- a/web/src/App.test.jsx
+++ b/web/src/App.test.jsx
@@ -135,8 +135,9 @@ it('renders single tenant', async () => {
// Link should be white-label scoped
const topMenuLinks = application.root.findAllByType(Link)
expect(topMenuLinks[0].props.to).toEqual('/status')
- expect(topMenuLinks[3].props.to.pathname).toEqual('/status')
- expect(topMenuLinks[4].props.to.pathname).toEqual('/projects')
+ expect(topMenuLinks[1].props.to).toEqual('/openapi')
+ expect(topMenuLinks[2].props.to.pathname).toEqual('/status')
+ expect(topMenuLinks[3].props.to.pathname).toEqual('/projects')
// Location should be /status
expect(location.pathname).toEqual('/status')
// Info should tell white label tenant openstack
diff --git a/web/src/containers/timezone/SelectTz.jsx b/web/src/containers/timezone/SelectTz.jsx
index aaa585336..576645f6c 100644
--- a/web/src/containers/timezone/SelectTz.jsx
+++ b/web/src/containers/timezone/SelectTz.jsx
@@ -12,9 +12,9 @@
import PropTypes from 'prop-types'
import React from 'react'
-import Select from 'react-select'
+import Select, { components } from 'react-select'
import moment from 'moment-timezone'
-import { OutlinedClockIcon } from '@patternfly/react-icons'
+import { OutlinedClockIcon, ChevronDownIcon } from '@patternfly/react-icons'
import { connect } from 'react-redux'
import { setTimezoneAction } from '../../actions/timezone'
@@ -58,7 +58,7 @@ class SelectTz extends React.Component {
}
render() {
- const textColor = '#d1d1d1'
+ const textColor = '#fff'
const containerStyles= {
border: 'solid #2b2b2b',
borderWidth: '0 0 0 1px',
@@ -83,7 +83,11 @@ class SelectTz extends React.Component {
}),
dropdownIndicator:(provided) => ({
...provided,
- padding: '3px'
+ color: '#fff',
+ padding: '3px',
+ ':hover': {
+ color: '#fff'
+ }
}),
indicatorSeparator: () => {},
menu: (provided) => ({
@@ -93,12 +97,22 @@ class SelectTz extends React.Component {
top: '22px',
})
}
+
+ const DropdownIndicator = (props) => {
+ return (
+ <components.DropdownIndicator {...props}>
+ <ChevronDownIcon />
+ </components.DropdownIndicator>
+ )
+ }
+
return (
<div style={containerStyles}>
<OutlinedClockIcon/>
<Select
className="zuul-select-tz"
styles={customStyles}
+ components={{ DropdownIndicator }}
value={this.state.currentValue}
onChange={this.handleChange}
options={this.state.availableTz}
diff --git a/web/src/index.css b/web/src/index.css
index 6fec50911..587804cfa 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -66,6 +66,21 @@ a.refresh {
font-weight: bold;
}
+.zuul-menu-dropdown-toggle:before {
+ content: none !important;
+}
+
+.zuul-menu-dropdown-toggle:hover {
+ border-bottom: none;
+}
+
+.zuul-menu-dropdown-toggle-expanded:before {
+ border-left: none;
+ border-right: none;
+ border-top: none;
+ border-bottom: none;
+}
+
/* Remove ugly outline when a Switch is selected */
.pf-c-switch {
--pf-c-switch__input--focus__toggle--OutlineWidth: 0;