mirror of
https://github.com/ansible/awx.git
synced 2026-02-01 01:28:09 -03:30
Merge remote-tracking branch 'origin/master' into lookup-form-component
This commit is contained in:
@@ -8,53 +8,57 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import towerLogo from '../../images/tower-logo-header.svg';
|
||||
import api from '../api';
|
||||
|
||||
class AtLogin extends Component {
|
||||
class AWXLogin extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
password: '',
|
||||
isValidPassword: true,
|
||||
loading: false
|
||||
isInputValid: true,
|
||||
isLoading: false
|
||||
};
|
||||
|
||||
this.onChangeUsername = this.onChangeUsername.bind(this);
|
||||
this.onChangePassword = this.onChangePassword.bind(this);
|
||||
this.onLoginButtonClick = this.onLoginButtonClick.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unmounting = true; // todo: state management
|
||||
onChangeUsername (value) {
|
||||
this.setState({ username: value, isInputValid: true });
|
||||
}
|
||||
|
||||
safeSetState = obj => !this.unmounting && this.setState(obj);
|
||||
onChangePassword (value) {
|
||||
this.setState({ password: value, isInputValid: true });
|
||||
}
|
||||
|
||||
handleUsernameChange = value => this.safeSetState({ username: value, isValidPassword: true });
|
||||
|
||||
handlePasswordChange = value => this.safeSetState({ password: value, isValidPassword: true });
|
||||
|
||||
handleSubmit = async event => {
|
||||
const { username, password, loading } = this.state;
|
||||
async onLoginButtonClick (event) {
|
||||
const { username, password, isLoading } = this.state;
|
||||
const { api } = this.props;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (!loading) {
|
||||
this.safeSetState({ loading: true });
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.login(username, password);
|
||||
} catch (error) {
|
||||
if (error.response.status === 401) {
|
||||
this.safeSetState({ isValidPassword: false });
|
||||
}
|
||||
} finally {
|
||||
this.safeSetState({ loading: false });
|
||||
this.setState({ isLoading: true });
|
||||
|
||||
try {
|
||||
await api.login(username, password);
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 401) {
|
||||
this.setState({ isInputValid: false });
|
||||
}
|
||||
} finally {
|
||||
this.setState({ isLoading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { username, password, isValidPassword } = this.state;
|
||||
const { logo, alt } = this.props;
|
||||
const { username, password, isInputValid } = this.state;
|
||||
const { api, alt, loginInfo, logo } = this.props;
|
||||
const logoSrc = logo ? `data:image/jpeg;${logo}` : towerLogo;
|
||||
|
||||
if (api.isAuthenticated()) {
|
||||
@@ -65,20 +69,21 @@ class AtLogin extends Component {
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<LoginPage
|
||||
mainBrandImgSrc={logoSrc}
|
||||
mainBrandImgAlt={alt || 'Ansible Tower'}
|
||||
brandImgSrc={logoSrc}
|
||||
brandImgAlt={alt || 'Ansible Tower'}
|
||||
loginTitle={i18n._(t`Welcome to Ansible Tower! Please Sign In.`)}
|
||||
textContent={loginInfo}
|
||||
>
|
||||
<LoginForm
|
||||
usernameLabel={i18n._(t`Username`)}
|
||||
usernameValue={username}
|
||||
onChangeUsername={this.handleUsernameChange}
|
||||
passwordLabel={i18n._(t`Password`)}
|
||||
passwordValue={password}
|
||||
onChangePassword={this.handlePasswordChange}
|
||||
isValidPassword={isValidPassword}
|
||||
passwordHelperTextInvalid={i18n._(t`Invalid username or password. Please try again.`)}
|
||||
onLoginButtonClick={this.handleSubmit}
|
||||
usernameValue={username}
|
||||
passwordValue={password}
|
||||
isValidPassword={isInputValid}
|
||||
onChangeUsername={this.onChangeUsername}
|
||||
onChangePassword={this.onChangePassword}
|
||||
onLoginButtonClick={this.onLoginButtonClick}
|
||||
/>
|
||||
</LoginPage>
|
||||
)}
|
||||
@@ -87,4 +92,4 @@ class AtLogin extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default AtLogin;
|
||||
export default AWXLogin;
|
||||
|
||||
@@ -3,7 +3,9 @@ import { Trans } from '@lingui/macro';
|
||||
import {
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Title,
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbHeading
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
Link
|
||||
@@ -21,20 +23,22 @@ const OrganizationBreadcrumb = ({ parentObj, organization, currentTab, location
|
||||
.map(({ url, name }, index) => {
|
||||
let elem;
|
||||
if (noLastLink && parentObj.length - 1 === index) {
|
||||
elem = (<Fragment key={name}>{name}</Fragment>);
|
||||
elem = (<BreadcrumbHeading className="heading" key={name}>{name}</BreadcrumbHeading>);
|
||||
} else {
|
||||
elem = (
|
||||
<Link
|
||||
key={name}
|
||||
to={{ pathname: url, state: { breadcrumb: parentObj, organization } }}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
<BreadcrumbItem key={name}>
|
||||
<Link
|
||||
key={name}
|
||||
to={{ pathname: url, state: { breadcrumb: parentObj, organization } }}
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
</BreadcrumbItem>
|
||||
);
|
||||
}
|
||||
return elem;
|
||||
})
|
||||
.reduce((prev, curr) => [prev, ' > ', curr])}
|
||||
.reduce((prev, curr) => [prev, curr])}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -42,25 +46,31 @@ const OrganizationBreadcrumb = ({ parentObj, organization, currentTab, location
|
||||
breadcrumb = (
|
||||
<Fragment>
|
||||
{generateCrumb()}
|
||||
{' > '}
|
||||
{getTabName(currentTab)}
|
||||
<BreadcrumbHeading className="heading">
|
||||
{getTabName(currentTab)}
|
||||
</BreadcrumbHeading>
|
||||
</Fragment>
|
||||
);
|
||||
} else if (location.pathname.indexOf('edit') > -1) {
|
||||
breadcrumb = (
|
||||
<Fragment>
|
||||
{generateCrumb()}
|
||||
<Trans>{' > edit'}</Trans>
|
||||
<BreadcrumbHeading className="heading">
|
||||
<Trans>Edit</Trans>
|
||||
</BreadcrumbHeading>
|
||||
</Fragment>
|
||||
);
|
||||
} else if (location.pathname.indexOf('add') > -1) {
|
||||
breadcrumb = (
|
||||
<Fragment>
|
||||
{generateCrumb()}
|
||||
<Trans>{' > add'}</Trans>
|
||||
<BreadcrumbHeading className="heading">
|
||||
<Trans>Add</Trans>
|
||||
</BreadcrumbHeading>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
|
||||
breadcrumb = (
|
||||
<Fragment>
|
||||
{generateCrumb(true)}
|
||||
@@ -71,7 +81,7 @@ const OrganizationBreadcrumb = ({ parentObj, organization, currentTab, location
|
||||
|
||||
return (
|
||||
<PageSection variant={light} className="pf-m-condensed">
|
||||
<Title size="2xl">{breadcrumb}</Title>
|
||||
<Breadcrumb>{breadcrumb}</Breadcrumb>
|
||||
</PageSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,10 +6,7 @@ import {
|
||||
CardHeader,
|
||||
CardBody,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
ToolbarSection,
|
||||
PageSectionVariants
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
Switch,
|
||||
@@ -17,39 +14,10 @@ import {
|
||||
Route
|
||||
} from 'react-router-dom';
|
||||
|
||||
import Tab from '../../../components/Tabs/Tab';
|
||||
import Tabs from '../../../components/Tabs/Tabs';
|
||||
import getTabName from '../utils';
|
||||
|
||||
import '../tabs.scss';
|
||||
|
||||
const DetailTab = ({ location, match, tab, currentTab, children, breadcrumb }) => {
|
||||
const tabClasses = () => {
|
||||
let classes = 'at-c-tabs__tab';
|
||||
if (tab === currentTab) {
|
||||
classes += ' at-m-selected';
|
||||
}
|
||||
|
||||
return classes;
|
||||
};
|
||||
|
||||
const updateTab = () => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.get('tab') !== undefined) {
|
||||
params.set('tab', tab);
|
||||
} else {
|
||||
params.append('tab', tab);
|
||||
}
|
||||
|
||||
return `?${params.toString()}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolbarItem className={tabClasses()}>
|
||||
<Link to={{ pathname: `${match.url}`, search: updateTab(), state: { breadcrumb } }} replace={tab === currentTab}>
|
||||
{children}
|
||||
</Link>
|
||||
</ToolbarItem>
|
||||
);
|
||||
};
|
||||
|
||||
const OrganizationDetail = ({
|
||||
location,
|
||||
@@ -61,6 +29,7 @@ const OrganizationDetail = ({
|
||||
}) => {
|
||||
// TODO: set objectName by param or through grabbing org detail get from api
|
||||
const { medium } = PageSectionVariants;
|
||||
const tabList=['details', 'access', 'teams', 'notifications'];
|
||||
|
||||
const deleteResourceView = () => (
|
||||
<Fragment>
|
||||
@@ -93,34 +62,29 @@ const OrganizationDetail = ({
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const detailTabs = (tabs) => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ToolbarSection aria-label={i18n._(t`Organization detail tabs`)}>
|
||||
<ToolbarGroup className="at-c-tabs">
|
||||
{tabs.map(tab => (
|
||||
<DetailTab
|
||||
key={tab}
|
||||
tab={tab}
|
||||
location={location}
|
||||
match={match}
|
||||
currentTab={currentTab}
|
||||
breadcrumb={parentBreadcrumbObj}
|
||||
>
|
||||
{getTabName(tab)}
|
||||
</DetailTab>
|
||||
))}
|
||||
</ToolbarGroup>
|
||||
</ToolbarSection>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
|
||||
return (
|
||||
<PageSection variant={medium}>
|
||||
<Card className="at-c-orgPane">
|
||||
<CardHeader>
|
||||
{detailTabs(['details', 'users', 'teams', 'admins', 'notifications'])}
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Tabs labelText={i18n._(t`Organization detail tabs`)}>
|
||||
{tabList.map(tab => (
|
||||
<Tab
|
||||
key={tab}
|
||||
tab={tab}
|
||||
location={location}
|
||||
match={match}
|
||||
currentTab={currentTab}
|
||||
breadcrumb={parentBreadcrumbObj}
|
||||
>
|
||||
<Trans>{getTabName(tab)}</Trans>
|
||||
</Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
)}
|
||||
</I18n>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
{(currentTab && currentTab !== 'details') ? (
|
||||
|
||||
@@ -14,7 +14,6 @@ export default ({
|
||||
name,
|
||||
userCount,
|
||||
teamCount,
|
||||
adminCount,
|
||||
isSelected,
|
||||
onSelect,
|
||||
detailUrl,
|
||||
@@ -46,7 +45,7 @@ export default ({
|
||||
</span>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell">
|
||||
<Link to={`${detailUrl}?tab=users`}>
|
||||
<Link to={`${detailUrl}?tab=access`}>
|
||||
<Trans>Users</Trans>
|
||||
</Link>
|
||||
<Badge isRead>
|
||||
@@ -62,14 +61,6 @@ export default ({
|
||||
{teamCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
<Link to={`${detailUrl}?tab=admins`}>
|
||||
<Trans>Admins</Trans>
|
||||
</Link>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{adminCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell" />
|
||||
</li>
|
||||
|
||||
@@ -5,12 +5,31 @@ import OrganizationAdd from './views/Organization.add';
|
||||
import OrganizationView from './views/Organization.view';
|
||||
import OrganizationsList from './views/Organizations.list';
|
||||
|
||||
const Organizations = ({ match }) => (
|
||||
export default ({ api, match }) => (
|
||||
<Switch>
|
||||
<Route path={`${match.path}/add`} component={OrganizationAdd} />
|
||||
<Route path={`${match.path}/:id`} component={OrganizationView} />
|
||||
<Route path={`${match.path}`} component={OrganizationsList} />
|
||||
<Route
|
||||
path={`${match.path}/add`}
|
||||
render={() => (
|
||||
<OrganizationAdd
|
||||
api={api}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}/:id`}
|
||||
render={() => (
|
||||
<OrganizationView
|
||||
api={api}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}`}
|
||||
render={() => (
|
||||
<OrganizationsList
|
||||
api={api}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
|
||||
export default Organizations;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.at-c-tabs {
|
||||
padding: 0 5px !important;
|
||||
margin: 0 -10px !important;
|
||||
|
||||
.at-c-tabs__tab {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.at-c-tabs__tab.at-m-selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.at-c-orgPane {
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,10 @@ const getTabName = (tab) => {
|
||||
let tabName = '';
|
||||
if (tab === 'details') {
|
||||
tabName = 'Details';
|
||||
} else if (tab === 'users') {
|
||||
tabName = 'Users';
|
||||
} else if (tab === 'access') {
|
||||
tabName = 'Access';
|
||||
} else if (tab === 'teams') {
|
||||
tabName = 'Teams';
|
||||
} else if (tab === 'admins') {
|
||||
tabName = 'Admins';
|
||||
} else if (tab === 'notifications') {
|
||||
tabName = 'Notifications';
|
||||
}
|
||||
|
||||
@@ -19,11 +19,9 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { ConfigContext } from '../../../context';
|
||||
import { API_ORGANIZATIONS } from '../../../endpoints';
|
||||
import { API_INSTANCE_GROUPS } from '../../../endpoints';
|
||||
import api from '../../../api';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||
import Lookup from '../../../components/Lookup';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect'
|
||||
const { light } = PageSectionVariants;
|
||||
|
||||
class OrganizationAdd extends React.Component {
|
||||
@@ -71,8 +69,9 @@ class OrganizationAdd extends React.Component {
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
const { api } = this.props;
|
||||
const data = Object.assign({}, { ...this.state });
|
||||
await api.post(API_ORGANIZATIONS, data);
|
||||
await api.createOrganization(data);
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
@@ -81,7 +80,8 @@ class OrganizationAdd extends React.Component {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { data } = await api.get(API_INSTANCE_GROUPS);
|
||||
const { api } = this.props;
|
||||
const { data } = await api.getInstanceGroups();
|
||||
let results = [];
|
||||
data.results.map((result) => {
|
||||
results.push({ id: result.id, name: result.name, isChecked: false });
|
||||
@@ -92,6 +92,7 @@ class OrganizationAdd extends React.Component {
|
||||
render() {
|
||||
const { name, results } = this.state;
|
||||
const enabled = name.length > 0; // TODO: add better form validation
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageSection variant={light} className="pf-m-condensed">
|
||||
|
||||
@@ -2,16 +2,13 @@ import React, { Component, Fragment } from 'react';
|
||||
import { i18nMark } from '@lingui/react';
|
||||
import {
|
||||
Switch,
|
||||
Route
|
||||
Route,
|
||||
withRouter,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import OrganizationBreadcrumb from '../components/OrganizationBreadcrumb';
|
||||
import OrganizationDetail from '../components/OrganizationDetail';
|
||||
import OrganizationEdit from '../components/OrganizationEdit';
|
||||
|
||||
import api from '../../../api';
|
||||
import { API_ORGANIZATIONS } from '../../../endpoints';
|
||||
|
||||
class OrganizationView extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
@@ -30,6 +27,8 @@ class OrganizationView extends Component {
|
||||
loading: false,
|
||||
mounted: false
|
||||
};
|
||||
|
||||
this.fetchOrganization = this.fetchOrganization.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -47,13 +46,15 @@ class OrganizationView extends Component {
|
||||
|
||||
async fetchOrganization () {
|
||||
const { mounted } = this.state;
|
||||
const { api } = this.props;
|
||||
|
||||
if (mounted) {
|
||||
this.setState({ error: false, loading: true });
|
||||
|
||||
const { match } = this.props;
|
||||
const { parentBreadcrumbObj, organization } = this.state;
|
||||
try {
|
||||
const { data } = await api.get(`${API_ORGANIZATIONS}${match.params.id}/`);
|
||||
const { data } = await api.getOrganizationDetails(match.params.id);
|
||||
if (organization === 'loading') {
|
||||
this.setState({ organization: data });
|
||||
}
|
||||
@@ -118,4 +119,4 @@ class OrganizationView extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default OrganizationView;
|
||||
export default withRouter(OrganizationView);
|
||||
|
||||
@@ -17,9 +17,6 @@ import DataListToolbar from '../../../components/DataListToolbar';
|
||||
import OrganizationListItem from '../components/OrganizationListItem';
|
||||
import Pagination from '../../../components/Pagination';
|
||||
|
||||
import api from '../../../api';
|
||||
import { API_ORGANIZATIONS } from '../../../endpoints';
|
||||
|
||||
import {
|
||||
encodeQueryString,
|
||||
parseQueryString,
|
||||
@@ -56,6 +53,15 @@ class Organizations extends Component {
|
||||
results: [],
|
||||
selected: [],
|
||||
};
|
||||
|
||||
this.onSearch = this.onSearch.bind(this);
|
||||
this.getQueryParams = this.getQueryParams.bind(this);
|
||||
this.onSort = this.onSort.bind(this);
|
||||
this.onSetPage = this.onSetPage.bind(this);
|
||||
this.onSelectAll = this.onSelectAll.bind(this);
|
||||
this.onSelect = this.onSelect.bind(this);
|
||||
this.updateUrl = this.updateUrl.bind(this);
|
||||
this.fetchOrganizations = this.fetchOrganizations.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -78,7 +84,7 @@ class Organizations extends Component {
|
||||
return Object.assign({}, this.defaultParams, searchParams, overrides);
|
||||
}
|
||||
|
||||
onSort = (sortedColumnKey, sortOrder) => {
|
||||
onSort(sortedColumnKey, sortOrder) {
|
||||
const { page_size } = this.state;
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
@@ -90,26 +96,26 @@ class Organizations extends Component {
|
||||
const queryParams = this.getQueryParams({ order_by, page_size });
|
||||
|
||||
this.fetchOrganizations(queryParams);
|
||||
};
|
||||
}
|
||||
|
||||
onSetPage = (pageNumber, pageSize) => {
|
||||
onSetPage (pageNumber, pageSize) {
|
||||
const page = parseInt(pageNumber, 10);
|
||||
const page_size = parseInt(pageSize, 10);
|
||||
|
||||
const queryParams = this.getQueryParams({ page, page_size });
|
||||
|
||||
this.fetchOrganizations(queryParams);
|
||||
};
|
||||
}
|
||||
|
||||
onSelectAll = isSelected => {
|
||||
onSelectAll (isSelected) {
|
||||
const { results } = this.state;
|
||||
|
||||
const selected = isSelected ? results.map(o => o.id) : [];
|
||||
|
||||
this.setState({ selected });
|
||||
};
|
||||
}
|
||||
|
||||
onSelect = id => {
|
||||
onSelect (id) {
|
||||
const { selected } = this.state;
|
||||
|
||||
const isSelected = selected.includes(id);
|
||||
@@ -119,7 +125,7 @@ class Organizations extends Component {
|
||||
} else {
|
||||
this.setState({ selected: selected.concat(id) });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
updateUrl (queryParams) {
|
||||
const { history, location } = this.props;
|
||||
@@ -132,6 +138,7 @@ class Organizations extends Component {
|
||||
}
|
||||
|
||||
async fetchOrganizations (queryParams) {
|
||||
const { api } = this.props;
|
||||
const { page, page_size, order_by } = queryParams;
|
||||
|
||||
let sortOrder = 'ascending';
|
||||
@@ -145,7 +152,7 @@ class Organizations extends Component {
|
||||
this.setState({ error: false, loading: true });
|
||||
|
||||
try {
|
||||
const { data } = await api.get(API_ORGANIZATIONS, queryParams);
|
||||
const { data } = await api.getOrganizations(queryParams);
|
||||
const { count, results } = data;
|
||||
|
||||
const pageCount = Math.ceil(count / page_size);
|
||||
@@ -218,7 +225,6 @@ class Organizations extends Component {
|
||||
parentBreadcrumb={parentBreadcrumb}
|
||||
userCount={o.summary_fields.related_field_counts.users}
|
||||
teamCount={o.summary_fields.related_field_counts.teams}
|
||||
adminCount={o.summary_fields.related_field_counts.admins}
|
||||
isSelected={selected.includes(o.id)}
|
||||
onSelect={() => this.onSelect(o.id)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user