Moved inventory detail load procedure from inventory controller to the helper so that it can be called when the detail page first loads and anytime we need to refresh tree data. Working on a way to refresh tree data, re-open previously open nodes and re-select previously active node after add/edit/delete of a group. Any change to tree data should be instantly reflect in the tree in an ajax/async fashion.

This commit is contained in:
chouseknecht
2013-06-03 07:08:08 -04:00
parent 9ca489d5ab
commit b19e1dd97a
10 changed files with 593 additions and 139 deletions

View File

@@ -348,4 +348,8 @@
#hosts-title { #hosts-title {
margin-bottom: 15px; margin-bottom: 15px;
}
#tree-view {
min-height: 100px;
} }

View File

@@ -49,7 +49,8 @@ angular.module('ansible', [
'JobFormDefinition', 'JobFormDefinition',
'JobEventsListDefinition', 'JobEventsListDefinition',
'JobEventFormDefinition', 'JobEventFormDefinition',
'JobHostDefinition' 'JobHostDefinition',
'GroupsHelper'
]) ])
.config(['$routeProvider', function($routeProvider) { .config(['$routeProvider', function($routeProvider) {
$routeProvider. $routeProvider.

View File

@@ -147,13 +147,13 @@ InventoriesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$route
function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope,
GenerateList, OrganizationList, SearchInit, PaginateInit, LookUpInit) GenerateList, OrganizationList, SearchInit, PaginateInit, LookUpInit, GetBasePath)
{ {
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.
// Inject dynamic view // Inject dynamic view
var defaultUrl = '/api/v1/inventories/'; var defaultUrl = GetBasePath('inventory');
var form = InventoryForm; var form = InventoryForm;
var generator = GenerateForm; var generator = GenerateForm;
var scope = generator.inject(form, {mode: 'add', related: false}); var scope = generator.inject(form, {mode: 'add', related: false});
@@ -197,13 +197,13 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa
InventoriesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', InventoriesAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm',
'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GenerateList', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GenerateList',
'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit' ]; 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit', 'GetBasePath' ];
function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm,
GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit,
RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt, RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt,
OrganizationList, TreeInit, GetBasePath) OrganizationList, TreeInit, GetBasePath, GroupsList, GroupsEdit, LoadInventory)
{ {
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.
@@ -214,11 +214,8 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
var scope = generator.inject(form, {mode: 'edit', related: true}); var scope = generator.inject(form, {mode: 'edit', related: true});
generator.reset(); generator.reset();
var base = $location.path().replace(/^\//,'').split('/')[0]; var base = $location.path().replace(/^\//,'').split('/')[0];
var master = {};
var id = $routeParams.id; var id = $routeParams.id;
var relatedSets = {};
var hostsUrl;
scope['inventory_id'] = id; scope['inventory_id'] = id;
// Retrieve each related set and any lookups // Retrieve each related set and any lookups
@@ -228,47 +225,11 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
scope.inventoryLoadedRemove = scope.$on('inventoryLoaded', function() { scope.inventoryLoadedRemove = scope.$on('inventoryLoaded', function() {
scope.groupTitle = 'All Hosts'; scope.groupTitle = 'All Hosts';
scope.createButtonShow = false; scope.createButtonShow = false;
scope.search(relatedSets['hosts'].iterator); scope.search(scope.relatedSets['hosts'].iterator);
TreeInit(scope.TreeParams);
}); });
// Retrieve detail record and prepopulate the form LoadInventory({ scope: scope });
Rest.setUrl(defaultUrl + ':id/');
Rest.get({ params: {id: id} })
.success( function(data, status, headers, config) {
LoadBreadCrumbs({ path: '/inventories/' + id, title: data.name });
for (var fld in form.fields) {
if (data[fld]) {
scope[fld] = data[fld];
master[fld] = scope[fld];
}
if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
}
}
LookUpInit({
scope: scope,
form: form,
current_item: data.organization,
list: OrganizationList,
field: 'organization'
});
// Load the tree view
TreeInit({ scope: scope, inventory: data });
hostsUrl = data.related.hosts;
relatedSets['hosts'] = { url: hostsUrl, iterator: 'host' };
RelatedSearchInit({ scope: scope, form: form, relatedSets: relatedSets });
RelatedPaginateInit({ scope: scope, relatedSets: relatedSets });
scope.$emit('inventoryLoaded');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $routeParams.id + '. GET status: ' + status });
});
// Save changes to the parent // Save changes to the parent
scope.formSave = function() { scope.formSave = function() {
@@ -291,8 +252,8 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
// Cancel // Cancel
scope.formReset = function() { scope.formReset = function() {
generator.reset(); generator.reset();
for (var fld in master) { for (var fld in scope.master) {
scope[fld] = master[fld]; scope[fld] = scope.master[fld];
} }
}; };
@@ -419,37 +380,42 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP
url = node.attr('all'); url = node.attr('all');
scope.groupAddHide = false; scope.groupAddHide = false;
scope.groupEditHide =false; scope.groupEditHide =false;
scope.groupDeleteHide = false;
scope.createButtonShow = true; scope.createButtonShow = true;
scope.group_id = node.attr('group_id'); scope.group_id = node.attr('group_id');
scope.groupName = n.data; scope.groupName = n.data;
scope.groupTitle = n.data; scope.groupTitle = n.data;
scope.groupTitle += (node.attr('description')) ? ' -' + node.attr('description') : ''; scope.groupTitle += (node.attr('description')) ? ' -' + node.attr('description') : '';
} }
else if (type == 'all-hosts-group') {
url = node.attr('url');
scope.createButtonShow = false;
scope.groupName = 'All Hosts';
scope.groupTitle = 'All Hosts';
}
else if (type == 'inventory') { else if (type == 'inventory') {
url = node.attr('hosts'); url = node.attr('hosts');
scope.groupAddHide = false; scope.groupAddHide = false;
scope.groupEditHide =true; scope.groupEditHide =true;
scope.groupDeleteHide = true;
scope.createButtonShow = false; scope.createButtonShow = false;
scope.groupName = 'All Hosts'; scope.groupName = 'All Hosts';
scope.groupTitle = 'All Hosts'; scope.groupTitle = 'All Hosts';
scope.group_id = null;
} }
relatedSets['hosts'] = { url: url, iterator: 'host' }; scope.relatedSets['hosts'] = { url: url, iterator: 'host' };
RelatedSearchInit({ scope: scope, form: form, relatedSets: relatedSets }); RelatedSearchInit({ scope: scope, form: form, relatedSets: scope.relatedSets });
RelatedPaginateInit({ scope: scope, relatedSets: relatedSets }); RelatedPaginateInit({ scope: scope, relatedSets: scope.relatedSets });
scope.search('host'); scope.search('host');
scope.$digest(); scope.$digest();
}); });
scope.addGroup = function() {
GroupsList({ "inventory_id": id, group_id: scope.group_id });
}
scope.editGroup = function() {
GroupsEdit({ "inventory_id": id, group_id: scope.group_id });
}
} }
InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm',
'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit',
'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt',
'OrganizationList', 'TreeInit', 'GetBasePath' 'OrganizationList', 'TreeInit', 'GetBasePath', 'GroupsList', 'GroupsEdit', 'LoadInventory'
]; ];

