1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 18:21:12 +03:00

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.

This commit is contained in:
chouseknecht 2013-10-29 07:01:45 +00:00
parent 70d8ab9af6
commit 5ed32c7062
12 changed files with 474 additions and 161 deletions

BIN
awx/ui/static/img/cow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -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'];

View File

@ -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';
}

View File

@ -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',

View File

@ -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 });
});
}
}
}]);
}
}]);

View File

@ -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) {

View File

@ -119,8 +119,10 @@ angular.module('InventorySummaryDefinition', [])
icon: "icon-question-sign",
mode: 'all',
'class': 'btn-xs btn-info btn-help',
awToolTip: "<div style=\"padding-top:10px; text-align: left;\"><p>Need help getting started creating your inventory?</p>" +
"<p>Click here for help with this page</p></div>",
awToolTip:
//"<div style=\"text-align:left;\"><img src=\"/static/img/cow.png\" style=\"width:50px; height:56px; float:left; padding-right:5px;\">" +
//"<p>Need help getting started creating your inventory?</p><p>Click here for help.</p></div>",
"<div style=\"text-align:left;\"><p>Need help getting started creating your inventory?</p><p>Click here for help.</p></div>",
iconSize: 'large',
ngClick: "showHelp()",
id: "inventory-summary-help"

View File

@ -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 {

View File

@ -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 = '<p>Inventory update has not been performed.</p>';
link = '';
break;
case 'failed':
tip = '<p>Inventory update failed! Click to view process output.</p>';
link = '/#/inventories/' + node.inventory + '/groups?name=' + node.name;
html_class = true;
break;
case 'successful':
tip = '<p>Inventory update completed on ' + last_update + '.</p>';
html_class = false;
link = '';
break;
case 'updating':
tip = '<p>Inventory update process running now. Click to view status.</p>';
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 += "<p>Contains " + active_failures +
[ (active_failures == 1) ? ' host' : ' hosts' ] + ' with failed jobs. Click to view the offending ' +
[ (active_failures == 1) ? ' host' : ' hosts' ] + '.</p>';
link = '/#/inventories/' + node.inventory + '/hosts?has_active_failures=true';
html_class = 'true';
}
else {
if (total_hosts == 0) {
// no hosts
tip += "<p>There are no hosts in this group. It's a sad empty shell.</p>";
html_class = (html_class == '') ? 'na' : html_class;
}
else if (total_hosts == 1) {
// on host with 0 failures
tip += "<p>The 1 host in this group is happy! It does not have a job failure.</p>";
html_class = 'false';
}
else {
// many hosts with 0 failures
tip += "<p>All " + total_hosts + " hosts in this group are happy! None of them have " +
" job failures.</p>";
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 += "<p>1 group with an inventory source is happy! No updates have failed.</p>";
link = '';
html_class = 'false';
}
else if (inventory_sources > 0) {
tip += "<p>" + inventory_sources + " groups with an inventory source are happy! No updates have failed.</p>";
link = 0;
html_class = 'false';
}
if (html_class !== 'true') {
// Add job status
if (active_failures > 0) {
tip += "<p>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' ] + '.</p>';
link = '/#/inventories/' + node.id + '/hosts?has_active_failures=true';
html_class = 'true';
}
else if (total_hosts == 0) {
tip += "<p>There are no hosts in this inventory. It's a sad empty shell.</p>";
link = "";
html_class = (html_class == '') ? 'na' : html_class;
}
else if (total_hosts == 1) {
tip += "<p>The 1 host found in this inventory is happy! There are no job failures.</p>";
link = "";
html_class = "false";
}
else if (total_hosts > 0) {
tip += "<p>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); //<a>
var parent = angular.element(e.target.parentNode.parentNode); //<li>
$('.search-tree .active').removeClass('active');
elm.addClass('active');
elm.parent().addClass('active'); // add active class to <div>
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(); // <li>
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) ? "<ul>\n" : "";
for(var i=0; i < sorted.length; i++) {
html += "<li id=\"search-node-0" + idx + "\" data-state=\"opened\" data-hosts=\"" + sorted[i].related.hosts + "\" " +
@ -365,10 +537,20 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector'])
else {
html += " ";
}
html += "</div> " +
"<div class=\"badge-container\"><i class=\"field-badge icon-failures-" + sorted[i].has_active_failures + "\" " +
"aw-tool-tip=\"" + toolTip + "\" data-placement=\"top\"></i></div> " +
"<div class=\"title-container\"><a class=\"activate\">" + sorted[i].name + "</a></div>";
html += "</div>";
toolTip = GetToolTip({ node: sorted[i] });
html += "<div class=\"badge-container\">";
html += "<a aw-tool-tip=\"" + toolTip.tooltip + "\" data-placement=\"top\"";
html += (toolTip.url !== '') ? " href=\"" + toolTip.url + "\"": "";
html += ">";
html += "<i class=\"field-badge icon-failures-" + toolTip['class'] + "\" ></i>";
html += "</a>";
html += "</div> ";
html += "<div class=\"title-container\"><a class=\"activate\">" + sorted[i].name + "</a></div>";
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 += "<div class=\"title\">Group Selector:</div>\n" +
"<div id=\"selector-tree\">\n" +
"<ul id=\"inventory-tree\" class=\"tree-root\">\n" +
@ -441,9 +626,16 @@ angular.module('TreeSelector', ['Utilities', 'RestServices', 'TreeSelector'])
"data-inventory=\"" + data.id + "\"" +
">" +
"<div class=\"expand-container\" id=\"root-expand-container\"><i class=\"icon-sitemap\"></i></div>" +
"<div class=\"badge-container\"><i class=\"field-badge icon-failures-" + data.has_active_failures + "\" " +
"aw-tool-tip=\"" + toolTip + "\" data-placement=\"top\"></i></div>" +
"<div class=\"title-container\" id=\"root-title-container\"><a class=\"activate\">" + data.name + "</a></div>";
"<div class=\"badge-container\" id=\"root-badge-container\">\n";
html += "<a aw-tool-tip=\"" + tip['tooltip'] + "\" data-placement=\"top\"";
html += (tip.link) ? " href=\"" + tip['link'] + "\"" : "";
html += ">";
html += "<i class=\"field-badge icon-failures-" + tip['class'] + "\"></i></a>";
html += "</div>\n";
html += "<div class=\"title-container\" id=\"root-title-container\">" +
"<a class=\"activate\">" + data.name + "</a></div>";
scope.$emit('buildAllGroups', data.name, data.related.tree, data.related.groups);

View File

@ -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 <div> elements
$(this).remove();
});
$('.popover').each(function(index) {
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$(window).unbind('resize');
}
})
}])
.factory('ToggleClass', function() {
return function(selector, cssClass) {

View File

@ -97,6 +97,8 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
// remove lingering popover <div>. 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 += "<div class=\"row\">\n";
html += "<div class=\"col-lg-4\">\n" +
html += "<div class=\"col-lg-4\" id=\"search-tree-target\">\n" +
"<div class=\"search-tree well\">\n" +
"<div id=\"search-tree-container\"></div>\n" +
"</div><!-- search-tree well -->\n" +

View File

@ -72,6 +72,7 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
// remove lingering popover <div>. Seems to be a bug in TB3 RC1
$(this).remove();
});
$(window).unbind('resize');
try {
$('#help-modal').empty().dialog('destroy');