Files
awx/awx/ui/client/features/templates/templatesList.controller.js
2019-05-02 13:55:20 -04:00

491 lines
17 KiB
JavaScript

/** ***********************************************
* Copyright (c) 2018 Ansible, Inc.
*
* All Rights Reserved
************************************************ */
const JOB_TEMPLATE_ALIASES = ['job_template', 'Job Template'];
const WORKFLOW_TEMPLATE_ALIASES = ['workflow_job_template', 'Workflow Job Template'];
const isJobTemplate = ({ type }) => JOB_TEMPLATE_ALIASES.indexOf(type) > -1;
const isWorkflowTemplate = ({ type }) => WORKFLOW_TEMPLATE_ALIASES.indexOf(type) > -1;
const mapChoices = choices => Object.assign(...choices.map(([k, v]) => ({[k]: v})));
function ListTemplatesController(
$filter,
$scope,
$state,
Alert,
Dataset,
ProcessErrors,
Prompt,
resolvedModels,
strings,
Wait,
qs,
GetBasePath,
ngToast,
$timeout
) {
const vm = this || {};
const [jobTemplate, workflowTemplate] = resolvedModels;
const choices = workflowTemplate.options('actions.GET.type.choices')
.concat(jobTemplate.options('actions.GET.type.choices'));
let launchModalOpen = false;
let refreshAfterLaunchClose = false;
let pendingRefresh = false;
let refreshTimerRunning = false;
let paginateQuerySet = {};
vm.strings = strings;
vm.templateTypes = mapChoices(choices);
vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_job_template_id);
vm.invalidTooltip = {
popover: {
text: strings.get('error.INVALID'),
on: 'mouseenter',
icon: 'fa-exclamation',
position: 'right',
arrowHeight: 15
}
};
$scope.canAddJobTemplate = jobTemplate.options('actions.POST');
$scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST');
$scope.canAdd = ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate);
// smart-search
vm.list = {
iterator: 'template',
name: 'templates'
};
vm.dataset = Dataset.data;
vm.templates = Dataset.data.results;
vm.defaultParams = $state.params.template_search;
const toolbarSortDefault = {
label: `${strings.get('sort.NAME_ASCENDING')}`,
value: 'name'
};
vm.toolbarSortOptions = [
toolbarSortDefault,
{ label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' },
{ label: `${strings.get('sort.MODIFIED_ASCENDING')}`, value: 'modified' },
{ label: `${strings.get('sort.MODIFIED_DESCENDING')}`, value: '-modified' },
{ label: `${strings.get('sort.LAST_JOB_RUN_ASCENDING')}`, value: 'last_job_run' },
{ label: `${strings.get('sort.LAST_JOB_RUN_DESCENDING')}`, value: '-last_job_run' },
{ label: `${strings.get('sort.INVENTORY_ASCENDING')}`, value: 'job_template__inventory__id' },
{ label: `${strings.get('sort.INVENTORY_DESCENDING')}`, value: '-job_template__inventory__id' },
{ label: `${strings.get('sort.PROJECT_ASCENDING')}`, value: 'jobtemplate__project__id' },
{ label: `${strings.get('sort.PROJECT_DESCENDING')}`, value: '-jobtemplate__project__id' },
];
vm.toolbarSortValue = toolbarSortDefault;
$scope.$on('updateDataset', (event, dataset, queryset) => {
paginateQuerySet = queryset;
});
vm.onToolbarSort = (sort) => {
vm.toolbarSortValue = sort;
const queryParams = Object.assign(
{},
$state.params.template_search,
paginateQuerySet,
{ order_by: sort.value }
);
// Update params
$state.go('.', {
template_search: queryParams
}, { notify: false, location: 'replace' });
};
$scope.$watch('vm.dataset.count', () => {
$scope.$emit('updateCount', vm.dataset.count, 'templates');
});
$scope.$watch('$state.params', function(newValue, oldValue) {
const job_template_id = _.get($state.params, 'job_template_id');
const workflow_job_template_id = _.get($state.params, 'workflow_job_template_id');
if((job_template_id || workflow_job_template_id)) {
vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_job_template_id);
} else {
vm.activeId = "";
}
setToolbarSort();
}, true);
$scope.$on(`ws-jobs`, () => {
if (!launchModalOpen) {
if (!refreshTimerRunning) {
refreshTemplates();
} else {
pendingRefresh = true;
}
} else {
refreshAfterLaunchClose = true;
}
});
$scope.$on('launchModalOpen', (evt, isOpen) => {
evt.stopPropagation();
if (!isOpen && refreshAfterLaunchClose) {
refreshAfterLaunchClose = false;
refreshTemplates();
}
launchModalOpen = isOpen;
});
vm.isInvalid = (template) => {
if(isJobTemplate(template)) {
return template.project === null || (template.inventory === null && template.ask_inventory_on_launch === false);
} else {
return false;
}
};
vm.isPortalMode = $state.includes('portalMode');
vm.openWorkflowVisualizer = template => {
const name = 'templates.editWorkflowJobTemplate.workflowMaker';
const params = { workflow_job_template_id: template.id };
const options = { reload: true };
$state.go(name, params, options);
};
vm.deleteTemplate = template => {
if (!template) {
Alert(strings.get('error.DELETE'), strings.get('alert.MISSING_PARAMETER'));
return;
}
if (isWorkflowTemplate(template)) {
displayWorkflowTemplateDeletePrompt(template);
} else if (isJobTemplate(template)) {
jobTemplate.getDependentResourceCounts(template.id)
.then(counts => displayJobTemplateDeletePrompt(template, counts));
} else {
Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_DELETE'));
}
};
vm.copyTemplate = template => {
if (!template) {
Alert(strings.get('error.COPY'), strings.get('alert.MISSING_PARAMETER'));
return;
}
if (isJobTemplate(template)) {
copyJobTemplate(template);
} else if (isWorkflowTemplate(template)) {
copyWorkflowTemplate(template);
} else {
Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_COPY'));
}
};
vm.getModified = template => {
const modified = _.get(template, 'modified');
if (!modified) {
return undefined;
}
let html = $filter('longDate')(modified);
const { username, id } = _.get(template, 'summary_fields.modified_by', {});
if (username && id) {
html += ` by <a href="/#/users/${id}">${$filter('sanitize')(username)}</a>`;
}
return html;
};
vm.buildCredentialTags = (credentials) => {
return credentials.map(credential => {
const icon = `${credential.kind}`;
const link = `/#/credentials/${credential.id}`;
const tooltip = strings.get('tooltips.VIEW_THE_CREDENTIAL');
const value = $filter('sanitize')(credential.name);
return { icon, link, tooltip, value };
});
};
vm.getLastRan = template => {
const lastJobRun = _.get(template, 'last_job_run');
if (!lastJobRun) {
return undefined;
}
let html = $filter('longDate')(lastJobRun);
// TODO: uncomment and update when last job run user is returned by api
// const { username, id } = _.get(template, 'summary_fields.last_job_run_by', {});
// if (username && id) {
// html += ` by <a href="/#/users/${id}">${$filter('sanitize')(username)}</a>`;
//}
return html;
};
vm.getType = template => {
if(isJobTemplate(template)) {
return strings.get('list.ADD_DD_JT_LABEL');
} else {
return strings.get('list.ADD_DD_WF_LABEL');;
}
};
function setToolbarSort () {
const orderByValue = _.get($state.params, 'template_search.order_by');
const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue);
if (sortValue) {
vm.toolbarSortValue = sortValue;
} else {
vm.toolbarSortValue = toolbarSortDefault;
}
}
function refreshTemplates() {
Wait('start');
let path = GetBasePath('unified_job_templates');
qs.search(path, $state.params.template_search, { 'X-WS-Session-Quiet': true })
.then(function(searchResponse) {
vm.dataset = searchResponse.data;
vm.templates = vm.dataset.results;
})
.finally(() => Wait('stop'));
pendingRefresh = false;
refreshTimerRunning = true;
$timeout(() => {
if (pendingRefresh) {
refreshTemplates();
} else {
refreshTimerRunning = false;
}
}, 5000);
}
function createErrorHandler(path, action) {
return ({ data, status }) => {
const hdr = strings.get('error.HEADER');
const msg = strings.get('error.CALL', { path, action, status });
ProcessErrors($scope, data, status, null, { hdr, msg });
};
}
function copyJobTemplate(template) {
Wait('start');
jobTemplate
.create('get', template.id)
.then(model => model.copy())
.then((copiedJT) => {
ngToast.success({
content: `
<div class="Toast-wrapper">
<div class="Toast-icon">
<i class="fa fa-check-circle Toast-successIcon"></i>
</div>
<div>
${strings.get('SUCCESSFUL_CREATION', copiedJT.name)}
</div>
</div>`,
dismissButton: false,
dismissOnTimeout: true
});
refreshTemplates();
})
.catch(createErrorHandler('copy job template', 'POST'))
.finally(() => Wait('stop'));
}
function copyWorkflowTemplate(template) {
Wait('start');
workflowTemplate
.create('get', template.id)
.then(model => model.extend('get', 'copy'))
.then(model => {
const action = () => {
$('#prompt-modal').modal('hide');
Wait('start');
model.copy()
.then((copiedWFJT) => {
ngToast.success({
content: `
<div class="Toast-wrapper">
<div class="Toast-icon">
<i class="fa fa-check-circle Toast-successIcon"></i>
</div>
<div>
${strings.get('SUCCESSFUL_CREATION', copiedWFJT.name)}
</div>
</div>`,
dismissButton: false,
dismissOnTimeout: true
});
refreshTemplates();
})
.catch(createErrorHandler('copy workflow', 'POST'))
.finally(() => Wait('stop'));
};
if (model.get('related.copy.can_copy_without_user_input')) {
action();
} else if (model.get('related.copy.can_copy')) {
Prompt({
action,
actionText: strings.get('COPY'),
body: buildWorkflowCopyPromptHTML(model.get('related.copy')),
class: 'Modal-primaryButton',
hdr: strings.get('listActions.COPY', template.name),
});
} else {
Alert(strings.get('error.COPY'), strings.get('alert.NO_PERMISSION'));
}
})
.catch(createErrorHandler('copy workflow', 'GET'))
.finally(() => Wait('stop'));
}
function handleSuccessfulDelete(template) {
const { page } = _.get($state.params, 'template_search');
let reloadListStateParams = null;
if (vm.templates.length === 1 && page && page !== '1') {
reloadListStateParams = _.cloneDeep($state.params);
const pageNumber = (parseInt(reloadListStateParams.template_search.page, 0) - 1);
reloadListStateParams.template_search.page = pageNumber.toString();
}
if (parseInt($state.params.job_template_id, 0) === template.id) {
$state.go('templates', reloadListStateParams, { reload: true });
} else if (parseInt($state.params.workflow_job_template_id, 0) === template.id) {
$state.go('templates', reloadListStateParams, { reload: true });
} else {
$state.go('.', reloadListStateParams, { reload: true });
}
}
function displayJobTemplateDeletePrompt(template, counts) {
Prompt({
action() {
$('#prompt-modal').modal('hide');
Wait('start');
jobTemplate
.request('delete', template.id)
.then(() => handleSuccessfulDelete(template))
.catch(createErrorHandler('delete template', 'DELETE'))
.finally(() => Wait('stop'));
},
hdr: strings.get('DELETE'),
resourceName: $filter('sanitize')(template.name),
body: buildJobTemplateDeletePromptHTML(counts),
});
}
function displayWorkflowTemplateDeletePrompt(template) {
Prompt({
action() {
$('#prompt-modal').modal('hide');
Wait('start');
workflowTemplate
.request('delete', template.id)
.then(() => handleSuccessfulDelete(template))
.catch(createErrorHandler('delete template', 'DELETE'))
.finally(() => Wait('stop'));
},
hdr: strings.get('DELETE'),
resourceName: $filter('sanitize')(template.name),
body: strings.get('deleteResource.CONFIRM', 'workflow template'),
});
}
function buildJobTemplateDeletePromptHTML(counts) {
const buildCount = count => `<span class="badge List-titleBadge">${count}</span>`;
const buildLabel = label => `<span class="Prompt-warningResourceTitle">
${$filter('sanitize')(label)}</span>`;
const buildCountLabel = ({ count, label }) => `<div>
${buildLabel(label)}${buildCount(count)}</div>`;
const displayedCounts = counts.filter(({ count }) => count > 0);
const html = `
${displayedCounts.length ? strings.get('deleteResource.USED_BY', 'job template') : ''}
${strings.get('deleteResource.CONFIRM', 'job template')}
${displayedCounts.map(buildCountLabel).join('')}
`;
return html;
}
function buildWorkflowCopyPromptHTML(data) {
const pull = (data, param) => _.get(data, param, []).map($filter('sanitize'));
const credentials = pull(data, 'credentials_unable_to_copy');
const inventories = pull(data, 'inventories_unable_to_copy');
const templates = pull(data, 'templates_unable_to_copy');
const html = `
<div class="Prompt-bodyQuery">
${strings.get('warnings.WORKFLOW_RESTRICTED_COPY')}
</div>
<div class="Prompt-bodyTarget">
${templates.length ? `<div>Unified Job Templates<ul>` : ''}
${templates.map(item => `<li>${item}</li>`).join('')}
${templates.length ? `</ul></div>` : ''}
</div>
<div class="Prompt-bodyTarget">
${credentials.length ? `<div>Credentials<ul>` : ''}
${credentials.map(item => `<li>${item}</li>`).join('')}
${credentials.length ? `</ul></div>` : ''}
</div>
<div class="Prompt-bodyTarget">
${inventories.length ? `<div>Inventories<ul>` : ''}
${inventories.map(item => `<li>${item}</li>`).join('')}
${inventories.length ? `</ul></div>` : ''}
</div>
`;
return html;
}
vm.isCollapsed = true;
vm.onCollapse = () => {
vm.isCollapsed = true;
};
vm.onExpand = () => {
vm.isCollapsed = false;
};
}
ListTemplatesController.$inject = [
'$filter',
'$scope',
'$state',
'Alert',
'Dataset',
'ProcessErrors',
'Prompt',
'resolvedModels',
'TemplatesStrings',
'Wait',
'QuerySet',
'GetBasePath',
'ngToast',
'$timeout'
];
export default ListTemplatesController;