mirror of
https://github.com/ansible/awx.git
synced 2026-03-13 15:09:32 -02:30
WIP
This commit is contained in:
@@ -11,7 +11,7 @@ import NotificationList from '@components/NotificationList';
|
|||||||
import { ResourceAccessList } from '@components/ResourceAccessList';
|
import { ResourceAccessList } from '@components/ResourceAccessList';
|
||||||
import ProjectDetail from './ProjectDetail';
|
import ProjectDetail from './ProjectDetail';
|
||||||
import ProjectEdit from './ProjectEdit';
|
import ProjectEdit from './ProjectEdit';
|
||||||
import ProjectJobTemplates from './ProjectJobTemplates';
|
import ProjectJobTemplatesList from './ProjectJobTemplatesList';
|
||||||
import ProjectSchedules from './ProjectSchedules';
|
import ProjectSchedules from './ProjectSchedules';
|
||||||
import { OrganizationsAPI, ProjectsAPI } from '@api';
|
import { OrganizationsAPI, ProjectsAPI } from '@api';
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ class Project extends Component {
|
|||||||
<Route
|
<Route
|
||||||
path="/projects/:id/job_templates"
|
path="/projects/:id/job_templates"
|
||||||
render={() => (
|
render={() => (
|
||||||
<ProjectJobTemplates id={Number(match.params.id)} />
|
<ProjectJobTemplatesList id={Number(match.params.id)} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
import TemplateList from '../../Template/TemplateList/TemplateList';
|
|
||||||
|
|
||||||
function ProjectJobTemplates() {
|
|
||||||
return <TemplateList />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withRouter(ProjectJobTemplates);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './ProjectJobTemplates';
|
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useParams, useLocation } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Card } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
JobTemplatesAPI,
|
||||||
|
UnifiedJobTemplatesAPI,
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
} from '@api';
|
||||||
|
import AlertModal from '@components/AlertModal';
|
||||||
|
import DatalistToolbar from '@components/DataListToolbar';
|
||||||
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
|
import PaginatedDataList, {
|
||||||
|
ToolbarDeleteButton,
|
||||||
|
} from '@components/PaginatedDataList';
|
||||||
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
|
import AddDropDownButton from '@components/AddDropDownButton';
|
||||||
|
import ProjectTemplatesListItem from '../../Template/TemplateList/TemplateListItem';
|
||||||
|
|
||||||
|
// The type value in const QS_CONFIG below does not have a space between job_template and
|
||||||
|
// workflow_job_template so the params sent to the API match what the api expects.
|
||||||
|
const QS_CONFIG = getQSConfig('template', {
|
||||||
|
page: 1,
|
||||||
|
page_size: 20,
|
||||||
|
order_by: 'name',
|
||||||
|
type: 'job_template,workflow_job_template',
|
||||||
|
});
|
||||||
|
|
||||||
|
function ProjectJobTemplatesList({ i18n }) {
|
||||||
|
const { id: projectId } = useParams();
|
||||||
|
const { pathname, search } = useLocation();
|
||||||
|
|
||||||
|
const [deletionError, setDeletionError] = useState(null);
|
||||||
|
const [contentError, setContentError] = useState(null);
|
||||||
|
const [hasContentLoading, setHasContentLoading] = useState(true);
|
||||||
|
const [jtActions, setJTActions] = useState(null);
|
||||||
|
const [wfjtActions, setWFJTActions] = useState(null);
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
const [templates, setTemplates] = useState([]);
|
||||||
|
const [selected, setSelected] = useState([]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const loadTemplates = async () => {
|
||||||
|
const params = {
|
||||||
|
...parseQueryString(QS_CONFIG, search),
|
||||||
|
};
|
||||||
|
|
||||||
|
let jtOptionsPromise;
|
||||||
|
if (jtActions) {
|
||||||
|
jtOptionsPromise = Promise.resolve({
|
||||||
|
data: { actions: jtActions },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
jtOptionsPromise = JobTemplatesAPI.readOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
let wfjtOptionsPromise;
|
||||||
|
if (wfjtActions) {
|
||||||
|
wfjtOptionsPromise = Promise.resolve({
|
||||||
|
data: { actions: wfjtActions },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
wfjtOptionsPromise = WorkflowJobTemplatesAPI.readOptions();
|
||||||
|
}
|
||||||
|
if (pathname.startsWith('/projects') && projectId) {
|
||||||
|
params.jobtemplate__project = projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = Promise.all([
|
||||||
|
UnifiedJobTemplatesAPI.read(params),
|
||||||
|
jtOptionsPromise,
|
||||||
|
wfjtOptionsPromise,
|
||||||
|
]);
|
||||||
|
setDeletionError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [
|
||||||
|
{
|
||||||
|
data: { count: itemCount, results },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: { actions: jobTemplateActions },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: { actions: workFlowJobTemplateActions },
|
||||||
|
},
|
||||||
|
] = await promises;
|
||||||
|
setJTActions(jobTemplateActions);
|
||||||
|
setWFJTActions(workFlowJobTemplateActions);
|
||||||
|
setCount(itemCount);
|
||||||
|
setTemplates(results);
|
||||||
|
setHasContentLoading(false);
|
||||||
|
} catch (err) {
|
||||||
|
setContentError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadTemplates();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[pathname, search, count, projectId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSelectAll = isSelected => {
|
||||||
|
const selectedItems = isSelected ? [...templates] : [];
|
||||||
|
setSelected(selectedItems);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = template => {
|
||||||
|
if (selected.some(s => s.id === template.id)) {
|
||||||
|
setSelected(selected.filter(s => s.id !== template.id));
|
||||||
|
} else {
|
||||||
|
setSelected(selected.concat(template));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTemplateDelete = async () => {
|
||||||
|
setHasContentLoading(true);
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
selected.map(({ type, id }) => {
|
||||||
|
let deletePromise;
|
||||||
|
if (type === 'job_template') {
|
||||||
|
deletePromise = JobTemplatesAPI.destroy(id);
|
||||||
|
} else if (type === 'workflow_job_template') {
|
||||||
|
deletePromise = WorkflowJobTemplatesAPI.destroy(id);
|
||||||
|
}
|
||||||
|
return deletePromise;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setCount(count - selected.length);
|
||||||
|
} catch (err) {
|
||||||
|
setDeletionError(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canAddJT =
|
||||||
|
jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST');
|
||||||
|
const canAddWFJT =
|
||||||
|
wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST');
|
||||||
|
const addButtonOptions = [];
|
||||||
|
if (canAddJT) {
|
||||||
|
addButtonOptions.push({
|
||||||
|
label: i18n._(t`Template`),
|
||||||
|
url: `/templates/job_template/add/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (canAddWFJT) {
|
||||||
|
addButtonOptions.push({
|
||||||
|
label: i18n._(t`Workflow Template`),
|
||||||
|
url: `/templates/workflow_job_template/add/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const isAllSelected =
|
||||||
|
selected.length === templates.length && selected.length > 0;
|
||||||
|
const addButton = (
|
||||||
|
<AddDropDownButton key="add" dropdownItems={addButtonOptions} />
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card>
|
||||||
|
<PaginatedDataList
|
||||||
|
contentError={contentError}
|
||||||
|
hasContentLoading={hasContentLoading}
|
||||||
|
items={templates}
|
||||||
|
itemCount={count}
|
||||||
|
pluralizedItemName={i18n._(t`Templates`)}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
onRowClick={handleSelect}
|
||||||
|
toolbarSearchColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Type`),
|
||||||
|
key: 'type',
|
||||||
|
options: [
|
||||||
|
[`job_template`, i18n._(t`Job Template`)],
|
||||||
|
[`workflow_job_template`, i18n._(t`Workflow Template`)],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Playbook name`),
|
||||||
|
key: 'job_template__playbook',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created By (Username)`),
|
||||||
|
key: 'created_by__username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified By (Username)`),
|
||||||
|
key: 'modified_by__username',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
toolbarSortColumns={[
|
||||||
|
{
|
||||||
|
name: i18n._(t`Inventory`),
|
||||||
|
key: 'job_template__inventory__id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Last Job Run`),
|
||||||
|
key: 'last_job_run',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified`),
|
||||||
|
key: 'modified',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Project`),
|
||||||
|
key: 'jobtemplate__project__id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Type`),
|
||||||
|
key: 'type',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
renderToolbar={props => (
|
||||||
|
<DatalistToolbar
|
||||||
|
{...props}
|
||||||
|
showSelectAll
|
||||||
|
showExpandCollapse
|
||||||
|
isAllSelected={isAllSelected}
|
||||||
|
onSelectAll={handleSelectAll}
|
||||||
|
qsConfig={QS_CONFIG}
|
||||||
|
additionalControls={[
|
||||||
|
<ToolbarDeleteButton
|
||||||
|
key="delete"
|
||||||
|
onDelete={handleTemplateDelete}
|
||||||
|
itemsToDelete={selected}
|
||||||
|
pluralizedItemName="Templates"
|
||||||
|
/>,
|
||||||
|
(canAddJT || canAddWFJT) && addButton,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
renderItem={template => (
|
||||||
|
<ProjectTemplatesListItem
|
||||||
|
key={template.id}
|
||||||
|
value={template.name}
|
||||||
|
template={template}
|
||||||
|
detailUrl={`/templates/${template.type}/${template.id}/details`}
|
||||||
|
onSelect={() => handleSelect(template)}
|
||||||
|
isSelected={selected.some(row => row.id === template.id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
emptyStateControls={(canAddJT || canAddWFJT) && addButton}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<AlertModal
|
||||||
|
isOpen={deletionError}
|
||||||
|
variant="danger"
|
||||||
|
title={i18n._(t`Error!`)}
|
||||||
|
onClose={() => setDeletionError(null)}
|
||||||
|
>
|
||||||
|
{i18n._(t`Failed to delete one or more templates.`)}
|
||||||
|
<ErrorDetail error={deletionError} />
|
||||||
|
</AlertModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(ProjectJobTemplatesList);
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export {
|
||||||
|
default
|
||||||
|
}
|
||||||
|
from './ProjectJobTemplatesList';
|
||||||
@@ -40,11 +40,11 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) {
|
|||||||
(!template.summary_fields.inventory &&
|
(!template.summary_fields.inventory &&
|
||||||
!template.ask_inventory_on_launch));
|
!template.ask_inventory_on_launch));
|
||||||
|
|
||||||
const location = useLocation();
|
// const location = useLocation();
|
||||||
|
|
||||||
if (location.pathname.startsWith('/projects')) {
|
// if (location.pathname.startsWith('/projects')) {
|
||||||
detailUrl = `/templates/job_template/${template.id}/details`;
|
// detailUrl = `/templates/job_template/${template.id}/details`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataListItem aria-labelledby={labelId} id={`${template.id}`}>
|
<DataListItem aria-labelledby={labelId} id={`${template.id}`}>
|
||||||
|
|||||||
Reference in New Issue
Block a user