View File

@@ -13,7 +13,7 @@ angular.module('GroupFormDefinition', [])
addTitle: 'Create Group', //Legend in add mode addTitle: 'Create Group', //Legend in add mode
editTitle: '{{ name }}', //Legend in edit mode editTitle: '{{ name }}', //Legend in edit mode
name: 'group', //Form name attribute name: 'group', //Form name attribute
well: true, //Wrap the form with TB well well: false, //Wrap the form with TB well
fields: { fields: {
name: { name: {
@@ -34,7 +34,6 @@ angular.module('GroupFormDefinition', [])
addRequired: false, addRequired: false,
editRequird: false, editRequird: false,
rows: 10, rows: 10,
class: 'span12',
default: "\{\}", default: "\{\}",
dataTitle: 'Group Variables', dataTitle: 'Group Variables',
dataPlacement: 'right', dataPlacement: 'right',

View File

@@ -0,0 +1,381 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* InventoryHelper
* Routines for building the tree. Everything related to the tree is here except
* for the menu piece. The routine for building the menu is in InventoriesEdit controller
* (controllers/Inventories.js)
*
*/
angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', 'GroupListDefinition',
'SearchHelper', 'PaginateHelper', 'ListGenerator', 'AuthService', 'GroupsHelper',
'InventoryHelper'
])
.factory('GroupsList', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupList', 'GenerateList',
'Prompt', 'SearchInit', 'PaginateInit', 'ProcessErrors', 'GetBasePath', 'GroupsAdd', 'RefreshTree',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, LoadBreadCrumbs, SearchInit,
PaginateInit, ProcessErrors, GetBasePath, GroupsAdd, RefreshTree) {
return function(params) {
var inventory_id = params.inventory_id;
var group_id = (params.group_id !== undefined) ? params.group_id : null;
var list = GroupList;
var defaultUrl = GetBasePath('inventory') + inventory_id + '/groups/';
var view = GenerateList;
var scope = view.inject(GroupList, {
id: 'form-modal-body',
mode: 'select',
breadCrumbs: false,
selectButton: false
});
scope.formModalActionLabel = 'Finished'
scope.formModalHeader = 'Add Group'
$('#form-modal').modal();
scope.selected = [];
if (scope.PostRefreshRemove) {
scope.PostRefreshRemove();
}
scope.PostRefreshRemove = scope.$on('PostRefresh', function() {
$("tr.success").each(function(index) {
var ngc = $(this).attr('ng-class');
scope[ngc] = "";
});
if ($routeParams.group_id) {
// Remove the current group from the list of available groups, thus
// preventing a group from being added to itself
for (var i=0; i < scope.groups.length; i++) {
if (scope.groups[i].id == $routeParams.group_id) {
scope.groups.splice(i,1);
}
}
}
//scope.$digest();
});
SearchInit({ scope: scope, set: 'groups', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl });
scope.search(list.iterator);
/*LoadBreadCrumbs();*/
scope.editGroup = function(id) {
$location.path($location.path() + '/' + id);
}
scope.deleteGroup = function(id, name) {
var action = function() {
var url = defaultUrl;
Rest.setUrl(url);
Rest.post({ id: id, disassociate: 1 })
.success( function(data, status, headers, config) {
$('#prompt-modal').modal('hide');
scope.search(list.iterator);
})
.error( function(data, status, headers, config) {
$('#prompt-modal').modal('hide');
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
});
};
Prompt({ hdr: 'Delete',
body: 'Are you sure you want to remove group' + name + '?',
action: action
});
}
scope.formModalAction = function() {
var url = (group_id) ? GetBasePath('groups') + group_id + '/children/' :
GetBasePath('inventory') + inventory_id + '/groups/';
Rest.setUrl(url);
scope.queue = [];
if (scope.callFinishedRemove) {
scope.callFinishedRemove();
}
scope.callFinishedRemove = scope.$on('callFinished', function() {
// We call the API for each selected item. We need to hang out until all the api
// calls are finished.
if (scope.queue.length == scope.selected.length) {
// All the api calls finished
$('input[type="checkbox"]').prop("checked",false);
scope.selected = [];
var errors = 0;
for (var i=0; i < scope.queue.length; i++) {
if (scope.queue[i].result == 'error') {
errors++;
}
}
if (errors > 0) {
Alert('Error', 'There was an error while adding one or more of the selected groups.');
}
else {
$('#form-modal').modal('hide');
}
}
});
if (scope.selected.length > 0 ) {
var group;
for (var i=0; i < scope.selected.length; i++) {
group = null;
for (var j=0; j < scope.groups.length; j++) {
if (scope.groups[j].id == scope.selected[i]) {
group = scope.groups[j];
}
}
if (group !== null) {
Rest.post(group)
.success( function(data, status, headers, config) {
scope.queue.push({ result: 'success', data: data, status: status });
scope.$emit('callFinished');
})
.error( function(data, status, headers, config) {
scope.queue.push({ result: 'error', data: data, status: status, headers: headers });
scope.$emit('callFinished');
});
}
}
}
else {
$('#form-modal').modal('hide');
}
}
scope.toggle_group = function(id) {
if (scope[list.iterator + "_" + id + "_class"] == "success") {
scope[list.iterator + "_" + id + "_class"] = "";
document.getElementById('check_' + id).checked = false;
if (scope.selected.indexOf(id) > -1) {
scope.selected.splice(scope.selected.indexOf(id),1);
}
}
else {
scope[list.iterator + "_" + id + "_class"] = "success";
document.getElementById('check_' + id).checked = true;
if (scope.selected.indexOf(id) == -1) {
scope.selected.push(id);
}
}
}
scope.createGroup = function() {
$('#form-modal').modal('hide');
GroupsAdd({ inventory_id: inventory_id, group_id: group_id });
}
}
}])
.factory('GroupsAdd', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath) {
return function(params) {
var inventory_id = params.inventory_id;
var group_id = (params.group_id !== undefined) ? params.group_id : null;
// Inject dynamic view
var defaultUrl = (group_id !== null) ? GetBasePath('groups') + group_id + '/children/' :
GetBasePath('inventory') + inventory_id + '/groups/';
var form = GroupForm;
var generator = GenerateForm;
var scope = generator.inject(form, {mode: 'add', modal: true, related: false});
scope.formModalActionLabel = 'Save'
scope.formModalHeader = 'Create Group'
generator.reset();
var master={};
// Save
scope.formModalAction = function() {
try {
// Make sure we have valid JSON
var myjson = JSON.parse(scope.variables);
var data = {}
for (var fld in form.fields) {
if (fld != 'variables') {
data[fld] = scope[fld];
}
}
if (inventory_id) {
data['inventory'] = inventory_id;
}
Rest.setUrl(defaultUrl);
Rest.post(data)
.success( function(data, status, headers, config) {
if (scope.variables) {
Rest.setUrl(data.related.variable_data);
Rest.put({data: scope.variables})
.success( function(data, status, headers, config) {
$('#form-modal').modal('hide');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to add group varaibles. PUT returned status: ' + status });
});
}
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to add new group. Post returned status: ' + status });
});
}
catch(err) {
Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err);
}
}
// Cancel
scope.formReset = function() {
// Defaults
generator.reset();
};
}
}])
.factory('GroupsEdit', ['$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath',
function($rootScope, $location, $log, $routeParams, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath) {
return function(params) {
var group_id = params.group_id;
var generator = GenerateForm;
var form = GroupForm;
var defaultUrl = GetBasePath('groups') + group_id + '/';
var scope = generator.inject(form, { mode: 'edit', modal: true, related: false});
generator.reset();
var master = {};
var relatedSets = {};
scope.formModalActionLabel = 'Save'
scope.formModalHeader = 'Edit Group'
// After the group record is loaded, retrieve any group variables
if (scope.groupLoadedRemove) {
scope.groupLoadedRemove();
}
scope.groupLoadedRemove = scope.$on('groupLoaded', function() {
for (var set in relatedSets) {
scope.search(relatedSets[set].iterator);
}
if (scope.variable_url) {
Rest.setUrl(scope.variable_url);
Rest.get()
.success( function(data, status, headers, config) {
if ($.isEmptyObject(data.data)) {
scope.variables = "\{\}";
}
else {
scope.variables = data.data;
}
})
.error( function(data, status, headers, config) {
scope.variables = null;
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve host variables. GET returned status: ' + status });
});
}
else {
scope.variables = "\{\}";
}
});
// Retrieve detail record and prepopulate the form
Rest.setUrl(defaultUrl);
Rest.get()
.success( function(data, status, headers, config) {
LoadBreadCrumbs();
for (var fld in form.fields) {
if (data[fld]) {
scope[fld] = data[fld];
master[fld] = scope[fld];
}
}
var related = data.related;
for (var set in form.related) {
if (related[set]) {
relatedSets[set] = { url: related[set], iterator: form.related[set].iterator };
}
}
scope.variable_url = data.related.variable_data;
// Initialize related search functions. Doing it here to make sure relatedSets object is populated.
RelatedSearchInit({ scope: scope, form: form, relatedSets: relatedSets });
RelatedPaginateInit({ scope: scope, relatedSets: relatedSets });
scope.$emit('groupLoaded');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve group: ' + id + '. GET status: ' + status });
});
// Save changes to the parent
scope.formModalAction = function() {
try {
// Make sure we have valid JSON
var myjson = JSON.parse(scope.variables);
var data = {}
for (var fld in form.fields) {
data[fld] = scope[fld];
}
Rest.setUrl(defaultUrl);
Rest.put(data)
.success( function(data, status, headers, config) {
if (scope.variables) {
//update group variables
Rest.setUrl(GetBasePath('groups') + data.id + '/variable_data/');
Rest.put({data: scope.variables})
.success( function(data, status, headers, config) {
var base = $location.path().replace(/^\//,'').split('/')[0];
(base == 'groups') ? ReturnToCaller() : ReturnToCaller(1);
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to update group varaibles. PUT returned status: ' + status });
});
}
$('#form-modal').modal('hide');
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to update group: ' + id + '. PUT status: ' + status });
});
}
catch(err) {
Alert("Error", "Error parsing group variables. Expecting valid JSON. Parser returned " + err);
}
};
// Cancel
scope.formReset = function() {
generator.reset();
for (var fld in master) {
scope[fld] = master[fld];
}
}
}
}]);

View File

@@ -9,10 +9,12 @@
*/ */
angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationListDefinition', angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationListDefinition',
'SearchHelper', 'PaginateHelper', 'ListGenerator', 'AuthService' 'SearchHelper', 'PaginateHelper', 'ListGenerator', 'AuthService',
'InventoryHelper', 'RelatedSearchHelper', 'RelatedPaginateHelper',
'InventoryFormDefinition'
]) ])
.factory('TreeInit', ['Alert', 'Rest', 'Authorization', '$http', .factory('LoadTreeData', ['Alert', 'Rest', 'Authorization', '$http',
function(Alert, Rest, Authorization, $http) { function(Alert, Rest, Authorization, $http) {
return function(params) { return function(params) {
@@ -24,15 +26,95 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
var inventory_url = inventory.url; var inventory_url = inventory.url;
var inventory_id = inventory.id; var inventory_id = inventory.id;
var inventory_descr = inventory.description; var inventory_descr = inventory.description;
var tree_id = '#tree-view';
var idx=0; var idx=0;
var treeData = []; var treeData = [];
// Ater inventory top-level hosts, load top-level groups
if (scope.HostLoadedRemove) {
scope.HostLoadedRemove();
}
scope.HostLoadedRemove = scope.$on('hostsLoaded', function() {
Rest.setUrl(groups + '?order_by=name');
Rest.get()
.success( function(data, status, headers, config) {
for (var i=0; i < data.results.length; i++) {
treeData[0].children.push({
data: {
title: data.results[i].name
},
attr: {
id: idx,
group_id: data.results[i].id,
type: 'group',
name: data.results[i].name,
description: data.results[i].description,
inventory: data.results[i].inventory,
all: data.results[i].related.all_hosts,
children: data.results[i].related.children,
hosts: data.results[i].related.hosts,
variable: data.results[i].related.variable_data
},
state: 'closed'
});
idx++;
}
scope.$emit('buildTree', treeData, idx);
})
.error( function(data, status, headers, config) {
Alert('Error', 'Failed to laod tree data. Url: ' + groups + ' GET status: ' + status);
});
});
// Setup tree_data
Rest.setUrl(hosts + '?order_by=name');
Rest.get()
.success ( function(data, status, headers, config) {
treeData =
[{
data: {
title: inventory_name
},
attr: {
type: 'inventory',
id: 'inventory-node',
url: inventory_url,
'inventory_id': inventory_id,
hosts: hosts,
name: inventory_name,
description: inventory_descr
},
state: 'open',
children:[]
}];
scope.$emit('hostsLoaded');
})
.error ( function(data, status, headers, config) {
Alert('Error', 'Failed to laod tree data. Url: ' + hosts + ' GET status: ' + status);
});
}
}])
.factory('TreeInit', ['Alert', 'Rest', 'Authorization', '$http', 'LoadTreeData',
function(Alert, Rest, Authorization, $http, LoadTreeData) {
return function(params) {
var scope = params.scope;
var inventory = params.inventory;
var groups = inventory.related.root_groups;
var hosts = inventory.related.hosts;
var inventory_name = inventory.name;
var inventory_url = inventory.url;
var inventory_id = inventory.id;
var inventory_descr = inventory.description;
var tree_id = '#tree-view';
// After loading the Inventory top-level data, initialize the tree // After loading the Inventory top-level data, initialize the tree
if (scope.buildTreeRemove) { if (scope.buildTreeRemove) {
scope.buildTreeRemove(); scope.buildTreeRemove();
} }
scope.buildTreeRemove = scope.$on('buildTree', function() { scope.buildTreeRemove = scope.$on('buildTree', function(e, treeData, index) {
var idx = index;
$(tree_id).jstree({ $(tree_id).jstree({
"core": { "initially_open":['inventory-node'] }, "core": { "initially_open":['inventory-node'] },
"plugins": ['themes', 'json_data', 'ui', 'contextmenu'], "plugins": ['themes', 'json_data', 'ui', 'contextmenu'],
@@ -83,75 +165,73 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi
}); });
// When user clicks on a group, display the related hosts in the list view // When user clicks on a group, display the related hosts in the list view
$(tree_id).bind("select_node.jstree", function(evt, data){ $(tree_id).bind("select_node.jstree", function(e, data){
//selected node object: data.inst.get_json()[0]; //selected node object: data.inst.get_json()[0];
//selected node text: data.inst.get_json()[0].data //selected node text: data.inst.get_json()[0].data
scope.$emit('NodeSelect',data.inst.get_json()[0]); scope.$emit('NodeSelect',data.inst.get_json()[0]);
}); });
}); });
LoadTreeData(params);
// Ater inventory top-level hosts, load top-level groups
if (scope.HostLoadedRemove) {
scope.HostLoadedRemove();
} }
scope.HostLoadedRemove = scope.$on('hostsLoaded', function() { }])
Rest.setUrl(groups + '?order_by=name');
Rest.get()
.success( function(data, status, headers, config) {
for (var i=0; i < data.results.length; i++) {
treeData[0].children.push({
data: {
title: data.results[i].name
},
attr: {
id: idx,
group_id: data.results[i].id,
type: 'group',
name: data.results[i].name,
description: data.results[i].description,
inventory: data.results[i].inventory,
all: data.results[i].related.all_hosts,
children: data.results[i].related.children,
hosts: data.results[i].related.hosts,
variable: data.results[i].related.variable_data
},
state: 'closed'
});
idx++;
}
scope.$emit('buildTree');
})
.error( function(data, status, headers, config) {
Alert('Error', 'Failed to laod tree data. Url: ' + groups + ' GET status: ' + status);
});
});
// Setup tree_data
Rest.setUrl(hosts + '?order_by=name'); .factory('RefreshTree', ['Alert', 'Rest', 'Authorization', '$http', 'TreeInit',
function(Alert, Rest, Authorization, $http, TreeInit) {
return function(params) {
$('#tree-view').jstree('destroy');
TreeInit(params);
}
}])
.factory('LoadInventory', ['$routeParams', 'Alert', 'Rest', 'Authorization', '$http', 'RefreshTree', 'ProcessErrors',
'RelatedSearchInit', 'RelatedPaginateInit', 'GetBasePath', 'LoadBreadCrumbs', 'InventoryForm',
function($routeParams, Alert, Rest, Authorization, $http, RefreshTree ,ProcessErrors, RelatedSearchInit, RelatedPaginateInit,
GetBasePath, LoadBreadCrumbs, InventoryForm) {
return function(params) {
// Load inventory detail record
var scope = params.scope;
var form = InventoryForm;
scope.relatedSets = [];
scope.master = {};
Rest.setUrl(GetBasePath('inventory') + $routeParams.id + '/');
Rest.get() Rest.get()
.success ( function(data, status, headers, config) { .success( function(data, status, headers, config) {
treeData = LoadBreadCrumbs({ path: '/inventories/' + $routeParams.id, title: data.name });
[{ for (var fld in form.fields) {
data: { if (data[fld]) {
title: inventory_name scope[fld] = data[fld];
}, scope.master[fld] = scope[fld];
attr: { }
type: 'inventory', if (form.fields[fld].type == 'lookup' && data.summary_fields[form.fields[fld].sourceModel]) {
id: 'inventory-node', scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
url: inventory_url, data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
'inventory_id': inventory_id, scope.master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] =
hosts: hosts, scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField];
name: inventory_name, }
description: inventory_descr }
},
state: 'open', // Load the tree view
children:[] scope.TreeParams = { scope: scope, inventory: data };
}]; scope.relatedSets['hosts'] = { url: data.related.hosts, iterator: 'host' };
scope.$emit('hostsLoaded'); RelatedSearchInit({ scope: scope, form: form, relatedSets: scope.relatedSets });
}) RelatedPaginateInit({ scope: scope, relatedSets: scope.relatedSets });
.error ( function(data, status, headers, config) { scope.$emit('inventoryLoaded');
Alert('Error', 'Failed to laod tree data. Url: ' + hosts + ' GET status: ' + status); })
}); .error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, form,
{ hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $routeParams.id + '. GET status: ' + status });
});
} }
}]); }]);

