diff --git a/awx/ui/static/img/cow.png b/awx/ui/static/img/cow.png new file mode 100644 index 0000000000..bd9ac11bf6 Binary files /dev/null and b/awx/ui/static/img/cow.png differ diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index a4fa35b27d..b1e99a22c8 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -11,8 +11,8 @@ 'use strict'; function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, JobStatus, InventorySyncStatus, SCMSyncStatus, - ClearScope) -{ + ClearScope) { + ClearScope('home'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -36,7 +36,7 @@ function Home ($routeParams, $scope, $rootScope, $location, Wait, ObjectCount, J Wait('stop'); } }); -} + } Home.$inject=[ '$routeParams', '$scope', '$rootScope', '$location', 'Wait', 'ObjectCount', 'JobStatus', 'InventorySyncStatus', 'SCMSyncStatus', 'ClearScope']; \ No newline at end of file diff --git a/awx/ui/static/js/controllers/Inventories.js b/awx/ui/static/js/controllers/Inventories.js index e94647e71c..457e710341 100644 --- a/awx/ui/static/js/controllers/Inventories.js +++ b/awx/ui/static/js/controllers/Inventories.js @@ -41,29 +41,30 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res scope.inventories[i].failed_hosts = scope.inventories[i].hosts_with_active_failures + ' / ' + scope.inventories[i].total_hosts; if (scope.inventories[i].hosts_with_active_failures > 0) { scope.inventories[i].failed_hosts_tip = "Contains " + scope.inventories[i].hosts_with_active_failures + - [ (scope.inventories[i].hosts_with_active_failures == 1) ? ' host' : ' hosts' ] + ' where the latest job failed. Click to view the ' + - [ (scope.inventories[i].hosts_with_active_failures == 1) ? ' offending host.' : ' hosts with failed jobs.' ]; + [ (scope.inventories[i].hosts_with_active_failures == 1) ? ' host' : ' hosts' ] + ' with job failures. Click to view the offending ' + + [ (scope.inventories[i].hosts_with_active_failures == 1) ? ' host' : ' hosts' ] + '.'; scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts?has_active_failures=true'; scope.inventories[i].failed_hosts_class = 'true'; } else { if (scope.inventories[i].total_hosts == 0) { // no hosts - scope.inventories[i].failed_hosts_tip = "There are no hosts in this inventory. It's a sad empty shell. Click to view the hosts page and add a host."; + scope.inventories[i].failed_hosts_tip = "There are no hosts in this inventory. It's a sad empty shell. Click to view the hosts page " + + "and add a host."; scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts'; scope.inventories[i].failed_hosts_class = 'na'; } else if (scope.inventories[i].total_hosts == 1) { // on host with 0 failures - scope.inventories[i].failed_hosts_tip = "The 1 host contained in this inventory does not have a current job failure. It's happy!" + + scope.inventories[i].failed_hosts_tip = "The 1 host found in this inventory is happy! There are no job failures." + " Click to view the host."; scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts'; scope.inventories[i].failed_hosts_class = 'false'; } else { // many hosts with 0 failures - scope.inventories[i].failed_hosts_tip = "All " + scope.inventories[i].total_hosts + " hosts are happy! None of them have " + - " a recent job failure. Click to view the hosts."; + scope.inventories[i].failed_hosts_tip = "All " + scope.inventories[i].total_hosts + " hosts are happy! There are no" + + " job failures. Click to view the hosts."; scope.inventories[i].failed_hosts_link = '/#/inventories/' + scope.inventories[i].id + '/hosts'; scope.inventories[i].failed_hosts_class = 'false'; } @@ -73,31 +74,31 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res scope.inventories[i].status = scope.inventories[i].inventory_sources_with_failures + ' / ' + scope.inventories[i].total_inventory_sources; if (scope.inventories[i].inventory_sources_with_failures > 0) { scope.inventories[i].status_tip = "Contains " + scope.inventories[i].inventory_sources_with_failures + - [ (scope.inventories[i].inventory_sources_with_failures == 1) ? ' group' : ' groups' ] + ' where the latest inventory update failed. ' + + [ (scope.inventories[i].inventory_sources_with_failures == 1) ? ' group' : ' groups' ] + ' with inventory update failures. ' + 'Click to view the ' + - [ (scope.inventories[i].inventory_sources_with_failures == 1) ? ' offending group.' : ' groups with failures.' ]; + [ (scope.inventories[i].inventory_sources_with_failures == 1) ? ' offending group.' : ' groups.' ]; scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups?status=failed'; scope.inventories[i].status_class = 'failed'; } else { if (scope.inventories[i].total_inventory_sources == 0) { // no groups are reporting a source - scope.inventories[i].status_tip = "There are no groups configured for an external inventory source. Click to view groups and " + + scope.inventories[i].status_tip = "Does not have an external inventory source. Click to view groups and " + "and add an inventory source."; scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups'; scope.inventories[i].status_class = 'na'; } else if (scope.inventories[i].total_inventory_sources == 1) { // on host with 0 failures - scope.inventories[i].status_tip = "The 1 group configured with an inventory source was updated successfully. It's happy!" + + scope.inventories[i].status_tip = "The 1 group with an inventory source is happy!. No updates have failed." + " Click to view the group."; scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups?has_external_source=true'; scope.inventories[i].status_class = 'successful'; } else { // many hosts with 0 failures - scope.inventories[i].status_tip = "The " + scope.inventories[i].total_inventory_sources + " groups with an inventory source are happy! " + - " The most recent update of each group was successful. Click to view the groups."; + scope.inventories[i].status_tip = scope.inventories[i].total_inventory_sources + " groups external inventory sources are happy! " + + " No updates have failed. Click to view the groups."; scope.inventories[i].status_link = '/#/inventories/' + scope.inventories[i].id + '/groups?has_external_source=true'; scope.inventories[i].status_class = 'successful'; } diff --git a/awx/ui/static/js/forms/InventoryHosts.js b/awx/ui/static/js/forms/InventoryHosts.js index afc479486a..419e2de29b 100644 --- a/awx/ui/static/js/forms/InventoryHosts.js +++ b/awx/ui/static/js/forms/InventoryHosts.js @@ -22,7 +22,7 @@ angular.module('InventoryHostsFormDefinition', []) ngClick: "editHost(\{\{ host.id \}\}, '\{\{ host.name \}\}')" }, active_failures: { - label: 'Job Status?', + label: 'Job Status', ngHref: "\{\{ host.activeFailuresLink \}\}", awToolTip: "\{\{ host.badgeToolTip \}\}", dataPlacement: 'top', @@ -55,7 +55,7 @@ angular.module('InventoryHostsFormDefinition', []) nosort: true }, has_active_failures: { - label: 'Has failed job?', + label: 'Has failed jobs?', searchSingleValue: true, searchType: 'boolean', searchValue: 'true', diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index c9d0131837..4acacbc787 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -76,11 +76,91 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' ]; } }]) + + .factory('HostsStatusMsg', [ function() { + return function(params) { + var active_failures = params.active_failures; + var total_hosts = params.total_hosts; + var inventory_id = params.inventory_id; + var tips, link, html_class; + + // Return values for use on host status indicator + + if (active_failures > 0) { + tip = "Contains " + active_failures + + [ (active_failures == 1) ? ' host' : ' hosts' ] + ' with failed jobs. Click to view the offending ' + + [ (active_failures == 1) ? ' host' : ' hosts' ] + '.'; + link = '/#/inventories/' + inventory_id + '/hosts?has_active_failures=true'; + html_class = 'true'; + } + else { + if (total_hosts == 0) { + // no hosts + tip = "There are no hosts in this group. It's a sad empty shell. Click to view the hosts page and add a host."; + link = '/#/inventories/' + inventory_id + '/hosts'; + html_class = 'na'; + } + else if (total_hosts == 1) { + // on host with 0 failures + tip = "The 1 host in this group is happy! It does not have a job failure. " + + " Click to view the host."; + link = '/#/inventories/' + inventory_id + '/hosts'; + html_class = 'false'; + } + else { + // many hosts with 0 failures + tip = "All " + total_hosts + " hosts in this group are happy! None of them have " + + " a recent job failure. Click to view the hosts."; + links = '/#/inventories/' + inventory_id + '/hosts'; + html_class = 'false'; + } + } + + return { tooltip: tip, url: link, 'class': html_class }; + + } + }]) + + .factory('UpdateStatusMsg', [ function() { + return function(params) { + + var status = params.status; + var stat, stat_class, status_tip; + stat = status; + stat_class = stat; + + switch (status) { + case 'never updated': + stat = 'never'; + stat_class = 'never'; + status_tip = 'Inventory update has not been performed. Click the Update button to start it now.'; + break; + case 'none': + case '': + stat = 'n/a'; + stat_class = 'na'; + status_tip = 'Not configured for inventory update.'; + break; + case 'failed': + status_tip = 'Inventory update completed with errors. Click to view process output.'; + break; + case 'successful': + status_tip = 'Inventory update completed with no errors. Click to view process output.'; + break; + case 'updating': + status_tip = 'Inventory update process running now.'; + break; + } + + return { 'class': stat_class, tooltip: status_tip, status: stat } + + } + }]) .factory('GroupsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupList', 'GenerateList', - 'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'GroupsAdd', 'SelectionInit', + 'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'GroupsAdd', 'SelectionInit', 'BuildTree', function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, Prompt, SearchInit, PaginateInit, - ProcessErrors, GetBasePath, GroupsAdd, SelectionInit) { + ProcessErrors, GetBasePath, GroupsAdd, SelectionInit, BuildTree) { return function(params) { // build and present the list of groups we can add to an existing group @@ -108,7 +188,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' $('#form-modal .btn-none').removeClass('btn-none').addClass('btn-success'); $('#form-modal').modal({ backdrop: 'static', keyboard: false }); - var url = (group_id) ? GetBasePath('groups') + group_id + '/potential_children/' : + var url = (group_id) ? GetBasePath('groups') + group_id + '/children/' : GetBasePath('inventory') + inventory_id + '/groups/'; SelectionInit({ scope: scope, list: list, url: url }); @@ -167,9 +247,12 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' } }); */ + + var searchUrl = (group_id) ? GetBasePath('groups') + group_id + '/potential_children/' : + GetBasePath('inventory') + inventory_id + '/groups/'; - SearchInit({ scope: scope, set: 'groups', list: list, url: url }); - PaginateInit({ scope: scope, list: list, url: url, mode: 'lookup' }); + SearchInit({ scope: scope, set: 'groups', list: list, url: searchUrl }); + PaginateInit({ scope: scope, list: list, url: searchUrl, mode: 'lookup' }); scope.search(list.iterator); if (!scope.$$phase) { @@ -180,17 +263,24 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' scope.removeModalClosed(); } scope.removeModalClosed = scope.$on('modalClosed', function() { - /* RefreshTree({ scope: scope }); */ + BuildTree({ + scope: scope, + inventory_id: inventory_id, + emit_on_select: 'NodeSelect', + target_id: 'search-tree-container', + refresh: true, + moveable: true + }); }); } }]) - .factory('InventoryStatus', [ '$rootScope', '$routeParams', 'Rest', 'Alert', 'ProcessErrors', 'GetBasePath', 'FormatDate', 'InventorySummary', 'GenerateList', 'ClearScope', 'SearchInit', 'PaginateInit', 'Refresh', 'InventoryUpdate', 'GroupsEdit', 'ShowUpdateStatus', 'HelpDialog', - 'InventorySummaryHelp', 'BuildTree', 'ClickNode', + 'InventorySummaryHelp', 'BuildTree', 'ClickNode', 'HostsStatusMsg', 'UpdateStatusMsg', function($rootScope, $routeParams, Rest, Alert, ProcessErrors, GetBasePath, FormatDate, InventorySummary, GenerateList, ClearScope, SearchInit, - PaginateInit, Refresh, InventoryUpdate, GroupsEdit, ShowUpdateStatus, HelpDialog, InventorySummaryHelp, BuildTree, ClickNode) { + PaginateInit, Refresh, InventoryUpdate, GroupsEdit, ShowUpdateStatus, HelpDialog, InventorySummaryHelp, BuildTree, ClickNode, + HostsStatusMsg, UpdateStatusMsg) { return function(params) { //Build a summary of a given inventory @@ -202,7 +292,8 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' var scope = view.inject(InventorySummary, { mode: 'summary', id: 'tree-form', breadCrumbs: false }); var defaultUrl = GetBasePath('inventory') + scope['inventory_id'] + '/groups/'; //?group__isnull=false'; - + var msg, update_status; + if (scope.PostRefreshRemove) { scope.PostRefreshRemove(); } @@ -210,90 +301,43 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' for (var i=0; i < scope.groups.length; i++) { var last_update = (scope.groups[i].summary_fields.inventory_source.last_updated == null) ? null : FormatDate(new Date(scope.groups[i].summary_fields.inventory_source.last_updated)); - - var stat, stat_class, status_tip; - - stat = scope.groups[i].summary_fields.inventory_source.status; - stat_class = stat; - - switch (scope.groups[i].summary_fields.inventory_source.status) { - case 'never updated': - stat = 'never'; - stat_class = 'never'; - status_tip = 'Inventory update has not been performed. Click Update button to start it now.'; - break; - case 'none': - case '': - stat = 'n/a'; - stat_class = 'na'; - status_tip = 'Not configured for inventory update.'; - break; - case 'failed': - status_tip = 'Inventory update completed with errors. Click to view process output.'; - break; - case 'successful': - status_tip = 'Inventory update completed with no errors. Click to view process output.'; - break; - case 'updating': - status_tip = 'Inventory update process running now.'; - break; - } - - if (scope.groups[i].hosts_with_active_failures > 0) { - scope.groups[i].active_failures_params = "/?has_active_failures=true"; - } - else { - scope.groups[i].active_failures_params = "/?has_active_failures=false"; - } - - + // Set values for Failed Hosts column scope.groups[i].failed_hosts = scope.groups[i].hosts_with_active_failures + ' / ' + scope.groups[i].total_hosts; - if (scope.groups[i].hosts_with_active_failures > 0) { - scope.groups[i].failed_hosts_tip = "Contains " + scope.groups[i].hosts_with_active_failures + - [ (scope.groups[i].hosts_with_active_failures == 1) ? ' host' : ' hosts' ] + ' where the latest job failed. Click to view the ' + - [ (scope.groups[i].hosts_with_active_failures == 1) ? ' offending host.' : ' hosts with failed jobs.' ]; - scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts?has_active_failures=true'; - scope.groups[i].failed_hosts_class = 'true'; - } - else { - if (scope.groups[i].total_hosts == 0) { - // no hosts - scope.groups[i].failed_hosts_tip = "There are no hosts in this inventory. It's a sad empty shell. Click to view the hosts page and add a host."; - scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts'; - scope.groups[i].failed_hosts_class = 'na'; - } - else if (scope.groups[i].total_hosts == 1) { - // on host with 0 failures - scope.groups[i].failed_hosts_tip = "The 1 host contained in this inventory does not have a current job failure. It's happy!" + - " Click to view the host."; - scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts'; - scope.groups[i].failed_hosts_class = 'false'; - } - else { - // many hosts with 0 failures - scope.groups[i].failed_hosts_tip = "All " + scope.groups[i].total_hosts + " hosts are happy! None of them have " + - " a recent job failure. Click to view the hosts."; - scope.groups[i].failed_hosts_link = '/#/inventories/' + scope.groups[i].inventory + '/hosts'; - scope.groups[i].failed_hosts_class = 'false'; - } - } - scope.groups[i].status = stat; + msg = HostsStatusMsg({ + active_failures: scope.groups[i].hosts_with_active_failures, + total_hosts: scope.groups[i].total_hosts, + inventory_id: scope['inventory_id'] + }); + + update_status = UpdateStatusMsg({ status: scope.groups[i].summary_fields.inventory_source.status }); + + scope.groups[i].failed_hosts_tip = msg['tooltip']; + scope.groups[i].failed_hosts_link = msg['url']; + scope.groups[i].failed_hosts_class = msg['class']; + scope.groups[i].status = update_status['status']; scope.groups[i].source = scope.groups[i].summary_fields.inventory_source.source; scope.groups[i].last_updated = last_update; - scope.groups[i].status_badge_class = stat_class; - scope.groups[i].status_badge_tooltip = status_tip; + scope.groups[i].status_badge_class = update_status['class']; + scope.groups[i].status_badge_tooltip = update_status['tooltip']; } + + if (scope.groups.length == 0) { + // Force display for help tooltip when no groups exist + $('#inventory-summary-help').focus(); + } + }); SearchInit({ scope: scope, set: 'groups', list: list, url: defaultUrl }); PaginateInit({ scope: scope, list: list, url: defaultUrl }); - if (scope['inventorySummaryGroup']) { + if (scope['inventorySummaryGroup'] || $routeParams['name']) { scope[list.iterator + 'SearchField'] = 'name'; scope[list.iterator + 'SearchType'] = 'iexact'; - scope[list.iterator + 'SearchValue'] = scope['inventorySummaryGroup']; + scope[list.iterator + 'SearchValue'] = (scope['inventorySummaryGroup']) ? + scope['inventorySummaryGroup'] : $routeParams['name']; scope[list.iterator + 'SearchFieldLabel'] = list.fields['name'].label; } else if ($routeParams['has_external_source']) { @@ -320,14 +364,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' scope.search(list.iterator, false, true); - if (scope.removeShowHelp) { - scope.removeShowHelp(); - } - scope.removeShowHelp = scope.$on('ShowHelp', function() { - // Force display fo help tooltip when no groups exist - $('#inventory-summary-help').focus(); - }); - scope.showHelp = function() { // Display help dialog $('.btn').blur(); //remove focus from the help button and all buttons @@ -457,11 +493,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' // Respond to refresh button scope.refresh = function() { - - //scope['groupSearchSpin'] = true; - //scope['groupLoading'] = false; scope.search(list.iterator, false, true); - BuildTree({ scope: scope, inventory_id: scope['inventory_id'], @@ -519,7 +551,6 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' } }]) - .factory('GroupsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'ParseTypeChange', 'GroupsEdit', 'BuildTree', 'ClickNode', function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors, @@ -1154,8 +1185,12 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' { hdr: 'Error!', msg: 'Failed to retrieve last update: ' + last_update + '. GET status: ' + status }); }); } - } - }]); + + } + }]); + + + diff --git a/awx/ui/static/js/helpers/Hosts.js b/awx/ui/static/js/helpers/Hosts.js index 9dae2331ba..332ea82b84 100644 --- a/awx/ui/static/js/helpers/Hosts.js +++ b/awx/ui/static/js/helpers/Hosts.js @@ -50,7 +50,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H scope.removeModalClosed(); } scope.removeModalClosed = scope.$on('modalClosed', function() { - // if the modal cloased, assume something got changed and reload the host list + // if the modal closed, assume something got changed and reload the host list HostsReload(params); }); @@ -408,15 +408,15 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H }]) - .factory('HostsReload', ['$routeParams', 'SearchInit', 'PaginateInit', 'InventoryHostsForm', 'GetBasePath', 'Wait', - function($routeParams, SearchInit, PaginateInit, InventoryHostsForm, GetBasePath, Wait) { + .factory('HostsReload', ['$location', '$routeParams', 'SearchInit', 'PaginateInit', 'InventoryHostsForm', 'GetBasePath', 'Wait', + function($location, $routeParams, SearchInit, PaginateInit, InventoryHostsForm, GetBasePath, Wait) { return function(params) { // Rerfresh the Hosts view on right side of page var scope = params.scope; var group_id = scope.group_id; - var postAction = params.action; - + var postAction = params.action; + scope['hosts'] = null; scope['toggleAllFlag'] = false; scope['hostDeleteHide'] = true; @@ -496,6 +496,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H scope[InventoryHostsForm.iterator + 'SearchField'] = 'has_active_failures'; scope[InventoryHostsForm.iterator + 'SearchFieldLabel'] = InventoryHostsForm.fields['has_active_failures'].label; scope[InventoryHostsForm.iterator + 'SearchSelectValue'] = ($routeParams['has_active_failures'] == 'true') ? { value: 1 } : { value: 0 }; + } else if ($routeParams['name']) { scope[InventoryHostsForm.iterator + 'InputDisable'] = false; @@ -504,7 +505,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H scope[InventoryHostsForm.iterator + 'SearchFieldLabel'] = InventoryHostsForm.fields['name'].label; scope[InventoryHostsForm.iterator + 'SearchSelectValue'] = null; } - + scope.search(InventoryHostsForm.iterator); if (!params.scope.$$phase) { diff --git a/awx/ui/static/js/lists/InventorySummary.js b/awx/ui/static/js/lists/InventorySummary.js index 22663559ca..6e3cd7f2e1 100644 --- a/awx/ui/static/js/lists/InventorySummary.js +++ b/awx/ui/static/js/lists/InventorySummary.js @@ -119,8 +119,10 @@ angular.module('InventorySummaryDefinition', []) icon: "icon-question-sign", mode: 'all', 'class': 'btn-xs btn-info btn-help', - awToolTip: "

Need help getting started creating your inventory?

" + - "

Click here for help with this page

", + awToolTip: + //"
" + + //"

Need help getting started creating your inventory?

Click here for help.

", + "

Need help getting started creating your inventory?

Click here for help.

", iconSize: 'large', ngClick: "showHelp()", id: "inventory-summary-help" diff --git a/awx/ui/static/less/ansible-ui.less b/awx/ui/static/less/ansible-ui.less index d8931a0bce..692a23b0de 100644 --- a/awx/ui/static/less/ansible-ui.less +++ b/awx/ui/static/less/ansible-ui.less @@ -21,6 +21,11 @@ @info-border: #bce8f1; /* alert info border color */ @info-color: #3a87ad; +@tip-background: #0088CC; +/*rgb(58, 135, 173);*/ +@tip-color: #fff; + + html { background-color: @black; } @@ -96,9 +101,56 @@ body { z-index: 2000; } -.tooltip { - z-index: 1050; -} +/* TB tooltip overrides */ + .tooltip { + z-index: 1050; + opacity: 1.0; + } + +/* + .tooltip-inner { + color: @tip-color; + background-color: @tip-background; + border-radius: 6px; + padding: 5px; + } + + .tooltip.in { + opacity: 1.0; + } + + .tooltip.top .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.top-left .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.top-right .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.right .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.left .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.bottom .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.bottom-left .tooltip-arrow { + border-top-color: @tip-background; + } + + .tooltip.bottom-right .tooltip-arrow { + border-top-color: @tip-background; + } +*/ hr { border-color: #e3e3e3; @@ -973,7 +1025,13 @@ select.field-mini-height { } } + /* Allow tree node title to float above surrounding elements on hover */ + #search-tree-target { + z-index: 10; + } + .search-tree { + padding: 10px 3px 10px 10px; .title { @@ -997,10 +1055,17 @@ select.field-mini-height { } .activate { - padding: 3px; + display: block; + padding: 2px 3px 1px 3px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + height: 23px; } .activate:hover { + display: inline-block; + overflow: visible; background-color: #ddd; cursor: pointer; } @@ -1010,6 +1075,11 @@ select.field-mini-height { box-shadow: 3px 3px 3px 0 @grey; border-bottom: 1px solid @grey; border-right: 1px solid @grey; + background-color: #fff; + + .activate:hover { + background-color: #fff; + } } .expand-container, @@ -1027,25 +1097,21 @@ select.field-mini-height { .badge-container { vertical-align: none; - margin-bottom: 3px; + padding-top: 2px; padding-bottom: 2px; } - #root-expand-container { - width: 20px; - text-align: left; + #root-badge-container { + margin-right: -2px; + margin-left: 3px; } - + .expand { padding: 3px; } .title-container { - word-break: break-all; - } - - #root-title-container { - margin-left: 5px; + height: 25px; } .expand-container:hover { diff --git a/awx/ui/static/lib/ansible/TreeSelector.js b/awx/ui/static/lib/ansible/TreeSelector.js index 92ca2ddcb3..8fe33e0999 100644 --- a/awx/ui/static/lib/ansible/TreeSelector.js +++ b/awx/ui/static/lib/ansible/TreeSelector.js @@ -7,7 +7,7 @@ * */ -angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) +angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector', 'GroupsHelper']) .factory('SortNodes', [ function() { return function(data) { @@ -28,9 +28,150 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) return newData; } }]) + + // Figure out the group level tool tip + .factory('GetToolTip', [ 'FormatDate', function(FormatDate) { + return function(params) { + + var node = params.node; + + var tip = ''; + var link = ''; + var html_class = ''; + var active_failures = node.hosts_with_active_failures; + var total_hosts = node.total_hosts; + var source = node.summary_fields.inventory_source.source; + var status = node.summary_fields.inventory_source.status; - .factory('BuildTree', ['Rest', 'GetBasePath', 'ProcessErrors', '$compile', '$rootScope', 'Wait', 'SortNodes', - function(Rest, GetBasePath, ProcessErrors, $compile, $rootScope, Wait, SortNodes) { + // Return values for the status indicator + var status_date = node.summary_fields.inventory_source.last_updated + var last_update = ( status_date == "" || status_date == null ) ? null : FormatDate(new Date(status_date)); + + switch (status) { + case 'never updated': + html_class = 'na'; + tip = '

Inventory update has not been performed.

'; + link = ''; + break; + case 'failed': + tip = '

Inventory update failed! Click to view process output.

'; + link = '/#/inventories/' + node.inventory + '/groups?name=' + node.name; + html_class = true; + break; + case 'successful': + tip = '

Inventory update completed on ' + last_update + '.

'; + html_class = false; + link = ''; + break; + case 'updating': + tip = '

Inventory update process running now. Click to view status.

'; + link = '/#/inventories/' + node.inventory + '/groups?name=' + node.name; + html_class = false; + break; + } + + if (status !== 'failed' && status !== 'updating') { + // update status will not override job status + if (active_failures > 0) { + tip += "

Contains " + active_failures + + [ (active_failures == 1) ? ' host' : ' hosts' ] + ' with failed jobs. Click to view the offending ' + + [ (active_failures == 1) ? ' host' : ' hosts' ] + '.

'; + link = '/#/inventories/' + node.inventory + '/hosts?has_active_failures=true'; + html_class = 'true'; + } + else { + if (total_hosts == 0) { + // no hosts + tip += "

There are no hosts in this group. It's a sad empty shell.

"; + html_class = (html_class == '') ? 'na' : html_class; + } + else if (total_hosts == 1) { + // on host with 0 failures + tip += "

The 1 host in this group is happy! It does not have a job failure.

"; + html_class = 'false'; + } + else { + // many hosts with 0 failures + tip += "

All " + total_hosts + " hosts in this group are happy! None of them have " + + " job failures.

"; + html_class = 'false'; + } + } + } + + return { tooltip: tip, url: link, 'class': html_class }; + + } + }]) + + .factory('GetInventoryToolTip', [ 'FormatDate', function(FormatDate) { + return function(params) { + + var node = params.node; + + var tip = ''; + var link = ''; + var html_class = ''; + var active_failures = node.hosts_with_active_failures; + var total_hosts = node.total_hosts; + var group_failures = node.groups_with_active_failures; + var total_groups = node.total_groups; + var inventory_sources = node.total_inventory_sources; + + if (group_failures > 0) { + tip += "Has " + group_failures + + [ (group_failures == 1) ? ' group' : ' groups' ] + ' with failed inventory updates. ' + + 'Click to view the offending ' + + [ (group_failures == 1) ? ' group.' : ' groups.' ]; + link = '/#/inventories/' + node.id + '/groups?status=failed'; + html_class = 'true'; + } + else if (inventory_sources == 1) { + // on host with 0 failures + tip += "

1 group with an inventory source is happy! No updates have failed.

"; + link = ''; + html_class = 'false'; + } + else if (inventory_sources > 0) { + tip += "

" + inventory_sources + " groups with an inventory source are happy! No updates have failed.

"; + link = 0; + html_class = 'false'; + } + + if (html_class !== 'true') { + // Add job status + if (active_failures > 0) { + tip += "

Contains " + scope.inventories[i].hosts_with_active_failures + + [ (active_failures == 1) ? ' host' : ' hosts' ] + ' with job failures. Click to view the offending ' + + [ (active_failures == 1) ? ' host' : ' hosts' ] + '.

'; + link = '/#/inventories/' + node.id + '/hosts?has_active_failures=true'; + html_class = 'true'; + } + else if (total_hosts == 0) { + tip += "

There are no hosts in this inventory. It's a sad empty shell.

"; + link = ""; + html_class = (html_class == '') ? 'na' : html_class; + } + else if (total_hosts == 1) { + tip += "

The 1 host found in this inventory is happy! There are no job failures.

"; + link = ""; + html_class = "false"; + } + else if (total_hosts > 0) { + tip += "

All " + total_hosts + " hosts are happy! There are no job failures."; + link = ""; + html_class = "false"; + } + } + + return { tooltip: tip, url: link, 'class': html_class }; + + } + }]) + + .factory('BuildTree', ['Rest', 'GetBasePath', 'ProcessErrors', '$compile', '$rootScope', 'Wait', 'SortNodes', 'GetToolTip', + 'GetInventoryToolTip', + function(Rest, GetBasePath, ProcessErrors, $compile, $rootScope, Wait, SortNodes, GetToolTip, GetInventoryToolTip) { return function(params) { var scope = params.scope; var inventory_id = params.inventory_id; @@ -65,7 +206,7 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) var elm = angular.element(e.target); // var parent = angular.element(e.target.parentNode.parentNode); //

  • $('.search-tree .active').removeClass('active'); - elm.addClass('active'); + elm.parent().addClass('active'); // add active class to
    refresh(parent); } @@ -108,10 +249,14 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) parent.attr('data-state','closed'); icon.removeClass('icon-caret-down').addClass('icon-caret-right'); var childlists = parent.find('ul'); + var sublist, subicon; if (childlists && childlists.length > 0) { // has childen for (var i=0; i < childlists.length; i++) { - angular.element(childlists[i]).addClass('hidden'); + sublist = angular.element(childlists[i]); + sublist.addClass('hidden'); + subicon = list.find('li')[0].children()[0]; + subicon.removeClass('icon-caret-down').addClass('icon-caret-right'); } } /* When the active node's parent is closed, activate the parent */ @@ -247,10 +392,41 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) scope.searchTreeReadyRemove(); } scope.searchTreeReadyRemove = scope.$on('searchTreeReady', function(e, html) { + var container = angular.element(document.getElementById(target_id)); container.empty(); var compiled = $compile(html)(scope); container.append(compiled); + + function setTitleWidth(elm) { + // Fix for overflowing title text + var container = $('#search-tree-target'); + var container_offset = container.offset(); + var parent = elm.parent(); //
  • + var parent_offset = parent.offset(); + var expander = parent.find('.expand-container').first(); + var badge = parent.find('.badge-container').first(); + var width = container.width() - parent_offset.left + container_offset.left - + badge.width() - expander.width() - 10; + elm.css('width', width + 'px'); + } + + // Fix overflowing title text now + $('#' + target_id).find('.title-container').each(function(idx) { + setTitleWidth($(this)); + }); + + // Fix overflowing title text on screen resize + var timeout; + $(window).resize(function() { + clearTimeout(timeout); //remove prior timer so we don't resize a million times + timeout = setTimeout(function() { + $('#' + target_id).find('.title-container').each(function(idx) { + setTitleWidth($(this)); + }); + }, 500); + }); + var links = container.find('a'); for (var i=0; i < links.length; i++) { var link = angular.element(links[i]); @@ -265,25 +441,17 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) } if (refresh_tree && group_id !== undefined) { - // pick a specific node on the tree + // pick a node by group_id $('li[data-group-id="' + group_id + '"] .activate').first().click(); } else if (refresh_tree && id !== undefined) { + // pick a node by id $('#' + id + ' .activate').first().click(); } else if (!refresh_tree) { // default to the root node $('#inventory-root-node .activate').first().click(); } - - // Attempt to stop the title from dropping to the next - // line - $(container).find('.title-container').each(function(idx) { - var parent = $(this).parent(); - if ($(this).width() >= parent.width()) { - $(this).css('width','80%'); - } - }); // Make the tree drag-n-droppable if (moveable) { @@ -294,7 +462,9 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) helper: 'clone', start: function (e, ui) { var txt = '[ ' + ui.helper.text() + ' ]'; - ui.helper.css({ 'font-weight': 'normal', 'color': '#171717', 'background-color': '#f5f5f5' }).text(txt); + ui.helper.css({ 'display': 'inline-block', 'font-weight': 'normal', 'color': '#171717', + 'background-color': '#f5f5f5', 'overflow': 'visible', 'white-space': 'normal', + 'z-index': 5000 }).text(txt); } }) .droppable({ @@ -349,6 +519,8 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) function buildHTML(tree_data) { var sorted = SortNodes(tree_data); + var toolTip; + html += (sorted.length > 0) ? "