diff --git a/awx/ui/static/js/controllers/Authentication.js b/awx/ui/static/js/controllers/Authentication.js index 856494b9f7..afa0c7be2e 100644 --- a/awx/ui/static/js/controllers/Authentication.js +++ b/awx/ui/static/js/controllers/Authentication.js @@ -41,7 +41,9 @@ function Authenticate($cookieStore, $compile, $window, $scope, $rootScope, $loca }); // Just in case, make sure the wait widget is not active + // and scroll the window to the top Wait('stop'); + window.scrollTo(0,0); // Display the login dialog $('#login-modal').modal({ diff --git a/awx/ui/static/js/controllers/JobDetail.js b/awx/ui/static/js/controllers/JobDetail.js index 5910f382c1..7aef281039 100644 --- a/awx/ui/static/js/controllers/JobDetail.js +++ b/awx/ui/static/js/controllers/JobDetail.js @@ -10,7 +10,7 @@ 'use strict'; function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest, ProcessErrors, DigestEvents, - SelectPlay, SelectTask, Socket, GetElapsed) { + SelectPlay, SelectTask, Socket, GetElapsed, SelectHost) { ClearScope(); @@ -27,6 +27,9 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc scope.hostResults = []; scope.job_status = {}; scope.job_id = job_id; + scope.auto_scroll = false; + scope.searchTaskHostsEnabled = true; + scope.searchSummaryHostsEnabled = true; event_socket = Socket({ scope: scope, @@ -272,39 +275,42 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc $scope.HostDetailOnTotalScroll = function(mcs) { var url = GetBasePath('jobs') + job_id + '/job_events/?parent=' + scope.activeTask; url += '&host__name__gt=' + scope.hostResults[scope.hostResults.length - 1].name + '&host__isnull=false&page_size=5&order_by=host__name'; - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success(function(data) { - setTimeout(function() { - scope.$apply(function() { - data.results.forEach(function(row) { - scope.hostResults.push({ - id: row.id, - status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ), - host_id: row.host, - task_id: row.parent, - name: row.event_data.host, - created: row.created, - msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' ) + if (!scope.auto_scroll) { + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + setTimeout(function() { + scope.$apply(function() { + data.results.forEach(function(row) { + scope.hostResults.push({ + id: row.id, + status: ( (row.failed) ? 'failed': (row.changed) ? 'changed' : 'successful' ), + host_id: row.host, + task_id: row.parent, + name: row.event_data.host, + created: row.created, + msg: ( (row.event_data && row.event_data.res) ? row.event_data.res.msg : '' ) + }); + if (scope.hostResults.length > 10) { + scope.hostResults.splice(0,1); + } }); - if (scope.hostResults.length > 10) { - scope.hostResults.splice(0,1); + //$('#hosts-table-detail').mCustomScrollbar("update"); + if (data.next) { + // there are more rows. move dragger up, letting user know. + setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop - 10) + 'px'}); }, 700); } }); - $('#tasks-table-detail').mCustomScrollbar("update"); - if (data.next) { - // there are more rows. move dragger up, letting user know. - setTimeout(function() { $('.mCSB_dragger').css({ top: (mcs.draggerTop - 10) + 'px'}); }, 700); - } - }); - }, 100); - Wait('stop'); - }) - .error(function(data, status) { - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Call to ' + url + '. GET returned: ' + status }); - }); + }, 100); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + scope.auto_scroll = false; }; $scope.HostDetailOnTotalScrollBack = function(mcs) { @@ -330,10 +336,10 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc scope.hostResults.pop(); } }); - $('#tasks-table-detail').mCustomScrollbar("update"); + //$('#hosts-table-detail').mCustomScrollbar("update"); if (data.next) { // there are more rows. move dragger down, letting user know. - setTimeout(function() { $('.mCSB_dragger').css({ top: (mcs.draggerTop + 10) + 'px' }); }, 700); + setTimeout(function() { $('#hosts-table-detail .mCSB_dragger').css({ top: (mcs.draggerTop + 10) + 'px' }); }, 700); } }); }, 100); @@ -344,8 +350,170 @@ function JobDetailController ($scope, $compile, $routeParams, ClearScope, Breadc msg: 'Call to ' + url + '. GET returned: ' + status }); }); }; + + $scope.HostSummaryOnTotalScroll = function(mcs) { + var url = GetBasePath('jobs') + job_id + '/job_host_summaries/'; + url += '?host__name__gt=' + scope.hosts[scope.hosts.length - 1].name + '&page_size=5&order_by=host__name'; + if (!scope.auto_scroll) { + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function(data) { + setTimeout(function() { + scope.$apply(function() { + data.results.forEach(function(row) { + scope.hosts.push({ + id: row.host, + name: row.summary_fields.host.name, + ok: row.ok, + changed: row.changed, + unreachable: row.dark, + failed: row.failures + }); + if (scope.hosts.length > 10) { + scope.hosts.splice(0,1); + } + }); + //$('#hosts-summary-table').mCustomScrollbar("update"); + if (data.next) { + // there are more rows. move dragger up, letting user know. + setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop - 10) + 'px'}); }, 700); + } + }); + }, 100); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + } + }; + + $scope.HostSummaryOnTotalScrollBack = function(mcs) { + var url = GetBasePath('jobs') + job_id + '/job_host_summaries/'; + Wait('start'); + url += '?host__name__lt=' + scope.hosts[0].name + '&page_size=5&order_by=-host__name'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + setTimeout(function() { + scope.$apply(function() { + data.results.forEach(function(row) { + scope.hosts.unshift({ + id: row.host, + name: row.summary_fields.host.name, + ok: row.ok, + changed: row.changed, + unreachable: row.dark, + failed: row.failures + }); + if (scope.hosts.length > 10) { + scope.hosts.pop(); + } + }); + $('#hosts-summary-table').mCustomScrollbar("update"); + if (data.next) { + // there are more rows. move dragger down, letting user know. + setTimeout(function() { $('#hosts-summary-table .mCSB_dragger').css({ top: (mcs.draggerTop + 10) + 'px' }); }, 700); + } + }); + }, 100); + Wait('stop'); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + }; + + $scope.searchTaskHosts = function() { + var url; + Wait('start'); + scope.hostResults = []; + url = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent=' + scope.activeTask; + url += (scope.task_host_name) ? '&host__name__icontains=' + scope.task_host_name : ''; + url += '&host__name__isnull=false&page_size=10&order_by=host__name'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + var i; + for (i = 0; i < data.results.length; i++) { + scope.hostResults.push({ + id: data.results[i].id, + status: ( (data.results[i].failed) ? 'failed' : (data.results[i].changed) ? 'changed' : 'successful' ), + host_id: data.results[i].host, + task_id: data.results[i].parent, + name: data.results[i].summary_fields.host.name, + created: data.results[i].created, + msg: data.results[i].event_data.res.msg + }); + } + Wait('stop'); + SelectHost({ scope: scope }); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + if (scope.task_host_name) { + scope.searchTaskHostsEnabled = false; + } + else { + scope.searchTaskHostsEnabled = true; + } + }; + + $scope.taskHostNameKeyPress = function(e) { + if (e.keyCode === 13) { + $scope.searchTaskHosts(); + } + }; + + $scope.searchSummaryHosts = function() { + var url; + Wait('start'); + scope.hosts = []; + url = GetBasePath('jobs') + $routeParams.id + '/job_host_summaries/?'; + url += (scope.summary_host_name) ? 'host__name__icontains=' + scope.summary_host_name + '&': ''; + url += 'page_size=10&order_by=host__name'; + Rest.setUrl(url); + Rest.get() + .success(function(data) { + data.results.forEach(function(row) { + scope.hosts.push({ + id: row.host, + name: row.summary_fields.host.name, + ok: row.ok, + changed: row.changed, + unreachable: row.dark, + failed: row.failures + }); + }); + Wait('stop'); + $('#hosts-summary-table').mCustomScrollbar("update"); + scope.auto_scroll = true; + setTimeout( function() { $('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom"); }, 700); + }) + .error(function(data, status) { + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + url + '. GET returned: ' + status }); + }); + if (scope.summary_host_name) { + scope.searchSummaryHostsEnabled = false; + } + else { + scope.searchSummaryHostsEnabled = true; + } + }; + + $scope.summaryHostNameKeyPress = function(e) { + if (e.keyCode === 13) { + $scope.searchSummaryHosts(); + } + }; } JobDetailController.$inject = [ '$scope', '$compile', '$routeParams', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath', 'Wait', - 'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed' + 'Rest', 'ProcessErrors', 'DigestEvents', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'SelectHost' ]; diff --git a/awx/ui/static/js/helpers/JobDetail.js b/awx/ui/static/js/helpers/JobDetail.js index 9488214862..7d2161d0bb 100644 --- a/awx/ui/static/js/helpers/JobDetail.js +++ b/awx/ui/static/js/helpers/JobDetail.js @@ -474,23 +474,33 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla }); if (!host_found) { - scope.hosts.push({ - id: host_id, - name: name, - ok: (status === 'successful') ? 1 : 0, - changed: (status === 'changed') ? 1 : 0, - unreachable: (status === 'unreachable') ? 1 : 0, - failed: (status === 'failed') ? 1 : 0 - }); - scope.hosts.sort(function(a,b) { - if (a.name < b.name) { - return -1; + if (scope.hosts.length < 10 || name > scope.hosts[0].name) { + // This is a new host we want added to the list + scope.hosts.push({ + id: host_id, + name: name, + ok: (status === 'successful') ? 1 : 0, + changed: (status === 'changed') ? 1 : 0, + unreachable: (status === 'unreachable') ? 1 : 0, + failed: (status === 'failed') ? 1 : 0 + }); + scope.hosts.sort(function(a,b) { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + // Only keep 10 hosts + if (scope.hosts.length > 10) { + scope.hosts.splice(0,1); } - if (a.name > b.name) { - return 1; - } - return 0; - }); + scope.auto_scroll = true; + $('#tasks-table-detail').mCustomScrollbar("update"); + setTimeout( function() { $('#hosts-summary-table').mCustomScrollbar("scrollTo", "bottom"); }, 700); + } } UpdateTaskStatus({ @@ -697,7 +707,7 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla }); } Wait('stop'); - SelectHost(); + SelectHost({ scope: scope }); }) .error(function(data, status) { ProcessErrors(scope, data, status, null, { hdr: 'Error!', @@ -707,11 +717,12 @@ function(UpdatePlayStatus, UpdatePlayNoHostsMatched, UpdateHostStatus, UpdatePla }]) .factory('SelectHost', [ function() { - return function() { + return function(params) { + var scope = params.scope; + scope.auto_scroll = true; setTimeout(function() { - var inner_height = $('#hosts-table-detail').innerHeight(); - $('#hosts-table-detail').scrollTop(inner_height); $('#tasks-table-detail').mCustomScrollbar("update"); + setTimeout( function() { $('#hosts-table-detail').mCustomScrollbar("scrollTo", "bottom"); }, 700); }, 100); }; }]); diff --git a/awx/ui/static/less/job-details.less b/awx/ui/static/less/job-details.less index 6839d3071e..630986f606 100644 --- a/awx/ui/static/less/job-details.less +++ b/awx/ui/static/less/job-details.less @@ -194,6 +194,36 @@ } } +.header { + width: 100%; + height: 28px; + padding: bottom: 5px; + + .title { + display: inline-block; + font-size: 14px; + font-weight: 500; + } + + .search-field { + display: inline-block; + position: relative; + float: right; + input { + width: 250px; + } + a { + position: absolute; + right: 3px; + top: 3px; + color: #a9a9a9; + } + a:hover { + color: @black; + } + } +} + #task-hosts-section { position: relative; top: 0; diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index ed92c0e444..5742b2f8aa 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -735,9 +735,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job scrollInertia: 0, callbacks: { onTotalScroll: scope[attrs.onTotalScroll], - //onTotalScrollOffset: 21, onTotalScrollBack: scope[attrs.onTotalScrollBack], - //onTotalScrollBackOffset: 21 } }); }; diff --git a/awx/ui/static/partials/job_detail.html b/awx/ui/static/partials/job_detail.html index 902396429f..d01a8e0723 100644 --- a/awx/ui/static/partials/job_detail.html +++ b/awx/ui/static/partials/job_detail.html @@ -48,7 +48,7 @@