diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index bfa9589fb3..894d71209f 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -10,9 +10,9 @@ 'use strict'; -function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, InventoryList, GenerateList, +function InventoriesList($scope, $rootScope, $location, $log, $routeParams, $compile, $filter, Rest, Alert, InventoryList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath, Wait, Stream, - EditInventoryProperties, Find) { + EditInventoryProperties, Find, Empty, LogViewer) { //ClearScope(); @@ -22,7 +22,7 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest paths = $location.path().replace(/^\//, '').split('/'), mode = (paths[0] === 'inventories') ? 'edit' : 'select'; - view.inject(InventoryList, { mode: mode, $scope: $scope }); + view.inject(InventoryList, { mode: mode, scope: $scope }); $rootScope.flashMessage = null; SearchInit({ @@ -82,30 +82,6 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest LoadBreadCrumbs(); - if ($scope.removeBuildPopover) { - $scope.removeBuildPopover(); - } - $scope.removeBuildPopover = $scope.$on('BuildPopover', function(e, data) { - var inventory, html = ''; - if (data.count) { - inventory = Find({ list: $scope.inventories, key: 'id', val: data.results[0].inventory }); - html += "" + - "" + - "" + - "" + - ""; - data.results.forEach(function(row) { - html += "" + - "" + - "" + - ""; - }); - html += "
GroupSourceLast RunStatus
" + row.summary_fields.group.name + "" + row.source + "" + row.last_update + "
\n"; - html += "
esc or click to close
\n"; - inventory.syncPopOver = "bob was here!"; //html; - } - }); - if ($scope.removePostRefresh) { $scope.removePostRefresh(); } @@ -141,14 +117,6 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest $scope.inventories[idx].hostsStatus = 'successful'; $scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.'; } - - if (inventory.has_inventory_sources) { - Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); - Rest.get() - .success( function(data) { - $scope.$emit('BuildPopover', data); - }); - } }); }); @@ -160,6 +128,92 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest $scope.search(list.iterator); }); + + if ($scope.removeGroupSummaryReady) { + $scope.removeGroupSummaryReady(); + } + $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) { + var j, elem, html, title, row; + + function ellipsis(a) { + if (a.length > 20) { + return a.substr(0,20) + '...'; + } + return a; + } + + if (data.count) { + html = "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + html += "\n"; + for (j=0; j < data.results.length; j++) { + row = data.results[j]; + html += ""; + html += ""; + html += ""; + html += ""; + html += "\n"; + } + html += "\n"; + html += "
StatusGroupLast Sync
" + ellipsis(row.summary_fields.group.name) + "" + $filter('date')(row.last_updated,'MM/dd HH:mm:ss') + "
\n"; + html += "
esc or click to close
\n"; + title = "Sync Status"; + elem = $(event.target).parent(); + try { + elem.tooltip('hide'); + elem.popover('destroy'); + } + catch(err) { + //ignore + } + elem.attr({ "aw-pop-over": html, "data-title": title, "data-placement": "right" }); + Wait('stop'); + $compile(elem)($scope); + elem.on('shown.bs.popover', function() { + $('.popover').each(function() { + $compile($(this))($scope); //make nested directives work! + }); + $('.popover-content, .popover-title').click(function() { + elem.popover('hide'); + }); + }); + elem.popover('show'); + } + }); + + $scope.showGroupSummary = function(event, id) { + var inventory; + if (!Empty(id)) { + inventory = Find({ list: $scope.inventories, key: 'id', val: id }); + if (inventory.syncStatus !== 'na') { + Wait('start'); + Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5'); + Rest.get() + .success(function(data) { + $scope.$emit('GroupSummaryReady', event, inventory, data); + }) + .error(function(data, status) { + ProcessErrors( $scope, data, status, null, { hdr: 'Error!', + msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status + }); + }); + } + } + }; + + $scope.viewJob = function(url) { + LogViewer({ + scope: $scope, + url: url + }); + }; + $scope.showActivity = function () { Stream({ scope: $scope }); }; @@ -190,7 +244,6 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest $scope.search(list.iterator); }) .error(function (data, status) { - Wait('stop'); ProcessErrors( $scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status }); @@ -199,7 +252,7 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest Prompt({ hdr: 'Delete', - body: '
Are you sure you want to delete ' + name + '?
', + body: '
Delete inventory ' + name + '?
', action: action }); }; @@ -223,9 +276,9 @@ function InventoriesList($scope, $rootScope, $location, $log, $routeParams, Rest }; } -InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'InventoryList', 'GenerateList', +InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$routeParams', '$compile', '$filter', 'Rest', 'Alert', 'InventoryList', 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', - 'GetBasePath', 'Wait', 'Stream', 'EditInventoryProperties', 'Find' + 'GetBasePath', 'Wait', 'Stream', 'EditInventoryProperties', 'Find', 'Empty', 'LogViewer' ]; @@ -340,7 +393,7 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, GenerateList 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) { + HelpDialog, InventoryGroupsHelp, Store, ViewJob) { ClearScope(); @@ -665,6 +718,10 @@ function InventoriesEdit($scope, $location, $routeParams, $compile, GenerateList HelpDialog(opts); }; + $scope.viewJob = function(id) { + ViewJob({ scope: $scope, id: id }); + }; + //Load tree data for the first time BuildTree({ scope: $scope, @@ -678,5 +735,6 @@ InventoriesEdit.$inject = ['$scope', '$location', '$routeParams', '$compile', 'G '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' + 'Stream', 'GetBasePath', 'ShowJobSummary', 'ApplyEllipsis', 'WatchInventoryWindowResize', 'HelpDialog', 'InventoryGroupsHelp', 'Store', + 'ViewJob' ]; diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index f8da1e772f..1400764b25 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -414,7 +414,13 @@ ToggleSchedule, DeleteSchedule, GetBasePath, SchedulesListInit) { schedule_scope.search(list.iterator); }); - schedule_scope.toggleSchedule = function(id) { + schedule_scope.toggleSchedule = function(event, id) { + try { + $(event.target).tooltip('hide'); + } + catch(e) { + // ignore + } ToggleSchedule({ scope: schedule_scope, id: id, diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js index db21033194..526d6c0a5a 100644 --- a/awx/ui/static/js/helpers/Hosts.js +++ b/awx/ui/static/js/helpers/Hosts.js @@ -14,7 +14,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'HostListDefinition', 'SearchHelper', 'PaginationHelpers', 'ListGenerator', 'AuthService', 'HostsHelper', 'InventoryHelper', 'RelatedSearchHelper', 'InventoryFormDefinition', 'SelectionHelper', - 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog' + 'HostGroupsFormDefinition', 'VariablesHelper', 'ModalDialog', 'LogViewerHelper' ]) .factory('SetEnabledMsg', [ function() { @@ -68,17 +68,24 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H } return a; } - + + function noRecentJobs() { + title = 'No job data'; + html = "

No recent job data available for this host.

\n" + + "
esc or click to close
\n"; + } + function setMsg(host) { var j, job, jobs; + if (host.has_active_failures === true || (host.has_active_failures === false && host.last_job !== null)) { if (host.has_active_failures === true) { host.badgeToolTip = 'Most recent job failed. Click to view jobs.'; - host.active_failures = 'failed'; + host.active_failures = 'error'; } else { host.badgeToolTip = "Most recent job successful. Click to view jobs."; - host.active_failures = 'success'; + host.active_failures = 'successful'; } if (host.summary_fields.recent_jobs.length > 0) { // build html table of job status info @@ -101,7 +108,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H for (j=0; j < jobs.length; j++) { job = jobs[j]; html += "\n"; - html += "" + job.id + "\n"; + html += "" + job.id + "\n"; html += "esc or click to close\n"; } else { - title = 'No job data'; - html = '

No recent job data available for this host.

'; - html += "
esc or click to close
\n"; + noRecentJobs(); } } else if (host.has_active_failures === false && host.last_job === null) { - host.has_active_failures = 'none'; host.badgeToolTip = "No job data available."; - host.active_failures = 'n/a'; + host.active_failures = 'none'; + noRecentJobs(); } host.job_status_html = html; host.job_status_title = title; @@ -145,7 +150,18 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }; }]) - + +.factory('ViewJob', ['LogViewer', 'GetBasePath', function(LogViewer, GetBasePath) { + return function(params) { + var scope = params.scope, + id = params.id; + LogViewer({ + scope: scope, + url: GetBasePath('jobs') + id + '/' + }); + }; +}]) + .factory('HostsReload', [ '$routeParams', 'Empty', 'InventoryHosts', 'GetBasePath', 'SearchInit', 'PaginateInit', 'Wait', 'SetHostStatus', 'SetStatus', 'ApplyEllipsis', function($routeParams, Empty, InventoryHosts, GetBasePath, SearchInit, PaginateInit, Wait, SetHostStatus, SetStatus, @@ -252,9 +268,8 @@ function(GetBasePath, Rest, Wait, ProcessErrors, Alert, Find, SetEnabledMsg) { }); } else { - Alert('Action Not Allowed', 'This host is part of a cloud inventory. It can only be disabled in the cloud.' + - ' After disabling it, run an inventory sync to see the new status reflected here.', - 'alert-info'); + Alert('Action Not Allowed', 'This host is managed by an external cloud source. Disable it at the external source, ' + + 'and then run an inventory sync to update Tower with the new status.', 'alert-info'); } }; }]) diff --git a/awx/ui/static/js/helpers/LogViewer.js b/awx/ui/static/js/helpers/LogViewer.js index 1c3b2899f2..f7c324f5cc 100644 --- a/awx/ui/static/js/helpers/LogViewer.js +++ b/awx/ui/static/js/helpers/LogViewer.js @@ -33,6 +33,25 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', scope.removeJobReady = scope.$on('JobReady', function(e, data) { var key, resizeText, elem; + $('#status-form-container').empty(); + $('#options-form-container').empty(); + $('#stdout-form-container').empty(); + $('#traceback-form-container').empty(); + $('#variables-container').empty(); + $('#source-container').empty(); + $('#logview-tabs li:eq(1)').hide(); + $('#logview-tabs li:eq(2)').hide(); + $('#logview-tabs li:eq(4)').hide(); + $('#logview-tabs li:eq(5)').hide(); + + // Make sure subsequenct scope references don't bubble up to the parent + for (key in LogViewerStatusForm.fields) { + scope[key] = ''; + } + for (key in LogViewerOptionsForm.fields) { + scope[key] = ''; + } + for (key in data) { scope[key] = data[key]; } @@ -47,10 +66,7 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', val: data.result_stdout }); } - else { - $('#logview-tabs li:eq(1)').hide(); - } - + if (data.result_traceback) { $('#logview-tabs li:eq(2)').show(); AddPreFormattedText({ @@ -58,10 +74,7 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', val: data.result_traceback }); } - else { - $('#logview-tabs li:eq(2)').hide(); - } - + /*if (data.job_env) { EnvTable({ id: 'env-form-container', @@ -77,10 +90,7 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', val: ParseVariableString(data.extra_vars) }); } - else { - $('#logview-tabs li:eq(4)').hide(); - } - + if (data.source_vars) { $('#logview-tabs li:eq(5)').show(); AddTextarea({ @@ -89,10 +99,7 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', val: ParseVariableString(data.source_vars) }); } - else { - $('#logview-tabs li:eq(5)').hide(); - } - + if (!Empty(scope.source)) { if (scope.removeChoicesReady) { scope.removeChoicesReady(); @@ -179,6 +186,7 @@ angular.module('LogViewerHelper', ['ModalDialog', 'Utilities', 'FormGenerator', onResizeStop: resizeText, onOpen: function() { $('#logview-tabs a:first').tab('show'); + $('#dialog-ok-button').focus(); resizeText(); } }); diff --git a/awx/ui/static/js/helpers/Schedules.js b/awx/ui/static/js/helpers/Schedules.js index 3d53c4c1b2..29859e6213 100644 --- a/awx/ui/static/js/helpers/Schedules.js +++ b/awx/ui/static/js/helpers/Schedules.js @@ -342,12 +342,12 @@ angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelpe Rest.setUrl(url); Rest.get() .success(function(data) { - scope.$emit('ScheduleFound', data); - }) - .error(function(data,status){ - ProcessErrors(scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); - }); + scope.$emit('ScheduleFound', data); + }) + .error(function(data,status){ + ProcessErrors(scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve schedule ' + id + ' GET returned: ' + status }); + }); }; }]) @@ -390,7 +390,12 @@ angular.module('SchedulesHelper', [ 'Utilities', 'RestServices', 'SchedulesHelpe scope.$emit(callback, id); }) .error(function (data, status) { - $('#prompt-modal').modal('hide'); + try { + $('#prompt-modal').modal('hide'); + } + catch(e) { + // ignore + } ProcessErrors(scope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned: ' + status }); }); diff --git a/awx/ui/static/js/lists/Inventories.js b/awx/ui/static/js/lists/Inventories.js index b3a50725d9..202d0253ba 100644 --- a/awx/ui/static/js/lists/Inventories.js +++ b/awx/ui/static/js/lists/Inventories.js @@ -32,14 +32,12 @@ angular.module('InventoriesListDefinition', []) icon: "{{ 'icon-cloud-' + inventory.syncStatus }}", awToolTip: "{{ inventory.syncTip }}", awTipPlacement: "top", - awPopOver: "{{ inventory.syncPopOver }}", - dataPlacement: "right" + ngClick: "showGroupSummary($event, inventory.id)" },{ icon: "{{ 'icon-job-' + inventory.hostsStatus }}", awToolTip: "{{ inventory.hostsTip }}", awTipPlacement: "top", - awPopOver: "{{ inventory.hostsPopOver }}", - dataPlacement: "right" + ngClick: "showHostSummary($event, inventory.id)" }] }, name: { @@ -106,7 +104,7 @@ angular.module('InventoriesListDefinition', []) }, "delete": { label: 'Delete', - ngClick: "deleteInventory(inventory.id, inventory.names)", + ngClick: "deleteInventory(inventory.id, inventory.name)", awToolTip: 'Delete inventory', dataPlacement: 'top' } diff --git a/awx/ui/static/js/lists/InventoryHosts.js b/awx/ui/static/js/lists/InventoryHosts.js index 2b4ef82413..7c3733313a 100644 --- a/awx/ui/static/js/lists/InventoryHosts.js +++ b/awx/ui/static/js/lists/InventoryHosts.js @@ -63,7 +63,7 @@ angular.module('InventoryHostsDefinition', []) awToolTip: "{{ host.badgeToolTip }}", awTipPlacement: 'top', dataPlacement: 'left', - iconClass: "{{ 'fa icon-failures-' + host.has_active_failures }}", + iconClass: "{{ 'fa icon-job-' + host.active_failures }}", id: 'active-failutes-action' }, edit: { diff --git a/awx/ui/static/lib/ansible/Modal.js b/awx/ui/static/lib/ansible/Modal.js index 6977905242..61d50fb1c7 100644 --- a/awx/ui/static/lib/ansible/Modal.js +++ b/awx/ui/static/lib/ansible/Modal.js @@ -137,6 +137,14 @@ angular.module('ModalDialog', ['Utilities', 'ParseHelper']) } }, open: function () { + $('.tooltip').each(function () { + // Remove any lingering tooltip
elements + $(this).remove(); + }); + $('.popover').each(function () { + // remove lingering popover
elements + $(this).remove(); + }); if (onOpen) { onOpen(); } diff --git a/awx/ui/static/partials/inventories.html b/awx/ui/static/partials/inventories.html index b73ab30545..833ea5bcce 100644 --- a/awx/ui/static/partials/inventories.html +++ b/awx/ui/static/partials/inventories.html @@ -1,3 +1,4 @@
+
\ No newline at end of file