diff options
author | Matthieu Huin <mhuin@redhat.com> | 2021-09-24 21:03:28 +0200 |
---|---|---|
committer | Matthieu Huin <mhuin@redhat.com> | 2023-02-02 15:41:18 +0100 |
commit | d3f5b9890e9b45dd5dab08dc787ef82a3ac59dec (patch) | |
tree | 092467b8fed5e68e553580c19cb3dd19de68e7b2 | |
parent | c0985cff39e0ec2b9f7252e7d4a29e9979cda99f (diff) | |
download | zuul-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.jsx | 105 | ||||
-rw-r--r-- | web/src/App.test.jsx | 5 | ||||
-rw-r--r-- | web/src/containers/timezone/SelectTz.jsx | 22 | ||||
-rw-r--r-- | web/src/index.css | 15 |
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; |