mirror of
https://github.com/ansible/awx.git
synced 2026-03-21 10:57:36 -02:30
Move Organization screens and tests into new folder structure
This commit is contained in:
128
src/pages/Organizations/screens/Organization/Organization.jsx
Normal file
128
src/pages/Organizations/screens/Organization/Organization.jsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { i18nMark } from '@lingui/react';
|
||||
import {
|
||||
Switch,
|
||||
Route,
|
||||
withRouter,
|
||||
} from 'react-router-dom';
|
||||
import {
|
||||
PageSection
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import OrganizationBreadcrumb from '../../components/OrganizationBreadcrumb';
|
||||
import OrganizationDetail from './OrganizationDetail';
|
||||
import OrganizationEdit from './OrganizationEdit';
|
||||
|
||||
class Organization extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
let { breadcrumb: parentBreadcrumbObj, organization } = props.location.state || {};
|
||||
if (!parentBreadcrumbObj) {
|
||||
parentBreadcrumbObj = 'loading';
|
||||
}
|
||||
if (!organization) {
|
||||
organization = 'loading';
|
||||
}
|
||||
this.state = {
|
||||
parentBreadcrumbObj,
|
||||
organization,
|
||||
error: false,
|
||||
loading: false,
|
||||
mounted: false
|
||||
};
|
||||
|
||||
this.fetchOrganization = this.fetchOrganization.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({ mounted: true }, () => {
|
||||
const { organization } = this.state;
|
||||
if (organization === 'loading') {
|
||||
this.fetchOrganization();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.setState({ mounted: false });
|
||||
}
|
||||
|
||||
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.getOrganizationDetails(match.params.id);
|
||||
if (organization === 'loading') {
|
||||
this.setState({ organization: data });
|
||||
}
|
||||
const { name } = data;
|
||||
if (parentBreadcrumbObj === 'loading') {
|
||||
this.setState({ parentBreadcrumbObj: [{ name: i18nMark('Organizations'), url: '/organizations' }, { name, url: match.url }] });
|
||||
}
|
||||
} catch (err) {
|
||||
this.setState({ error: true });
|
||||
} finally {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, match } = this.props;
|
||||
const { parentBreadcrumbObj, organization, error, loading } = this.state;
|
||||
const params = new URLSearchParams(location.search);
|
||||
const currentTab = params.get('tab') || 'details';
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<OrganizationBreadcrumb
|
||||
parentObj={parentBreadcrumbObj}
|
||||
currentTab={currentTab}
|
||||
location={location}
|
||||
organization={organization}
|
||||
/>
|
||||
<PageSection>
|
||||
<Switch>
|
||||
<Route
|
||||
path={`${match.path}/edit`}
|
||||
component={() => (
|
||||
<OrganizationEdit
|
||||
location={location}
|
||||
match={match}
|
||||
parentBreadcrumbObj={parentBreadcrumbObj}
|
||||
organization={organization}
|
||||
params={params}
|
||||
currentTab={currentTab}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
path={`${match.path}`}
|
||||
component={() => (
|
||||
<OrganizationDetail
|
||||
location={location}
|
||||
match={match}
|
||||
parentBreadcrumbObj={parentBreadcrumbObj}
|
||||
organization={organization}
|
||||
params={params}
|
||||
currentTab={currentTab}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
{error ? 'error!' : ''}
|
||||
{loading ? 'loading...' : ''}
|
||||
</PageSection>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Organization);
|
||||
@@ -0,0 +1,105 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
Switch,
|
||||
Link,
|
||||
Route
|
||||
} from 'react-router-dom';
|
||||
|
||||
import Tab from '../../../../components/Tabs/Tab';
|
||||
import Tabs from '../../../../components/Tabs/Tabs';
|
||||
import getTabName from '../../utils';
|
||||
|
||||
|
||||
const OrganizationDetail = ({
|
||||
location,
|
||||
match,
|
||||
parentBreadcrumbObj,
|
||||
organization,
|
||||
params,
|
||||
currentTab
|
||||
}) => {
|
||||
// TODO: set objectName by param or through grabbing org detail get from api
|
||||
const tabList=['details', 'access', 'teams', 'notifications'];
|
||||
|
||||
const deleteResourceView = () => (
|
||||
<Fragment>
|
||||
<Trans>{`deleting ${currentTab} association with orgs `}</Trans>
|
||||
<Link to={{ pathname: `${match.url}`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||
<Trans>{`confirm removal of ${currentTab}/cancel and go back to ${currentTab} view.`}</Trans>
|
||||
</Link>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const addResourceView = () => (
|
||||
<Fragment>
|
||||
<Trans>{`adding ${currentTab} `}</Trans>
|
||||
<Link to={{ pathname: `${match.url}`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||
<Trans>{`save/cancel and go back to ${currentTab} view`}</Trans>
|
||||
</Link>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const resourceView = () => (
|
||||
<Fragment>
|
||||
<Trans>{`${currentTab} detail view `}</Trans>
|
||||
<Link to={{ pathname: `${match.url}/add-resource`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||
<Trans>{`add ${currentTab}`}</Trans>
|
||||
</Link>
|
||||
{' '}
|
||||
<Link to={{ pathname: `${match.url}/delete-resources`, search: `?${params.toString()}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||
<Trans>{`delete ${currentTab}`}</Trans>
|
||||
</Link>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<Card className="at-c-orgPane">
|
||||
<CardHeader>
|
||||
<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') ? (
|
||||
<Switch>
|
||||
<Route path={`${match.path}/delete-resources`} component={() => deleteResourceView()} />
|
||||
<Route path={`${match.path}/add-resource`} component={() => addResourceView()} />
|
||||
<Route path={`${match.path}`} component={() => resourceView()} />
|
||||
</Switch>
|
||||
) : (
|
||||
<Fragment>
|
||||
{'detail view '}
|
||||
<Link to={{ pathname: `${match.url}/edit`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||
{'edit'}
|
||||
</Link>
|
||||
</Fragment>
|
||||
)}
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationDetail;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import {
|
||||
Card,
|
||||
CardBody
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
Link
|
||||
} from 'react-router-dom';
|
||||
|
||||
const OrganizationEdit = ({ match, parentBreadcrumbObj, organization }) => {
|
||||
|
||||
return (
|
||||
<Card className="at-c-orgPane">
|
||||
<CardBody>
|
||||
<Trans>edit view </Trans>
|
||||
<Link to={{ pathname: `/organizations/${match.params.id}`, state: { breadcrumb: parentBreadcrumbObj, organization } }}>
|
||||
<Trans>save/cancel and go back to view</Trans>
|
||||
</Link>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default OrganizationEdit;
|
||||
152
src/pages/Organizations/screens/OrganizationAdd.jsx
Normal file
152
src/pages/Organizations/screens/OrganizationAdd.jsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Trans } from '@lingui/macro';
|
||||
import {
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Title,
|
||||
Form,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
ActionGroup,
|
||||
Toolbar,
|
||||
ToolbarGroup,
|
||||
Button,
|
||||
Gallery,
|
||||
Card,
|
||||
CardBody,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { ConfigContext } from '../../../context';
|
||||
import AnsibleSelect from '../../../components/AnsibleSelect'
|
||||
const { light } = PageSectionVariants;
|
||||
|
||||
class OrganizationAdd extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
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 = {
|
||||
name: '',
|
||||
description: '',
|
||||
instanceGroups: '',
|
||||
custom_virtualenv: '',
|
||||
error:'',
|
||||
};
|
||||
|
||||
onSelectChange(value, _) {
|
||||
this.setState({ custom_virtualenv: value });
|
||||
};
|
||||
|
||||
resetForm() {
|
||||
this.setState({
|
||||
...this.state,
|
||||
name: '',
|
||||
description: ''
|
||||
})
|
||||
}
|
||||
|
||||
handleChange(_, evt) {
|
||||
this.setState({ [evt.target.name]: evt.target.value });
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
const { api } = this.props;
|
||||
const data = Object.assign({}, { ...this.state });
|
||||
await api.createOrganization(data);
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.props.history.push('/organizations');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { name } = this.state;
|
||||
const enabled = name.length > 0; // TODO: add better form validation
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageSection variant={light} className="pf-m-condensed">
|
||||
<Title size="2xl">
|
||||
<Trans>Organization Add</Trans>
|
||||
</Title>
|
||||
</PageSection>
|
||||
<PageSection>
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Form autoComplete="off">
|
||||
<Gallery gutter="md">
|
||||
<FormGroup
|
||||
label="Name"
|
||||
isRequired
|
||||
fieldId="add-org-form-name"
|
||||
>
|
||||
<TextInput
|
||||
isRequired
|
||||
type="text"
|
||||
id="add-org-form-name"
|
||||
name="name"
|
||||
value={this.state.name}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup label="Description" fieldId="add-org-form-description">
|
||||
<TextInput
|
||||
id="add-org-form-description"
|
||||
name="description"
|
||||
value={this.state.description}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
{/* LOOKUP MODAL PLACEHOLDER */}
|
||||
<FormGroup label="Instance Groups" fieldId="simple-form-instance-groups">
|
||||
<TextInput
|
||||
id="add-org-form-instance-groups"
|
||||
name="instance-groups"
|
||||
value={this.state.instanceGroups}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
<ConfigContext.Consumer>
|
||||
{({ custom_virtualenvs }) =>
|
||||
<AnsibleSelect
|
||||
labelName="Ansible Environment"
|
||||
selected={this.state.custom_virtualenv}
|
||||
selectChange={this.onSelectChange}
|
||||
data={custom_virtualenvs}
|
||||
/>
|
||||
}
|
||||
</ConfigContext.Consumer>
|
||||
</Gallery>
|
||||
<ActionGroup className="at-align-right">
|
||||
<Toolbar>
|
||||
<ToolbarGroup>
|
||||
<Button className="at-C-SubmitButton" variant="primary" onClick={this.onSubmit} isDisabled={!enabled}>Save</Button>
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
<Button className="at-C-CancelButton" variant="secondary" onClick={this.onCancel}>Cancel</Button>
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</ActionGroup>
|
||||
</Form>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</PageSection>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
OrganizationAdd.contextTypes = {
|
||||
custom_virtualenvs: PropTypes.array,
|
||||
};
|
||||
|
||||
export default withRouter(OrganizationAdd);
|
||||
251
src/pages/Organizations/screens/OrganizationsList.jsx
Normal file
251
src/pages/Organizations/screens/OrganizationsList.jsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import React, {
|
||||
Component,
|
||||
Fragment
|
||||
} from 'react';
|
||||
import {
|
||||
withRouter
|
||||
} from 'react-router-dom';
|
||||
import { I18n, i18nMark } from '@lingui/react';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import {
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Title,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import DataListToolbar from '../../../components/DataListToolbar';
|
||||
import OrganizationListItem from '../components/OrganizationListItem';
|
||||
import Pagination from '../../../components/Pagination';
|
||||
|
||||
import {
|
||||
encodeQueryString,
|
||||
parseQueryString,
|
||||
} from '../../../qs';
|
||||
|
||||
class OrganizationsList extends Component {
|
||||
columns = [
|
||||
{ name: i18nMark('Name'), key: 'name', isSortable: true },
|
||||
{ name: i18nMark('Modified'), key: 'modified', isSortable: true, isNumeric: true },
|
||||
{ name: i18nMark('Created'), key: 'created', isSortable: true, isNumeric: true },
|
||||
];
|
||||
|
||||
defaultParams = {
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
};
|
||||
|
||||
pageSizeOptions = [5, 10, 25, 50];
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
const { page, page_size } = this.getQueryParams();
|
||||
|
||||
this.state = {
|
||||
page,
|
||||
page_size,
|
||||
sortedColumnKey: 'name',
|
||||
sortOrder: 'ascending',
|
||||
count: null,
|
||||
error: null,
|
||||
loading: true,
|
||||
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 () {
|
||||
const queryParams = this.getQueryParams();
|
||||
this.fetchOrganizations(queryParams);
|
||||
}
|
||||
|
||||
onSearch () {
|
||||
const { sortedColumnKey, sortOrder } = this.state;
|
||||
|
||||
this.onSort(sortedColumnKey, sortOrder);
|
||||
}
|
||||
|
||||
getQueryParams (overrides = {}) {
|
||||
const { location } = this.props;
|
||||
const { search } = location;
|
||||
|
||||
const searchParams = parseQueryString(search.substring(1));
|
||||
|
||||
return Object.assign({}, this.defaultParams, searchParams, overrides);
|
||||
}
|
||||
|
||||
onSort(sortedColumnKey, sortOrder) {
|
||||
const { page_size } = this.state;
|
||||
|
||||
let order_by = sortedColumnKey;
|
||||
|
||||
if (sortOrder === 'descending') {
|
||||
order_by = `-${order_by}`;
|
||||
}
|
||||
|
||||
const queryParams = this.getQueryParams({ order_by, page_size });
|
||||
|
||||
this.fetchOrganizations(queryParams);
|
||||
}
|
||||
|
||||
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) {
|
||||
const { results } = this.state;
|
||||
|
||||
const selected = isSelected ? results.map(o => o.id) : [];
|
||||
|
||||
this.setState({ selected });
|
||||
}
|
||||
|
||||
onSelect (id) {
|
||||
const { selected } = this.state;
|
||||
|
||||
const isSelected = selected.includes(id);
|
||||
|
||||
if (isSelected) {
|
||||
this.setState({ selected: selected.filter(s => s !== id) });
|
||||
} else {
|
||||
this.setState({ selected: selected.concat(id) });
|
||||
}
|
||||
}
|
||||
|
||||
updateUrl (queryParams) {
|
||||
const { history, location } = this.props;
|
||||
const pathname = '/organizations';
|
||||
const search = `?${encodeQueryString(queryParams)}`;
|
||||
|
||||
if (search !== location.search) {
|
||||
history.replace({ pathname, search });
|
||||
}
|
||||
}
|
||||
|
||||
async fetchOrganizations (queryParams) {
|
||||
const { api } = this.props;
|
||||
const { page, page_size, order_by } = queryParams;
|
||||
|
||||
let sortOrder = 'ascending';
|
||||
let sortedColumnKey = order_by;
|
||||
|
||||
if (order_by.startsWith('-')) {
|
||||
sortOrder = 'descending';
|
||||
sortedColumnKey = order_by.substring(1);
|
||||
}
|
||||
|
||||
this.setState({ error: false, loading: true });
|
||||
|
||||
try {
|
||||
const { data } = await api.getOrganizations(queryParams);
|
||||
const { count, results } = data;
|
||||
|
||||
const pageCount = Math.ceil(count / page_size);
|
||||
|
||||
this.setState({
|
||||
count,
|
||||
page,
|
||||
pageCount,
|
||||
page_size,
|
||||
sortOrder,
|
||||
sortedColumnKey,
|
||||
results,
|
||||
selected: [],
|
||||
});
|
||||
this.updateUrl(queryParams);
|
||||
} catch (err) {
|
||||
this.setState({ error: true });
|
||||
} finally {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
light,
|
||||
medium,
|
||||
} = PageSectionVariants;
|
||||
const {
|
||||
count,
|
||||
error,
|
||||
loading,
|
||||
page,
|
||||
pageCount,
|
||||
page_size,
|
||||
sortedColumnKey,
|
||||
sortOrder,
|
||||
results,
|
||||
selected,
|
||||
} = this.state;
|
||||
const { match } = this.props;
|
||||
const parentBreadcrumb = { name: i18nMark('Organizations'), url: match.url };
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageSection variant={light} className="pf-m-condensed">
|
||||
<Title size="2xl">
|
||||
<Trans>Organizations</Trans>
|
||||
</Title>
|
||||
</PageSection>
|
||||
<PageSection variant={medium}>
|
||||
<DataListToolbar
|
||||
addUrl={`${match.url}/add`}
|
||||
isAllSelected={selected.length === results.length}
|
||||
sortedColumnKey={sortedColumnKey}
|
||||
sortOrder={sortOrder}
|
||||
columns={this.columns}
|
||||
onSearch={this.onSearch}
|
||||
onSort={this.onSort}
|
||||
onSelectAll={this.onSelectAll}
|
||||
/>
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ul className="pf-c-data-list" aria-label={i18n._(t`Organizations List`)}>
|
||||
{ results.map(o => (
|
||||
<OrganizationListItem
|
||||
key={o.id}
|
||||
itemId={o.id}
|
||||
name={o.name}
|
||||
detailUrl={`${match.url}/${o.id}`}
|
||||
parentBreadcrumb={parentBreadcrumb}
|
||||
userCount={o.summary_fields.related_field_counts.users}
|
||||
teamCount={o.summary_fields.related_field_counts.teams}
|
||||
isSelected={selected.includes(o.id)}
|
||||
onSelect={() => this.onSelect(o.id)}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</I18n>
|
||||
<Pagination
|
||||
count={count}
|
||||
page={page}
|
||||
pageCount={pageCount}
|
||||
page_size={page_size}
|
||||
pageSizeOptions={this.pageSizeOptions}
|
||||
onSetPage={this.onSetPage}
|
||||
/>
|
||||
{ loading ? <div>loading...</div> : '' }
|
||||
{ error ? <div>error</div> : '' }
|
||||
</PageSection>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(OrganizationsList);
|
||||
Reference in New Issue
Block a user