AC-331 Custom tree on Inventories detail Hosts tab is starting to work well.

This commit is contained in:
chouseknecht
2013-08-15 04:11:09 -04:00
parent 718b566a03
commit 9d7018767d
8 changed files with 347 additions and 138 deletions

View File

@@ -185,7 +185,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt, RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt,
OrganizationList, TreeInit, GetBasePath, GroupsList, GroupsAdd, GroupsEdit, LoadInventory, OrganizationList, TreeInit, GetBasePath, GroupsList, GroupsAdd, GroupsEdit, LoadInventory,
GroupsDelete, HostsList, HostsAdd, HostsEdit, HostsDelete, RefreshGroupName, ParseTypeChange, GroupsDelete, HostsList, HostsAdd, HostsEdit, HostsDelete, RefreshGroupName, ParseTypeChange,
HostsReload, EditInventory, RefreshTree) HostsReload, EditInventory, RefreshTree, LoadSearchTree)
{ {
ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior
//scope. //scope.
@@ -201,21 +201,26 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
$('#inventory-tabs a:first').tab('show'); //activate the groups tab $('#inventory-tabs a:first').tab('show'); //activate the groups tab
scope.inventoryParseType = 'yaml'; scope['inventoryParseType'] = 'yaml';
scope['inventory_id'] = id; scope['inventory_id'] = id;
scope['inventoryFailureFilter'] = false;
// Retrieve each related set and any lookups // Retrieve each related set and any lookups
if (scope.inventoryLoadedRemove) { if (scope.inventoryLoadedRemove) {
scope.inventoryLoadedRemove(); scope.inventoryLoadedRemove();
} }
scope.inventoryLoadedRemove = scope.$on('inventoryLoaded', function() { scope.inventoryLoadedRemove = scope.$on('inventoryLoaded', function() {
scope.groupTitle = '<h4>All Hosts</h4>';
scope.createButtonShow = false;
scope.search(scope.relatedSets['hosts'].iterator);
TreeInit(scope.TreeParams); TreeInit(scope.TreeParams);
}); });
LoadInventory({ scope: scope, doPostSteps: true }); LoadInventory({ scope: scope, doPostSteps: true });
$('#inventory-tabs a[href="#inventory-hosts"]').on('show.bs.tab', function() {
LoadSearchTree({ scope: scope, inventory_id: scope['inventory_id'] });
HostsReload({ scope: scope, inventory_id: scope['inventory_id'], group_id: scope['group_id'] });
if (!scope.$$phase) {
scope.$digest();
}
});
scope.filterInventory = function() { scope.filterInventory = function() {
RefreshTree({ scope: scope }); RefreshTree({ scope: scope });
@@ -365,7 +370,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
scope['selectedNodeName'] = node.attr('name'); scope['selectedNodeName'] = node.attr('name');
scope['selectedNodeName'] += (node.attr('data-failures') == 'true') ? scope['selectedNodeName'] += (node.attr('data-failures') == 'true') ?
' <span class="nav-badge">' + ' <span class="nav-badge">' +
'<i class="icon-exclamation-sign" title="Contains hosts with active failures"></i></span>' : ''; '<i class="icon-exclamation-sign" title="Contains hosts with failed jobs"></i></span>' : '';
$('#tree-view').jstree('open_node',node); $('#tree-view').jstree('open_node',node);
@@ -399,7 +404,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
scope.$digest(); scope.$digest();
} }
HostsReload({ scope: scope, inventory_id: scope['inventory_id'], group_id: scope['group_id'] }); //HostsReload({ scope: scope, inventory_id: scope['inventory_id'], group_id: scope['group_id'] });
}); });
@@ -455,6 +460,12 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
}); });
} }
scope.showHosts = function(e) {
console.log('here');
var elm = angular.elment(e.srcElement);
console.log('Need to show hosts: ' + elm.attr('data-hosts'));
}
} }
InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm',
@@ -462,6 +473,6 @@ InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$l
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt',
'OrganizationList', 'TreeInit', 'GetBasePath', 'GroupsList', 'GroupsAdd', 'GroupsEdit', 'LoadInventory', 'OrganizationList', 'TreeInit', 'GetBasePath', 'GroupsList', 'GroupsAdd', 'GroupsEdit', 'LoadInventory',
'GroupsDelete', 'HostsList', 'HostsAdd', 'HostsEdit', 'HostsDelete', 'RefreshGroupName', 'GroupsDelete', 'HostsList', 'HostsAdd', 'HostsEdit', 'HostsDelete', 'RefreshGroupName',
'ParseTypeChange', 'HostsReload', 'EditInventory', 'RefreshTree' 'ParseTypeChange', 'HostsReload', 'EditInventory', 'RefreshTree', 'LoadSearchTree'
]; ];

