/********************************************* * Copyright (c) 2014 AnsibleWorks, Inc. * * JobsHelper * * Routines shared by job related controllers * */ /** * @ngdoc function * @name helpers.function:Jobs * @description routines shared by job related controllers */ 'use strict'; angular.module('JobsHelper', ['Utilities', 'RestServices', 'FormGenerator', 'JobSummaryDefinition', 'InventoryHelper', 'GeneratorHelpers', 'JobSubmissionHelper', 'LogViewerHelper', 'SearchHelper', 'PaginationHelpers', 'ListGenerator']) /** * JobsControllerInit({ scope: $scope }); * * Initialize calling scope with all the bits required to support a jobs list * */ .factory('JobsControllerInit', ['$location', 'Find', 'DeleteJob', 'RelaunchJob', 'LogViewer', '$window', function($location, Find, DeleteJob, RelaunchJob, LogViewer, $window) { return function(params) { var scope = params.scope, iterator = (params.iterator) ? params.iterator : scope.iterator, base = $location.path().replace(/^\//, '').split('/')[0]; scope.deleteJob = function(id) { DeleteJob({ scope: scope, id: id }); }; scope.relaunchJob = function(event, id) { var list, job, typeId; try { $(event.target).tooltip('hide'); } catch(e) { //ignore } if (scope.completed_jobs) { list = scope.completed_jobs; } else if (scope.running_jobs) { list = scope.running_jobs; } else if (scope.queued_jobs) { list = scope.queued_jobs; } else if (scope.jobs) { list = scope.jobs; } job = Find({ list: list, key: 'id', val: id }); if (job.type === 'inventory_update') { typeId = job.inventory_source; } else if (job.type === 'project_update') { typeId = job.project; } else if (job.type === 'job') { typeId = job.id; } RelaunchJob({ scope: scope, id: typeId, type: job.type, name: job.name }); }; scope.refreshJobs = function() { if (base !== 'jobs') { scope.search(iterator); } }; scope.viewJobLog = function(id) { var list, job; if (scope.completed_jobs) { list = scope.completed_jobs; } else if (scope.running_jobs) { list = scope.running_jobs; } else if (scope.queued_jobs) { list = scope.queued_jobs; } else if (scope.jobs) { list = scope.jobs; } else if(scope.portal_jobs){ list=scope.portal_jobs; } job = Find({ list: list, key: 'id', val: id }); if (job.type === 'job') { if(scope.$parent.portalMode===true){ $window.open('/#/jobs/' + job.id, '_blank'); } else $location.url('/jobs/' + job.id); } else { LogViewer({ scope: scope, url: job.url }); } }; }; } ]) .factory('RelaunchJob', ['RelaunchInventory', 'RelaunchPlaybook', 'RelaunchSCM', function(RelaunchInventory, RelaunchPlaybook, RelaunchSCM) { return function(params) { var scope = params.scope, id = params.id, type = params.type, name = params.name; if (type === 'inventory_update') { RelaunchInventory({ scope: scope, id: id}); } else if (type === 'job') { RelaunchPlaybook({ scope: scope, id: id, name: name }); } else if (type === 'project_update') { RelaunchSCM({ scope: scope, id: id }); } }; } ]) .factory('JobStatusToolTip', [ function () { return function (status) { var toolTip; switch (status) { case 'successful': case 'success': toolTip = 'There were no failed tasks.'; break; case 'failed': toolTip = 'Some tasks encountered errors.'; break; case 'canceled': toolTip = 'Stopped by user request.'; break; case 'new': toolTip = 'In queue, waiting on task manager.'; break; case 'waiting': toolTip = 'SCM Update or Inventory Update is executing.'; break; case 'pending': toolTip = 'Not in queue, waiting on task manager.'; break; case 'running': toolTip = 'Playbook tasks executing.'; break; } return toolTip; }; } ]) .factory('ShowJobSummary', ['Rest', 'Wait', 'GetBasePath', 'FormatDate', 'ProcessErrors', 'GenerateForm', 'JobSummary', 'WatchInventoryWindowResize', function (Rest, Wait, GetBasePath, FormatDate, ProcessErrors, GenerateForm, JobSummary, WatchInventoryWindowResize) { return function (params) { // Display status info in a modal dialog- called from inventory edit page var job_id = params.job_id, generator = GenerateForm, form = JobSummary, scope, ww, wh, x, y, maxrows, url, html; html = '
' + '
\n'; $('#inventory-modal-container').empty().append(html); scope = generator.inject(form, { mode: 'edit', id: 'form-container', breadCrumbs: false, related: false }); // Set modal dimensions based on viewport width ww = $(document).width(); wh = $('body').height(); if (ww > 1199) { // desktop x = 675; y = (750 > wh) ? wh - 20 : 750; maxrows = 20; } else if (ww <= 1199 && ww >= 768) { x = 550; y = (620 > wh) ? wh - 15 : 620; maxrows = 15; } else { x = (ww - 20); y = (500 > wh) ? wh : 500; maxrows = 10; } // Create the modal $('#status-modal-dialog').dialog({ buttons: { 'OK': function () { $(this).dialog('close'); } }, modal: true, width: x, height: y, autoOpen: false, closeOnEscape: false, create: function () { // fix the close button $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-titlebar button') .empty().attr({ 'class': 'close' }).text('x'); // fix the OK button $('.ui-dialog[aria-describedby="status-modal-dialog"]').find('.ui-dialog-buttonset button:first') .attr({ 'class': 'btn btn-primary' }); }, resizeStop: function () { // for some reason, after resizing dialog the form and fields (the content) doesn't expand to 100% var dialog = $('.ui-dialog[aria-describedby="status-modal-dialog"]'), titleHeight = dialog.find('.ui-dialog-titlebar').outerHeight(), buttonHeight = dialog.find('.ui-dialog-buttonpane').outerHeight(), content = dialog.find('#status-modal-dialog'); content.width(dialog.width() - 28); content.css({ height: (dialog.height() - titleHeight - buttonHeight - 10) }); }, close: function () { // Destroy on close $('.tooltip').each(function () { // Remove any lingering tooltip
elements $(this).remove(); }); $('.popover').each(function () { // remove lingering popover
elements $(this).remove(); }); $('#status-modal-dialog').dialog('destroy'); $('#inventory-modal-container').empty(); WatchInventoryWindowResize(); }, open: function () { Wait('stop'); } }); function calcRows(content) { var n = content.match(/\n/g), rows = (n) ? n.length : 1; return (rows > maxrows) ? 20 : rows; } Wait('start'); url = GetBasePath('jobs') + job_id + '/'; Rest.setUrl(url); Rest.get() .success(function (data) { var cDate; scope.id = data.id; scope.name = data.name; scope.status = data.status; scope.result_stdout = data.result_stdout; scope.result_traceback = data.result_traceback; scope.stdout_rows = calcRows(scope.result_stdout); scope.traceback_rows = calcRows(scope.result_traceback); cDate = new Date(data.created); scope.created = FormatDate(cDate); $('#status-modal-dialog').dialog('open'); }) .error(function (data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Attempt to load job failed. GET returned status: ' + status }); }); }; } ]) .factory('JobsListUpdate', ['Rest', function(Rest) { return function(params) { var scope = params.scope, parent_scope = params.parent_scope, list = params.list; scope[list.name].forEach(function(item, item_idx) { var fld, field, itm = scope[list.name][item_idx]; //if (item.type === 'inventory_update') { // itm.name = itm.name.replace(/^.*?:/,'').replace(/^: /,''); //} // Set the item type label if (list.fields.type) { parent_scope.type_choices.every(function(choice) { if (choice.value === item.type) { itm.type_label = choice.label; return false; } return true; }); } // Set the job status label parent_scope.status_choices.every(function(status) { if (status.value === item.status) { itm.status_label = status.label; return false; } return true; }); //Set the name link if (item.type === "inventory_update") { Rest.setUrl(item.related.inventory_source); Rest.get() .success(function(data) { itm.nameHref = "/home/groups?id=" + data.group; }); } else if (item.type === "project_update") { itm.nameHref = "/projects/" + item.project; } else if (item.type === "job") { itm.nameHref = ""; } if (list.name === 'completed_jobs' || list.name === 'running_jobs') { itm.status_tip = itm.status_label + '. Click for details.'; } else if (list.name === 'queued_jobs') { itm.status_tip = 'Pending'; } // Copy summary_field values for (field in list.fields) { fld = list.fields[field]; if (fld.sourceModel) { if (itm.summary_fields[fld.sourceModel]) { itm[field] = itm.summary_fields[fld.sourceModel][fld.sourceField]; } } } }); }; }]) /** * * Called from JobsList controller to load each section or list on the page * */ .factory('LoadJobsScope', ['$routeParams', '$location', '$compile', 'SearchInit', 'PaginateInit', 'GenerateList', 'JobsControllerInit', 'JobsListUpdate', 'SearchWidget', function($routeParams, $location, $compile, SearchInit, PaginateInit, GenerateList, JobsControllerInit, JobsListUpdate, SearchWidget) { return function(params) { var parent_scope = params.parent_scope, scope = params.scope, list = params.list, id = params.id, url = params.url, pageSize = params.pageSize || 5, base = $location.path().replace(/^\//, '').split('/')[0], search_params = params.searchParams, spinner = (params.spinner === undefined) ? true : params.spinner, e, html, key; // Add the search widget. We want it arranged differently, so we're injecting and compiling it separately html = SearchWidget({ iterator: list.iterator, template: params.list, includeSize: false }); e = angular.element(document.getElementById(id + '-search-container')).append(html); $compile(e)(scope); GenerateList.inject(list, { mode: 'edit', id: id, breadCrumbs: false, scope: scope, showSearch: false }); SearchInit({ scope: scope, set: list.name, list: list, url: url }); PaginateInit({ scope: scope, list: list, url: url, pageSize: pageSize }); scope.iterator = list.iterator; if (scope.removePostRefresh) { scope.removePostRefresh(); } scope.$on('PostRefresh', function(){ JobsControllerInit({ scope: scope, parent_scope: parent_scope }); JobsListUpdate({ scope: scope, parent_scope: parent_scope, list: list }); parent_scope.$emit('listLoaded'); // setTimeout(function(){ // scope.$apply(); // }, 300); }); if (base === 'jobs' && list.name === 'completed_jobs') { if ($routeParams.id__int) { scope[list.iterator + 'SearchField'] = 'id'; scope[list.iterator + 'SearchValue'] = $routeParams.id__int; scope[list.iterator + 'SearchFieldLabel'] = 'Job ID'; } } if (search_params) { for (key in search_params) { scope[key] = search_params[key]; } } scope.search(list.iterator, null, null, null, null, spinner); }; }]) .factory('DeleteJob', ['Find', 'GetBasePath', 'Rest', 'Wait', 'ProcessErrors', 'Prompt', 'Alert', function(Find, GetBasePath, Rest, Wait, ProcessErrors, Prompt, Alert){ return function(params) { var scope = params.scope, id = params.id, job = params.job, callback = params.callback, action, jobs, url, action_label, hdr; if (!job) { if (scope.completed_jobs) { jobs = scope.completed_jobs; } else if (scope.running_jobs) { jobs = scope.running_jobs; } else if (scope.queued_jobs) { jobs = scope.queued_jobs; } else if (scope.jobs) { jobs = scope.jobs; } job = Find({list: jobs, key: 'id', val: id }); } if (job.status === 'pending' || job.status === 'running' || job.status === 'waiting') { url = job.related.cancel; action_label = 'cancel'; hdr = 'Cancel Job'; } else { url = job.url; action_label = 'delete'; hdr = 'Delete Job'; } action = function () { Wait('start'); Rest.setUrl(url); if (action_label === 'cancel') { Rest.post() .success(function () { $('#prompt-modal').modal('hide'); if (callback) { scope.$emit(callback, action_label); } else { scope.search(scope.iterator); } }) .error(function() { Wait('stop'); $('#prompt-modal').modal('hide'); // Ignore the error. The job most likely already finished. // ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + // ' failed. POST returned status: ' + status }); }); } else { Rest.destroy() .success(function () { $('#prompt-modal').modal('hide'); if (callback) { scope.$emit(callback, action_label); } else { scope.search(scope.iterator); } }) .error(function () { Wait('stop'); $('#prompt-modal').modal('hide'); // Ignore the error. The job most likely already finished. //ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + // ' failed. DELETE returned status: ' + status }); }); } }; if (scope.removeCancelNotAllowed) { scope.removeCancelNotAllowed(); } scope.removeCancelNotAllowed = scope.$on('CancelNotAllowed', function() { Wait('stop'); Alert('Job Completed', 'The request to cancel the job could not be submitted. The job already completed.', 'alert-info'); }); if (scope.removeCancelJob) { scope.removeCancelJob(); } scope.removeCancelJob = scope.$on('CancelJob', function() { var body; body = (action_label === 'cancel' || job.status === 'new') ? "Submit the request to cancel" : "Delete"; Prompt({ hdr: hdr, body: "
" + body + " job #" + id + " " + job.name + "?
", action: action }); }); if (action_label === 'cancel') { Rest.setUrl(url); Rest.get() .success(function(data) { if (data.can_cancel) { scope.$emit('CancelJob'); } else { scope.$emit('CancelNotAllowed'); } }) .error(function(data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. GET returned: ' + status }); }); } else { scope.$emit('CancelJob'); } }; }]) .factory('RelaunchInventory', ['Find', 'Wait', 'Rest', 'InventoryUpdate', 'ProcessErrors', 'GetBasePath', function(Find, Wait, Rest, InventoryUpdate, ProcessErrors, GetBasePath) { return function(params) { var scope = params.scope, id = params.id, url = GetBasePath('inventory_sources') + id + '/'; Wait('start'); Rest.setUrl(url); Rest.get() .success(function (data) { InventoryUpdate({ scope: scope, url: data.related.update, group_name: data.summary_fields.group.name, group_source: data.source, tree_id: null, group_id: data.group }); }) .error(function (data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' + url + ' GET returned: ' + status }); }); }; }]) .factory('RelaunchPlaybook', ['PlaybookRun', function(PlaybookRun) { return function(params) { var scope = params.scope, id = params.id; PlaybookRun({ scope: scope, id: id }); }; }]) .factory('RelaunchSCM', ['ProjectUpdate', function(ProjectUpdate) { return function(params) { var scope = params.scope, id = params.id; ProjectUpdate({ scope: scope, project_id: id }); }; }]);