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 += "