diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index a4b237fb6a..acf62cf560 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -471,11 +471,11 @@ InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location', '$log -function InventoriesEdit($scope, $location, $routeParams, $compile, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait, +function InventoriesEdit($scope, $location, $routeParams, $compile, $log, GenerateList, ClearScope, InventoryGroups, InventoryHosts, BuildTree, Wait, GetSyncStatusMsg, InjectHosts, HostsReload, GroupsEdit, GroupsDelete, Breadcrumbs, LoadBreadCrumbs, Empty, Rest, ProcessErrors, InventoryUpdate, Alert, ToggleChildren, ViewUpdateStatus, GroupsCancelUpdate, Find, EditInventoryProperties, HostsEdit, HostsDelete, ToggleHostEnabled, CopyMoveGroup, CopyMoveHost, Stream, GetBasePath, ShowJobSummary, ApplyEllipsis, WatchInventoryWindowResize, - HelpDialog, InventoryGroupsHelp, Store, ViewJob) { + HelpDialog, InventoryGroupsHelp, Store, ViewJob, Socket) { ClearScope(); @@ -643,6 +643,38 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, GenerateList } }; + if ($scope.removeWatchUpdateStatus) { + $scope.removeWatchUpdateStatus(); + } + $scope.removeWatchUpdateStatus = $scope.$on('WatchUpdateStatus', function(e, job_id, group_id, tree_id) { + var io = Socket({ scope: $scope, endpoint: "jobs" }), + group = Find({ list: $scope.groups, key: 'id', val: tree_id }), + stat; + $log.debug('Watching for updates to job: ' + job_id + ' for group: ' + group_id + ' ' + group.name); + io.init(); + io.on("status_changed", function(data) { + Wait('stop'); + if (data.status === "failed" || data.status === "successful") { + $log.debug('Update completed. Refreshing the tree.'); + $scope.refreshGroups(tree_id, group_id); + } + else { + $log.debug('Status changed to: ' + data.status); + stat = GetSyncStatusMsg({ + status: data.status, + has_inventory_sources: group.has_inventory_sources, + source: group.source + }); + $log.debug('changing tooltip to: ' + stat.tooltip); + group.status = data.status; + group.status_class = stat['class']; + group.status_tooltip = stat.tooltip; + group.launch_tooltip = stat.launch_tip; + group.launch_class = stat.launch_class; + } + }); + }); + $scope.createGroup = function () { GroupsEdit({ scope: $scope, @@ -813,10 +845,10 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, GenerateList } -InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', 'GenerateList', 'ClearScope', 'InventoryGroups', 'InventoryHosts', +InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', '$log', 'GenerateList', 'ClearScope', 'InventoryGroups', 'InventoryHosts', 'BuildTree', 'Wait', 'GetSyncStatusMsg', 'InjectHosts', 'HostsReload', 'GroupsEdit', 'GroupsDelete', 'Breadcrumbs', 'LoadBreadCrumbs', 'Empty', 'Rest', 'ProcessErrors', 'InventoryUpdate', 'Alert', 'ToggleChildren', 'ViewUpdateStatus', 'GroupsCancelUpdate', 'Find', 'EditInventoryProperties', 'HostsEdit', 'HostsDelete', 'ToggleHostEnabled', 'CopyMoveGroup', 'CopyMoveHost', 'Stream', 'GetBasePath', 'ShowJobSummary', 'ApplyEllipsis', 'WatchInventoryWindowResize', 'HelpDialog', 'InventoryGroupsHelp', 'Store', - 'ViewJob' + 'ViewJob', 'Socket' ]; diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 166817435c..b418305453 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -46,7 +46,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc } }); return results; - } + }; // Reduce an array of objects down to just the bits we want from each object by // passing in a function that returns just those parts. @@ -59,7 +59,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc results.push(parameterFunction(row)); }); return results; - } + }; // Apply each event to the view @@ -67,8 +67,6 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc scope.removeEventsReady(); } scope.removeEventsReady = scope.$on('EventsReady', function(e, events) { - console.log('Inside EventsReady!'); - console.log(events); DigestEvents({ scope: scope, events: events @@ -78,17 +76,14 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc event_socket.on("job_events-" + job_id, function(data) { var matches; data.id = data.event_id; - console.log(data); if (api_complete) { - matches = processed_events.find(function(x) { return x === data.id }); + matches = processed_events.find(function(x) { return x === data.id; }); if (matches.length === 0) { // event not processed - console.log('process event: ' + data.id); scope.$emit('EventsReady', [ data ]); } } else { - console.log('queue event: ' + data.id); event_queue.push(data); } }); @@ -113,8 +108,6 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc }); return (!matched); //return true when event.id not in the list of processed_events }); - console.log('processing queued events: '); - console.log(events.reduce(function(x) { return x.id })); if (events.length > 0) { scope.$emit('EventsReady', events); api_complete = true; @@ -133,7 +126,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc Rest.setUrl(next); Rest.get() .success(function(data) { - processed_events = processed_events.concat( data.results.reduce(function(x) { return x.id }) ); + processed_events = processed_events.concat( data.results.reduce(function(x) { return x.id; }) ); scope.$emit('EventsReady', data.results); if (data.next) { scope.$emit('JobReady', data.next); @@ -206,7 +199,7 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc // In the case that the job is already completed, or an error already happened, // populate scope.job_status info - scope.job_status.status = data.status; + scope.job_status.status = data.status; scope.job_status.started = data.started; scope.job_status.status_class = ((data.status === 'error' || data.status === 'failed') && data.job_explanation) ? "alert alert-danger" : ""; scope.job_status.finished = data.finished; diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index acba13de3a..82415f5ece 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -137,9 +137,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' has_inventory_sources = params.has_inventory_sources, launch_class = '', launch_tip = 'Start sync process', - stat, stat_class, status_tip; - + stat = status; stat_class = stat; @@ -157,14 +156,25 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' status_tip = 'Cloud source not configured. Click to update.'; launch_tip = 'Cloud source not configured.'; break; + case 'canceled': + status_tip = 'Sync canceled. Click to view log.'; + break; case 'failed': status_tip = 'Sync failed. Click to view log.'; break; case 'successful': status_tip = 'Sync completed. Click to view log.'; break; + case 'pending': + status_tip = 'Sync pending.'; + launch_class = "btn-disabled"; + launch_tip = "Sync pending"; + break; case 'updating': - status_tip = 'Sync running'; + case 'running': + launch_class = "btn-disabled"; + launch_tip = "Sync running"; + status_tip = "Sync running. Click to view log."; break; } @@ -268,13 +278,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' Rest.post() .success(function () { Wait('stop'); - Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' + - 'Click the button to monitor the status.', 'alert-info'); + //Alert('Inventory Sync Cancelled', 'Request to cancel the sync process was submitted to the task manger. ' + + // 'Click the button to monitor the status.', 'alert-info'); }) .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. POST status: ' + status }); }); @@ -292,16 +300,17 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' .success(function (data) { if (data.can_cancel) { scope.$emit('CancelUpdate', url); - } else { + //} else { + // Wait('stop'); + // Alert('Cancel Inventory Sync', 'The sync process completed. Click the button to view ' + + // 'the latest status.', 'alert-info'); + } + else { Wait('stop'); - Alert('Cancel Inventory Sync', 'The sync process completed. Click the button to view ' + - 'the latest status.', 'alert-info'); } }) .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. GET status: ' + status }); }); @@ -323,17 +332,11 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' scope.$emit('CheckCancel', data.related.last_update, data.related.current_update); }) .error(function (data, status) { - Wait('stop'); - ProcessErrors(scope, data, status, null, { - hdr: 'Error!', + ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + group.related.inventory_source + ' failed. GET status: ' + status }); }); - } else { - Alert('Cancel Inventory Sync', 'The sync process completed. Click the to' + - ' view the latest status.', 'alert-info'); } - }; } ]) diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index f1de3a9230..ee75ef91ee 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -40,9 +40,8 @@ angular.module('JobDetailHelper', ['Utilities', 'RestServices']) .factory('DigestEvents', ['UpdatePlayStatus', 'UpdatePlayNoHostsMatched', 'UpdateHostStatus', 'UpdatePlayChild', 'AddHostResult', 'SelectPlay', 'SelectTask', - 'GetHostCount', 'GetElapsed', 'UpdateJobStatus', -function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed, - UpdateJobStatus) { + 'GetHostCount', 'GetElapsed', +function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePlayChild, AddHostResult, SelectPlay, SelectTask, GetHostCount, GetElapsed) { return function(params) { var scope = params.scope, @@ -89,7 +88,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla failedCount: 0, changedCount: 0, skippedCount: 0, - successfulStyle: { display: 'none'}, + successfulStyle: { display: 'none'}, failedStyle: { display: 'none' }, changedStyle: { display: 'none' }, skippedStyle: { display: 'none' } @@ -125,7 +124,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla failedCount: 0, changedCount: 0, skippedCount: 0, - successfulStyle: { display: 'none'}, + successfulStyle: { display: 'none'}, failedStyle: { display: 'none' }, changedStyle: { display: 'none' }, skippedStyle: { display: 'none' } @@ -338,27 +337,24 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla return function(params) { var scope = params.scope, failed = params.failed, - modified = params.modified; + modified = params.modified, started = params.started; - if (failed && scope.job_status.status !== 'failed' && scope.job_status.status !== 'error' - && scope.job_status.status !== 'canceled') { + if (failed && scope.job_status.status !== 'failed' && scope.job_status.status !== 'error' && + scope.job_status.status !== 'canceled') { scope.job_status.status = 'error'; } if (!Empty(modified)) { scope.job_status.finished = modified; } if (!Empty(started) && Empty(scope.job_status.started)) { - scope.job_status.started = started; - } + scope.job_status.started = started; + } if (!Empty(scope.job_status.finished) && !Empty(scope.job_status.started)) { - console.log('scope.job_status.started: ' + scope.job_status.started); - console.log('scope.job_status.finished: ' + scope.job_status.finished); scope.job_status.elapsed = GetElapsed({ start: scope.job_status.started, end: scope.job_status.finished }); - console.log('elapsed: ' + scope.job_status.elapsed); } }; }]) @@ -385,11 +381,11 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla start: play.created, end: modified }); - /*UpdateJobStatus({ + UpdateJobStatus({ scope: scope, failed: failed, modified: modified - });*/ + }); return false; } return true; @@ -407,7 +403,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla scope.tasks.every(function (task, i) { if (task.id === id) { if (failed) { - scope.tasks[i].status = 'failed'; + scope.tasks[i].status = 'failed'; } else if (task.status !== 'changed' && task.status !== 'failed') { // once the status becomes 'changed' or 'failed' don't modify it diff --git a/awx/ui/static/js/helpers/JobSubmission.js b/awx/ui/static/js/helpers/JobSubmission.js index 5ca4bcfd33..38dfb169e9 100644 --- a/awx/ui/static/js/helpers/JobSubmission.js +++ b/awx/ui/static/js/helpers/JobSubmission.js @@ -20,8 +20,8 @@ angular.module('JobSubmissionHelper', [ 'RestServices', 'Utilities', 'Credential Wait('start'); Rest.setUrl(url); Rest.post(passwords) - .success(function () { - scope.$emit(callback); + .success(function(data) { + scope.$emit(callback, data); }) .error(function (data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', @@ -445,10 +445,10 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi url = params.url, group_id = params.group_id, tree_id = params.tree_id, - base = $location.path().replace(/^\//, '').split('/')[0], + //base = $location.path().replace(/^\//, '').split('/')[0], inventory_source; - if (scope.removeHostReloadComplete) { + /*if (scope.removeHostReloadComplete) { scope.removeHostReloadComplete(); } scope.removeHostReloadComplete = scope.$on('HostReloadComplete', function () { @@ -458,13 +458,44 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi if (scope.removeHostReloadComplete) { scope.removeHostReloadComplete(); } - }); + });*/ + + function getJobID(url) { + var result=''; + url.split(/\//).every(function(path) { + if (/^\d+$/.test(path)) { + result = path; + return false; + } + return true; + }); + return result; + } if (scope.removeUpdateSubmitted) { scope.removeUpdateSubmitted(); } scope.removeUpdateSubmitted = scope.$on('UpdateSubmitted', function () { - setTimeout(function() { + // Get the current job + var path = url.replace(/update\/$/,''); + Rest.setUrl(path); + Rest.get() + .success(function(data) { + if (data.related.current_job) { + scope.$emit('WatchUpdateStatus', getJobID(data.related.current_job), group_id, tree_id); + } + else { + Wait('stop'); + } + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get inventory source ' + url + ' GET returned: ' + status }); + }); + + //console.log('job submitted. callback returned: '); + //console.log(data); + /*setTimeout(function() { if (base === 'jobs') { scope.refreshJobs(); } @@ -476,7 +507,7 @@ function($location, Wait, GetBasePath, LookUpInit, JobTemplateForm, CredentialLi scope.refresh(); } scope.$emit('HostReloadComplete'); - }, 300); + }, 300);*/ }); if (scope.removePromptForPasswords) { diff --git a/awx/ui/static/js/lists/InventoryGroups.js b/awx/ui/static/js/lists/InventoryGroups.js index a41084db04..e2bbdd3616 100644 --- a/awx/ui/static/js/lists/InventoryGroups.js +++ b/awx/ui/static/js/lists/InventoryGroups.js @@ -51,11 +51,11 @@ angular.module('InventoryGroupsDefinition', []) awToolTip: "Edit inventory properties", ngClick: 'editInventoryProperties()' }, - refresh: { + /*refresh: { mode: 'all', awToolTip: "Refresh the page", ngClick: "refreshGroups()" - }, + },*/ stream: { ngClick: "showGroupActivity()", awToolTip: "View Activity Stream", @@ -75,9 +75,9 @@ angular.module('InventoryGroupsDefinition', []) ngClick: "viewUpdateStatus(group.id, group.group_id)", ngShow: "group.id > 1", // hide for all hosts awToolTip: "{{ group.status_tooltip }}", - dataTipWatch: "group.launch_tooltip", + dataTipWatch: "group.status_tooltip", iconClass: "{{ 'fa icon-cloud-' + group.status_class }}", - ngClass: "group.launch_class", + ngClass: "group.status_class", dataPlacement: "top" }, failed_hosts: { diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index 218b35f6fd..71968627e6 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -1126,7 +1126,9 @@ input[type="checkbox"].checkbox-no-label { .icon-cloud-updating:before, .icon-cloud-running:before, .icon-cloud-successful:before, + .icon-cloud-pending:before, .icon-cloud-failed:before, + .icon-cloud-canceled:before, .icon-cloud-error:before { content: "\f0c2"; } @@ -1146,6 +1148,7 @@ input[type="checkbox"].checkbox-no-label { .icon-cloud-updating, .icon-cloud-running, .icon-cloud-successful, + .icon-cloud-pending, a.icon-cloud-updating:hover, a.icon-cloud-successful:hover { color: @green; @@ -1153,12 +1156,14 @@ input[type="checkbox"].checkbox-no-label { .icon-cloud-failed, .icon-cloud-error, + .icon-cloud-canceled, a.icon-cloud-failed:hover { color: @red; } .icon-cloud-updating, - .icon-cloud-running { + .icon-cloud-running, + .icon-cloud-pending { .pulsate(); }