From b19e1dd97afe1ebd0b3a574c9ebca9dc643f26a5 Mon Sep 17 00:00:00 2001 From: chouseknecht Date: Mon, 3 Jun 2013 07:08:08 -0400 Subject: [PATCH] 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. --- ansibleworks/ui/static/css/ansible-ui.css | 4 + ansibleworks/ui/static/js/app.js | 3 +- .../ui/static/js/controllers/Inventories.js | 84 ++-- ansibleworks/ui/static/js/forms/Groups.js | 3 +- ansibleworks/ui/static/js/helpers/Groups.js | 381 ++++++++++++++++++ .../ui/static/js/helpers/inventory.js | 208 +++++++--- ansibleworks/ui/static/js/lists/Groups.js | 5 +- .../ui/static/lib/ansible/form-generator.js | 18 +- .../ui/static/lib/ansible/list-generator.js | 11 +- ansibleworks/ui/templates/ui/index.html | 15 + 10 files changed, 593 insertions(+), 139 deletions(-) create mode 100644 ansibleworks/ui/static/js/helpers/Groups.js diff --git a/ansibleworks/ui/static/css/ansible-ui.css b/ansibleworks/ui/static/css/ansible-ui.css index 232fc4097d..bbd82e7f15 100644 --- a/ansibleworks/ui/static/css/ansible-ui.css +++ b/ansibleworks/ui/static/css/ansible-ui.css @@ -348,4 +348,8 @@ #hosts-title { margin-bottom: 15px; + } + + #tree-view { + min-height: 100px; } \ No newline at end of file diff --git a/ansibleworks/ui/static/js/app.js b/ansibleworks/ui/static/js/app.js index fcc82e6a05..14bffc832b 100644 --- a/ansibleworks/ui/static/js/app.js +++ b/ansibleworks/ui/static/js/app.js @@ -49,7 +49,8 @@ angular.module('ansible', [ 'JobFormDefinition', 'JobEventsListDefinition', 'JobEventFormDefinition', - 'JobHostDefinition' + 'JobHostDefinition', + 'GroupsHelper' ]) .config(['$routeProvider', function($routeProvider) { $routeProvider. diff --git a/ansibleworks/ui/static/js/controllers/Inventories.js b/ansibleworks/ui/static/js/controllers/Inventories.js index f33b614651..f9e3313429 100644 --- a/ansibleworks/ui/static/js/controllers/Inventories.js +++ b/ansibleworks/ui/static/js/controllers/Inventories.js @@ -147,13 +147,13 @@ InventoriesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$route function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, 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 //scope. // Inject dynamic view - var defaultUrl = '/api/v1/inventories/'; + var defaultUrl = GetBasePath('inventory'); var form = InventoryForm; var generator = GenerateForm; 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', '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, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, 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 //scope. @@ -214,11 +214,8 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP var scope = generator.inject(form, {mode: 'edit', related: true}); generator.reset(); var base = $location.path().replace(/^\//,'').split('/')[0]; - var master = {}; var id = $routeParams.id; - var relatedSets = {}; - var hostsUrl; - + scope['inventory_id'] = id; // 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.groupTitle = 'All Hosts'; scope.createButtonShow = false; - scope.search(relatedSets['hosts'].iterator); + scope.search(scope.relatedSets['hosts'].iterator); + TreeInit(scope.TreeParams); }); - // Retrieve detail record and prepopulate the form - 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 }); - }); + LoadInventory({ scope: scope }); // Save changes to the parent scope.formSave = function() { @@ -291,8 +252,8 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP // Cancel scope.formReset = function() { generator.reset(); - for (var fld in master) { - scope[fld] = master[fld]; + for (var fld in scope.master) { + scope[fld] = scope.master[fld]; } }; @@ -419,37 +380,42 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP url = node.attr('all'); scope.groupAddHide = false; scope.groupEditHide =false; + scope.groupDeleteHide = false; scope.createButtonShow = true; scope.group_id = node.attr('group_id'); scope.groupName = n.data; scope.groupTitle = n.data; 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') { url = node.attr('hosts'); scope.groupAddHide = false; scope.groupEditHide =true; + scope.groupDeleteHide = true; scope.createButtonShow = false; scope.groupName = 'All Hosts'; scope.groupTitle = 'All Hosts'; + scope.group_id = null; } - relatedSets['hosts'] = { url: url, iterator: 'host' }; - RelatedSearchInit({ scope: scope, form: form, relatedSets: relatedSets }); - RelatedPaginateInit({ scope: scope, relatedSets: relatedSets }); + scope.relatedSets['hosts'] = { url: url, iterator: 'host' }; + RelatedSearchInit({ scope: scope, form: form, relatedSets: scope.relatedSets }); + RelatedPaginateInit({ scope: scope, relatedSets: scope.relatedSets }); scope.search('host'); 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', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', - 'OrganizationList', 'TreeInit', 'GetBasePath' + 'OrganizationList', 'TreeInit', 'GetBasePath', 'GroupsList', 'GroupsEdit', 'LoadInventory' ]; \ No newline at end of file diff --git a/ansibleworks/ui/static/js/forms/Groups.js b/ansibleworks/ui/static/js/forms/Groups.js index fb16f4b2a2..689aebd509 100644 --- a/ansibleworks/ui/static/js/forms/Groups.js +++ b/ansibleworks/ui/static/js/forms/Groups.js @@ -13,7 +13,7 @@ angular.module('GroupFormDefinition', []) addTitle: 'Create Group', //Legend in add mode editTitle: '{{ name }}', //Legend in edit mode name: 'group', //Form name attribute - well: true, //Wrap the form with TB well + well: false, //Wrap the form with TB well fields: { name: { @@ -34,7 +34,6 @@ angular.module('GroupFormDefinition', []) addRequired: false, editRequird: false, rows: 10, - class: 'span12', default: "\{\}", dataTitle: 'Group Variables', dataPlacement: 'right', diff --git a/ansibleworks/ui/static/js/helpers/Groups.js b/ansibleworks/ui/static/js/helpers/Groups.js new file mode 100644 index 0000000000..df0bc7bdaa --- /dev/null +++ b/ansibleworks/ui/static/js/helpers/Groups.js @@ -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]; + } + } + } + }]); + + diff --git a/ansibleworks/ui/static/js/helpers/inventory.js b/ansibleworks/ui/static/js/helpers/inventory.js index 8b20a8ed8d..d1aae05225 100644 --- a/ansibleworks/ui/static/js/helpers/inventory.js +++ b/ansibleworks/ui/static/js/helpers/inventory.js @@ -9,10 +9,12 @@ */ 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) { return function(params) { @@ -24,15 +26,95 @@ angular.module('InventoryHelper', [ 'RestServices', 'Utilities', 'OrganizationLi var inventory_url = inventory.url; var inventory_id = inventory.id; var inventory_descr = inventory.description; - var tree_id = '#tree-view'; var idx=0; 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 if (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({ "core": { "initially_open":['inventory-node'] }, "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 - $(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 text: data.inst.get_json()[0].data 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() - .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); - }); + .success( function(data, status, headers, config) { + LoadBreadCrumbs({ path: '/inventories/' + $routeParams.id, title: data.name }); + for (var fld in form.fields) { + if (data[fld]) { + scope[fld] = data[fld]; + scope.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]; + scope.master[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField] = + scope[form.fields[fld].sourceModel + '_' + form.fields[fld].sourceField]; + } + } + + // Load the tree view + scope.TreeParams = { scope: scope, inventory: data }; + scope.relatedSets['hosts'] = { url: data.related.hosts, iterator: 'host' }; + RelatedSearchInit({ scope: scope, form: form, relatedSets: scope.relatedSets }); + RelatedPaginateInit({ scope: scope, relatedSets: scope.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 }); + }); } }]); + diff --git a/ansibleworks/ui/static/js/lists/Groups.js b/ansibleworks/ui/static/js/lists/Groups.js index 43fd4ee290..8217c9cc32 100644 --- a/ansibleworks/ui/static/js/lists/Groups.js +++ b/ansibleworks/ui/static/js/lists/Groups.js @@ -14,8 +14,9 @@ angular.module('GroupListDefinition', []) iterator: 'group', selectTitle: 'Add Group', editTitle: 'Groups', - selectInstructions: 'Click on a row to select it, and click Finished when done. Use the green button to create a new row.', + selectInstructions: 'Click on a row to select it, and Finished when done. Click the green Add to create a new row.', index: true, + well: false, fields: { name: { @@ -32,7 +33,7 @@ angular.module('GroupListDefinition', []) label: 'Add', icon: 'icon-plus', mode: 'all', // One of: edit, select, all - ngClick: 'addGroup()', + ngClick: 'createGroup()', class: 'btn-success btn-small', awToolTip: 'Create a new group' } diff --git a/ansibleworks/ui/static/lib/ansible/form-generator.js b/ansibleworks/ui/static/lib/ansible/form-generator.js index d474d980ea..51ff8a4281 100644 --- a/ansibleworks/ui/static/lib/ansible/form-generator.js +++ b/ansibleworks/ui/static/lib/ansible/form-generator.js @@ -76,9 +76,12 @@ angular.module('FormGenerator', ['GeneratorHelpers']) element = angular.element(document.getElementById('form-modal-body')); } 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); element.html(this.build(options)); // Inject the html 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) { - (options.mode == 'add') ? scope.formHeader = form.addTitle : form.editTitle; + this.scope.formHeader = (options.mode == 'add') ? form.addTitle : form.editTitle; $('#form-modal').modal(); } - - this.mode = options.mode; - this.modal = (options.modal) ? true : false; return this.scope; }, @@ -641,8 +641,12 @@ angular.module('FormGenerator', ['GeneratorHelpers']) if (form.related[itm].type == 'tree') { html += "
"; html += "
"; - html += ""; - html += ""; + html += ""; + html += ""; + html += ""; html += "
\n"; html += "
\n"; html += "
\n"; diff --git a/ansibleworks/ui/static/lib/ansible/list-generator.js b/ansibleworks/ui/static/lib/ansible/list-generator.js index 8cb6d98c3c..01c911d15c 100644 --- a/ansibleworks/ui/static/lib/ansible/list-generator.js +++ b/ansibleworks/ui/static/lib/ansible/list-generator.js @@ -67,7 +67,10 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) // For options.mode == 'lookup', include the following: // // hdr: - // + // + // Inject into a custom element using options.id: <'.selector'> + // Control breadcrumb creation with options.breadCrumbs: + // if (options.mode == 'lookup') { var element = angular.element(document.getElementById('lookup-modal-body')); } @@ -130,7 +133,7 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) html += "\n"; } - if (options.mode != 'lookup') { + if (options.mode != 'lookup' && (list.well == undefined || list.well == 'true')) { html += "
\n"; } @@ -161,7 +164,7 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) } } } - if (options.mode == 'select') { + if (options.mode == 'select' && (options.selectButton == undefined || options.selectButton == true)) { html += " \n"; } @@ -272,7 +275,7 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) html += "\n"; html += "\n"; - if (options.mode != 'lookup') { + if (options.mode != 'lookup' && (list.well == undefined || list.well == 'true')) { html += "
\n"; //well } diff --git a/ansibleworks/ui/templates/ui/index.html b/ansibleworks/ui/templates/ui/index.html index 16ca71acaa..4601fffa01 100644 --- a/ansibleworks/ui/templates/ui/index.html +++ b/ansibleworks/ui/templates/ui/index.html @@ -72,6 +72,7 @@ + @@ -165,6 +166,20 @@ + + +