mirror of
https://github.com/ansible/awx.git
synced 2026-02-02 10:08:10 -03:30
Merge pull request #141 from AlexSCorey/48-deleteOrgs
Add alert for org. delete.
This commit is contained in:
@@ -56,6 +56,11 @@ class APIClient {
|
||||
return this.http.get(API_CONFIG);
|
||||
}
|
||||
|
||||
destroyOrganization (id) {
|
||||
const endpoint = `${API_ORGANIZATIONS}${id}/`;
|
||||
return (this.http.delete(endpoint));
|
||||
}
|
||||
|
||||
getOrganizations (params = {}) {
|
||||
return this.http.get(API_ORGANIZATIONS, { params });
|
||||
}
|
||||
|
||||
13
src/app.scss
13
src/app.scss
@@ -279,6 +279,17 @@
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.orgListAlert-actionBtn{
|
||||
margin:0 10px;
|
||||
}
|
||||
.orgListDetete-progressBar{
|
||||
padding-right: 32px;
|
||||
}
|
||||
.orgListDelete-progressBar-noShow{
|
||||
display: none;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.awx-c-form-action-group {
|
||||
float: right;
|
||||
display: block;
|
||||
@@ -287,4 +298,4 @@
|
||||
margin-top: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,25 +28,26 @@ import VerticalSeparator from '../VerticalSeparator';
|
||||
class DataListToolbar extends React.Component {
|
||||
render () {
|
||||
const {
|
||||
addUrl,
|
||||
columns,
|
||||
isAllSelected,
|
||||
disableTrashCanIcon,
|
||||
onSelectAll,
|
||||
sortedColumnKey,
|
||||
sortOrder,
|
||||
addUrl,
|
||||
showDelete,
|
||||
showSelectAll,
|
||||
isAllSelected,
|
||||
isLookup,
|
||||
isCompact,
|
||||
onSort,
|
||||
onSearch,
|
||||
onCompact,
|
||||
onExpand,
|
||||
add
|
||||
add,
|
||||
onOpenDeleteModal
|
||||
} = this.props;
|
||||
|
||||
const showExpandCollapse = (onCompact && onExpand);
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -115,10 +116,13 @@ class DataListToolbar extends React.Component {
|
||||
position="top"
|
||||
>
|
||||
<Button
|
||||
className="awx-ToolBarBtn"
|
||||
variant="plain"
|
||||
aria-label={i18n._(t`Delete`)}
|
||||
onClick={onOpenDeleteModal}
|
||||
isDisabled={disableTrashCanIcon}
|
||||
>
|
||||
<TrashAltIcon />
|
||||
<TrashAltIcon className="awx-ToolBarTrashCanIcon" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -80,3 +80,16 @@
|
||||
.awx-toolbar .pf-l-toolbar__item .pf-c-button.pf-m-plain {
|
||||
font-size: 18px;
|
||||
}
|
||||
.pf-c-button--disabled--BackgroundColor{
|
||||
background-color: #b7b7b7;
|
||||
}
|
||||
.awx-ToolBarBtn{
|
||||
width: 30px;
|
||||
}
|
||||
.awx-ToolBarBtn:hover{
|
||||
.awx-ToolBarTrashCanIcon {
|
||||
color:white;
|
||||
}
|
||||
background-color:#d9534f;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,54 +11,61 @@ import {
|
||||
|
||||
import VerticalSeparator from '../../../components/VerticalSeparator';
|
||||
|
||||
export default ({
|
||||
itemId,
|
||||
name,
|
||||
userCount,
|
||||
teamCount,
|
||||
isSelected,
|
||||
onSelect,
|
||||
detailUrl,
|
||||
}) => (
|
||||
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
aria-label={i18n._(t`select organization ${itemId}`)}
|
||||
id={`select-organization-${itemId}`}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
<VerticalSeparator />
|
||||
<div className="pf-c-data-list__cell">
|
||||
<span id="check-action-item1">
|
||||
<Link
|
||||
to={`${detailUrl}`}
|
||||
>
|
||||
<b>{name}</b>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell">
|
||||
<Link to={`${detailUrl}/access`}>
|
||||
<Trans>Users</Trans>
|
||||
</Link>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{userCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
<Link to={`${detailUrl}/teams`}>
|
||||
<Trans>Teams</Trans>
|
||||
</Link>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{teamCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell" />
|
||||
</li>
|
||||
);
|
||||
class OrganizationListItem extends React.Component {
|
||||
render () {
|
||||
const {
|
||||
itemId,
|
||||
name,
|
||||
userCount,
|
||||
teamCount,
|
||||
isSelected,
|
||||
onSelect,
|
||||
detailUrl,
|
||||
} = this.props;
|
||||
return (
|
||||
<li key={itemId} className="pf-c-data-list__item" aria-labelledby="check-action-item1">
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onChange={onSelect}
|
||||
aria-label={i18n._(t`select organization ${itemId}`)}
|
||||
id={`select-organization-${itemId}`}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
<VerticalSeparator />
|
||||
<div className="pf-c-data-list__cell">
|
||||
<span id="check-action-item1">
|
||||
<Link
|
||||
to={`${detailUrl}`}
|
||||
>
|
||||
<b>{name}</b>
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell">
|
||||
<Link to={`${detailUrl}/access`}>
|
||||
<Trans>Users</Trans>
|
||||
</Link>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{userCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
<Link to={`${detailUrl}/teams`}>
|
||||
<Trans>Teams</Trans>
|
||||
</Link>
|
||||
<Badge isRead>
|
||||
{' '}
|
||||
{teamCount}
|
||||
{' '}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="pf-c-data-list__cell" />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default OrganizationListItem;
|
||||
|
||||
|
||||
@@ -12,10 +12,15 @@ import {
|
||||
EmptyState,
|
||||
EmptyStateIcon,
|
||||
EmptyStateBody,
|
||||
Modal,
|
||||
PageSection,
|
||||
PageSectionVariants,
|
||||
Title
|
||||
Title,
|
||||
Button,
|
||||
Progress,
|
||||
ProgressVariant
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { CubesIcon } from '@patternfly/react-icons';
|
||||
import DataListToolbar from '../../../components/DataListToolbar';
|
||||
import OrganizationListItem from '../components/OrganizationListItem';
|
||||
@@ -54,6 +59,11 @@ class OrganizationsList extends Component {
|
||||
loading: true,
|
||||
results: [],
|
||||
selected: [],
|
||||
isModalOpen: false,
|
||||
orgsToDelete: [],
|
||||
orgsDeleted: [],
|
||||
deleteSuccess: false,
|
||||
|
||||
};
|
||||
|
||||
this.onSearch = this.onSearch.bind(this);
|
||||
@@ -64,6 +74,9 @@ class OrganizationsList extends Component {
|
||||
this.onSelect = this.onSelect.bind(this);
|
||||
this.updateUrl = this.updateUrl.bind(this);
|
||||
this.fetchOrganizations = this.fetchOrganizations.bind(this);
|
||||
this.handleOrgDelete = this.handleOrgDelete.bind(this);
|
||||
this.handleOpenOrgDeleteModal = this.handleOpenOrgDeleteModal.bind(this);
|
||||
this.handleClearOrgsToDelete = this.handleClearOrgsToDelete.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -129,6 +142,60 @@ class OrganizationsList extends Component {
|
||||
return Object.assign({}, this.defaultParams, searchParams, overrides);
|
||||
}
|
||||
|
||||
handleClearOrgsToDelete () {
|
||||
this.setState({
|
||||
isModalOpen: false,
|
||||
orgsDeleted: [],
|
||||
deleteSuccess: false,
|
||||
orgsToDelete: [],
|
||||
deleteStarted: false
|
||||
});
|
||||
this.onSelectAll();
|
||||
}
|
||||
|
||||
handleOpenOrgDeleteModal () {
|
||||
const { results, selected } = this.state;
|
||||
const warningTitle = i18nMark('Delete Organization');
|
||||
const warningMsg = i18nMark('Are you sure you want to delete:');
|
||||
|
||||
const orgsToDelete = [];
|
||||
results.forEach((result) => {
|
||||
selected.forEach((selectedOrg) => {
|
||||
if (result.id === selectedOrg) {
|
||||
orgsToDelete.push({ name: result.name, id: selectedOrg });
|
||||
}
|
||||
});
|
||||
});
|
||||
this.setState({
|
||||
orgsToDelete,
|
||||
isModalOpen: true,
|
||||
warningTitle,
|
||||
warningMsg,
|
||||
loading: false });
|
||||
}
|
||||
|
||||
async handleOrgDelete (event) {
|
||||
const { orgsToDelete, orgsDeleted } = this.state;
|
||||
const { api } = this.props;
|
||||
this.setState({ deleteStarted: true });
|
||||
|
||||
orgsToDelete.forEach(async (org) => {
|
||||
try {
|
||||
const res = await api.destroyOrganization(org.id);
|
||||
this.setState({
|
||||
orgsDeleted: orgsDeleted.concat(res)
|
||||
});
|
||||
} catch {
|
||||
this.setState({ deleteSuccess: false });
|
||||
} finally {
|
||||
this.setState({ deleteSuccess: true });
|
||||
const queryParams = this.getQueryParams();
|
||||
this.fetchOrganizations(queryParams);
|
||||
}
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
updateUrl (queryParams) {
|
||||
const { history, location } = this.props;
|
||||
const pathname = '/organizations';
|
||||
@@ -194,20 +261,65 @@ class OrganizationsList extends Component {
|
||||
const {
|
||||
count,
|
||||
error,
|
||||
deleteSuccess,
|
||||
deleteStarted,
|
||||
loading,
|
||||
noInitialResults,
|
||||
orgsToDelete,
|
||||
orgsDeleted,
|
||||
page,
|
||||
pageCount,
|
||||
page_size,
|
||||
selected,
|
||||
sortedColumnKey,
|
||||
sortOrder,
|
||||
results,
|
||||
selected,
|
||||
isModalOpen,
|
||||
warningTitle,
|
||||
warningMsg,
|
||||
} = this.state;
|
||||
const { match } = this.props;
|
||||
return (
|
||||
<PageSection variant={medium}>
|
||||
<Card>
|
||||
{ isModalOpen && (
|
||||
<Modal
|
||||
className="orgListAlert"
|
||||
title={warningTitle}
|
||||
isOpen={isModalOpen}
|
||||
style={{ width: '1000px' }}
|
||||
variant="danger"
|
||||
onClose={this.handleClearOrgsToDelete}
|
||||
>
|
||||
{warningMsg}
|
||||
<br />
|
||||
{orgsToDelete.map((org) => (
|
||||
<span key={org.id}>
|
||||
<strong>
|
||||
{org.name}
|
||||
</strong>
|
||||
<br />
|
||||
</span>
|
||||
))}
|
||||
<div className={deleteStarted ? 'orgListDetete-progressBar' : 'orgListDelete-progressBar-noShow'}>
|
||||
<Progress
|
||||
value={deleteSuccess ? 100 : 67}
|
||||
variant={deleteStarted ? ProgressVariant.success : ProgressVariant.danger}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div className="awx-c-form-action-group">
|
||||
{orgsDeleted.length
|
||||
? <Button className="orgListAlert-actionBtn" keys="cancel" variant="primary" aria-label="close-delete" onClick={this.handleClearOrgsToDelete}>Close</Button>
|
||||
: (
|
||||
<span>
|
||||
<Button className="orgListAlert-actionBtn" keys="cancel" variant="secondary" aria-label="cancel-delete" onClick={this.handleClearOrgsToDelete}>Cancel</Button>
|
||||
<Button className="orgListAlert-actionBtn" keys="cancel" variant="danger" aria-label="confirm-delete" onClick={this.handleOrgDelete}>Delete</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
{noInitialResults && (
|
||||
<EmptyState>
|
||||
<EmptyStateIcon icon={CubesIcon} />
|
||||
@@ -229,6 +341,8 @@ class OrganizationsList extends Component {
|
||||
onSearch={this.onSearch}
|
||||
onSort={this.onSort}
|
||||
onSelectAll={this.onSelectAll}
|
||||
onOpenDeleteModal={this.handleOpenOrgDeleteModal}
|
||||
disableTrashCanIcon={selected.length === 0}
|
||||
showDelete
|
||||
showSelectAll
|
||||
/>
|
||||
@@ -244,7 +358,8 @@ class OrganizationsList extends Component {
|
||||
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)}
|
||||
onSelect={() => this.onSelect(o.id, o.name)}
|
||||
onOpenOrgDeleteModal={this.handleOpenOrgDeleteModal}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user