mirror of
https://github.com/ansible/awx.git
synced 2026-01-24 16:01:20 -03:30
Merge pull request #1201 from jakemcdermott/item_copy_ui
api-backed copy ui
This commit is contained in:
commit
c1b6595a0b
@ -1,200 +1,406 @@
|
||||
function ListTemplatesController (model, JobTemplate, WorkflowJobTemplate, strings, $state, $scope, rbacUiControlService, Dataset, $filter, Alert, InitiatePlaybookRun, Prompt, Wait, ProcessErrors, TemplateCopyService, $q, Empty, i18n, PromptService) {
|
||||
const vm = this || {},
|
||||
unifiedJobTemplate = model,
|
||||
jobTemplate = new JobTemplate(),
|
||||
workflowJobTemplate = new WorkflowJobTemplate();
|
||||
/** ***********************************************
|
||||
* 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,
|
||||
InitiatePlaybookRun,
|
||||
ProcessErrors,
|
||||
Prompt,
|
||||
PromptService,
|
||||
resolvedModels,
|
||||
strings,
|
||||
Wait,
|
||||
) {
|
||||
const vm = this || {};
|
||||
const [jobTemplate, workflowTemplate] = resolvedModels;
|
||||
|
||||
const choices = workflowTemplate.options('actions.GET.type.choices')
|
||||
.concat(jobTemplate.options('actions.GET.type.choices'));
|
||||
|
||||
vm.strings = strings;
|
||||
|
||||
// TODO: add the permission based functionality to the base model
|
||||
$scope.canAdd = false;
|
||||
rbacUiControlService.canAdd("job_templates")
|
||||
.then(function(params) {
|
||||
$scope.canAddJobTemplate = params.canAdd;
|
||||
});
|
||||
rbacUiControlService.canAdd("workflow_job_templates")
|
||||
.then(function(params) {
|
||||
$scope.canAddWorkflowJobTemplate = params.canAdd;
|
||||
});
|
||||
$scope.$watchGroup(["canAddJobTemplate", "canAddWorkflowJobTemplate"], function() {
|
||||
if ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate) {
|
||||
$scope.canAdd = true;
|
||||
} else {
|
||||
$scope.canAdd = false;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.list = {
|
||||
iterator: 'template',
|
||||
name: 'templates'
|
||||
};
|
||||
$scope.collection = {
|
||||
basePath: 'unified_job_templates',
|
||||
iterator: 'template'
|
||||
};
|
||||
$scope[`${$scope.list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[$scope.list.name] = $scope[`${$scope.list.iterator}_dataset`].results;
|
||||
$scope.$on('updateDataset', function(e, dataset) {
|
||||
$scope[`${$scope.list.iterator}_dataset`] = dataset;
|
||||
$scope[$scope.list.name] = dataset.results;
|
||||
});
|
||||
|
||||
// get modified date and user who modified it
|
||||
vm.getModified = function(template) {
|
||||
let val = "";
|
||||
if (template.modified) {
|
||||
val += $filter('longDate')(template.modified);
|
||||
}
|
||||
if (_.has(template, 'summary_fields.modified_by.username')) {
|
||||
val += ` by <a href="/#/users/${template.summary_fields.modified_by.id}">${template.summary_fields.modified_by.username}</a>`;
|
||||
}
|
||||
if (val === "") {
|
||||
val = undefined;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
// get last ran date and user who ran it
|
||||
vm.getRan = function(template) {
|
||||
let val = "";
|
||||
if (template.last_job_run) {
|
||||
val += $filter('longDate')(template.last_job_run);
|
||||
}
|
||||
|
||||
// TODO: when API gives back a user who last ran the job in summary fields, uncomment and
|
||||
// update this code
|
||||
// if (template && template.summary_fields && template.summary_fields.modified_by &&
|
||||
// template.summary_fields.modified_by.username) {
|
||||
// val += ` by <a href="/#/users/${template.summary_fields.modified_by.id}">${template.summary_fields.modified_by.username}</a>`;
|
||||
// }
|
||||
|
||||
if (val === "") {
|
||||
val = undefined;
|
||||
}
|
||||
return val;
|
||||
};
|
||||
|
||||
// get pretified template type names from options
|
||||
vm.templateTypes = unifiedJobTemplate.options('actions.GET.type.choices')
|
||||
.reduce((acc, i) => {
|
||||
acc[i[0]] = i[1];
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// get if you should show the active indicator for the row or not
|
||||
// TODO: edit indicator doesn't update when you enter edit route after initial load right now
|
||||
vm.templateTypes = mapChoices(choices);
|
||||
vm.activeId = parseInt($state.params.job_template_id || $state.params.workflow_template_id);
|
||||
|
||||
vm.submitJob = function(template) {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
let jobTemplate = new JobTemplate();
|
||||
$scope.canAddJobTemplate = jobTemplate.options('actions.POST')
|
||||
$scope.canAddWorkflowJobTemplate = workflowTemplate.options('actions.POST')
|
||||
$scope.canAdd = ($scope.canAddJobTemplate || $scope.canAddWorkflowJobTemplate);
|
||||
|
||||
$q.all([jobTemplate.optionsLaunch(template.id), jobTemplate.getLaunch(template.id)])
|
||||
.then((responses) => {
|
||||
if(jobTemplate.canLaunchWithoutPrompt()) {
|
||||
jobTemplate.postLaunch({id: template.id})
|
||||
.then((launchRes) => {
|
||||
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
|
||||
});
|
||||
} else {
|
||||
// smart-search
|
||||
const name = 'templates';
|
||||
const iterator = 'template';
|
||||
const key = 'template_dataset';
|
||||
|
||||
if(responses[1].data.survey_enabled) {
|
||||
$scope.list = { iterator, name };
|
||||
$scope.collection = { iterator, basePath: 'unified_job_templates' };
|
||||
$scope[key] = Dataset.data;
|
||||
$scope[name] = Dataset.data.results;
|
||||
$scope.$on('updateDataset', (e, dataset) => {
|
||||
$scope[key] = dataset;
|
||||
$scope[name] = dataset.results;
|
||||
});
|
||||
|
||||
// go out and get the survey questions
|
||||
jobTemplate.getSurveyQuestions(template.id)
|
||||
.then((surveyQuestionRes) => {
|
||||
|
||||
let processed = PromptService.processSurveyQuestions({
|
||||
surveyQuestions: surveyQuestionRes.data.spec
|
||||
});
|
||||
|
||||
$scope.promptData = {
|
||||
launchConf: responses[1].data,
|
||||
launchOptions: responses[0].data,
|
||||
surveyQuestions: processed.surveyQuestions,
|
||||
template: template.id,
|
||||
prompts: PromptService.processPromptValues({
|
||||
launchConf: responses[1].data,
|
||||
launchOptions: responses[0].data
|
||||
}),
|
||||
triggerModalOpen: true
|
||||
};
|
||||
});
|
||||
}
|
||||
else {
|
||||
$scope.promptData = {
|
||||
launchConf: responses[1].data,
|
||||
launchOptions: responses[0].data,
|
||||
template: template.id,
|
||||
prompts: PromptService.processPromptValues({
|
||||
launchConf: responses[1].data,
|
||||
launchOptions: responses[0].data
|
||||
}),
|
||||
triggerModalOpen: true
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
|
||||
}
|
||||
else {
|
||||
// Something went wrong - Let the user know that we're unable to launch because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while launching.');
|
||||
}
|
||||
vm.runTemplate = template => {
|
||||
if (!template) {
|
||||
Alert(strings.get('error.LAUNCH'), strings.get('alert.MISSING_PARAMETER'));
|
||||
return;
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to launch template', 'Template parameter is missing');
|
||||
|
||||
if (isJobTemplate(template)) {
|
||||
runJobTemplate(template);
|
||||
} else if (isWorkflowTemplate(template)) {
|
||||
runWorkflowTemplate(template);
|
||||
} else {
|
||||
Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_LAUNCH'));
|
||||
}
|
||||
};
|
||||
|
||||
$scope.launchJob = () => {
|
||||
vm.scheduleTemplate = template => {
|
||||
if (!template) {
|
||||
Alert(strings.get('error.SCHEDULE'), strings.get('alert.MISSING_PARAMETER'));
|
||||
return;
|
||||
}
|
||||
|
||||
let jobLaunchData = {
|
||||
if (isJobTemplate(template)) {
|
||||
$state.go('jobTemplateSchedules', { id: template.id });
|
||||
} else if (isWorkflowTemplate(template)) {
|
||||
$state.go('workflowJobTemplateSchedules', { id: template.id });
|
||||
} else {
|
||||
Alert(strings.get('error.UNKNOWN'), strings.get('alert.UNKNOWN_SCHEDULE'));
|
||||
}
|
||||
};
|
||||
|
||||
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.getLastRan = template => {
|
||||
const lastJobRun = _.get(template, 'last_job_run');
|
||||
|
||||
if (!lastJobRun) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let html = $filter('longDate')(modified);
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
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(({ id }) => {
|
||||
const params = { job_template_id: id };
|
||||
$state.go('templates.editJobTemplate', params, { reload: true });
|
||||
})
|
||||
.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 = () => {
|
||||
Wait('start');
|
||||
model.copy()
|
||||
.then(({ id }) => {
|
||||
const params = { workflow_job_template_id: id };
|
||||
$state.go('templates.editWorkflowJobTemplate', params, { reload: true });
|
||||
})
|
||||
.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('actions.COPY_WORKFLOW'),
|
||||
});
|
||||
} 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 ($scope.templates.length === 1 && !_.isEmpty(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;
|
||||
}
|
||||
|
||||
function runJobTemplate(template) {
|
||||
const selectedJobTemplate = jobTemplate.create();
|
||||
const preLaunchPromises = [
|
||||
selectedJobTemplate.getLaunch(template.id),
|
||||
selectedJobTemplate.optionsLaunch(template.id),
|
||||
];
|
||||
|
||||
Promise.all(preLaunchPromises)
|
||||
.then(([launchData, launchOptions]) => {
|
||||
if (selectedJobTemplate.canLaunchWithoutPrompt()) {
|
||||
return selectedJobTemplate
|
||||
.postLaunch({ id: template.id })
|
||||
.then(({ data }) => {
|
||||
$state.go('jobResult', { id: data.job }, { reload: true })
|
||||
});
|
||||
}
|
||||
|
||||
const promptData = {
|
||||
launchConf: launchData.data,
|
||||
launchOptions: launchOptions.data,
|
||||
template: template.id,
|
||||
prompts: PromptService.processPromptValues({
|
||||
launchConf: launchData.data,
|
||||
launchOptions: launchOptions.data
|
||||
}),
|
||||
triggerModalOpen: true,
|
||||
};
|
||||
|
||||
if (launchData.data.survey_enabled) {
|
||||
selectedJobTemplate.getSurveyQuestions(template.id)
|
||||
.then(({ data }) => {
|
||||
const processed = PromptService.processSurveyQuestions({ surveyQuestions: data.spec });
|
||||
promptData.surveyQuestions = processed.surveyQuestions;
|
||||
$scope.promptData = promptData;
|
||||
});
|
||||
} else {
|
||||
$scope.promptData = promptData;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function runWorkflowTemplate(template) {
|
||||
InitiatePlaybookRun({ scope: $scope, id: template.id, job_type: 'workflow_job_template' });
|
||||
}
|
||||
|
||||
$scope.launchJob = () => {
|
||||
const jobLaunchData = {
|
||||
extra_vars: $scope.promptData.extraVars
|
||||
};
|
||||
|
||||
let jobTemplate = new JobTemplate();
|
||||
|
||||
if($scope.promptData.launchConf.ask_tags_on_launch){
|
||||
if ($scope.promptData.launchConf.ask_tags_on_launch){
|
||||
jobLaunchData.job_tags = $scope.promptData.prompts.tags.value.map(a => a.value).join();
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_skip_tags_on_launch){
|
||||
|
||||
if ($scope.promptData.launchConf.ask_skip_tags_on_launch){
|
||||
jobLaunchData.skip_tags = $scope.promptData.prompts.skipTags.value.map(a => a.value).join();
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_limit_on_launch && _.has($scope, 'promptData.prompts.limit.value')){
|
||||
|
||||
if ($scope.promptData.launchConf.ask_limit_on_launch && _.has($scope, 'promptData.prompts.limit.value')){
|
||||
jobLaunchData.limit = $scope.promptData.prompts.limit.value;
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_job_type_on_launch && _.has($scope, 'promptData.prompts.jobType.value.value')) {
|
||||
|
||||
if ($scope.promptData.launchConf.ask_job_type_on_launch && _.has($scope, 'promptData.prompts.jobType.value.value')) {
|
||||
jobLaunchData.job_type = $scope.promptData.prompts.jobType.value.value;
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_verbosity_on_launch && _.has($scope, 'promptData.prompts.verbosity.value.value')) {
|
||||
|
||||
if ($scope.promptData.launchConf.ask_verbosity_on_launch && _.has($scope, 'promptData.prompts.verbosity.value.value')) {
|
||||
jobLaunchData.verbosity = $scope.promptData.prompts.verbosity.value.value;
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_inventory_on_launch && !Empty($scope.promptData.prompts.inventory.value.id)){
|
||||
|
||||
if ($scope.promptData.launchConf.ask_inventory_on_launch && !_.isEmpty($scope.promptData.prompts.inventory.value.id)){
|
||||
jobLaunchData.inventory_id = $scope.promptData.prompts.inventory.value.id;
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_credential_on_launch){
|
||||
|
||||
if ($scope.promptData.launchConf.ask_credential_on_launch){
|
||||
jobLaunchData.credentials = [];
|
||||
$scope.promptData.prompts.credentials.value.forEach((credential) => {
|
||||
jobLaunchData.credentials.push(credential.id);
|
||||
});
|
||||
}
|
||||
if($scope.promptData.launchConf.ask_diff_mode_on_launch && _.has($scope, 'promptData.prompts.diffMode.value')) {
|
||||
|
||||
if ($scope.promptData.launchConf.ask_diff_mode_on_launch && _.has($scope, 'promptData.prompts.diffMode.value')) {
|
||||
jobLaunchData.diff_mode = $scope.promptData.prompts.diffMode.value;
|
||||
}
|
||||
|
||||
if($scope.promptData.prompts.credentials.passwords) {
|
||||
if ($scope.promptData.prompts.credentials.passwords) {
|
||||
_.forOwn($scope.promptData.prompts.credentials.passwords, (val, key) => {
|
||||
if(!jobLaunchData.credential_passwords) {
|
||||
if (!jobLaunchData.credential_passwords) {
|
||||
jobLaunchData.credential_passwords = {};
|
||||
}
|
||||
if(key === "ssh_key_unlock") {
|
||||
if (key === "ssh_key_unlock") {
|
||||
jobLaunchData.credential_passwords.ssh_key_unlock = val.value;
|
||||
} else if(key !== "vault") {
|
||||
} else if (key !== "vault") {
|
||||
jobLaunchData.credential_passwords[`${key}_password`] = val.value;
|
||||
} else {
|
||||
_.each(val, (vaultCred) => {
|
||||
@ -209,265 +415,30 @@ function ListTemplatesController (model, JobTemplate, WorkflowJobTemplate, strin
|
||||
delete jobLaunchData.extra_vars;
|
||||
}
|
||||
|
||||
jobTemplate.postLaunch({
|
||||
jobTemplate.create().postLaunch({
|
||||
id: $scope.promptData.template,
|
||||
launchData: jobLaunchData
|
||||
}).then((launchRes) => {
|
||||
})
|
||||
.then((launchRes) => {
|
||||
$state.go('jobResult', { id: launchRes.data.job }, { reload: true });
|
||||
}).catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, { hdr: i18n._('Error!'),
|
||||
msg: i18n.sprintf(i18n._('Failed to launch job template. POST returned: %d'), $scope.promptData.template, status) });
|
||||
});
|
||||
};
|
||||
|
||||
vm.scheduleJob = (template) => {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
$state.go('jobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
$state.go('workflowJobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
else {
|
||||
// Something went wrong Let the user know that we're unable to redirect to schedule because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while routing to schedule.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to schedule job', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
vm.scheduleJob = (template) => {
|
||||
if(template) {
|
||||
if(template.type && (template.type === 'Job Template' || template.type === 'job_template')) {
|
||||
$state.go('jobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
else if(template.type && (template.type === 'Workflow Job Template' || template.type === 'workflow_job_template')) {
|
||||
$state.go('workflowJobTemplateSchedules', {id: template.id});
|
||||
}
|
||||
else {
|
||||
// Something went wrong Let the user know that we're unable to redirect to schedule because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while routing to schedule.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to schedule job', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
vm.copyTemplate = function(template) {
|
||||
|
||||
if(template) {
|
||||
if(template.type && template.type === 'job_template') {
|
||||
Wait('start');
|
||||
TemplateCopyService.get(template.id)
|
||||
.then(function(response){
|
||||
TemplateCopyService.set(response.data.results)
|
||||
.then(function(results){
|
||||
Wait('stop');
|
||||
if(results.type && results.type === 'job_template') {
|
||||
$state.go('templates.editJobTemplate', {job_template_id: results.id}, {reload: true});
|
||||
}
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: ' + status
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {hdr: 'Error!',
|
||||
msg: 'Call failed. Return status: '+ status});
|
||||
});
|
||||
}
|
||||
else if(template.type && template.type === 'workflow_job_template') {
|
||||
TemplateCopyService.getWorkflowCopy(template.id)
|
||||
.then(function(result) {
|
||||
|
||||
if(result.data.can_copy) {
|
||||
if(result.data.can_copy_without_user_input) {
|
||||
// Go ahead and copy the workflow - the user has full priveleges on all the resources
|
||||
TemplateCopyService.copyWorkflow(template.id)
|
||||
.then(function(result) {
|
||||
$state.go('templates.editWorkflowJobTemplate', {workflow_job_template_id: result.data.id}, {reload: true});
|
||||
})
|
||||
.catch(function (response) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, response.data, response.status, null, { hdr: 'Error!',
|
||||
msg: 'Call to copy workflow job template failed. Return status: ' + response.status + '.'});
|
||||
});
|
||||
}
|
||||
else {
|
||||
|
||||
let bodyHtml = `
|
||||
<div class="Prompt-bodyQuery">
|
||||
You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.
|
||||
</div>
|
||||
<div class="Prompt-bodyTarget">`;
|
||||
|
||||
// List the unified job templates user can not access
|
||||
if (result.data.templates_unable_to_copy.length > 0) {
|
||||
bodyHtml += '<div>Unified Job Templates that can not be copied<ul>';
|
||||
_.forOwn(result.data.templates_unable_to_copy, function(ujt) {
|
||||
if(ujt) {
|
||||
bodyHtml += '<li>' + ujt + '</li>';
|
||||
}
|
||||
});
|
||||
bodyHtml += '</ul></div>';
|
||||
}
|
||||
// List the prompted inventories user can not access
|
||||
if (result.data.inventories_unable_to_copy.length > 0) {
|
||||
bodyHtml += '<div>Node prompted inventories that can not be copied<ul>';
|
||||
_.forOwn(result.data.inventories_unable_to_copy, function(inv) {
|
||||
if(inv) {
|
||||
bodyHtml += '<li>' + inv + '</li>';
|
||||
}
|
||||
});
|
||||
bodyHtml += '</ul></div>';
|
||||
}
|
||||
// List the prompted credentials user can not access
|
||||
if (result.data.credentials_unable_to_copy.length > 0) {
|
||||
bodyHtml += '<div>Node prompted credentials that can not be copied<ul>';
|
||||
_.forOwn(result.data.credentials_unable_to_copy, function(cred) {
|
||||
if(cred) {
|
||||
bodyHtml += '<li>' + cred + '</li>';
|
||||
}
|
||||
});
|
||||
bodyHtml += '</ul></div>';
|
||||
}
|
||||
|
||||
bodyHtml += '</div>';
|
||||
|
||||
|
||||
Prompt({
|
||||
hdr: 'Copy Workflow',
|
||||
body: bodyHtml,
|
||||
action: function() {
|
||||
$('#prompt-modal').modal('hide');
|
||||
Wait('start');
|
||||
TemplateCopyService.copyWorkflow(template.id)
|
||||
.then(function(result) {
|
||||
Wait('stop');
|
||||
$state.go('templates.editWorkflowJobTemplate', {workflow_job_template_id: result.data.id}, {reload: true});
|
||||
}, function (data) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to copy template failed. POST returned status: ' + status });
|
||||
});
|
||||
},
|
||||
actionText: 'COPY',
|
||||
class: 'Modal-primaryButton'
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to copy workflow job template', 'You do not have permission to perform this action.');
|
||||
}
|
||||
}, function (data) {
|
||||
Wait('stop');
|
||||
ProcessErrors($scope, data, status, null, { hdr: 'Error!',
|
||||
msg: 'Call to copy template failed. GET returned status: ' + status });
|
||||
});
|
||||
}
|
||||
else {
|
||||
// Something went wrong - Let the user know that we're unable to copy because we don't know
|
||||
// what type of job template this is
|
||||
Alert('Error: Unable to determine template type', 'We were unable to determine this template\'s type while copying.');
|
||||
}
|
||||
}
|
||||
else {
|
||||
Alert('Error: Unable to copy job', 'Template parameter is missing');
|
||||
}
|
||||
};
|
||||
|
||||
vm.deleteTemplate = function(template) {
|
||||
var action = function() {
|
||||
$('#prompt-modal').modal('hide');
|
||||
Wait('start');
|
||||
|
||||
let deleteComplete = () => {
|
||||
let reloadListStateParams = null;
|
||||
|
||||
if($scope.templates.length === 1 && $state.params.template_search && !_.isEmpty($state.params.template_search.page) && $state.params.template_search.page !== '1') {
|
||||
reloadListStateParams = _.cloneDeep($state.params);
|
||||
reloadListStateParams.template_search.page = (parseInt(reloadListStateParams.template_search.page)-1).toString();
|
||||
}
|
||||
|
||||
if (parseInt($state.params.template_id) === template.id) {
|
||||
$state.go("^", reloadListStateParams, { reload: true });
|
||||
} else {
|
||||
$state.go('.', reloadListStateParams, {reload: true});
|
||||
}
|
||||
};
|
||||
|
||||
if(template.type === "job_template") {
|
||||
jobTemplate.request('delete', template.id)
|
||||
.then(() => {
|
||||
deleteComplete();
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: strings.get('error.HEADER'),
|
||||
msg: strings.get('error.CALL', {path: "" + unifiedJobTemplate.path + template.id, status})
|
||||
});
|
||||
})
|
||||
.finally(function() {
|
||||
Wait('stop');
|
||||
});
|
||||
} else if(template.type === "workflow_job_template") {
|
||||
workflowJobTemplate.request('delete', template.id)
|
||||
.then(() => {
|
||||
deleteComplete();
|
||||
})
|
||||
.catch(({data, status}) => {
|
||||
ProcessErrors($scope, data, status, null, {
|
||||
hdr: strings.get('error.HEADER'),
|
||||
msg: strings.get('error.CALL', {path: "" + unifiedJobTemplate.path + template.id, status})
|
||||
});
|
||||
})
|
||||
.finally(function() {
|
||||
Wait('stop');
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let deleteModalBody = `<div class="Prompt-bodyQuery">${strings.get('deleteResource.CONFIRM', 'template')}</div>`;
|
||||
|
||||
Prompt({
|
||||
hdr: strings.get('deleteResource.HEADER'),
|
||||
resourceName: $filter('sanitize')(template.name),
|
||||
body: deleteModalBody,
|
||||
action: action,
|
||||
actionText: 'DELETE'
|
||||
});
|
||||
})
|
||||
.catch(createErrorHandler('launch job template', 'POST'))
|
||||
};
|
||||
}
|
||||
|
||||
ListTemplatesController.$inject = [
|
||||
'resolvedModels',
|
||||
'JobTemplateModel',
|
||||
'WorkflowJobTemplateModel',
|
||||
'TemplatesStrings',
|
||||
'$state',
|
||||
'$scope',
|
||||
'rbacUiControlService',
|
||||
'Dataset',
|
||||
'$filter',
|
||||
'$scope',
|
||||
'$state',
|
||||
'Alert',
|
||||
'Dataset',
|
||||
'InitiatePlaybookRun',
|
||||
'Prompt',
|
||||
'Wait',
|
||||
'ProcessErrors',
|
||||
'TemplateCopyService',
|
||||
'$q',
|
||||
'Empty',
|
||||
'i18n',
|
||||
'PromptService'
|
||||
'Prompt',
|
||||
'PromptService',
|
||||
'resolvedModels',
|
||||
'TemplatesStrings',
|
||||
'Wait',
|
||||
];
|
||||
|
||||
export default ListTemplatesController;
|
||||
|
||||
@ -2,14 +2,6 @@ import ListController from './list-templates.controller';
|
||||
const listTemplate = require('~features/templates/list.view.html');
|
||||
import { N_ } from '../../src/i18n';
|
||||
|
||||
function TemplatesResolve (UnifiedJobTemplate) {
|
||||
return new UnifiedJobTemplate(['get', 'options']);
|
||||
}
|
||||
|
||||
TemplatesResolve.$inject = [
|
||||
'UnifiedJobTemplateModel'
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'templates',
|
||||
route: '/templates',
|
||||
@ -32,10 +24,10 @@ export default {
|
||||
},
|
||||
params: {
|
||||
template_search: {
|
||||
dynamic: true,
|
||||
value: {
|
||||
type: 'workflow_job_template,job_template'
|
||||
},
|
||||
dynamic: true
|
||||
type: 'workflow_job_template,job_template',
|
||||
},
|
||||
}
|
||||
},
|
||||
searchPrefix: 'template',
|
||||
@ -43,16 +35,34 @@ export default {
|
||||
'@': {
|
||||
controller: ListController,
|
||||
templateUrl: listTemplate,
|
||||
controllerAs: 'vm'
|
||||
controllerAs: 'vm',
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
resolvedModels: TemplatesResolve,
|
||||
Dataset: ['TemplateList', 'QuerySet', '$stateParams', 'GetBasePath',
|
||||
function(list, qs, $stateParams, GetBasePath) {
|
||||
let path = GetBasePath(list.basePath) || GetBasePath(list.name);
|
||||
return qs.search(path, $stateParams[`${list.iterator}_search`]);
|
||||
resolvedModels: [
|
||||
'JobTemplateModel',
|
||||
'WorkflowJobTemplateModel',
|
||||
(JobTemplate, WorkflowJobTemplate) => {
|
||||
const models = [
|
||||
new JobTemplate(['options']),
|
||||
new WorkflowJobTemplate(['options']),
|
||||
];
|
||||
return Promise.all(models);
|
||||
},
|
||||
],
|
||||
Dataset: [
|
||||
'$stateParams',
|
||||
'Wait',
|
||||
'GetBasePath',
|
||||
'QuerySet',
|
||||
($stateParams, Wait, GetBasePath, qs) => {
|
||||
const searchParam = $stateParams.template_search;
|
||||
const searchPath = GetBasePath('unified_job_templates');
|
||||
|
||||
Wait('start');
|
||||
return qs.search(searchPath, searchParam)
|
||||
.finally(() => Wait('stop'))
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
@ -89,16 +89,16 @@
|
||||
</at-row-item>
|
||||
<at-row-item
|
||||
label-value="{{:: vm.strings.get('list.ROW_ITEM_LABEL_RAN') }}"
|
||||
value="{{ vm.getRan(template) }}">
|
||||
value="{{ vm.getLastRan(template) }}">
|
||||
</at-row-item>
|
||||
<labels-list class="LabelList" show-delete="false" is-row-item="true">
|
||||
</labels-list>
|
||||
</div>
|
||||
<div class="at-Row-actions">
|
||||
<at-row-action icon="icon-launch" ng-click="vm.submitJob(template)"
|
||||
<at-row-action icon="icon-launch" ng-click="vm.runTemplate(template)"
|
||||
ng-show="template.summary_fields.user_capabilities.start">
|
||||
</at-row-action>
|
||||
<at-row-action icon="fa-calendar" ng-click="vm.scheduleJob(template)"
|
||||
<at-row-action icon="fa-calendar" ng-click="vm.scheduleTemplate(template)"
|
||||
ng-show="template.summary_fields.user_capabilities.schedule">
|
||||
</at-row-action>
|
||||
<at-row-action icon="fa-copy" ng-click="vm.copyTemplate(template)"
|
||||
|
||||
@ -55,8 +55,37 @@ function TemplatesStrings (BaseString) {
|
||||
VALID_DECIMAL: t.s('Please enter an answer that is a decimal number.'),
|
||||
PLAYBOOK_RUN: t.s('Playbook Run'),
|
||||
CHECK: t.s('Check'),
|
||||
NO_CREDS_MATCHING_TYPE: t.s('No Credentials Matching This Type Have Been Created')
|
||||
NO_CREDS_MATCHING_TYPE: t.s('No Credentials Matching This Type Have Been Created'),
|
||||
};
|
||||
|
||||
ns.alert = {
|
||||
MISSING_PARAMETER: t.s('Template parameter is missing.'),
|
||||
NO_PERMISSION: t.s('You do not have permission to perform this action.'),
|
||||
UNKNOWN_COPY: t.s('Unable to determine this template\'s type while copying.'),
|
||||
UNKNOWN_DELETE: t.s('Unable to determine this template\'s type while deleting.'),
|
||||
UNKNOWN_EDIT: t.s('Unable to determine this template\'s type while editing.'),
|
||||
UNKNOWN_LAUNCH: t.s('Unable to determine this template\'s type while launching.'),
|
||||
UNKNOWN_SCHEDULE: t.s('Unable to determine this template\'s type while scheduling.'),
|
||||
};
|
||||
|
||||
ns.actions = {
|
||||
COPY_WORKFLOW: t.s('Copy Workflow')
|
||||
};
|
||||
|
||||
ns.error = {
|
||||
HEADER: this.error.HEADER,
|
||||
CALL: this.error.CALL,
|
||||
EDIT: t.s('Unable to edit template.'),
|
||||
DELETE: t.s('Unable to delete template.'),
|
||||
LAUNCH: t.s('Unable to launch template.'),
|
||||
UNKNOWN: t.s('Unable to determine template type.'),
|
||||
SCHEDULE: t.s('Unable to schedule job.'),
|
||||
COPY: t.s('Unable to copy template.'),
|
||||
};
|
||||
|
||||
ns.warnings = {
|
||||
WORKFLOW_RESTRICTED_COPY: t.s('You do not have access to all resources used by this workflow. Resources that you don\'t have access to will not be copied and will result in an incomplete workflow.')
|
||||
}
|
||||
}
|
||||
|
||||
TemplatesStrings.$inject = ['BaseStringService'];
|
||||
|
||||
@ -418,6 +418,25 @@ function getDependentResourceCounts (id) {
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function copy () {
|
||||
if (!this.has('POST', 'related.copy')) {
|
||||
return Promise.reject(new Error('No related property, copy, exists'));
|
||||
}
|
||||
|
||||
const date = new Date();
|
||||
const name = `${this.get('name')}@${date.toLocaleTimeString()}`;
|
||||
|
||||
const url = `${this.path}${this.get('id')}/copy/`;
|
||||
|
||||
const req = {
|
||||
url,
|
||||
method: 'POST',
|
||||
data: { name }
|
||||
};
|
||||
|
||||
return $http(req).then(res => res.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* `create` is called on instantiation of every model. Models can be
|
||||
* instantiated empty or with `GET` and/or `OPTIONS` requests that yield data.
|
||||
@ -517,6 +536,7 @@ function BaseModel (resource, settings) {
|
||||
this.set = set;
|
||||
this.unset = unset;
|
||||
this.extend = extend;
|
||||
this.copy = copy;
|
||||
this.getDependentResourceCounts = getDependentResourceCounts;
|
||||
|
||||
this.http = {
|
||||
|
||||
21
awx/ui/client/lib/models/NotificationTemplate.js
Normal file
21
awx/ui/client/lib/models/NotificationTemplate.js
Normal file
@ -0,0 +1,21 @@
|
||||
let Base;
|
||||
|
||||
function NotificationTemplateModel (method, resource, config) {
|
||||
Base.call(this, 'notification_templates');
|
||||
|
||||
this.Constructor = NotificationTemplateModel;
|
||||
|
||||
return this.create(method, resource, config);
|
||||
}
|
||||
|
||||
function NotificationTemplateModelLoader (BaseModel) {
|
||||
Base = BaseModel;
|
||||
|
||||
return NotificationTemplateModel;
|
||||
}
|
||||
|
||||
NotificationTemplateModelLoader.$inject = [
|
||||
'BaseModel'
|
||||
];
|
||||
|
||||
export default NotificationTemplateModelLoader;
|
||||
@ -4,22 +4,22 @@ import Base from '~models/Base';
|
||||
import Config from '~models/Config';
|
||||
import Credential from '~models/Credential';
|
||||
import CredentialType from '~models/CredentialType';
|
||||
import Me from '~models/Me';
|
||||
import Organization from '~models/Organization';
|
||||
import Project from '~models/Project';
|
||||
import JobTemplate from '~models/JobTemplate';
|
||||
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
|
||||
import Instance from '~models/Instance';
|
||||
import InstanceGroup from '~models/InstanceGroup';
|
||||
import InventorySource from '~models/InventorySource';
|
||||
import Inventory from '~models/Inventory';
|
||||
import InventoryScript from '~models/InventoryScript';
|
||||
import InventorySource from '~models/InventorySource';
|
||||
import Job from '~models/Job';
|
||||
import JobTemplate from '~models/JobTemplate';
|
||||
import Me from '~models/Me';
|
||||
import ModelsStrings from '~models/models.strings';
|
||||
import NotificationTemplate from '~models/NotificationTemplate';
|
||||
import Organization from '~models/Organization';
|
||||
import Project from '~models/Project';
|
||||
import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
|
||||
import WorkflowJob from '~models/WorkflowJob';
|
||||
import WorkflowJobTemplate from '~models/WorkflowJobTemplate';
|
||||
|
||||
import ModelsStrings from '~models/models.strings';
|
||||
import UnifiedJobTemplate from '~models/UnifiedJobTemplate';
|
||||
import WorkflowJobTemplateNode from '~models/WorkflowJobTemplateNode';
|
||||
|
||||
const MODULE_NAME = 'at.lib.models';
|
||||
|
||||
@ -31,21 +31,21 @@ angular
|
||||
.service('ConfigModel', Config)
|
||||
.service('CredentialModel', Credential)
|
||||
.service('CredentialTypeModel', CredentialType)
|
||||
.service('MeModel', Me)
|
||||
.service('OrganizationModel', Organization)
|
||||
.service('ProjectModel', Project)
|
||||
.service('JobTemplateModel', JobTemplate)
|
||||
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode)
|
||||
.service('InstanceModel', Instance)
|
||||
.service('InstanceGroupModel', InstanceGroup)
|
||||
.service('InventorySourceModel', InventorySource)
|
||||
.service('InstanceModel', Instance)
|
||||
.service('InventoryModel', Inventory)
|
||||
.service('InventoryScriptModel', InventoryScript)
|
||||
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)
|
||||
.service('ModelsStrings', ModelsStrings)
|
||||
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
|
||||
.service('InventorySourceModel', InventorySource)
|
||||
.service('JobModel', Job)
|
||||
.service('JobTemplateModel', JobTemplate)
|
||||
.service('MeModel', Me)
|
||||
.service('ModelsStrings', ModelsStrings)
|
||||
.service('NotificationTemplate', NotificationTemplate)
|
||||
.service('OrganizationModel', Organization)
|
||||
.service('ProjectModel', Project)
|
||||
.service('UnifiedJobTemplateModel', UnifiedJobTemplate)
|
||||
.service('WorkflowJobModel', WorkflowJob)
|
||||
.service('WorkflowJobTemplateModel', WorkflowJobTemplate);
|
||||
.service('WorkflowJobTemplateModel', WorkflowJobTemplate)
|
||||
.service('WorkflowJobTemplateNodeModel', WorkflowJobTemplateNode);
|
||||
|
||||
export default MODULE_NAME;
|
||||
|
||||
@ -67,15 +67,18 @@ function BaseStringService (namespace) {
|
||||
this.OFF = t.s('OFF');
|
||||
this.YAML = t.s('YAML');
|
||||
this.JSON = t.s('JSON');
|
||||
this.DELETE = t.s('DELETE');
|
||||
this.COPY = t.s('COPY');
|
||||
|
||||
this.deleteResource = {
|
||||
HEADER: t.s('Delete'),
|
||||
USED_BY: resourceType => t.s('The {{ resourceType }} is currently being used by other resources.', { resourceType }),
|
||||
CONFIRM: resourceType => t.s('Are you sure you want to delete this {{ resourceType }}?', { resourceType })
|
||||
};
|
||||
|
||||
this.error = {
|
||||
HEADER: t.s('Error!'),
|
||||
CALL: ({ path, status }) => t.s('Call to {{ path }} failed. DELETE returned status: {{ status }}.', { path, status })
|
||||
CALL: ({ path, action, status }) => t.s('Call to {{ path }} failed. {{ action }} returned status: {{ status }}.', { path, action, status }),
|
||||
};
|
||||
|
||||
this.ALERT = ({ header, body }) => t.s('{{ header }} {{ body }}', { header, body });
|
||||
|
||||
@ -69,7 +69,14 @@ export default ['i18n', function(i18n) {
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'credential.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
|
||||
copy: {
|
||||
label: i18n._('Copy'),
|
||||
ngClick: 'copyCredential(credential)',
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: i18n._('Copy credential'),
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'credential.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
view: {
|
||||
ngClick: "editCredential(credential.id)",
|
||||
label: i18n._('View'),
|
||||
|
||||
@ -89,6 +89,21 @@ export default ['$scope', 'Rest', 'CredentialList', 'Prompt', 'ProcessErrors', '
|
||||
}
|
||||
}
|
||||
|
||||
$scope.copyCredential = credential => {
|
||||
Wait('start');
|
||||
new Credential('get', credential.id)
|
||||
.then(model => model.copy())
|
||||
.then(({ id }) => {
|
||||
const params = { credential_id: id };
|
||||
$state.go('credentials.edit', params, { reload: true });
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` };
|
||||
ProcessErrors($scope, data, status, null, params);
|
||||
})
|
||||
.finally(() => Wait('stop'));
|
||||
};
|
||||
|
||||
$scope.addCredential = function() {
|
||||
$state.go('credentials.add');
|
||||
};
|
||||
|
||||
@ -100,6 +100,14 @@ export default ['i18n', function(i18n) {
|
||||
dataPlacement: 'top',
|
||||
ngShow: '!inventory.pending_deletion && inventory.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
copy: {
|
||||
label: i18n._('Copy'),
|
||||
ngClick: 'copyInventory(inventory)',
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: i18n._('Copy inventory'),
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'inventory.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
view: {
|
||||
label: i18n._('View'),
|
||||
ngClick: 'editInventory(inventory)',
|
||||
|
||||
@ -73,6 +73,18 @@ function InventoriesList($scope,
|
||||
inventory.linkToDetails = (inventory.kind && inventory.kind === 'smart') ? `inventories.editSmartInventory({smartinventory_id:${inventory.id}})` : `inventories.edit({inventory_id:${inventory.id}})`;
|
||||
}
|
||||
|
||||
$scope.copyInventory = inventory => {
|
||||
Wait('start');
|
||||
new Inventory('get', inventory.id)
|
||||
.then(model => model.copy())
|
||||
.then(copy => $scope.editInventory(copy))
|
||||
.catch(({ data, status }) => {
|
||||
const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` };
|
||||
ProcessErrors($scope, data, status, null, params);
|
||||
})
|
||||
.finally(() => Wait('stop'));
|
||||
};
|
||||
|
||||
$scope.editInventory = function (inventory) {
|
||||
if(inventory.kind && inventory.kind === 'smart') {
|
||||
$state.go('inventories.editSmartInventory', {smartinventory_id: inventory.id});
|
||||
|
||||
@ -57,6 +57,14 @@ export default ['i18n', function(i18n){
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'inventory_script.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
copy: {
|
||||
label: i18n._('Copy'),
|
||||
ngClick: 'copyCustomInv(inventory_script)',
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: i18n._('Copy inventory scruot'),
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'inventory_script.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
view: {
|
||||
ngClick: "editCustomInv(inventory_script.id)",
|
||||
label: i18n._('View'),
|
||||
|
||||
@ -47,6 +47,21 @@ export default ['$rootScope', '$scope', 'Wait', 'InventoryScriptsList',
|
||||
});
|
||||
};
|
||||
|
||||
$scope.copyCustomInv = inventoryScript => {
|
||||
Wait('start');
|
||||
new InventoryScript('get', inventoryScript.id)
|
||||
.then(model => model.copy())
|
||||
.then(({ id }) => {
|
||||
const params = { inventory_script_id: id };
|
||||
$state.go('inventoryScripts.edit', params, { reload: true });
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` };
|
||||
ProcessErrors($scope, data, status, null, params);
|
||||
})
|
||||
.finally(() => Wait('stop'));
|
||||
};
|
||||
|
||||
$scope.deleteCustomInv = function(id, name) {
|
||||
|
||||
var action = function() {
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
export default ['$scope', 'Wait', 'NotificationTemplatesList',
|
||||
'GetBasePath', 'Rest', 'ProcessErrors', 'Prompt', '$state',
|
||||
'ngToast', '$filter', 'Dataset', 'rbacUiControlService',
|
||||
'i18n',
|
||||
'i18n', 'NotificationTemplate',
|
||||
function(
|
||||
$scope, Wait, NotificationTemplatesList,
|
||||
GetBasePath, Rest, ProcessErrors, Prompt, $state,
|
||||
ngToast, $filter, Dataset, rbacUiControlService,
|
||||
i18n) {
|
||||
i18n, NotificationTemplate) {
|
||||
|
||||
var defaultUrl = GetBasePath('notification_templates'),
|
||||
list = NotificationTemplatesList;
|
||||
@ -31,7 +31,7 @@
|
||||
$scope.list = list;
|
||||
$scope[`${list.iterator}_dataset`] = Dataset.data;
|
||||
$scope[list.name] = $scope[`${list.iterator}_dataset`].results;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on(`notification_template_options`, function(event, data){
|
||||
$scope.options = data.data.actions.GET;
|
||||
@ -88,6 +88,24 @@
|
||||
notification_template.template_status_html = html;
|
||||
}
|
||||
|
||||
$scope.copyNotification = notificationTemplate => {
|
||||
Wait('start');
|
||||
new NotificationTemplate('get', notificationTemplate.id)
|
||||
.then(model => model.copy())
|
||||
.then(({ id }) => {
|
||||
const params = {
|
||||
notification_template_id: id,
|
||||
notification_template: this.notification_templates
|
||||
};
|
||||
$state.go('notifications.edit', params, { reload: true });
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` };
|
||||
ProcessErrors($scope, data, status, null, params);
|
||||
})
|
||||
.finally(() => Wait('stop'));
|
||||
};
|
||||
|
||||
$scope.testNotification = function() {
|
||||
var name = $filter('sanitize')(this.notification_template.name),
|
||||
pending_retries = 10;
|
||||
@ -152,6 +170,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$scope.addNotification = function() {
|
||||
$state.go('notifications.add');
|
||||
};
|
||||
|
||||
@ -77,6 +77,14 @@ export default ['i18n', function(i18n){
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'notification_template.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
copy: {
|
||||
label: i18n._('Copy'),
|
||||
ngClick: 'copyNotification(notification_template)',
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: i18n._('Copy notification'),
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'notification_template.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
view: {
|
||||
ngClick: "editNotification(notification_template.id)",
|
||||
label: i18n._('View'),
|
||||
|
||||
@ -154,6 +154,21 @@ export default ['$scope', '$rootScope', '$log', 'Rest', 'Alert',
|
||||
}
|
||||
});
|
||||
|
||||
$scope.copyProject = project => {
|
||||
Wait('start');
|
||||
new Project('get', project.id)
|
||||
.then(model => model.copy())
|
||||
.then(({ id }) => {
|
||||
const params = { project_id: id };
|
||||
$state.go('projects.edit', params, { reload: true });
|
||||
})
|
||||
.catch(({ data, status }) => {
|
||||
const params = { hdr: 'Error!', msg: `Call to copy failed. Return status: ${status}` };
|
||||
ProcessErrors($scope, data, status, null, params);
|
||||
})
|
||||
.finally(() => Wait('stop'));
|
||||
};
|
||||
|
||||
$scope.showSCMStatus = function(id) {
|
||||
// Refresh the project list
|
||||
var project = Find({ list: $scope.projects, key: 'id', val: id });
|
||||
|
||||
@ -100,6 +100,14 @@ export default ['i18n', function(i18n) {
|
||||
dataPlacement: 'top',
|
||||
ngShow: "project.summary_fields.user_capabilities.schedule"
|
||||
},
|
||||
copy: {
|
||||
label: i18n._('Copy'),
|
||||
ngClick: 'copyProject(project)',
|
||||
"class": 'btn-danger btn-xs',
|
||||
awToolTip: i18n._('Copy project'),
|
||||
dataPlacement: 'top',
|
||||
ngShow: 'project.summary_fields.user_capabilities.edit'
|
||||
},
|
||||
edit: {
|
||||
ngClick: "editProject(project.id)",
|
||||
awToolTip: i18n._('Edit the project'),
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
/*************************************************
|
||||
* Copyright (c) 2016 Ansible, Inc.
|
||||
*
|
||||
* All Rights Reserved
|
||||
*************************************************/
|
||||
|
||||
export default
|
||||
['$rootScope', 'Rest', 'ProcessErrors', 'GetBasePath', 'moment',
|
||||
function($rootScope, Rest, ProcessErrors, GetBasePath, moment){
|
||||
return {
|
||||
get: function(id){
|
||||
var defaultUrl = GetBasePath('job_templates') + '?id=' + id;
|
||||
Rest.setUrl(defaultUrl);
|
||||
return Rest.get()
|
||||
.then(response => response)
|
||||
.catch((error) => {
|
||||
ProcessErrors($rootScope, error.response, error.status, null, {hdr: 'Error!',
|
||||
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
|
||||
});
|
||||
},
|
||||
getSurvey: function(endpoint){
|
||||
Rest.setUrl(endpoint);
|
||||
return Rest.get();
|
||||
},
|
||||
copySurvey: function(source, target){
|
||||
return this.getSurvey(source.related.survey_spec).then( (response) => {
|
||||
Rest.setUrl(target.related.survey_spec);
|
||||
return Rest.post(response.data);
|
||||
});
|
||||
},
|
||||
set: function(results){
|
||||
var defaultUrl = GetBasePath('job_templates');
|
||||
var self = this;
|
||||
Rest.setUrl(defaultUrl);
|
||||
var name = this.buildName(results[0].name);
|
||||
results[0].name = name + ' @ ' + moment().format('h:mm:ss a'); // 2:49:11 pm
|
||||
return Rest.post(results[0])
|
||||
.then((response) => {
|
||||
// also copy any associated survey_spec
|
||||
if (results[0].summary_fields.survey){
|
||||
return self.copySurvey(results[0], response.data).then( () => response.data);
|
||||
}
|
||||
else {
|
||||
return response.data;
|
||||
}
|
||||
})
|
||||
.catch(({res, status}) => {
|
||||
ProcessErrors($rootScope, res, status, null, {hdr: 'Error!',
|
||||
msg: 'Call to '+ defaultUrl + ' failed. Return status: '+ status});
|
||||
});
|
||||
},
|
||||
buildName: function(name){
|
||||
var result = name.split('@')[0];
|
||||
return result;
|
||||
},
|
||||
getWorkflowCopy: function(id) {
|
||||
let url = GetBasePath('workflow_job_templates');
|
||||
|
||||
url = url + id + '/copy';
|
||||
|
||||
Rest.setUrl(url);
|
||||
return Rest.get();
|
||||
},
|
||||
copyWorkflow: function(id) {
|
||||
let url = GetBasePath('workflow_job_templates');
|
||||
|
||||
url = url + id + '/copy';
|
||||
|
||||
Rest.setUrl(url);
|
||||
return Rest.post();
|
||||
}
|
||||
};
|
||||
}
|
||||
];
|
||||
@ -15,7 +15,6 @@ import workflowChart from './workflows/workflow-chart/main';
|
||||
import workflowMaker from './workflows/workflow-maker/main';
|
||||
import workflowControls from './workflows/workflow-controls/main';
|
||||
import workflowService from './workflows/workflow.service';
|
||||
import templateCopyService from './copy-template/template-copy.service';
|
||||
import WorkflowForm from './workflows.form';
|
||||
import CompletedJobsList from './completed-jobs.list';
|
||||
import InventorySourcesList from './inventory-sources.list';
|
||||
@ -29,7 +28,6 @@ angular.module('templates', [surveyMaker.name, jobTemplates.name, labels.name, p
|
||||
])
|
||||
.service('TemplatesService', templatesService)
|
||||
.service('WorkflowService', workflowService)
|
||||
.service('TemplateCopyService', templateCopyService)
|
||||
.factory('WorkflowForm', WorkflowForm)
|
||||
.factory('CompletedJobsList', CompletedJobsList)
|
||||
// TODO: currently being kept arround for rbac selection, templates within projects and orgs, etc.
|
||||
|
||||
@ -77,6 +77,4 @@ module.exports = {
|
||||
post,
|
||||
patch,
|
||||
put,
|
||||
all: axios.all,
|
||||
spread: axios.spread
|
||||
};
|
||||
|
||||
@ -3,18 +3,15 @@ import uuid from 'uuid';
|
||||
import { AWX_E2E_PASSWORD } from './settings';
|
||||
|
||||
import {
|
||||
all,
|
||||
get,
|
||||
post,
|
||||
spread
|
||||
} from './api';
|
||||
|
||||
const session = `e2e-${uuid().substr(0, 8)}`;
|
||||
|
||||
const store = {};
|
||||
|
||||
const getOrCreate = (endpoint, data) => {
|
||||
const identifier = Object.keys(data).find(key => ['name', 'username'].includes(key));
|
||||
const getOrCreate = (endpoint, data, unique = ['name', 'username', 'id']) => {
|
||||
const identifier = Object.keys(data).find(key => unique.includes(key));
|
||||
|
||||
if (identifier === undefined) {
|
||||
throw new Error('A unique key value must be provided.');
|
||||
@ -22,12 +19,10 @@ const getOrCreate = (endpoint, data) => {
|
||||
|
||||
const identity = data[identifier];
|
||||
|
||||
if (store[endpoint] && store[endpoint][identity]) {
|
||||
return store[endpoint][identity].then(created => created.data);
|
||||
}
|
||||
store[endpoint] = store[endpoint] || {};
|
||||
|
||||
if (!store[endpoint]) {
|
||||
store[endpoint] = {};
|
||||
if (store[endpoint][identity]) {
|
||||
return store[endpoint][identity].then(created => created.data);
|
||||
}
|
||||
|
||||
const query = { params: { [identifier]: identity } };
|
||||
@ -61,13 +56,8 @@ const getInventory = (namespace = session) => getOrganization(namespace)
|
||||
.then(organization => getOrCreate('/inventories/', {
|
||||
name: `${namespace}-inventory`,
|
||||
description: namespace,
|
||||
organization: organization.id,
|
||||
}).then(inventory => getOrCreate('/hosts/', {
|
||||
name: `${namespace}-host`,
|
||||
description: namespace,
|
||||
inventory: inventory.id,
|
||||
variables: JSON.stringify({ ansible_connection: 'local' }),
|
||||
}).then(() => inventory)));
|
||||
organization: organization.id
|
||||
}));
|
||||
|
||||
const getHost = (namespace = session) => getInventory(namespace)
|
||||
.then(inventory => getOrCreate('/hosts/', {
|
||||
@ -75,7 +65,7 @@ const getHost = (namespace = session) => getInventory(namespace)
|
||||
description: namespace,
|
||||
inventory: inventory.id,
|
||||
variables: JSON.stringify({ ansible_connection: 'local' }),
|
||||
}).then((host) => host));
|
||||
}));
|
||||
|
||||
const getInventoryScript = (namespace = session) => getOrganization(namespace)
|
||||
.then(organization => getOrCreate('/inventory_scripts/', {
|
||||
@ -91,14 +81,14 @@ const getInventorySource = (namespace = session) => {
|
||||
getInventoryScript(namespace)
|
||||
];
|
||||
|
||||
return all(promises)
|
||||
.then(spread((inventory, inventoryScript) => getOrCreate('/inventory_sources/', {
|
||||
return Promise.all(promises)
|
||||
.then(([inventory, inventoryScript]) => getOrCreate('/inventory_sources/', {
|
||||
name: `${namespace}-inventory-source-custom`,
|
||||
description: namespace,
|
||||
source: 'custom',
|
||||
inventory: inventory.id,
|
||||
source_script: inventoryScript.id
|
||||
})));
|
||||
}));
|
||||
};
|
||||
|
||||
const getAdminAWSCredential = (namespace = session) => {
|
||||
@ -109,8 +99,8 @@ const getAdminAWSCredential = (namespace = session) => {
|
||||
})
|
||||
];
|
||||
|
||||
return all(promises)
|
||||
.then(spread((me, credentialType) => {
|
||||
return Promise.all(promises)
|
||||
.then(([me, credentialType]) => {
|
||||
const [admin] = me.data.results;
|
||||
|
||||
return getOrCreate('/credentials/', {
|
||||
@ -124,7 +114,7 @@ const getAdminAWSCredential = (namespace = session) => {
|
||||
security_token: 'AAAAAAAAAAAAAAAA'
|
||||
}
|
||||
});
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
const getAdminMachineCredential = (namespace = session) => {
|
||||
@ -133,17 +123,16 @@ const getAdminMachineCredential = (namespace = session) => {
|
||||
getOrCreate('/credential_types/', { name: 'Machine' })
|
||||
];
|
||||
|
||||
return all(promises)
|
||||
.then(spread((me, credentialType) => {
|
||||
return Promise.all(promises)
|
||||
.then(([me, credentialType]) => {
|
||||
const [admin] = me.data.results;
|
||||
|
||||
return getOrCreate('/credentials/', {
|
||||
name: `${namespace}-credential-machine-admin`,
|
||||
description: namespace,
|
||||
credential_type: credentialType.id,
|
||||
user: admin.id
|
||||
});
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
const getTeam = (namespace = session) => getOrganization(namespace)
|
||||
@ -235,15 +224,57 @@ const getJobTemplate = (namespace = session) => {
|
||||
getUpdatedProject(namespace)
|
||||
];
|
||||
|
||||
return all(promises)
|
||||
.then(spread((inventory, credential, project) => getOrCreate('/job_templates/', {
|
||||
return Promise.all(promises)
|
||||
.then(([inventory, credential, project]) => getOrCreate('/job_templates/', {
|
||||
name: `${namespace}-job-template`,
|
||||
description: namespace,
|
||||
inventory: inventory.id,
|
||||
credential: credential.id,
|
||||
project: project.id,
|
||||
playbook: 'hello_world.yml',
|
||||
})));
|
||||
}));
|
||||
};
|
||||
|
||||
const getWorkflowTemplate = (namespace = session) => {
|
||||
const endpoint = '/workflow_job_templates/';
|
||||
|
||||
const workflowTemplatePromise = getOrganization(namespace)
|
||||
.then(organization => getOrCreate(endpoint, {
|
||||
name: `${namespace}-workflow-template`,
|
||||
organization: organization.id,
|
||||
variables: '---',
|
||||
extra_vars: '',
|
||||
}));
|
||||
|
||||
const resources = [
|
||||
workflowTemplatePromise,
|
||||
getInventorySource(namespace),
|
||||
getUpdatedProject(namespace),
|
||||
getJobTemplate(namespace),
|
||||
];
|
||||
|
||||
const workflowNodePromise = Promise.all(resources)
|
||||
.then(([workflowTemplate, source, project, jobTemplate]) => {
|
||||
const workflowNodes = workflowTemplate.related.workflow_nodes;
|
||||
const unique = 'unified_job_template';
|
||||
|
||||
const nodes = [
|
||||
getOrCreate(workflowNodes, { [unique]: project.id }, [unique]),
|
||||
getOrCreate(workflowNodes, { [unique]: jobTemplate.id }, [unique]),
|
||||
getOrCreate(workflowNodes, { [unique]: source.id }, [unique]),
|
||||
];
|
||||
|
||||
const createSuccessNodes = ([projectNode, jobNode, sourceNode]) => Promise.all([
|
||||
getOrCreate(projectNode.related.success_nodes, { id: jobNode.id }),
|
||||
getOrCreate(jobNode.related.success_nodes, { id: sourceNode.id }),
|
||||
]);
|
||||
|
||||
return Promise.all(nodes)
|
||||
.then(createSuccessNodes);
|
||||
});
|
||||
|
||||
return Promise.all([workflowTemplatePromise, workflowNodePromise])
|
||||
.then(([workflowTemplate, nodes]) => workflowTemplate);
|
||||
};
|
||||
|
||||
const getAuditor = (namespace = session) => getOrganization(namespace)
|
||||
@ -287,10 +318,10 @@ const getJobTemplateAdmin = (namespace = session) => {
|
||||
}));
|
||||
|
||||
const assignRolePromise = Promise.all([userPromise, rolePromise])
|
||||
.then(spread((user, role) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })));
|
||||
.then(([user, role]) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id }));
|
||||
|
||||
return Promise.all([userPromise, assignRolePromise])
|
||||
.then(spread(user => user));
|
||||
.then(([user, assignment]) => user);
|
||||
};
|
||||
|
||||
const getProjectAdmin = (namespace = session) => {
|
||||
@ -310,10 +341,10 @@ const getProjectAdmin = (namespace = session) => {
|
||||
}));
|
||||
|
||||
const assignRolePromise = Promise.all([userPromise, rolePromise])
|
||||
.then(spread((user, role) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })));
|
||||
.then(([user, role]) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id }));
|
||||
|
||||
return Promise.all([userPromise, assignRolePromise])
|
||||
.then(spread(user => user));
|
||||
.then(([user, assignment]) => user);
|
||||
};
|
||||
|
||||
const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace)
|
||||
@ -334,21 +365,23 @@ module.exports = {
|
||||
getAdminAWSCredential,
|
||||
getAdminMachineCredential,
|
||||
getAuditor,
|
||||
getHost,
|
||||
getInventory,
|
||||
getInventoryScript,
|
||||
getInventorySource,
|
||||
getInventorySourceSchedule,
|
||||
getJob,
|
||||
getJobTemplate,
|
||||
getJobTemplateAdmin,
|
||||
getJobTemplateSchedule,
|
||||
getNotificationTemplate,
|
||||
getOrCreate,
|
||||
getOrganization,
|
||||
getOrCreate,
|
||||
getProject,
|
||||
getProjectAdmin,
|
||||
getSmartInventory,
|
||||
getTeam,
|
||||
getUpdatedProject,
|
||||
getUser,
|
||||
getJob,
|
||||
getHost,
|
||||
getWorkflowTemplate,
|
||||
};
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
import { all } from '../api';
|
||||
|
||||
import {
|
||||
getAdminAWSCredential,
|
||||
getAdminMachineCredential,
|
||||
@ -49,7 +47,7 @@ module.exports = {
|
||||
getUpdatedProject().then(obj => { data.project = obj; })
|
||||
];
|
||||
|
||||
all(promises)
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
client.useCss();
|
||||
|
||||
|
||||
50
awx/ui/test/e2e/tests/test-credentials-list-actions.js
Normal file
50
awx/ui/test/e2e/tests/test-credentials-list-actions.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { getAdminMachineCredential } from '../fixtures';
|
||||
|
||||
const data = {};
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
getAdminMachineCredential('test-actions')
|
||||
.then(obj => { data.credential = obj; })
|
||||
.then(done);
|
||||
},
|
||||
'copy credential': client => {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
credentials.navigate();
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list.expect.element('smart-search').visible;
|
||||
credentials.section.list.expect.element('smart-search input').enabled;
|
||||
|
||||
credentials.section.list
|
||||
.sendKeys('smart-search input', `id:>${data.credential.id - 1} id:<${data.credential.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.expect.element(`#credentials_table tr[id="${data.credential.id}"]`).visible;
|
||||
credentials.expect.element('i[class*="copy"]').visible;
|
||||
credentials.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
credentials.click('i[class*="copy"]');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.expect.element('div[ui-view="edit"] form').visible;
|
||||
credentials.section.edit.expect.element('@title').visible;
|
||||
credentials.section.edit.expect.element('@title').text.contain(data.credential.name);
|
||||
credentials.section.edit.expect.element('@title').text.not.equal(data.credential.name);
|
||||
credentials.section.edit.section.details.expect.element('@save').visible;
|
||||
credentials.section.edit.section.details.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
50
awx/ui/test/e2e/tests/test-inventories-list-actions.js
Normal file
50
awx/ui/test/e2e/tests/test-inventories-list-actions.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { getInventory } from '../fixtures';
|
||||
|
||||
const data = {};
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
getInventory('test-actions')
|
||||
.then(obj => { data.inventory = obj; })
|
||||
.then(done);
|
||||
},
|
||||
'copy inventory': client => {
|
||||
const inventories = client.page.inventories();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
inventories.navigate();
|
||||
inventories.waitForElementVisible('div.spinny');
|
||||
inventories.waitForElementNotVisible('div.spinny');
|
||||
|
||||
inventories.section.list.expect.element('smart-search').visible;
|
||||
inventories.section.list.section.search.expect.element('@input').enabled;
|
||||
|
||||
inventories.section.list.section.search
|
||||
.sendKeys('@input', `id:>${data.inventory.id - 1} id:<${data.inventory.id + 1}`)
|
||||
.sendKeys('@input', client.Keys.ENTER);
|
||||
|
||||
inventories.waitForElementVisible('div.spinny');
|
||||
inventories.waitForElementNotVisible('div.spinny');
|
||||
|
||||
inventories.expect.element(`#inventories_table tr[id="${data.inventory.id}"]`).visible;
|
||||
inventories.expect.element('i[class*="copy"]').visible;
|
||||
inventories.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
inventories.click('i[class*="copy"]');
|
||||
inventories.waitForElementVisible('div.spinny');
|
||||
inventories.waitForElementNotVisible('div.spinny');
|
||||
|
||||
inventories.expect.element('#inventory_form').visible;
|
||||
inventories.section.editStandardInventory.expect.element('@title').visible;
|
||||
inventories.section.editStandardInventory.expect.element('@title').text.contain(data.inventory.name);
|
||||
inventories.section.editStandardInventory.expect.element('@title').text.not.equal(data.inventory.name);
|
||||
inventories.expect.element('@save').visible;
|
||||
inventories.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
50
awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js
Normal file
50
awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { getInventoryScript } from '../fixtures';
|
||||
|
||||
const data = {};
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
getInventoryScript('test-actions')
|
||||
.then(obj => { data.inventoryScript = obj; })
|
||||
.then(done);
|
||||
},
|
||||
'copy inventory script': client => {
|
||||
const inventoryScripts = client.page.inventoryScripts();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
inventoryScripts.navigate();
|
||||
inventoryScripts.waitForElementVisible('div.spinny');
|
||||
inventoryScripts.waitForElementNotVisible('div.spinny');
|
||||
|
||||
inventoryScripts.section.list.expect.element('smart-search').visible;
|
||||
inventoryScripts.section.list.expect.element('smart-search input').enabled;
|
||||
|
||||
inventoryScripts.section.list
|
||||
.sendKeys('smart-search input', `id:>${data.inventoryScript.id - 1} id:<${data.inventoryScript.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
inventoryScripts.waitForElementVisible('div.spinny');
|
||||
inventoryScripts.waitForElementNotVisible('div.spinny');
|
||||
|
||||
inventoryScripts.expect.element(`#inventory_scripts_table tr[id="${data.inventoryScript.id}"]`).visible;
|
||||
inventoryScripts.expect.element('i[class*="copy"]').visible;
|
||||
inventoryScripts.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
inventoryScripts.click('i[class*="copy"]');
|
||||
inventoryScripts.waitForElementVisible('div.spinny');
|
||||
inventoryScripts.waitForElementNotVisible('div.spinny');
|
||||
|
||||
inventoryScripts.expect.element('#inventory_script_form').visible;
|
||||
inventoryScripts.section.edit.expect.element('@title').visible;
|
||||
inventoryScripts.section.edit.expect.element('@title').text.contain(data.inventoryScript.name);
|
||||
inventoryScripts.section.edit.expect.element('@title').text.not.equal(data.inventoryScript.name);
|
||||
inventoryScripts.expect.element('@save').visible;
|
||||
inventoryScripts.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
50
awx/ui/test/e2e/tests/test-notifications-list-actions.js
Normal file
50
awx/ui/test/e2e/tests/test-notifications-list-actions.js
Normal file
@ -0,0 +1,50 @@
|
||||
import { getNotificationTemplate } from '../fixtures';
|
||||
|
||||
const data = {};
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
getNotificationTemplate('test-actions')
|
||||
.then(obj => { data.notification = obj; })
|
||||
.then(done);
|
||||
},
|
||||
'copy notification template': client => {
|
||||
const notifications = client.page.notificationTemplates();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
notifications.navigate();
|
||||
notifications.waitForElementVisible('div.spinny');
|
||||
notifications.waitForElementNotVisible('div.spinny');
|
||||
|
||||
notifications.section.list.expect.element('smart-search').visible;
|
||||
notifications.section.list.expect.element('smart-search input').enabled;
|
||||
|
||||
notifications.section.list
|
||||
.sendKeys('smart-search input', `id:>${data.notification.id - 1} id:<${data.notification.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
notifications.waitForElementVisible('div.spinny');
|
||||
notifications.waitForElementNotVisible('div.spinny');
|
||||
|
||||
notifications.expect.element(`#notification_templates_table tr[id="${data.notification.id}"]`).visible;
|
||||
notifications.expect.element('i[class*="copy"]').visible;
|
||||
notifications.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
notifications.click('i[class*="copy"]');
|
||||
notifications.waitForElementVisible('div.spinny');
|
||||
notifications.waitForElementNotVisible('div.spinny');
|
||||
|
||||
notifications.expect.element('#notification_template_form').visible;
|
||||
notifications.section.edit.expect.element('@title').visible;
|
||||
notifications.section.edit.expect.element('@title').text.contain(data.notification.name);
|
||||
notifications.section.edit.expect.element('@title').text.not.equal(data.notification.name);
|
||||
notifications.expect.element('@save').visible;
|
||||
notifications.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
51
awx/ui/test/e2e/tests/test-projects-list-actions.js
Normal file
51
awx/ui/test/e2e/tests/test-projects-list-actions.js
Normal file
@ -0,0 +1,51 @@
|
||||
import { getProject } from '../fixtures';
|
||||
|
||||
const data = {};
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
getProject('test-actions')
|
||||
.then(obj => { data.project = obj; })
|
||||
.then(done);
|
||||
},
|
||||
'copy project': client => {
|
||||
const projects = client.page.projects();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
projects.navigate();
|
||||
projects.waitForElementVisible('div.spinny');
|
||||
projects.waitForElementNotVisible('div.spinny');
|
||||
|
||||
projects.section.list.expect.element('smart-search').visible;
|
||||
projects.section.list.section.search.expect.element('@input').enabled;
|
||||
|
||||
projects.section.list.section.search
|
||||
.sendKeys('@input', `id:>${data.project.id - 1} id:<${data.project.id + 1}`)
|
||||
.sendKeys('@input', client.Keys.ENTER);
|
||||
|
||||
projects.waitForElementVisible('div.spinny');
|
||||
projects.waitForElementNotVisible('div.spinny');
|
||||
|
||||
projects.section.list.expect.element('@badge').text.equal('1');
|
||||
projects.expect.element(`#projects_table tr[id="${data.project.id}"]`).visible;
|
||||
projects.expect.element('i[class*="copy"]').visible;
|
||||
projects.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
projects.click('i[class*="copy"]');
|
||||
projects.waitForElementVisible('div.spinny');
|
||||
projects.waitForElementNotVisible('div.spinny');
|
||||
|
||||
projects.expect.element('#project_form').visible;
|
||||
projects.section.edit.expect.element('@title').visible;
|
||||
projects.section.edit.expect.element('@title').text.contain(data.project.name);
|
||||
projects.section.edit.expect.element('@title').text.not.equal(data.project.name);
|
||||
projects.expect.element('@save').visible;
|
||||
projects.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
214
awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js
Normal file
214
awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js
Normal file
@ -0,0 +1,214 @@
|
||||
import { post } from '../api';
|
||||
import {
|
||||
getInventoryScript,
|
||||
getInventorySource,
|
||||
getJobTemplate,
|
||||
getOrCreate,
|
||||
getOrganization,
|
||||
getProject,
|
||||
getUser,
|
||||
getWorkflowTemplate,
|
||||
} from '../fixtures';
|
||||
|
||||
let data;
|
||||
|
||||
const promptHeader = '#prompt-header';
|
||||
const promptWarning = '#prompt-body';
|
||||
const promptResource = 'span[class="Prompt-warningResourceTitle"]';
|
||||
const promptResourceCount = 'span[class="badge List-titleBadge"]';
|
||||
const promptCancelButton = '#prompt_cancel_btn';
|
||||
const promptActionButton = '#prompt_action_btn';
|
||||
const promptCloseButton = '#prompt-header + div i[class*="times-circle"]';
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
const resources = [
|
||||
getUser('test-warnings'),
|
||||
getOrganization('test-warnings'),
|
||||
getWorkflowTemplate('test-warnings'),
|
||||
getProject('test-warnings'),
|
||||
getJobTemplate('test-warnings'),
|
||||
getInventoryScript('external-org'),
|
||||
getInventorySource('external-org'),
|
||||
];
|
||||
|
||||
Promise.all(resources)
|
||||
.then(([user, org, workflow, project, template, script, source]) => {
|
||||
const unique = 'unified_job_template';
|
||||
const nodes = workflow.related.workflow_nodes;
|
||||
const nodePromise = getOrCreate(nodes, { [unique]: source.id }, [unique]);
|
||||
|
||||
const permissions = [
|
||||
[org, 'admin_role'],
|
||||
[workflow, 'admin_role'],
|
||||
[project, 'admin_role'],
|
||||
[template, 'admin_role'],
|
||||
[script, 'read_role'],
|
||||
];
|
||||
|
||||
const assignments = permissions
|
||||
.map(([resource, name]) => resource.summary_fields.object_roles[name])
|
||||
.map(role => `/api/v2/roles/${role.id}/users/`)
|
||||
.map(url => post(url, { id: user.id }))
|
||||
.concat([nodePromise]);
|
||||
|
||||
Promise.all(assignments)
|
||||
.then(() => { data = { user, project, source, template, workflow }; })
|
||||
.then(done);
|
||||
});
|
||||
},
|
||||
'verify job template delete warning': client => {
|
||||
const templates = client.page.templates();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
templates.navigate();
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('smart-search').visible;
|
||||
templates.expect.element('smart-search input').enabled;
|
||||
|
||||
templates
|
||||
.sendKeys('smart-search input', `id:>${data.template.id - 1} id:<${data.template.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
templates.expect.element(`#row-${data.template.id}`).visible;
|
||||
templates.expect.element('i[class*="trash"]').visible;
|
||||
templates.expect.element('i[class*="trash"]').enabled;
|
||||
|
||||
templates.click('i[class*="trash"]');
|
||||
|
||||
templates.expect.element(promptHeader).visible;
|
||||
templates.expect.element(promptWarning).visible;
|
||||
templates.expect.element(promptResource).visible;
|
||||
templates.expect.element(promptResourceCount).visible;
|
||||
templates.expect.element(promptCancelButton).visible;
|
||||
templates.expect.element(promptActionButton).visible;
|
||||
templates.expect.element(promptCloseButton).visible;
|
||||
|
||||
templates.expect.element(promptCancelButton).enabled;
|
||||
templates.expect.element(promptActionButton).enabled;
|
||||
templates.expect.element(promptCloseButton).enabled;
|
||||
|
||||
templates.expect.element(promptHeader).text.contain('DELETE');
|
||||
templates.expect.element(promptHeader).text.contain(`${data.template.name.toUpperCase()}`);
|
||||
|
||||
templates.expect.element(promptWarning).text.contain('job template');
|
||||
|
||||
templates.expect.element(promptResource).text.contain('Workflow Job Template Nodes');
|
||||
templates.expect.element(promptResourceCount).text.contain('1');
|
||||
|
||||
templates.click(promptCancelButton);
|
||||
|
||||
templates.expect.element(promptHeader).not.visible;
|
||||
|
||||
client.end();
|
||||
},
|
||||
'verify workflow template delete warning': client => {
|
||||
const templates = client.page.templates();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
templates.navigate();
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('smart-search').visible;
|
||||
templates.expect.element('smart-search input').enabled;
|
||||
|
||||
templates
|
||||
.sendKeys('smart-search input', `id:>${data.workflow.id - 1} id:<${data.workflow.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
templates.expect.element(`#row-${data.workflow.id}`).visible;
|
||||
templates.expect.element('i[class*="trash"]').visible;
|
||||
templates.expect.element('i[class*="trash"]').enabled;
|
||||
|
||||
templates.click('i[class*="trash"]');
|
||||
|
||||
templates.expect.element(promptHeader).visible;
|
||||
templates.expect.element(promptWarning).visible;
|
||||
templates.expect.element(promptCancelButton).visible;
|
||||
templates.expect.element(promptActionButton).visible;
|
||||
templates.expect.element(promptCloseButton).visible;
|
||||
|
||||
templates.expect.element(promptCancelButton).enabled;
|
||||
templates.expect.element(promptActionButton).enabled;
|
||||
templates.expect.element(promptCloseButton).enabled;
|
||||
|
||||
templates.expect.element(promptHeader).text.contain('DELETE');
|
||||
templates.expect.element(promptHeader).text.contain(`${data.workflow.name.toUpperCase()}`);
|
||||
|
||||
templates.expect.element(promptWarning).text.contain('workflow template');
|
||||
|
||||
templates.click(promptCancelButton);
|
||||
|
||||
templates.expect.element(promptHeader).not.visible;
|
||||
|
||||
client.end();
|
||||
},
|
||||
'verify workflow restricted copy warning': client => {
|
||||
const templates = client.page.templates();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login(data.user.username);
|
||||
client.waitForAngular();
|
||||
|
||||
templates.navigate();
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('smart-search').visible;
|
||||
templates.expect.element('smart-search input').enabled;
|
||||
|
||||
templates
|
||||
.sendKeys('smart-search input', `id:>${data.workflow.id - 1} id:<${data.workflow.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
templates.expect.element(`#row-${data.workflow.id}`).visible;
|
||||
templates.expect.element('i[class*="copy"]').visible;
|
||||
templates.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
templates.click('i[class*="copy"]');
|
||||
|
||||
templates.expect.element(promptHeader).visible;
|
||||
templates.expect.element(promptWarning).visible;
|
||||
templates.expect.element(promptCancelButton).visible;
|
||||
templates.expect.element(promptActionButton).visible;
|
||||
templates.expect.element(promptCloseButton).visible;
|
||||
|
||||
templates.expect.element(promptCancelButton).enabled;
|
||||
templates.expect.element(promptActionButton).enabled;
|
||||
templates.expect.element(promptCloseButton).enabled;
|
||||
|
||||
templates.expect.element(promptHeader).text.contain('COPY WORKFLOW');
|
||||
templates.expect.element(promptWarning).text.contain('Unified Job Templates');
|
||||
templates.expect.element(promptWarning).text.contain(`${data.source.name}`);
|
||||
|
||||
templates.click(promptCancelButton);
|
||||
|
||||
templates.expect.element(promptHeader).not.visible;
|
||||
|
||||
client.end();
|
||||
},
|
||||
};
|
||||
161
awx/ui/test/e2e/tests/test-templates-list-actions.js
Normal file
161
awx/ui/test/e2e/tests/test-templates-list-actions.js
Normal file
@ -0,0 +1,161 @@
|
||||
import {
|
||||
getInventorySource,
|
||||
getJobTemplate,
|
||||
getProject,
|
||||
getWorkflowTemplate
|
||||
} from '../fixtures';
|
||||
|
||||
let data;
|
||||
|
||||
module.exports = {
|
||||
before: (client, done) => {
|
||||
const resources = [
|
||||
getInventorySource('test-actions'),
|
||||
getJobTemplate('test-actions'),
|
||||
getProject('test-actions'),
|
||||
getWorkflowTemplate('test-actions'),
|
||||
];
|
||||
|
||||
Promise.all(resources)
|
||||
.then(([source, template, project, workflow]) => {
|
||||
data = { source, template, project, workflow };
|
||||
done();
|
||||
});
|
||||
},
|
||||
'copy job template': client => {
|
||||
const templates = client.page.templates();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
templates.navigate();
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('smart-search').visible;
|
||||
templates.expect.element('smart-search input').enabled;
|
||||
|
||||
templates
|
||||
.sendKeys('smart-search input', `id:>${data.template.id - 1} id:<${data.template.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
templates.expect.element(`#row-${data.template.id}`).visible;
|
||||
templates.expect.element('i[class*="copy"]').visible;
|
||||
templates.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
templates.click('i[class*="copy"]');
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('#job_template_form').visible;
|
||||
templates.section.addJobTemplate.expect.element('@title').visible;
|
||||
templates.section.addJobTemplate.expect.element('@title').text.contain(data.template.name);
|
||||
templates.section.addJobTemplate.expect.element('@title').text.not.equal(data.template.name);
|
||||
templates.expect.element('@save').visible;
|
||||
templates.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
},
|
||||
'copy workflow template': client => {
|
||||
const templates = client.page.templates();
|
||||
|
||||
client.useCss();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
templates.navigate();
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('smart-search').visible;
|
||||
templates.expect.element('smart-search input').enabled;
|
||||
|
||||
templates
|
||||
.sendKeys('smart-search input', `id:>${data.workflow.id - 1} id:<${data.workflow.id + 1}`)
|
||||
.sendKeys('smart-search input', client.Keys.ENTER);
|
||||
|
||||
templates.waitForElementVisible('div.spinny');
|
||||
templates.waitForElementNotVisible('div.spinny');
|
||||
|
||||
templates.expect.element('.at-Panel-headingTitleBadge').text.equal('1');
|
||||
templates.expect.element(`#row-${data.workflow.id}`).visible;
|
||||
templates.expect.element('i[class*="copy"]').visible;
|
||||
templates.expect.element('i[class*="copy"]').enabled;
|
||||
|
||||
templates
|
||||
.click('i[class*="copy"]')
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny')
|
||||
.waitForAngular();
|
||||
|
||||
templates.expect.element('#workflow_job_template_form').visible;
|
||||
templates.section.editWorkflowJobTemplate.expect.element('@title').visible;
|
||||
templates.section.editWorkflowJobTemplate.expect.element('@title').text.contain(data.workflow.name);
|
||||
templates.section.editWorkflowJobTemplate.expect.element('@title').text.not.equal(data.workflow.name);
|
||||
|
||||
templates.expect.element('@save').visible;
|
||||
templates.expect.element('@save').enabled;
|
||||
|
||||
client
|
||||
.useXpath()
|
||||
.pause(1000)
|
||||
.waitForElementVisible('//*[text()=" Workflow Editor"]')
|
||||
.click('//*[text()=" Workflow Editor"]')
|
||||
.useCss()
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny')
|
||||
.waitForAngular();
|
||||
|
||||
client.expect.element('#workflow-modal-dialog').visible;
|
||||
client.expect.element('#workflow-modal-dialog span[class^="badge"]').visible;
|
||||
client.expect.element('#workflow-modal-dialog span[class^="badge"]').text.equal('3');
|
||||
client.expect.element('div[class="WorkflowMaker-title"]').visible;
|
||||
client.expect.element('div[class="WorkflowMaker-title"]').text.contain(data.workflow.name);
|
||||
client.expect.element('div[class="WorkflowMaker-title"]').text.not.equal(data.workflow.name);
|
||||
|
||||
client.expect.element('#workflow-modal-dialog i[class*="fa-cog"]').visible;
|
||||
client.expect.element('#workflow-modal-dialog i[class*="fa-cog"]').enabled;
|
||||
|
||||
client.click('#workflow-modal-dialog i[class*="fa-cog"]');
|
||||
|
||||
client.waitForElementVisible('workflow-controls');
|
||||
client.waitForElementVisible('div[class*="-zoomPercentage"]');
|
||||
|
||||
client.click('i[class*="fa-home"]').expect.element('div[class*="-zoomPercentage"]').text.equal('100%');
|
||||
client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('90%');
|
||||
client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('80%');
|
||||
client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('70%');
|
||||
client.click('i[class*="fa-minus"]').expect.element('div[class*="-zoomPercentage"]').text.equal('60%');
|
||||
|
||||
client.expect.element('#node-1').visible;
|
||||
client.expect.element('#node-2').visible;
|
||||
client.expect.element('#node-3').visible;
|
||||
client.expect.element('#node-4').visible;
|
||||
|
||||
client.expect.element('#node-1 text').text.not.equal('').after(5000);
|
||||
client.expect.element('#node-2 text').text.not.equal('').after(5000);
|
||||
client.expect.element('#node-3 text').text.not.equal('').after(5000);
|
||||
client.expect.element('#node-4 text').text.not.equal('').after(5000);
|
||||
|
||||
const checkNodeText = (selector, text) => client.getText(selector, ({ value }) => {
|
||||
client.assert.equal(text.indexOf(value.replace('...', '')) >= 0, true);
|
||||
});
|
||||
|
||||
checkNodeText('#node-1 text', 'START');
|
||||
checkNodeText('#node-2 text', data.project.name);
|
||||
checkNodeText('#node-3 text', data.template.name);
|
||||
checkNodeText('#node-4 text', data.source.name);
|
||||
|
||||
templates.expect.element('@save').visible;
|
||||
templates.expect.element('@save').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -113,7 +113,7 @@ module.exports = {
|
||||
client.expect.element('.at-Panel smart-search').visible;
|
||||
client.expect.element('.at-Panel smart-search input').enabled;
|
||||
|
||||
client.sendKeys('.at-Panel smart-search input', `id:${data.jobTemplate.id}`);
|
||||
client.sendKeys('.at-Panel smart-search input', `id:>${data.jobTemplate.id - 1} id:<${data.jobTemplate.id + 1}`);
|
||||
client.sendKeys('.at-Panel smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
@ -176,7 +176,7 @@ module.exports = {
|
||||
client.expect.element('div[ui-view="related"]').visible;
|
||||
client.expect.element('div[ui-view="related"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', `id:${adminRole.id}`);
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', `id:>${adminRole.id - 1} id:<${adminRole.id + 1}`);
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
@ -231,7 +231,7 @@ module.exports = {
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.notification.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.notification.id - 1} id:<${data.notification.id + 1}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -282,7 +282,7 @@ module.exports = {
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.organization.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.organization.id - 1} id:<${data.organization.id + 1}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -333,7 +333,7 @@ module.exports = {
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.inventory.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.inventory.id - 1} id:<${data.inventory.id + 1}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -393,7 +393,7 @@ module.exports = {
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.inventoryScript.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.inventoryScript.id - 1} id:<${data.inventoryScript.id + 1}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -455,7 +455,7 @@ module.exports = {
|
||||
client.expect.element('div[ui-view="related"]').visible;
|
||||
client.expect.element('div[ui-view="related"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', `id:${data.user.id}`);
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', `id:>${data.user.id - 1} id:<${data.user.id + 1}`);
|
||||
client.sendKeys('div[ui-view="related"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').not.visible;
|
||||
@ -514,7 +514,7 @@ module.exports = {
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.project.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.project.id - 1} id:<${data.project.id + 1}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -566,7 +566,7 @@ module.exports = {
|
||||
client.expect.element('div[ui-view="list"] smart-search').visible;
|
||||
client.expect.element('div[ui-view="list"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[ui-view="list"] smart-search input', `id:${data.credential.id}`);
|
||||
client.sendKeys('div[ui-view="list"] smart-search input', `id:>${data.credential.id - 1} id:<${data.credential.id + 1}`);
|
||||
client.sendKeys('div[ui-view="list"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -618,7 +618,7 @@ module.exports = {
|
||||
client.expect.element('div[class^="Panel"] smart-search').visible;
|
||||
client.expect.element('div[class^="Panel"] smart-search input').enabled;
|
||||
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:${data.team.id}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.team.id - 1} id:<${data.team.id + 1}`);
|
||||
client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER);
|
||||
|
||||
client.expect.element('div.spinny').visible;
|
||||
@ -684,7 +684,6 @@ module.exports = {
|
||||
.contains('<div id="xss" class="xss">test</div>');
|
||||
});
|
||||
});
|
||||
client.end();
|
||||
},
|
||||
'check host recent jobs popup for unsanitized content': client => {
|
||||
const itemRow = `#hosts_table tr[id="${data.host.id}"]`;
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
import './components';
|
||||
|
||||
import './models';
|
||||
|
||||
25
awx/ui/test/unit/models/base.unit.js
Normal file
25
awx/ui/test/unit/models/base.unit.js
Normal file
@ -0,0 +1,25 @@
|
||||
describe('Models | BaseModel', () => {
|
||||
let baseModel;
|
||||
|
||||
beforeEach(() => {
|
||||
angular.mock.module('at.lib.services');
|
||||
angular.mock.module('at.lib.models');
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject(($injector) => {
|
||||
baseModel = new ($injector.get('BaseModel'))('test');
|
||||
}));
|
||||
|
||||
describe('parseRequestConfig', () => {
|
||||
it('always returns the expected configuration', () => {
|
||||
const { parseRequestConfig } = baseModel;
|
||||
const data = { name: 'foo' };
|
||||
|
||||
expect(parseRequestConfig('get')).toEqual({ method: 'get', resource: undefined });
|
||||
expect(parseRequestConfig('get', 1)).toEqual({ method: 'get', resource: 1 });
|
||||
expect(parseRequestConfig('post', { data })).toEqual({ method: 'post', data });
|
||||
expect(parseRequestConfig(['get', 'post'], [1, 2], { data }))
|
||||
.toEqual({ resource: [1, 2], method: ['get', 'post'] });
|
||||
});
|
||||
});
|
||||
});
|
||||
5
awx/ui/test/unit/models/index.js
Normal file
5
awx/ui/test/unit/models/index.js
Normal file
@ -0,0 +1,5 @@
|
||||
// Import angular and angular-mocks to the global scope
|
||||
import 'angular-mocks';
|
||||
|
||||
// Import tests
|
||||
import './base.unit';
|
||||
Loading…
x
Reference in New Issue
Block a user