Merge remote-tracking branch 'origin/master' into react-context-api

This commit is contained in:
kialam
2018-12-18 10:52:09 -05:00
9 changed files with 284 additions and 212 deletions

View File

@@ -13,9 +13,7 @@ import {
BackgroundImage,
BackgroundImageSrc,
Nav,
NavExpandable,
NavList,
NavItem,
Page,
PageHeader,
PageSidebar,
@@ -32,6 +30,7 @@ import HelpDropdown from './components/HelpDropdown';
import LogoutButton from './components/LogoutButton';
import TowerLogo from './components/TowerLogo';
import ConditionalRedirect from './components/ConditionalRedirect';
import NavExpandableGroup from './components/NavExpandableGroup';
import Applications from './pages/Applications';
import Credentials from './pages/Credentials';
@@ -69,41 +68,6 @@ const language = (navigator.languages && navigator.languages[0])
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
const SideNavItems = ({ items, history }) => {
const currentPath = history.location.pathname.split('/')[1];
let activeGroup;
if (currentPath !== '') {
[{ groupName: activeGroup }] = items
.map(({ groupName, routes }) => ({
groupName,
paths: routes.map(({ path }) => path)
}))
.filter(({ paths }) => paths.indexOf(currentPath) > -1);
} else {
activeGroup = 'views';
}
return (items.map(({ title, groupName, routes }) => (
<NavExpandable
key={groupName}
title={title}
groupId={`${groupName}_group`}
isActive={`${activeGroup}_group` === `${groupName}_group`}
isExpanded={`${activeGroup}_group` === `${groupName}_group`}
>
{routes.map(({ path, title: itemTitle }) => (
<NavItem
key={path}
to={`#/${path}`}
groupId={`${groupName}_group`}
isActive={currentPath === path}
>
{itemTitle}
</NavItem>
))}
</NavExpandable>
)));
};
class App extends React.Component {
constructor(props) {
@@ -176,7 +140,12 @@ class App extends React.Component {
}}
/>
<Switch>
<ConditionalRedirect shouldRedirect={() => api.isAuthenticated()} redirectPath="/" path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} />
<ConditionalRedirect
shouldRedirect={() => api.isAuthenticated()}
redirectPath="/"
path="/login"
component={() => <Login logo={logo} loginInfo={loginInfo} />}
/>
<Fragment>
<Page
header={(
@@ -195,127 +164,56 @@ class App extends React.Component {
{({ i18n }) => (
<Nav aria-label={i18n._(t`Primary Navigation`)}>
<NavList>
<SideNavItems
history={history}
items={[
{
groupName: 'views',
title: i18n._('Views'),
routes: [
{
path: 'home',
title: i18n._('Dashboard')
},
{
path: 'jobs',
title: i18n._('Jobs')
},
{
path: 'schedules',
title: i18n._('Schedules')
},
{
path: 'portal',
title: i18n._('Portal Mode')
},
]
},
{
groupName: 'resources',
title: i18n._('Resources'),
routes: [
{
path: 'templates',
title: i18n._('Templates')
},
{
path: 'credentials',
title: i18n._('Credentials')
},
{
path: 'projects',
title: i18n._('Projects')
},
{
path: 'inventories',
title: i18n._('Inventories')
},
{
path: 'inventory_scripts',
title: i18n._('Inventory Scripts')
}
]
},
{
groupName: 'access',
title: i18n._('Access'),
routes: [
{
path: 'organizations',
title: i18n._('Organizations')
},
{
path: 'users',
title: i18n._('Users')
},
{
path: 'teams',
title: i18n._('Teams')
}
]
},
{
groupName: 'administration',
title: i18n._('Administration'),
routes: [
{
path: 'credential_types',
title: i18n._('Credential Types'),
},
{
path: 'notification_templates',
title: i18n._('Notifications')
},
{
path: 'management_jobs',
title: i18n._('Management Jobs')
},
{
path: 'instance_groups',
title: i18n._('Instance Groups')
},
{
path: 'applications',
title: i18n._('Integrations')
}
]
},
{
groupName: 'settings',
title: i18n._('Settings'),
routes: [
{
path: 'auth_settings',
title: i18n._('Authentication'),
},
{
path: 'jobs_settings',
title: i18n._('Jobs')
},
{
path: 'system_settings',
title: i18n._('System')
},
{
path: 'ui_settings',
title: i18n._('User Interface')
},
{
path: 'license',
title: i18n._('License')
}
]
}
<NavExpandableGroup
groupId="views_group"
title={i18n._("Views")}
routes={[
{ path: '/home', title: i18n._('Dashboard') },
{ path: '/jobs', title: i18n._('Jobs') },
{ path: '/schedules', title: i18n._('Schedules') },
{ path: '/portal', title: i18n._('Portal Mode') },
]}
/>
<NavExpandableGroup
groupId="resources_group"
title={i18n._("Resources")}
routes={[
{ path: '/templates', title: i18n._('Templates') },
{ path: '/credentials', title: i18n._('Credentials') },
{ path: '/projects', title: i18n._('Projects') },
{ path: '/inventories', title: i18n._('Inventories') },
{ path: '/inventory_scripts', title: i18n._('Inventory Scripts') }
]}
/>
<NavExpandableGroup
groupId="access_group"
title={i18n._("Access")}
routes={[
{ path: '/organizations', title: i18n._('Organizations') },
{ path: '/users', title: i18n._('Users') },
{ path: '/teams', title: i18n._('Teams') }
]}
/>
<NavExpandableGroup
groupId="administration_group"
title={i18n._("Administration")}
routes={[
{ path: '/credential_types', title: i18n._('Credential Types') },
{ path: '/notification_templates', title: i18n._('Notifications') },
{ path: '/management_jobs', title: i18n._('Management Jobs') },
{ path: '/instance_groups', title: i18n._('Instance Groups') },
{ path: '/applications', title: i18n._('Integrations') }
]}
/>
<NavExpandableGroup
groupId="settings_group"
title={i18n._("Settings")}
routes={[
{ path: '/auth_settings', title: i18n._('Authentication') },
{ path: '/jobs_settings', title: i18n._('Jobs') },
{ path: '/system_settings', title: i18n._('System') },
{ path: '/ui_settings', title: i18n._('User Interface') },
{ path: '/license', title: i18n._('License') }
]}
/>
</NavList>

View File

@@ -118,3 +118,13 @@
--pf-c-about-modal-box--MaxHeight: 40rem;
--pf-c-about-modal-box--MaxWidth: 63rem;
}
//
// layout styles
//
.at-align-right {
display: flex;
flex-direction: row;
justify-content: flex-end;
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { I18n } from '@lingui/react';
import { Trans, t } from '@lingui/macro';
import { t } from '@lingui/macro';
import {
Button,
Checkbox,
@@ -23,6 +23,7 @@ import {
SortNumericDownIcon,
SortNumericUpIcon,
TrashAltIcon,
PlusIcon
} from '@patternfly/react-icons';
import {
Link
@@ -85,7 +86,8 @@ class DataListToolbar extends React.Component {
onSort,
sortedColumnKey,
sortOrder,
addUrl
addUrl,
showExpandCollapse
} = this.props;
const {
// isActionDropdownOpen,
@@ -113,6 +115,22 @@ class DataListToolbar extends React.Component {
return icon;
};
const searchDropdownItems = columns
.filter(({ key }) => key !== searchKey)
.map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
));
const sortDropdownItems = columns
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
.map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
));
return (
<I18n>
{({ i18n }) => (
@@ -145,13 +163,8 @@ class DataListToolbar extends React.Component {
{ searchColumnName }
</DropdownToggle>
)}
>
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
))}
</Dropdown>
dropdownItems={searchDropdownItems}
/>
<TextInput
type="search"
aria-label={i18n._(t`Search text input`)}
@@ -182,15 +195,8 @@ class DataListToolbar extends React.Component {
{ sortedColumnName }
</DropdownToggle>
)}
>
{columns
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
.map(({ key, name }) => (
<DropdownItem key={key} component="button">
{ name }
</DropdownItem>
))}
</Dropdown>
dropdownItems={sortDropdownItems}
/>
</ToolbarItem>
<ToolbarItem>
<Button
@@ -202,18 +208,20 @@ class DataListToolbar extends React.Component {
</Button>
</ToolbarItem>
</ToolbarGroup>
<ToolbarGroup>
<ToolbarItem>
<Button variant="plain" aria-label={i18n._(t`Expand`)}>
<BarsIcon />
</Button>
</ToolbarItem>
<ToolbarItem>
<Button variant="plain" aria-label={i18n._(t`Collapse`)}>
<EqualsIcon />
</Button>
</ToolbarItem>
</ToolbarGroup>
{ showExpandCollapse && (
<ToolbarGroup>
<ToolbarItem>
<Button variant="plain" aria-label={i18n._(t`Expand`)}>
<BarsIcon />
</Button>
</ToolbarItem>
<ToolbarItem>
<Button variant="plain" aria-label={i18n._(t`Collapse`)}>
<EqualsIcon />
</Button>
</ToolbarItem>
</ToolbarGroup>
)}
</Toolbar>
</LevelItem>
<LevelItem>
@@ -225,7 +233,7 @@ class DataListToolbar extends React.Component {
{addUrl && (
<Link to={addUrl}>
<Button variant="primary" aria-label={i18n._(t`Add`)}>
<Trans>Add</Trans>
<PlusIcon />
</Button>
</Link>
)}

View File

@@ -33,7 +33,7 @@
margin-right: 20px;
}
.awx-toolbar button {
.awx-toolbar button.pf-c-button {
height: 30px;
padding: 0px;
}
@@ -43,7 +43,7 @@
height: 30px;
input {
padding: 0px;
padding: 0 10px;
width: 300px;
}
@@ -57,7 +57,7 @@
min-height: 30px;
min-width: 70px;
height: 30px;
padding: 0px;
padding: 0 10px;
margin: 0px;
.pf-c-dropdown__toggle-icon {
@@ -74,10 +74,9 @@
.awx-toolbar .pf-c-button.pf-m-primary {
background-color: #5cb85c;
min-width: 0px;
width: 58px;
width: 30px;
height: 30px;
text-align: center;
padding: 0px;
margin: 0px;
margin-right: 20px;

View File

@@ -0,0 +1,53 @@
import React, { Component } from 'react';
import {
withRouter
} from 'react-router-dom';
import {
NavExpandable,
NavItem,
} from '@patternfly/react-core';
class NavExpandableGroup extends Component {
constructor (props) {
super(props);
const { routes } = this.props;
// Extract a list of paths from the route params and store them for later. This creates
// an array of url paths associated with any NavItem component rendered by this component.
this.navItemPaths = routes.map(({ path }) => path);
}
isActiveGroup = () => this.navItemPaths.some(this.isActivePath);
isActivePath = (path) => {
const { history } = this.props;
return history.location.pathname.startsWith(path);
};
render () {
const { routes, groupId, staticContext, ...rest } = this.props;
const isActive = this.isActiveGroup();
return (
<NavExpandable
isActive={isActive}
isExpanded={isActive}
groupId={groupId}
{...rest}
>
{routes.map(({ path, title }) => (
<NavItem
groupId={groupId}
isActive={this.isActivePath(path)}
key={path}
to={`/#${path}`}
>
{title}
</NavItem>
))}
</NavExpandable>
);
}
}
export default withRouter(NavExpandableGroup);

View File

@@ -41,7 +41,7 @@ export default ({
state: { breadcrumb: [parentBreadcrumb, { name, url: detailUrl }] }
}}
>
{name}
<b>{name}</b>
</Link>
</span>
</div>

View File

@@ -1,5 +1,6 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { Trans } from '@lingui/macro';
import {
PageSection,
@@ -31,6 +32,7 @@ class OrganizationAdd extends React.Component {
this.onSelectChange = this.onSelectChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.resetForm = this.resetForm.bind(this);
this.onCancel = this.onCancel.bind(this);
}
state = {
@@ -38,6 +40,7 @@ class OrganizationAdd extends React.Component {
description: '',
instanceGroups: '',
custom_virtualenv: '',
error:'',
};
onSelectChange(value, _) {
@@ -62,6 +65,23 @@ class OrganizationAdd extends React.Component {
this.resetForm();
}
onCancel() {
this.props.history.push('/organizations');
}
async componentDidMount() {
try {
const { data } = await api.get(API_CONFIG);
this.setState({ custom_virtualenvs: [...data.custom_virtualenvs] });
if (this.state.custom_virtualenvs.length > 1) {
// Show dropdown if we have more than one ansible environment
this.setState({ hideAnsibleSelect: !this.state.hideAnsibleSelect });
}
} catch (error) {
this.setState({ error })
}
}
render() {
const { name } = this.state;
const enabled = name.length > 0; // TODO: add better form validation
@@ -126,7 +146,7 @@ class OrganizationAdd extends React.Component {
<Button className="at-C-SubmitButton" variant="primary" onClick={this.onSubmit} isDisabled={!enabled}>Save</Button>
</ToolbarGroup>
<ToolbarGroup>
<Button variant="secondary">Cancel</Button>
<Button className="at-C-CancelButton" variant="secondary" onClick={this.onCancel}>Cancel</Button>
</ToolbarGroup>
</Toolbar>
</ActionGroup>
@@ -143,4 +163,4 @@ OrganizationAdd.contextTypes = {
custom_virtualenvs: PropTypes.array,
};
export default OrganizationAdd;
export default withRouter(OrganizationAdd);