View File

@@ -413,7 +413,7 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
scope = params.scope; scope = params.scope;
scope['hosts'] = null; scope['hosts'] = null;
var url = (scope.group_id !== null) ? GetBasePath('groups') + scope.group_id + '/all_hosts/' : var url = (scope.group_id !== null && scope.group_id !== undefined) ? GetBasePath('groups') + scope.group_id + '/all_hosts/' :
GetBasePath('inventory') + params.inventory_id + '/hosts/'; GetBasePath('inventory') + params.inventory_id + '/hosts/';
var relatedSets = { hosts: { url: url, iterator: 'host' } }; var relatedSets = { hosts: { url: url, iterator: 'host' } };
@@ -433,12 +433,45 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'H
} }
} }
params.scope.search('host'); scope.search('host');
if (!params.scope.$$phase) { if (!params.scope.$$phase) {
params.scope.$digest(); params.scope.$digest();
} }
} }
}])
.factory('LoadSearchTree', ['Rest', 'GetBasePath', 'ProcessErrors', '$compile',
function(Rest, GetBasePath, ProcessErrors, $compile) {
return function(params) {
var scope = params.scope;
var inventory_id = params.inventory_id;
var newTree = [];
scope.searchTree = [];
// Load the root node
Rest.setUrl (GetBasePath('inventory') + inventory_id + '/');
Rest.get()
.success( function(data, status, headers, config) {
scope.searchTree.push({
name: data.name,
description: data.description,
hosts: data.related.hosts,
failures: data.has_active_failures,
groups: data.related.root_groups,
children: []
});
scope.$emit('hostTabInit');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status });
});
}
}]); }]);

View File

@@ -57,7 +57,7 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
description: data.results[i].description, description: data.results[i].description,
inventory: data.results[i].inventory, inventory: data.results[i].inventory,
all: data.results[i].related.all_hosts, all: data.results[i].related.all_hosts,
children: data.results[i].related.children, children: data.results[i].related.children + '?' + filter + 'order_by=name',
hosts: data.results[i].related.hosts, hosts: data.results[i].related.hosts,
variable: data.results[i].related.variable_data, variable: data.results[i].related.variable_data,
"data-failures": data.results[i].has_active_failures "data-failures": data.results[i].has_active_failures

View File

