mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 01:17:37 -02:30
Merge remote-tracking branch 'origin/master' into react-context-api
This commit is contained in:
63
__tests__/components/NavExpandableGroup.test.jsx
Normal file
63
__tests__/components/NavExpandableGroup.test.jsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { Nav } from '@patternfly/react-core';
|
||||||
|
import NavExpandableGroup from '../../src/components/NavExpandableGroup';
|
||||||
|
|
||||||
|
describe('NavExpandableGroup', () => {
|
||||||
|
test('initialization and render', () => {
|
||||||
|
const component = mount(
|
||||||
|
<MemoryRouter initialEntries={['/foo']}>
|
||||||
|
<Nav aria-label="Test Navigation">
|
||||||
|
<NavExpandableGroup
|
||||||
|
groupId="test"
|
||||||
|
title="Test"
|
||||||
|
routes={[
|
||||||
|
{ path: '/foo', title: 'Foo' },
|
||||||
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup').instance();
|
||||||
|
|
||||||
|
expect(component.navItemPaths).toEqual(['/foo', '/bar', '/fiz']);
|
||||||
|
expect(component.isActiveGroup()).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isActivePath', () => {
|
||||||
|
const params = [
|
||||||
|
['/fo', '/foo', false],
|
||||||
|
['/foo', '/foo', true],
|
||||||
|
['/foo/1/bar/fiz', '/foo', true],
|
||||||
|
['/foo/1/bar/fiz', 'foo', false],
|
||||||
|
['/foo/1/bar/fiz', 'foo/', false],
|
||||||
|
['/foo/1/bar/fiz', '/bar', false],
|
||||||
|
['/foo/1/bar/fiz', '/fiz', false],
|
||||||
|
];
|
||||||
|
|
||||||
|
params.forEach(([location, path, expected]) => {
|
||||||
|
test(`when location is ${location}', isActivePath('${path}') returns ${expected} `, () => {
|
||||||
|
const component = mount(
|
||||||
|
<MemoryRouter initialEntries={[location]}>
|
||||||
|
<Nav aria-label="Test Navigation">
|
||||||
|
<NavExpandableGroup
|
||||||
|
groupId="test"
|
||||||
|
title="Test"
|
||||||
|
routes={[
|
||||||
|
{ path: '/foo', title: 'Foo' },
|
||||||
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup').instance();
|
||||||
|
|
||||||
|
expect(component.isActivePath(path)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
|
||||||
let OrganizationAdd;
|
let OrganizationAdd;
|
||||||
const getAppWithConfigContext = (context = {
|
const getAppWithConfigContext = (context = {
|
||||||
@@ -26,19 +27,23 @@ beforeEach(() => {
|
|||||||
describe('<OrganizationAdd />', () => {
|
describe('<OrganizationAdd />', () => {
|
||||||
test('initially renders succesfully', () => {
|
test('initially renders succesfully', () => {
|
||||||
mount(
|
mount(
|
||||||
<OrganizationAdd
|
<MemoryRouter>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('calls "handleChange" when input values change', () => {
|
test('calls "handleChange" when input values change', () => {
|
||||||
const spy = jest.spyOn(OrganizationAdd.prototype, 'handleChange');
|
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'handleChange');
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<OrganizationAdd
|
<MemoryRouter>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
wrapper.find('input#add-org-form-name').simulate('change', { target: { value: 'foo' } });
|
wrapper.find('input#add-org-form-name').simulate('change', { target: { value: 'foo' } });
|
||||||
@@ -46,15 +51,31 @@ describe('<OrganizationAdd />', () => {
|
|||||||
expect(spy).toHaveBeenCalledTimes(2);
|
expect(spy).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
test('calls "onSubmit" when Save button is clicked', () => {
|
test('calls "onSubmit" when Save button is clicked', () => {
|
||||||
const spy = jest.spyOn(OrganizationAdd.prototype, 'onSubmit');
|
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSubmit');
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<OrganizationAdd
|
<MemoryRouter>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
wrapper.find('button.at-C-SubmitButton').prop('onClick')();
|
wrapper.find('button.at-C-SubmitButton').prop('onClick')();
|
||||||
expect(spy).toBeCalled();
|
expect(spy).toBeCalled();
|
||||||
});
|
});
|
||||||
|
test('calls "onCancel" when Cancel button is clicked', () => {
|
||||||
|
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onCancel');
|
||||||
|
const wrapper = mount(
|
||||||
|
<MemoryRouter>
|
||||||
|
<OrganizationAdd
|
||||||
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
wrapper.find('button.at-C-CancelButton').prop('onClick')();
|
||||||
|
expect(spy).toBeCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
216
src/App.jsx
216
src/App.jsx
@@ -13,9 +13,7 @@ import {
|
|||||||
BackgroundImage,
|
BackgroundImage,
|
||||||
BackgroundImageSrc,
|
BackgroundImageSrc,
|
||||||
Nav,
|
Nav,
|
||||||
NavExpandable,
|
|
||||||
NavList,
|
NavList,
|
||||||
NavItem,
|
|
||||||
Page,
|
Page,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PageSidebar,
|
PageSidebar,
|
||||||
@@ -32,6 +30,7 @@ import HelpDropdown from './components/HelpDropdown';
|
|||||||
import LogoutButton from './components/LogoutButton';
|
import LogoutButton from './components/LogoutButton';
|
||||||
import TowerLogo from './components/TowerLogo';
|
import TowerLogo from './components/TowerLogo';
|
||||||
import ConditionalRedirect from './components/ConditionalRedirect';
|
import ConditionalRedirect from './components/ConditionalRedirect';
|
||||||
|
import NavExpandableGroup from './components/NavExpandableGroup';
|
||||||
|
|
||||||
import Applications from './pages/Applications';
|
import Applications from './pages/Applications';
|
||||||
import Credentials from './pages/Credentials';
|
import Credentials from './pages/Credentials';
|
||||||
@@ -69,41 +68,6 @@ const language = (navigator.languages && navigator.languages[0])
|
|||||||
|
|
||||||
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[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 {
|
class App extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -176,7 +140,12 @@ class App extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Switch>
|
<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>
|
<Fragment>
|
||||||
<Page
|
<Page
|
||||||
header={(
|
header={(
|
||||||
@@ -195,127 +164,56 @@ class App extends React.Component {
|
|||||||
{({ i18n }) => (
|
{({ i18n }) => (
|
||||||
<Nav aria-label={i18n._(t`Primary Navigation`)}>
|
<Nav aria-label={i18n._(t`Primary Navigation`)}>
|
||||||
<NavList>
|
<NavList>
|
||||||
<SideNavItems
|
<NavExpandableGroup
|
||||||
history={history}
|
groupId="views_group"
|
||||||
items={[
|
title={i18n._("Views")}
|
||||||
{
|
routes={[
|
||||||
groupName: 'views',
|
{ path: '/home', title: i18n._('Dashboard') },
|
||||||
title: i18n._('Views'),
|
{ path: '/jobs', title: i18n._('Jobs') },
|
||||||
routes: [
|
{ path: '/schedules', title: i18n._('Schedules') },
|
||||||
{
|
{ path: '/portal', title: i18n._('Portal Mode') },
|
||||||
path: 'home',
|
]}
|
||||||
title: i18n._('Dashboard')
|
/>
|
||||||
},
|
<NavExpandableGroup
|
||||||
{
|
groupId="resources_group"
|
||||||
path: 'jobs',
|
title={i18n._("Resources")}
|
||||||
title: i18n._('Jobs')
|
routes={[
|
||||||
},
|
{ path: '/templates', title: i18n._('Templates') },
|
||||||
{
|
{ path: '/credentials', title: i18n._('Credentials') },
|
||||||
path: 'schedules',
|
{ path: '/projects', title: i18n._('Projects') },
|
||||||
title: i18n._('Schedules')
|
{ path: '/inventories', title: i18n._('Inventories') },
|
||||||
},
|
{ path: '/inventory_scripts', title: i18n._('Inventory Scripts') }
|
||||||
{
|
]}
|
||||||
path: 'portal',
|
/>
|
||||||
title: i18n._('Portal Mode')
|
<NavExpandableGroup
|
||||||
},
|
groupId="access_group"
|
||||||
]
|
title={i18n._("Access")}
|
||||||
},
|
routes={[
|
||||||
{
|
{ path: '/organizations', title: i18n._('Organizations') },
|
||||||
groupName: 'resources',
|
{ path: '/users', title: i18n._('Users') },
|
||||||
title: i18n._('Resources'),
|
{ path: '/teams', title: i18n._('Teams') }
|
||||||
routes: [
|
]}
|
||||||
{
|
/>
|
||||||
path: 'templates',
|
<NavExpandableGroup
|
||||||
title: i18n._('Templates')
|
groupId="administration_group"
|
||||||
},
|
title={i18n._("Administration")}
|
||||||
{
|
routes={[
|
||||||
path: 'credentials',
|
{ path: '/credential_types', title: i18n._('Credential Types') },
|
||||||
title: i18n._('Credentials')
|
{ path: '/notification_templates', title: i18n._('Notifications') },
|
||||||
},
|
{ path: '/management_jobs', title: i18n._('Management Jobs') },
|
||||||
{
|
{ path: '/instance_groups', title: i18n._('Instance Groups') },
|
||||||
path: 'projects',
|
{ path: '/applications', title: i18n._('Integrations') }
|
||||||
title: i18n._('Projects')
|
]}
|
||||||
},
|
/>
|
||||||
{
|
<NavExpandableGroup
|
||||||
path: 'inventories',
|
groupId="settings_group"
|
||||||
title: i18n._('Inventories')
|
title={i18n._("Settings")}
|
||||||
},
|
routes={[
|
||||||
{
|
{ path: '/auth_settings', title: i18n._('Authentication') },
|
||||||
path: 'inventory_scripts',
|
{ path: '/jobs_settings', title: i18n._('Jobs') },
|
||||||
title: i18n._('Inventory Scripts')
|
{ path: '/system_settings', title: i18n._('System') },
|
||||||
}
|
{ path: '/ui_settings', title: i18n._('User Interface') },
|
||||||
]
|
{ path: '/license', title: i18n._('License') }
|
||||||
},
|
|
||||||
{
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</NavList>
|
</NavList>
|
||||||
|
|||||||
10
src/app.scss
10
src/app.scss
@@ -118,3 +118,13 @@
|
|||||||
--pf-c-about-modal-box--MaxHeight: 40rem;
|
--pf-c-about-modal-box--MaxHeight: 40rem;
|
||||||
--pf-c-about-modal-box--MaxWidth: 63rem;
|
--pf-c-about-modal-box--MaxWidth: 63rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// layout styles
|
||||||
|
//
|
||||||
|
.at-align-right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { I18n } from '@lingui/react';
|
import { I18n } from '@lingui/react';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
SortNumericDownIcon,
|
SortNumericDownIcon,
|
||||||
SortNumericUpIcon,
|
SortNumericUpIcon,
|
||||||
TrashAltIcon,
|
TrashAltIcon,
|
||||||
|
PlusIcon
|
||||||
} from '@patternfly/react-icons';
|
} from '@patternfly/react-icons';
|
||||||
import {
|
import {
|
||||||
Link
|
Link
|
||||||
@@ -85,7 +86,8 @@ class DataListToolbar extends React.Component {
|
|||||||
onSort,
|
onSort,
|
||||||
sortedColumnKey,
|
sortedColumnKey,
|
||||||
sortOrder,
|
sortOrder,
|
||||||
addUrl
|
addUrl,
|
||||||
|
showExpandCollapse
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
// isActionDropdownOpen,
|
// isActionDropdownOpen,
|
||||||
@@ -113,6 +115,22 @@ class DataListToolbar extends React.Component {
|
|||||||
return icon;
|
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 (
|
return (
|
||||||
<I18n>
|
<I18n>
|
||||||
{({ i18n }) => (
|
{({ i18n }) => (
|
||||||
@@ -145,13 +163,8 @@ class DataListToolbar extends React.Component {
|
|||||||
{ searchColumnName }
|
{ searchColumnName }
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
)}
|
)}
|
||||||
>
|
dropdownItems={searchDropdownItems}
|
||||||
{columns.filter(({ key }) => key !== searchKey).map(({ key, name }) => (
|
/>
|
||||||
<DropdownItem key={key} component="button">
|
|
||||||
{ name }
|
|
||||||
</DropdownItem>
|
|
||||||
))}
|
|
||||||
</Dropdown>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
type="search"
|
type="search"
|
||||||
aria-label={i18n._(t`Search text input`)}
|
aria-label={i18n._(t`Search text input`)}
|
||||||
@@ -182,15 +195,8 @@ class DataListToolbar extends React.Component {
|
|||||||
{ sortedColumnName }
|
{ sortedColumnName }
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
)}
|
)}
|
||||||
>
|
dropdownItems={sortDropdownItems}
|
||||||
{columns
|
/>
|
||||||
.filter(({ key, isSortable }) => isSortable && key !== sortedColumnKey)
|
|
||||||
.map(({ key, name }) => (
|
|
||||||
<DropdownItem key={key} component="button">
|
|
||||||
{ name }
|
|
||||||
</DropdownItem>
|
|
||||||
))}
|
|
||||||
</Dropdown>
|
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
<Button
|
<Button
|
||||||
@@ -202,18 +208,20 @@ class DataListToolbar extends React.Component {
|
|||||||
</Button>
|
</Button>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
<ToolbarGroup>
|
{ showExpandCollapse && (
|
||||||
<ToolbarItem>
|
<ToolbarGroup>
|
||||||
<Button variant="plain" aria-label={i18n._(t`Expand`)}>
|
<ToolbarItem>
|
||||||
<BarsIcon />
|
<Button variant="plain" aria-label={i18n._(t`Expand`)}>
|
||||||
</Button>
|
<BarsIcon />
|
||||||
</ToolbarItem>
|
</Button>
|
||||||
<ToolbarItem>
|
</ToolbarItem>
|
||||||
<Button variant="plain" aria-label={i18n._(t`Collapse`)}>
|
<ToolbarItem>
|
||||||
<EqualsIcon />
|
<Button variant="plain" aria-label={i18n._(t`Collapse`)}>
|
||||||
</Button>
|
<EqualsIcon />
|
||||||
</ToolbarItem>
|
</Button>
|
||||||
</ToolbarGroup>
|
</ToolbarItem>
|
||||||
|
</ToolbarGroup>
|
||||||
|
)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</LevelItem>
|
</LevelItem>
|
||||||
<LevelItem>
|
<LevelItem>
|
||||||
@@ -225,7 +233,7 @@ class DataListToolbar extends React.Component {
|
|||||||
{addUrl && (
|
{addUrl && (
|
||||||
<Link to={addUrl}>
|
<Link to={addUrl}>
|
||||||
<Button variant="primary" aria-label={i18n._(t`Add`)}>
|
<Button variant="primary" aria-label={i18n._(t`Add`)}>
|
||||||
<Trans>Add</Trans>
|
<PlusIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.awx-toolbar button {
|
.awx-toolbar button.pf-c-button {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
padding: 0px;
|
padding: 0 10px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 0px;
|
padding: 0 10px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
|
||||||
.pf-c-dropdown__toggle-icon {
|
.pf-c-dropdown__toggle-icon {
|
||||||
@@ -74,10 +74,9 @@
|
|||||||
.awx-toolbar .pf-c-button.pf-m-primary {
|
.awx-toolbar .pf-c-button.pf-m-primary {
|
||||||
background-color: #5cb85c;
|
background-color: #5cb85c;
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
width: 58px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
|||||||
53
src/components/NavExpandableGroup.jsx
Normal file
53
src/components/NavExpandableGroup.jsx
Normal 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);
|
||||||
@@ -41,7 +41,7 @@ export default ({
|
|||||||
state: { breadcrumb: [parentBreadcrumb, { name, url: detailUrl }] }
|
state: { breadcrumb: [parentBreadcrumb, { name, url: detailUrl }] }
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
<b>{name}</b>
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import {
|
import {
|
||||||
PageSection,
|
PageSection,
|
||||||
@@ -31,6 +32,7 @@ class OrganizationAdd extends React.Component {
|
|||||||
this.onSelectChange = this.onSelectChange.bind(this);
|
this.onSelectChange = this.onSelectChange.bind(this);
|
||||||
this.onSubmit = this.onSubmit.bind(this);
|
this.onSubmit = this.onSubmit.bind(this);
|
||||||
this.resetForm = this.resetForm.bind(this);
|
this.resetForm = this.resetForm.bind(this);
|
||||||
|
this.onCancel = this.onCancel.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@@ -38,6 +40,7 @@ class OrganizationAdd extends React.Component {
|
|||||||
description: '',
|
description: '',
|
||||||
instanceGroups: '',
|
instanceGroups: '',
|
||||||
custom_virtualenv: '',
|
custom_virtualenv: '',
|
||||||
|
error:'',
|
||||||
};
|
};
|
||||||
|
|
||||||
onSelectChange(value, _) {
|
onSelectChange(value, _) {
|
||||||
@@ -62,6 +65,23 @@ class OrganizationAdd extends React.Component {
|
|||||||
this.resetForm();
|
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() {
|
render() {
|
||||||
const { name } = this.state;
|
const { name } = this.state;
|
||||||
const enabled = name.length > 0; // TODO: add better form validation
|
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>
|
<Button className="at-C-SubmitButton" variant="primary" onClick={this.onSubmit} isDisabled={!enabled}>Save</Button>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
<ToolbarGroup>
|
<ToolbarGroup>
|
||||||
<Button variant="secondary">Cancel</Button>
|
<Button className="at-C-CancelButton" variant="secondary" onClick={this.onCancel}>Cancel</Button>
|
||||||
</ToolbarGroup>
|
</ToolbarGroup>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
@@ -143,4 +163,4 @@ OrganizationAdd.contextTypes = {
|
|||||||
custom_virtualenvs: PropTypes.array,
|
custom_virtualenvs: PropTypes.array,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OrganizationAdd;
|
export default withRouter(OrganizationAdd);
|
||||||
|
|||||||
Reference in New Issue
Block a user