From 5ed32c706232ba8c3792535528af97a2dfde426a Mon Sep 17 00:00:00 2001 From: chouseknecht Date: Tue, 29 Oct 2013 07:01:45 +0000 Subject: [PATCH] Added support for long node names on tree. Fixed tree styling issues. Fixed node copy. Fixed tree collapse/expand bug. Added dynamic tooltips to the status icon that account for inventory updates and jobs, giving precedence to inventory updates. --- awx/ui/static/img/cow.png | Bin 0 -> 54310 bytes awx/ui/static/js/controllers/Home.js | 6 +- awx/ui/static/js/controllers/Inventories.js | 25 +- awx/ui/static/js/forms/InventoryHosts.js | 4 +- awx/ui/static/js/helpers/Groups.js | 227 +++++++++++-------- awx/ui/static/js/helpers/Hosts.js | 13 +- awx/ui/static/js/lists/InventorySummary.js | 6 +- awx/ui/static/less/ansible-ui.less | 94 ++++++-- awx/ui/static/lib/ansible/TreeSelector.js | 238 ++++++++++++++++++-- awx/ui/static/lib/ansible/Utilities.js | 17 +- awx/ui/static/lib/ansible/form-generator.js | 4 +- awx/ui/static/lib/ansible/list-generator.js | 1 + 12 files changed, 474 insertions(+), 161 deletions(-) create mode 100644 awx/ui/static/img/cow.png diff --git a/awx/ui/static/img/cow.png b/awx/ui/static/img/cow.png new file mode 100644 index 0000000000000000000000000000000000000000..bd9ac11bf6a9b6c3b5aa8fd7568dcb3156828422 GIT binary patch literal 54310 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 zⅈEW13NdL0qO%RdM8{jpM4o$Q_aC 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 be$W2{EnwO<3I9kH00000NkvXXu0mjfU~BQ? literal 0 HcmV?d00001 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) ? "
      \n" : ""; for(var i=0; i < sorted.length; i++) { html += "
    • " + - ""; + html += ""; + + toolTip = GetToolTip({ node: sorted[i] }); + + html += "
      "; + html += ""; + html += ""; + html += "
      "; + + html += ""; + idx++; if (sorted[i].children.length > 0) { buildHTML(sorted[i].children); @@ -430,6 +612,9 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) Rest.setUrl (GetBasePath('inventory') + inventory_id + '/'); Rest.get() .success( function(data, status, headers, config) { + + var tip = GetInventoryToolTip({ node: data }); + html += "
      Group Selector:
      \n" + "
      \n" + "
        \n" + @@ -441,9 +626,16 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector']) "data-inventory=\"" + data.id + "\"" + ">" + "
        " + - "
        " + - ""; + "
        \n"; + + html += ""; + html += "
        \n"; + + html += ""; scope.$emit('buildAllGroups', data.name, data.related.tree, data.related.groups); diff --git a/awx/ui/static/lib/ansible/Utilities.js b/awx/ui/static/lib/ansible/Utilities.js index ea37f81afd..667ed517c7 100644 --- a/awx/ui/static/lib/ansible/Utilities.js +++ b/awx/ui/static/lib/ansible/Utilities.js @@ -7,13 +7,26 @@ */ angular.module('Utilities',['RestServices', 'Utilities']) - .factory('ClearScope', function() { + .factory('ClearScope', [ function() { return function(id) { var element = document.getElementById(id); var scope = angular.element(element).scope(); scope.$destroy(); + + $('.tooltip').each( function(index) { + // Remove any lingering tooltip and popover
        elements + $(this).remove(); + }); + + $('.popover').each(function(index) { + // remove lingering popover
        . Seems to be a bug in TB3 RC1 + $(this).remove(); + }); + + $(window).unbind('resize'); + } - }) + }]) .factory('ToggleClass', function() { return function(selector, cssClass) { diff --git a/awx/ui/static/lib/ansible/form-generator.js b/awx/ui/static/lib/ansible/form-generator.js index 0cc946c556..1fc37b1d25 100644 --- a/awx/ui/static/lib/ansible/form-generator.js +++ b/awx/ui/static/lib/ansible/form-generator.js @@ -97,6 +97,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies']) // remove lingering popover
        . Seems to be a bug in TB3 RC1 $(this).remove(); }); + + $(window).unbind('resize'); // Prepend an asterisk to required field label $('.form-control[required]').each(function() { @@ -1197,7 +1199,7 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies']) navigation.groups.active = true; html += this.breadCrumbs(options, navigation); html += "
        \n"; - html += "
        \n" + + html += "
        \n" + "
        \n" + "
        \n" + "
        \n" + diff --git a/awx/ui/static/lib/ansible/list-generator.js b/awx/ui/static/lib/ansible/list-generator.js index a27902d933..83603e403c 100644 --- a/awx/ui/static/lib/ansible/list-generator.js +++ b/awx/ui/static/lib/ansible/list-generator.js @@ -72,6 +72,7 @@ angular.module('ListGenerator', ['GeneratorHelpers']) // remove lingering popover
        . Seems to be a bug in TB3 RC1 $(this).remove(); }); + $(window).unbind('resize'); try { $('#help-modal').empty().dialog('destroy');