@@ -33,6 +33,9 @@ angular.module('RefreshRelatedHelper', ['RestServices', 'Utilities'])
scope[iterator + 'PageCount'] = Math.ceil((data.count / scope[iterator + 'PageSize'])); scope[iterator + 'PageCount'] = Math.ceil((data.count / scope[iterator + 'PageSize']));
scope[iterator + 'SearchSpin'] = false; scope[iterator + 'SearchSpin'] = false;
scope[iterator + 'Loading'] = false; scope[iterator + 'Loading'] = false;
if (!params.scope.$$phase) {
params.scope.$digest();
}
}) })
.error ( function(data, status, headers, config) { .error ( function(data, status, headers, config) {
scope[iterator + 'SearchSpin'] = true; scope[iterator + 'SearchSpin'] = true;

View File

@@ -510,137 +510,112 @@ input[type="text"].job-successful {
/* End Jobs Page */ /* End Jobs Page */
/* Inventory detail */ /* Inventory Detail Groups tab */
.inventory-content {
padding: 15px;
border: 1px solid #ddd;
border-radius: 6px;
}
.inventory-content { .groups-menu {
padding: 15px; min-height: 30px;
border: 1px solid #ddd; background-color: #f5f5f5;
border-radius: 6px; border: 1px solid #e3e3e3;
} border-radius: 6px;
.nav a {
color: @blue-link;
font-size: 12px;
i {
font-size: 14px;
}
}
.groups-menu { .navbar-form {
min-height: 30px; display: inline-block;
background-color: #f5f5f5; float: right;
border: 1px solid #e3e3e3; margin-top: 13px;
border-radius: 6px; margin-left: 20px;
margin-right: 10px;
.nav a {
color: @blue-link; label {
font-size: 12px; font-size: 12px;
i { line-height: normal;
}
input[type="checkbox"] {
margin-top: 0;
}
}
/* the brand is't really a link */
.navbar-brand {
color: @black;
text-align: left;
font-size: 14px; font-size: 14px;
} max-width: 100%;
i {
color: @red;
}
}
.navbar-brand:hover {
color: @black;
cursor: default;
}
/* neither is the status spinner */
.nav .status {
color: @black;
}
.nav .status:hover {
color: @black;
cursor: default;
}
} }
.navbar-form { .tree-badge {
display: inline-block; color: @red;
float: right; font-size: 12px;
margin-top: 13px;
margin-left: 20px;
margin-right: 10px;
label {
font-size: 12px;
line-height: normal;
}
input[type="checkbox"] {
margin-top: 0;
}
} }
/* the brand is't really a link */ /* Inventory Detail Hosts tab */
.navbar-brand {
color: @black; .hosts-well {
text-align: left; padding-top: 5px;
font-size: 14px;
max-width: 100%; .search-widget {
i { margin-top: 10px;
color: @red; }
}
} .list-actions {
.navbar-brand:hover { padding-top: 10px;
color: @black; }
cursor: default;
}
/* neither is the status spinner */
.nav .status {
color: @black;
}
.nav .status:hover {
color: @black;
cursor: default;
} }
} .hosts-title p {
font-size: 12px;
}
.tree-badge { .hosts-title h4 {
color: @red; margin: 5px 0;
font-size: 12px; }
}
.inventory-title { .search-tree {
margin-top: 15px; ul {
font-weight: bold; list-style-type: none;
color: @blue-link; padding-left: 10px;
} }
ul:first-child {
padding-left: 0;
}
}
.inventory-buttons { .search-tree .active {
text-align: right; background-color: #ddd;
background-color: #f5f5f5; padding: 1px 1px 1px 0;
border-top: 1px solid #e3e3e3; border: 1px solid #ddd;
border-right: 1px solid #e3e3e3; border-radius: 4px;
border-left: 1px solid #e3e3e3; }
-webkit-border-top-right-radius: 4px;
-moz-border-top-right-radius: 4px;
border-top-right-radius: 4px;
-webkit-border-top-left-radius: 4px;
-moz-border-top-left-radius: 4px;
border-top-left-radius: 4px;
}
.inventory-buttons button {
margin: 5px 5px 3px 0;
}
.inventory-filter {
padding: 0 3px 3px 3px;
text-align: right;
background-color: #f5f5f5;
border-right: 1px solid #e3e3e3;
border-bottom: 1px solid #e3e3e3;
border-left: 1px solid #e3e3e3;
-webkit-border-bottom-right-radius: 4px;
-moz-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
-moz-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.inventory-filter label {
margin-right: 10px;
}
.hosts-well {
padding-top: 5px;
}
.hosts-title p {
font-size: 12px;
}
.hosts-title h4 {
margin: 5px 0;
}
.hosts-well .search-widget {
margin-top: 10px;
}
.hosts-well .list-actions {
padding-top: 10px;
}
.parse-selection { .parse-selection {
display: inline-block; display: inline-block;

View File

@@ -8,7 +8,7 @@
var INTEGER_REGEXP = /^\-?\d*$/; var INTEGER_REGEXP = /^\-?\d*$/;
angular.module('AWDirectives', ['RestServices']) angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'HostsHelper'])
// awpassmatch: Add to password_confirm field. Will test if value // awpassmatch: Add to password_confirm field. Will test if value
// matches that of 'input[name="password"]' // matches that of 'input[name="password"]'
.directive('awpassmatch', function() { .directive('awpassmatch', function() {
@@ -312,13 +312,191 @@ angular.module('AWDirectives', ['RestServices'])
if (disabled) { if (disabled) {
opts['disabled'] = true; opts['disabled'] = true;
} }
$(elm).spinner(opts); $(elm).spinner(opts);
}
}
}])
/*$('#' + name + '-number').change( function() { .directive('awTree', ['Rest', 'ProcessErrors', 'Authorization', '$compile', '$rootScope', 'HostsReload',
$('#' + name + '-slider').slider('value', parseInt( $(this).val() )); function(Rest, ProcessErrors, Authorization, $compile, $rootScope, HostsReload) {
});*/ return {
//require: 'ngModel',
replace: true,
transclude: true,
scope: {
treeData: '=awTree'
},
replace: true,
template:
"<div class=\"search-tree well\" id=\"search-tree-container\">\n" +
"<ul>\n" +
"<li><a href=\"\" id=\"search-node-1000\" class=\"active\" data-state=\"closed\" data-hosts=\"{{ treeData[0].hosts}}\" " +
"data-hosts=\"{{ treeData[0].hosts }}\" " +
"data-description=\"{{ treeData[0].description }}\" " +
"data-failures=\"{{ treeData[0].failures }}\" " +
"data-groups=\"{{ treeData[0].groups }}\" " +
"data-name=\"{{ treeData[0].name }}\" " +
"><i class=\"icon-caret-right\"></i> {{ treeData[0].name}}</a></li>\n" +
"</ul>\n" +
"</div>\n",
link: function(scope, elm , attrs) {
var idx=1000;
function toggle(e) {
var id = (e.target.tagName == 'I') ? e.target.parentNode.attributes.id.value : e.target.attributes.id.value;
var elm = angular.element(document.getElementById(id));
function activate() {
/* Set the clicked node as active */
$('.search-tree .active').removeClass('active');
elm.addClass('active');
var group = (elm.attr('data-group-id')) ? elm.attr('data-group-id') : null;
var parentScope = angular.element(document.getElementById('htmlTemplate')).scope();
console.log('calling for group: ' + group);
HostsReload({ scope: parentScope, inventory_id: parentScope['inventory_id'], group_id: group });
}
/* Open/close the node and expand */
if (scope.childrenLoadedRemove) {
scope.childrenLoadedRemove();
}
scope.childrenLoadedRemove = scope.$on('childrenLoaded', function() {
childlists = elm.parent().find('ul'); //look for children
if (childlists && childlists.length > 0) {
// bind toggle() to click event of each link in the group we clicked on
var parent = angular.element(elm.parent()[0]);
var links = parent.find('a');
for (var i=0; i < links.length; i++) {
var link = angular.element(links[i]);
link.unbind('click', toggle);
link.bind('click', toggle);
}
toggle(e);
}
else {
var icon = angular.element(elm.children()[0]);
icon.removeClass('icon-caret-down').removeClass('icon-caret-right').addClass('icon-ellipsis-horizontal');
}
});
if (elm.attr('data-state') == 'closed') {
// expand the elment
var childlists = elm.parent().find('ul');
if (childlists && childlists.length > 0) {
// already has childen
for (var i=0; i < childlists.length; i++) {
var listChild = angular.element(childlists[i]);
var listParent = angular.element(listChild.parent().find('a')[0]);
if (listParent.attr('id') == elm.attr('id')) {
angular.element(childlists[i]).removeClass('hidden');
}
// all the children should be in a closed state
var aList = listChild.find('a');
for (var j=0; j < aList.length; j++) {
var thisList = angular.element(aList[j]);
thisList.attr('data-state', 'closed');
var icon = angular.element(thisList.children()[0]);
icon.removeClass('icon-caret-down').removeClass('icon-ellipsis-horizontal').addClass('icon-caret-right');
}
}
elm.attr('data-state','open');
var icon = angular.element(elm.children()[0]);
icon.removeClass('icon-caret-right').removeClass('icon-ellipsis-horizontal').addClass('icon-caret-down');
activate();
}
else {
getChildren(elm);
}
}
else {
// close the element
elm.attr('data-state','closed');
var icon = angular.element(elm.children()[0]);
icon.removeClass('icon-caret-down').removeClass('icon-ellipsis-horizontal').addClass('icon-caret-right');
var childlists = elm.parent().find('ul');
if (childlists && childlists.length > 0) {
// has childen
for (var i=0; i < childlists.length; i++) {
angular.element(childlists[i]).addClass('hidden');
}
}
activate();
}
}
function getChildren(elm) {
var url = elm.attr('data-groups');
var html = '';
var token = Authorization.getToken();
/* For reasons unknown calling Rest fails. It just dies with no errors
or any info */
$.ajax({
url: url,
headers: { 'Authorization': 'Token ' + token },
dataType: 'json',
success: function(data) {
// build html and append to parent of clicked link
for (var i=0; i < data.results.length; i++) {
idx++;
html += "<li>\n";
html += "<a href=\"\" data-state=\"closed\" ";
html += "id=\"search-tree-" + idx +"\" ";
html += "data-hosts=\"" + data.results[i].related.all_hosts + "\" ";
html += "data-description=\"" + data.results[i].description + "\" ";
html += "data-failures=\"" +data.results[i].has_active_failures + "\" ";
html += "data-groups=\"" + data.results[i].related.children + "\" ";
html += "data-name=\"" + data.results[i].name + "\" ";
html += "data-group-id=\"" + data.results[i].id + "\" ";
html += "><i class=\"icon-caret-right\"></i> " + data.results[i].name;
html += "</a></li>\n";
}
html = (html !== '') ? "<ul>" + html + "</ul>\n" : "";
var parent = angular.element(elm.parent()[0]);
var compiled = $compile(html)(scope);
parent.append(compiled); //append the new list to the parent <li>
console.log('childrenLoaded');
scope.$emit('childrenLoaded');
},
error: function(data, status) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Failed to get child groups for ' + elm.attr('name') +
'. GET returned: ' + status });
}
});
}
function initialize() {
var root = angular.element(document.getElementById('search-node-1000'));
root.bind('click', toggle);
}
if ($rootScope.hostTabInitRemove) {
$rootScope.hostTabInitRemove();
}
$rootScope.hostTabInitRemove = $rootScope.$on('hostTabInit', function(e) {
var container = angular.element(document.getElementById('search-tree-container'));
container.empty();
var html = "<ul>\n" +
"<li><a href=\"\" id=\"search-node-1000\" class=\"active\" data-state=\"closed\" data-hosts=\"{{ treeData[0].hosts}}\" " +
"data-hosts=\"{{ treeData[0].hosts }}\" " +
"data-description=\"{{ treeData[0].description }}\" " +
"data-failures=\"{{ treeData[0].failures }}\" " +
"data-groups=\"{{ treeData[0].groups }}\" " +
"data-name=\"{{ treeData[0].name }}\" " +
"><i class=\"icon-caret-right\"></i> {{ treeData[0].name }}</a></li>\n" +
"</ul>\n";
var compiled = $compile(html)(scope);
container.append(compiled);
initialize();
//setTimeout(function() { $('.search-tree .active').click(); }, 1000); //click the root node, forcing level 1 nodes to appear
});
} }
} }
@@ -327,3 +505,4 @@ angular.module('AWDirectives', ['RestServices'])

View File

@@ -942,6 +942,11 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
// build the hosts tab // build the hosts tab
itm = "hosts"; itm = "hosts";
html += "<div class=\"tab-pane\" id=\"inventory-hosts\">\n"; html += "<div class=\"tab-pane\" id=\"inventory-hosts\">\n";
html += "<div class=\"row\">\n";
html += "<div class=\"col-lg-3\" id=\"search-tree-target\">\n";
html += "<div aw-tree=\"searchTree\"></div>\n";
html += "</div>\n";
html += "<div class=\"col-lg-9\">\n";
html += "<div class=\"hosts-well well\">\n"; html += "<div class=\"hosts-well well\">\n";
html += "<div class=\"hosts-title\" ng-bind-html-unsafe=\"" + form.related[itm].title + "\"></div>\n"; html += "<div class=\"hosts-title\" ng-bind-html-unsafe=\"" + form.related[itm].title + "\"></div>\n";
html += SearchWidget({ iterator: form.related[itm].iterator, template: form.related[itm], mini: true, size: 'col-lg-6'}); html += SearchWidget({ iterator: form.related[itm].iterator, template: form.related[itm], mini: true, size: 'col-lg-6'});
@@ -1047,6 +1052,9 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "</div>\n"; // close well html += "</div>\n"; // close well
html += PaginateWidget({ set: itm, iterator: form.related[itm].iterator, mini: true }); html += PaginateWidget({ set: itm, iterator: form.related[itm].iterator, mini: true });
html += "</div>\n";
html += "</div>\n";
html += "</div><!-- inventory-hosts -->\n"; html += "</div><!-- inventory-hosts -->\n";
html += "</div><!-- tab-content -->\n"; html += "</div><!-- tab-content -->\n";

View File

@@ -15,6 +15,7 @@
<link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico" /> <link rel="shortcut icon" href="{{ STATIC_URL }}img/favicon.ico" />
<script src="{{ STATIC_URL }}lib/jquery/jquery-1.10.2.min.js"></script>
<script src="{{ STATIC_URL }}js/config.js"></script> <script src="{{ STATIC_URL }}js/config.js"></script>
<script src="{{ STATIC_URL }}lib/angular/angular.js"></script> <script src="{{ STATIC_URL }}lib/angular/angular.js"></script>
<script src="{{ STATIC_URL }}lib/angular/angular-resource.js"></script> <script src="{{ STATIC_URL }}lib/angular/angular-resource.js"></script>
@@ -338,8 +339,7 @@
</div> </div>
</div> </div>
</div><!-- site footer --> </div><!-- site footer -->
<script src="{{ STATIC_URL }}lib/jquery/jquery-1.10.2.min.js"></script>
<script src="{{ STATIC_URL }}lib/jquery/jquery-ui-1.10.3.custom.min.js"></script> <script src="{{ STATIC_URL }}lib/jquery/jquery-ui-1.10.3.custom.min.js"></script>
<script src="{{ STATIC_URL }}lib/twitter/bootstrap.min.js"></script> <script src="{{ STATIC_URL }}lib/twitter/bootstrap.min.js"></script>
<script src="{{ STATIC_URL }}lib/jstree/jquery.jstree.js"></script> <script src="{{ STATIC_URL }}lib/jstree/jquery.jstree.js"></script>