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,
OrganizationList, TreeInit, GetBasePath, GroupsList, GroupsAdd, GroupsEdit, LoadInventory,
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
//scope.
@ -201,21 +201,26 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
$('#inventory-tabs a:first').tab('show'); //activate the groups tab
scope.inventoryParseType = 'yaml';
scope['inventoryParseType'] = 'yaml';
scope['inventory_id'] = id;
scope['inventoryFailureFilter'] = false;
// Retrieve each related set and any lookups
if (scope.inventoryLoadedRemove) {
scope.inventoryLoadedRemove();
}
scope.inventoryLoadedRemove = scope.$on('inventoryLoaded', function() {
scope.groupTitle = '<h4>All Hosts</h4>';
scope.createButtonShow = false;
scope.search(scope.relatedSets['hosts'].iterator);
TreeInit(scope.TreeParams);
});
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() {
RefreshTree({ scope: scope });
@ -365,7 +370,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
scope['selectedNodeName'] = node.attr('name');
scope['selectedNodeName'] += (node.attr('data-failures') == 'true') ?
' <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);
@ -399,7 +404,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
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',
@ -462,6 +473,6 @@ InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$l
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt',
'OrganizationList', 'TreeInit', 'GetBasePath', 'GroupsList', 'GroupsAdd', 'GroupsEdit', 'LoadInventory',
'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['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/';
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) {
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,
inventory: data.results[i].inventory,
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,
variable: data.results[i].related.variable_data,
"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 + 'SearchSpin'] = false;
scope[iterator + 'Loading'] = false;
if (!params.scope.$$phase) {
params.scope.$digest();
}
})
.error ( function(data, status, headers, config) {
scope[iterator + 'SearchSpin'] = true;

View File

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

View File

@ -8,7 +8,7 @@
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
// matches that of 'input[name="password"]'
.directive('awpassmatch', function() {
@ -312,13 +312,191 @@ angular.module('AWDirectives', ['RestServices'])
if (disabled) {
opts['disabled'] = true;
}
$(elm).spinner(opts);
}
}
}])
/*$('#' + name + '-number').change( function() {
$('#' + name + '-slider').slider('value', parseInt( $(this).val() ));
});*/
.directive('awTree', ['Rest', 'ProcessErrors', 'Authorization', '$compile', '$rootScope', 'HostsReload',
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
itm = "hosts";
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-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'});
@ -1047,6 +1052,9 @@ angular.module('FormGenerator', ['GeneratorHelpers', 'ngCookies'])
html += "</div>\n"; // close well
html += PaginateWidget({ set: itm, iterator: form.related[itm].iterator, mini: true });
html += "</div>\n";
html += "</div>\n";
html += "</div><!-- inventory-hosts -->\n";
html += "</div><!-- tab-content -->\n";

View File

@ -15,6 +15,7 @@
<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 }}lib/angular/angular.js"></script>
<script src="{{ STATIC_URL }}lib/angular/angular-resource.js"></script>
@ -338,8 +339,7 @@
</div>
</div>
</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/twitter/bootstrap.min.js"></script>
<script src="{{ STATIC_URL }}lib/jstree/jquery.jstree.js"></script>