mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 20:30:46 -03:30
Merge remote-tracking branch 'origin/master' into react-context-api
This commit is contained in:
commit
fe857ad68b
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 { mount } from 'enzyme';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
let OrganizationAdd;
|
||||
const getAppWithConfigContext = (context = {
|
||||
@ -26,19 +27,23 @@ beforeEach(() => {
|
||||
describe('<OrganizationAdd />', () => {
|
||||
test('initially renders succesfully', () => {
|
||||
mount(
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
});
|
||||
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(
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
wrapper.find('input#add-org-form-name').simulate('change', { target: { value: 'foo' } });
|
||||
@ -46,15 +51,31 @@ describe('<OrganizationAdd />', () => {
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
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(
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
<MemoryRouter>
|
||||
<OrganizationAdd
|
||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||
location={{ search: '', pathname: '/organizations/add' }}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
wrapper.find('button.at-C-SubmitButton').prop('onClick')();
|
||||
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,
|
||||
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>
|
||||
|
||||
10
src/app.scss
10
src/app.scss
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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;
|
||||
|
||||
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 }] }
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
<b>{name}</b>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user