View File

@@ -14,8 +14,9 @@ angular.module('GroupListDefinition', [])
iterator: 'group', iterator: 'group',
selectTitle: 'Add Group', selectTitle: 'Add Group',
editTitle: 'Groups', editTitle: 'Groups',
selectInstructions: 'Click on a row to select it, and click Finished when done. Use the green <i class=\"icon-plus\"></i> button to create a new row.', selectInstructions: 'Click on a row to select it, and Finished when done. Click the green <i class=\"icon-plus\"></i> Add to create a new row.',
index: true, index: true,
well: false,
fields: { fields: {
name: { name: {
@@ -32,7 +33,7 @@ angular.module('GroupListDefinition', [])
label: 'Add', label: 'Add',
icon: 'icon-plus', icon: 'icon-plus',
mode: 'all', // One of: edit, select, all mode: 'all', // One of: edit, select, all
ngClick: 'addGroup()', ngClick: 'createGroup()',
class: 'btn-success btn-small', class: 'btn-success btn-small',
awToolTip: 'Create a new group' awToolTip: 'Create a new group'
} }

View File

@@ -76,9 +76,12 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
element = angular.element(document.getElementById('form-modal-body')); element = angular.element(document.getElementById('form-modal-body'));
} }
else { else {
var element = angular.element(document.getElementById('htmlTemplate')); element = angular.element(document.getElementById('htmlTemplate'));
} }
this.mode = options.mode;
this.modal = (options.modal) ? true : false;
this.setForm(form); this.setForm(form);
element.html(this.build(options)); // Inject the html element.html(this.build(options)); // Inject the html
this.scope = element.scope(); // Set scope specific to the element we're compiling, avoids circular reference this.scope = element.scope(); // Set scope specific to the element we're compiling, avoids circular reference
@@ -94,12 +97,9 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
} }
if (options.modal) { if (options.modal) {
(options.mode == 'add') ? scope.formHeader = form.addTitle : form.editTitle; this.scope.formHeader = (options.mode == 'add') ? form.addTitle : form.editTitle;
$('#form-modal').modal(); $('#form-modal').modal();
} }
this.mode = options.mode;
this.modal = (options.modal) ? true : false;
return this.scope; return this.scope;
}, },
@@ -641,8 +641,12 @@ angular.module('FormGenerator', ['GeneratorHelpers'])
if (form.related[itm].type == 'tree') { if (form.related[itm].type == 'tree') {
html += "<div class=\"span5\">"; html += "<div class=\"span5\">";
html += "<div class=\"inventory-buttons pull-right\">"; html += "<div class=\"inventory-buttons pull-right\">";
html += "<button ng-hide=\"groupAddHide\" id=\"inv-group-add\" class=\"btn btn-mini btn-success\"><i class=\"icon-plus\"></i> Add Group</button>"; html += "<button ng-click=\"addGroup()\" ng-hide=\"groupAddHide\" id=\"inv-group-add\" " +
html += "<button ng-hide=\"groupEditHide\" id=\"inv-group-edit\" class=\"btn btn-mini btn-success\"><i class=\"icon-edit\"></i> Edit Group</button>"; "class=\"btn btn-mini btn-success\"><i class=\"icon-plus\"></i> Add Group</button>";
html += "<button ng-hide=\"groupEditHide\" id=\"inv-group-edit\" class=\"btn btn-mini btn-success\">" +
"<i class=\"icon-edit\"></i> Edit Group</button>";
html += "<button ng-hide=\"groupDeleteHide\" id=\"inv-group-delete\" class=\"btn btn-mini btn-danger\">" +
"<i class=\"icon-remove\"></i> Delete Group</button>";
html += "</div>\n"; html += "</div>\n";
html += "<div id=\"tree-view\"></div>\n"; html += "<div id=\"tree-view\"></div>\n";
html += "</div>\n"; html += "</div>\n";

View File

@@ -67,7 +67,10 @@ angular.module('ListGenerator', ['GeneratorHelpers',])
// For options.mode == 'lookup', include the following: // For options.mode == 'lookup', include the following:
// //
// hdr: <lookup dialog header> // hdr: <lookup dialog header>
// //
// Inject into a custom element using options.id: <'.selector'>
// Control breadcrumb creation with options.breadCrumbs: <true | false>
//
if (options.mode == 'lookup') { if (options.mode == 'lookup') {
var element = angular.element(document.getElementById('lookup-modal-body')); var element = angular.element(document.getElementById('lookup-modal-body'));
} }
@@ -130,7 +133,7 @@ angular.module('ListGenerator', ['GeneratorHelpers',])
html += "</div>\n"; html += "</div>\n";
} }
if (options.mode != 'lookup') { if (options.mode != 'lookup' && (list.well == undefined || list.well == 'true')) {
html += "<div class=\"well\">\n"; html += "<div class=\"well\">\n";
} }
@@ -161,7 +164,7 @@ angular.module('ListGenerator', ['GeneratorHelpers',])
} }
} }
} }
if (options.mode == 'select') { if (options.mode == 'select' && (options.selectButton == undefined || options.selectButton == true)) {
html += " <button class=\"btn btn-small btn-success\" aw-tool-tip=\"Complete your selection\" " + html += " <button class=\"btn btn-small btn-success\" aw-tool-tip=\"Complete your selection\" " +
"ng-click=\"finishSelection()\"><i class=\"icon-ok\"></i> Finished</button>\n"; "ng-click=\"finishSelection()\"><i class=\"icon-ok\"></i> Finished</button>\n";
} }
@@ -272,7 +275,7 @@ angular.module('ListGenerator', ['GeneratorHelpers',])
html += "</tbody>\n"; html += "</tbody>\n";
html += "</table>\n"; html += "</table>\n";
if (options.mode != 'lookup') { if (options.mode != 'lookup' && (list.well == undefined || list.well == 'true')) {
html += "</div>\n"; //well html += "</div>\n"; //well
} }

