diff --git a/web/src/App.jsx b/web/src/App.jsx
index 6a5c6e010e..9a0caf5512 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 (
+
+ Tenant {tenant.name}
+
+ )
+ } 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 {_tenant.name}
+ }
+
+ const options = tenants.tenants.filter(
+ (_tenant) => (_tenant.name !== tenant.name)
+ ).map(
+ (_tenant, idx) => {
+ return (
+
+ )
+ })
+ options.push(
+ ,
+ Go to tenants page} />
+ )
+
+ return (tenants.isFetching ?
+
+ Loading tenants ...
+ :
+ <>
+
+ { this.setState({ isTenantDropdownOpen: isOpen }) }}
+ toggleIndicator={ChevronDownIcon}
+ >
+ Tenant {tenant.name}
+ }
+ onSelect={() => { this.setState({ isTenantDropdownOpen: !isTenantDropdownOpen }) }}
+ dropdownItems={options}
+ />
+
+ >)
+ }
+ }
+
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)}
>
- Tenant
+ Tenants
)
}
@@ -445,15 +537,7 @@ class App extends React.Component {
- {tenant.name && (
-
-
-
-
-
- )}
+ {tenant.name && (this.renderTenantDropdown())}
{/* 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 a1d0234d93..519980c7ac 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 aaa585336f..576645f6c7 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 (
+
+
+
+ )
+ }
+
return (