View File

@@ -72,6 +72,7 @@
<script src="{{ STATIC_URL }}js/helpers/teams.js"></script> <script src="{{ STATIC_URL }}js/helpers/teams.js"></script>
<script src="{{ STATIC_URL }}js/helpers/JobTemplate.js"></script> <script src="{{ STATIC_URL }}js/helpers/JobTemplate.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Lookup.js"></script> <script src="{{ STATIC_URL }}js/helpers/Lookup.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Groups.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/directives.js"></script> <script src="{{ STATIC_URL }}lib/ansible/directives.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/filters.js"></script> <script src="{{ STATIC_URL }}lib/ansible/filters.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/api-loader.js"></script> <script src="{{ STATIC_URL }}lib/ansible/api-loader.js"></script>
@@ -165,6 +166,20 @@
</div> </div>
</div> </div>
<!-- Generic Form dialog -->
<div id="form-modal" class="modal hide">
<div class="modal-header">
<button type="button" class="close" data-target="#alert-modal"
data-dismiss="modal" aria-hidden="true">&times;</button>
<h3 ng-bind="formModalHeader"></h3>
</div>
<div class="modal-body" id="form-modal-body"></div>
<div class="modal-footer">
<a href="#" data-target="#form-modal" data-dismiss="modal" class="btn btn">Cancel</a>
<a href="" ng-bind="formModalActionLabel" ng-click="formModalAction()" class="btn btn-success"></a>
</div>
</div>
<!-- Alerts/error handling dialog --> <!-- Alerts/error handling dialog -->
<div id="alert-modal" class="modal hide"> <div id="alert-modal" class="modal hide">
<div class="modal-header"> <div class="modal-header">