diff --git a/lib/ui/static/css/ansible-ui.css b/lib/ui/static/css/ansible-ui.css index fc157fdc99..9ce20cb80b 100644 --- a/lib/ui/static/css/ansible-ui.css +++ b/lib/ui/static/css/ansible-ui.css @@ -222,3 +222,13 @@ .ask-checkbox { margin-left: 10px; } + + #tree-view { + padding: 0; + margin: 5px 0 15px 0; + } + + .no-padding { + margin: 0; + padding: 0; + } \ No newline at end of file diff --git a/lib/ui/static/js/app.js b/lib/ui/static/js/app.js index 58abf871bc..86bfe262de 100644 --- a/lib/ui/static/js/app.js +++ b/lib/ui/static/js/app.js @@ -33,7 +33,6 @@ angular.module('ansible', [ 'AWFilters', 'HostFormDefinition', 'HostListDefinition', - 'HostHelper', 'GroupFormDefinition', 'GroupListDefinition', 'TeamsListDefinition', @@ -70,7 +69,25 @@ angular.module('ansible', [ controller: GroupsAdd }). when('/inventories/:inventory_id/groups/:id', { templateUrl: urlPrefix + 'partials/inventories.html', - controller: GroupsEdit }). + controller: GroupsEdit }). + + when('/inventories/:inventory_id/groups/:group_id/children', + { templateUrl: urlPrefix + 'partials/inventories.html', controller: GroupsList }). + + when('/inventories/:inventory_id/groups/:group_id/children/add', + { templateUrl: urlPrefix + 'partials/inventories.html', controller: GroupsAdd }). + + when('/inventories/:inventory_id/groups/:group_id/children/:id', + { templateUrl: urlPrefix + 'partials/inventories.html', controller: GroupsEdit }). + + when('/inventories/:inventory_id/groups/:group_id/hosts', + { templateUrl: urlPrefix + 'partials/inventories.html', controller: HostsList }). + + when('/inventories/:inventory_id/groups/:group_id/hosts/add', + { templateUrl: urlPrefix + 'partials/inventories.html', controller: HostsAdd }). + + when('/inventories/:inventory_id/groups/:group_id/hosts/:id', + { templateUrl: urlPrefix + 'partials/inventories.html', controller: HostsEdit }). when('/organizations', { templateUrl: urlPrefix + 'partials/organizations.html', controller: OrganizationsList }). diff --git a/lib/ui/static/js/controllers/Groups.js b/lib/ui/static/js/controllers/Groups.js index 4f30a03cce..a026242813 100644 --- a/lib/ui/static/js/controllers/Groups.js +++ b/lib/ui/static/js/controllers/Groups.js @@ -12,16 +12,17 @@ function GroupsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, GroupList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, - PaginateInit, ReturnToCaller, ClearScope, ProcessErrors) + PaginateInit, ReturnToCaller, ClearScope, ProcessErrors, GetBasePath) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. var list = GroupList; - var defaultUrl = '/api/v1/inventories/' + $routeParams.id + '/groups/'; + var base = $location.path().replace(/^\//,'').split('/')[0]; + var defaultUrl = GetBasePath('groups'); var view = GenerateList; - var paths = $location.path().replace(/^\//,'').split('/'); - var scope = view.inject(GroupList, { mode: 'edit' }); // Inject our view + + var scope = view.inject(GroupList, { mode: 'select' }); // Inject our view scope.selected = []; SearchInit({ scope: scope, set: 'groups', list: list, url: defaultUrl }); @@ -61,21 +62,90 @@ function GroupsList ($scope, $rootScope, $location, $log, $routeParams, Rest, }); } + scope.finishSelection = function() { + var url = ($routeParams.group_id) ? GetBasePath('groups') + $routeParams.group_id + '/children/' : + GetBasePath('inventory') + $routeParams.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 { + ReturnToCaller(1); + } + } + }); + + 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 { + ReturnToCaller(1); + } + } + + scope.toggle_group = function(idx) { + if (scope.selected.indexOf(idx) > -1) { + scope.selected.splice(scope.selected.indexOf(idx),1); + } + else { + scope.selected.push(idx); + } + } + } GroupsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'GroupList', 'GenerateList', - 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInventoryInit']; + 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', + 'GetBasePath' ]; function GroupsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, GroupForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, - ClearScope, LookUpInventoryInit) + ClearScope, 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 = ($routeParams.group_id) ? GetBasePath('groups') + $routeParams.group_id + '/children/' : + GetBasePath('inventory') + $routeParams.inventory_id + '/'; var form = GroupForm; var generator = GenerateForm; var scope = generator.inject(form, {mode: 'add', related: false}); @@ -84,30 +154,18 @@ function GroupsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, LoadBreadCrumbs(); - LookUpInventoryInit({ scope: scope }); - - // Load inventory lookup value - var url = defaultUrl + $routeParams.id + '/'; - Rest.setUrl(url); - Rest.get() - .success( function(data, status, headers, config) { - scope['inventory'] = data.id; - master['inventory'] = data.id; - scope['inventory_name'] = data.name; - master['inventory_name'] = data.name; - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to retrieve: ' + url + '. GET status: ' + status }); - }); - // Save scope.formSave = function() { - Rest.setUrl(defaultUrl + $routeParams.id + '/groups/'); + Rest.setUrl(defaultUrl); var data = {} for (var fld in form.fields) { data[fld] = scope[fld]; - } + } + + if ($routeParams.group_id) { + data['inventory'] = $routeParams.inventory_id; + } + Rest.post(data) .success( function(data, status, headers, config) { ReturnToCaller(1); @@ -127,47 +185,38 @@ function GroupsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, } GroupsAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'GroupForm', 'GenerateForm', - 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'LookUpInventoryInit' ]; + 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GetBasePath' ]; function GroupsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, GroupForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, - RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInventoryInit) + RelatedPaginateInit, ReturnToCaller, ClearScope, GetBasePath) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. - var defaultUrl='/api/v1/groups/'; var generator = GenerateForm; var form = GroupForm; + var defaultUrl = GetBasePath('groups') + $routeParams.id + '/'; var scope = generator.inject(form, {mode: 'edit', related: true}); generator.reset(); var master = {}; var id = $routeParams.id; var relatedSets = {}; - LookUpInventoryInit({ scope: scope }); - // After the Organization is loaded, retrieve each related set - scope.$on('groupLoaded', function() { - Rest.setUrl(scope['inventory_url']); - Rest.get() - .success( function(data, status, headers, config) { - scope['inventory_name'] = data.name; - master['inventory_name'] = data.name; - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to retrieve: ' + scope.orgnization_url + '. GET status: ' + status }); - }); + if (scope.groupLoadedRemove) { + scope.groupLoadedRemove(); + } + scope.groupLoadedRemove = scope.$on('groupLoaded', function() { for (var set in relatedSets) { scope.search(relatedSets[set].iterator); } }); // Retrieve detail record and prepopulate the form - Rest.setUrl(defaultUrl + ':id/'); - Rest.get({ params: {id: id} }) + Rest.setUrl(defaultUrl); + Rest.get() .success( function(data, status, headers, config) { LoadBreadCrumbs(); for (var fld in form.fields) { @@ -185,7 +234,6 @@ function GroupsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams // 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['inventory_url'] = data.related.inventory; scope.$emit('groupLoaded'); }) .error( function(data, status, headers, config) { @@ -195,7 +243,7 @@ function GroupsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams // Save changes to the parent scope.formSave = function() { - Rest.setUrl(defaultUrl + id + '/'); + Rest.setUrl(defaultUrl); var data = {} for (var fld in form.fields) { data[fld] = scope[fld]; @@ -203,11 +251,11 @@ function GroupsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams Rest.put(data) .success( function(data, status, headers, config) { var base = $location.path().replace(/^\//,'').split('/')[0]; - (base == 'inventories') ? ReturnToCaller() : ReturnToCaller(1); + (base == 'groups') ? ReturnToCaller() : ReturnToCaller(1); }) .error( function(data, status, headers, config) { ProcessErrors(scope, data, status, form, - { hdr: 'Error!', msg: 'Failed to update host: ' + $routeParams.id + '. PUT status: ' + status }); + { hdr: 'Error!', msg: 'Failed to update group: ' + $routeParams.id + '. PUT status: ' + status }); }); }; @@ -221,7 +269,7 @@ function GroupsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams } -HostsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'HostForm', +GroupsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'HostForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', - 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInventoryInit' ]; + 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'GetBasePath' ]; \ No newline at end of file diff --git a/lib/ui/static/js/controllers/Hosts.js b/lib/ui/static/js/controllers/Hosts.js index f4d4bc6fbe..ee51ee047d 100644 --- a/lib/ui/static/js/controllers/Hosts.js +++ b/lib/ui/static/js/controllers/Hosts.js @@ -12,23 +12,23 @@ function HostsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, HostList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, - ReturnToCaller, ClearScope, ProcessErrors) + ReturnToCaller, ClearScope, ProcessErrors, GetBasePath) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. - - var list = HostList; - var defaultUrl = '/api/v1/inventories/' + $routeParams.id + '/hosts/'; + + var list = HostList + var base = $location.path().replace(/^\//,'').split('/')[0]; + var defaultUrl = GetBasePath('hosts'); var view = GenerateList; - var paths = $location.path().replace(/^\//,'').split('/'); - var scope = view.inject(HostList, { mode: 'edit' }); // Inject our view + var mode = (base == 'hosts') ? 'edit' : 'select'; + var scope = view.inject(list, { mode: mode }); // Inject our view scope.selected = []; SearchInit({ scope: scope, set: 'hosts', list: list, url: defaultUrl }); PaginateInit({ scope: scope, list: list, url: defaultUrl }); scope.search(list.iterator); - LoadBreadCrumbs(); scope.addHost = function() { @@ -62,21 +62,89 @@ function HostsList ($scope, $rootScope, $location, $log, $routeParams, Rest, }); } + scope.finishSelection = function() { + var url = ($routeParams.group_id) ? GetBasePath('groups') + $routeParams.group_id + '/hosts/' : + GetBasePath('inventory') + $routeParams.inventory_id + '/hosts/'; + + Rest.setUrl(url); // We're assuming the path matches the api path. + // Will this always be true?? + scope.queue = []; + + 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 hosts.'); + } + else { + ReturnToCaller(1); + } + } + }); + + if (scope.selected.length > 0 ) { + var host; + for (var i=0; i < scope.selected.length; i++) { + host = null; + for (var j=0; j < scope.hosts.length; j++) { + if (scope.hosts[j].id == scope.selected[i]) { + host = scope.hosts[j]; + } + } + if (host !== null) { + Rest.post(host) + .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 { + ReturnToCaller(1); + } + } + + scope.toggle_host = function(idx) { + if (scope.selected.indexOf(idx) > -1) { + scope.selected.splice(scope.selected.indexOf(idx),1); + } + else { + scope.selected.push(idx); + } + } + } HostsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'HostList', 'GenerateList', - 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors' ]; + 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', + 'GetBasePath' ]; function HostsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, HostForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, - ClearScope, LookUpInventoryInit) + ClearScope, 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 = ($routeParams.group_id) ? GetBasePath('groups') + $routeParams.group_id + '/hosts/' : + GetBasePath('inventory') + $routeParams.inventory_id + '/hosts/'; var form = HostForm; var generator = GenerateForm; var scope = generator.inject(form, {mode: 'add', related: false}); @@ -85,30 +153,18 @@ function HostsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, LoadBreadCrumbs(); - LookUpInventoryInit({ scope: scope }); - - // Load inventory lookup value - var url = defaultUrl + $routeParams.id + '/'; - Rest.setUrl(url); - Rest.get() - .success( function(data, status, headers, config) { - scope['inventory'] = data.id; - master['inventory'] = data.id; - scope['inventory_name'] = data.name; - master['inventory_name'] = data.name; - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to retrieve: ' + url + '. GET status: ' + status }); - }); - // Save scope.formSave = function() { - Rest.setUrl(defaultUrl + $routeParams.id + '/hosts/'); + Rest.setUrl(defaultUrl); var data = {} for (var fld in form.fields) { data[fld] = scope[fld]; - } + } + + if ($routeParams.group_id) { + data['inventory'] = $routeParams.inventory_id; + } + Rest.post(data) .success( function(data, status, headers, config) { ReturnToCaller(1); @@ -128,39 +184,28 @@ function HostsAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, } HostsAdd.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'HostForm', 'GenerateForm', - 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'LookUpInventoryInit' ]; + 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'ReturnToCaller', 'ClearScope', 'GetBasePath' ]; -function HostsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, UserForm, +function HostsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, HostForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, - RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInventoryInit) + RelatedPaginateInit, ReturnToCaller, ClearScope, GetBasePath) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. - var defaultUrl='/api/v1/hosts/'; + var defaultUrl = GetBasePath('hosts'); var generator = GenerateForm; - var form = UserForm; + var base = $location.path().replace(/^\//,'').split('/')[0]; + var form = HostForm; var scope = generator.inject(form, {mode: 'edit', related: true}); generator.reset(); var master = {}; var id = $routeParams.id; var relatedSets = {}; - LookUpInventoryInit({ scope: scope }); - // After form data loads, load related sets and lookups scope.$on('hostLoaded', function() { - Rest.setUrl(scope['inventory_url']); - Rest.get() - .success( function(data, status, headers, config) { - scope['inventory_name'] = data.name; - master['inventory_name'] = data.name; - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to retrieve: ' + scope.inventory_url + '. GET status: ' + status }); - }); for (var set in relatedSets) { scope.search(relatedSets[set].iterator); } @@ -186,7 +231,6 @@ function HostsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, // 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['inventory_url'] = data.related.inventory; scope.$emit('hostLoaded'); }) .error( function(data, status, headers, config) { @@ -203,8 +247,7 @@ function HostsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, } Rest.put(data) .success( function(data, status, headers, config) { - var base = $location.path().replace(/^\//,'').split('/')[0]; - (base == 'inventories') ? ReturnToCaller() : ReturnToCaller(1); + (base == 'hosts') ? ReturnToCaller() : ReturnToCaller(1); }) .error( function(data, status, headers, config) { ProcessErrors(scope, data, status, form, @@ -224,5 +267,5 @@ function HostsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, HostsEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'HostForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', - 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInventoryInit' ]; + 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'GetBasePath' ]; \ No newline at end of file diff --git a/lib/ui/static/js/controllers/Inventories.js b/lib/ui/static/js/controllers/Inventories.js index 670d7553c7..6ae9ecf628 100644 --- a/lib/ui/static/js/controllers/Inventories.js +++ b/lib/ui/static/js/controllers/Inventories.js @@ -12,7 +12,7 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, InventoryList, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, - ClearScope, ProcessErrors, SetListeners) + ClearScope, ProcessErrors) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -24,7 +24,6 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res var scope = view.inject(InventoryList, { mode: mode }); // Inject our view scope.selected = []; - SetListeners({ scope: scope, set: 'inventories', iterator: list.iterator }); SearchInit({ scope: scope, set: 'inventories', list: list, url: defaultUrl }); PaginateInit({ scope: scope, list: list, url: defaultUrl }); scope.search(list.iterator); @@ -86,13 +85,6 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res for (var i=0; i < scope.queue.length; i++) { if (scope.queue[i].result == 'error') { errors++; - // there is no way to know which user raised the error. no data comes - // back from the api call. - // $('td.username-column').each(function(index) { - // if ($(this).text() == scope.queue[i].username) { - // $(this).addClass("error"); - // } - // }); } } if (errors > 0) { @@ -141,13 +133,12 @@ function InventoriesList ($scope, $rootScope, $location, $log, $routeParams, Res } InventoriesList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'InventoryList', 'GenerateList', - 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors', - 'SetListeners' ]; + 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors' ]; function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, ReturnToCaller, ClearScope, - GenerateList, OrganizationList, SearchInit, PaginateInit, LookUpOrganizationInit) + GenerateList, OrganizationList, SearchInit, PaginateInit, LookUpInit) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. @@ -159,7 +150,14 @@ function InventoriesAdd ($scope, $rootScope, $compile, $location, $log, $routePa var scope = generator.inject(form, {mode: 'add', related: false}); generator.reset(); LoadBreadCrumbs(); - LookUpOrganizationInit({ scope: scope }); + + LookUpInit({ + scope: scope, + form: form, + current_item: ($routeParams.organization_id) ? $routeParams.organization_id : null, + list: OrganizationList, + field: 'organization' + }); // Save scope.formSave = function() { @@ -187,44 +185,32 @@ 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', 'LookUpOrganizationInit' ]; + 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit' ]; function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors, LoadBreadCrumbs, RelatedSearchInit, - RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpOrganizationInit, Prompt, - TreeInit) + RelatedPaginateInit, ReturnToCaller, ClearScope, LookUpInit, Prompt, + OrganizationList, TreeInit, GetBasePath) { ClearScope('htmlTemplate'); //Garbage collection. Don't leave behind any listeners/watchers from the prior //scope. - var defaultUrl='/api/v1/inventories/'; var generator = GenerateForm; var form = InventoryForm; + var defaultUrl=GetBasePath('inventory'); 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 = {}; - - LookUpOrganizationInit({ scope: scope }); - + // After inventory is loaded, retrieve each related set and any lookups scope.$on('inventoryLoaded', function() { - Rest.setUrl(scope['organization_url']); - Rest.get() - .success( function(data, status, headers, config) { - scope['organization_name'] = data.name; - master['organization_name'] = data.name; - }) - .error( function(data, status, headers, config) { - ProcessErrors(scope, data, status, null, - { hdr: 'Error!', msg: 'Failed to retrieve: ' + scope.orgnization_url + '. GET status: ' + status }); - }); for (var set in relatedSets) { scope.search(relatedSets[set].iterator); - } + } }); // Retrieve detail record and prepopulate the form @@ -237,7 +223,22 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP 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' + }); + var related = data.related; for (var set in form.related) { if (related[set]) { @@ -246,12 +247,11 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP } // Load the tree view - TreeInit(related.groups); + TreeInit({ scope: scope, inventory: 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['organization_url'] = data.related.organization; scope.$emit('inventoryLoaded'); }) .error( function(data, status, headers, config) { @@ -261,7 +261,7 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP // Save changes to the parent scope.formSave = function() { - Rest.setUrl(defaultUrl + $routeParams.id); + Rest.setUrl(defaultUrl + $routeParams.id + '/'); var data = {} for (var fld in form.fields) { data[fld] = scope[fld]; @@ -321,12 +321,142 @@ function InventoriesEdit ($scope, $rootScope, $compile, $location, $log, $routeP action: action }); + }; + + function changePath(path) { + // For reasons unknown, calling $location.path() from inside + // treeController fails to work. This is the work-around. + window.location = '/#' + path; + }; + + scope.treeController = function($node) { + if ($($node).attr('type') == 'host') { + return { + edit: { + label: 'Edit Host', + action: function(obj) { changePath($location.path() + '/hosts/' + $(obj).attr('id')); } + }, + delete: { + label: 'Delete Host', + action: function(obj) { + var action_to_take = function() { + var url = defaultUrl + $routeParams.id + '/hosts/'; + Rest.setUrl(url); + Rest.post({ id: $(obj).attr('id'), disassociate: 1 }) + .success( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + $('#tree-view').jstree("delete_node",obj); + }) + .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 }); + }); + }; + //Force binds to work. Not working usual way. + $('#prompt-header').text('Delete'); + $('#prompt-body').text('Are you sure you want to delete host ' + $(obj).attr('name') + '?'); + $('#prompt-action-btn').addClass('btn-danger'); + scope.promptAction = action_to_take; // for some reason this binds? + $('#prompt-modal').modal({ + backdrop: 'static', + keyboard: true, + show: true + }); + } + }, + vairables: { + label: 'Host Variables', + separator_before: true, + "_disabled": true, + action: function(obj) { } + } + } + } + else if ($($node).attr('type') == 'inventory') { + return { + addGroup: { + label: 'Add Group', + action: function() { changePath($location.path() + '/groups'); } + }, + addHost: { + label: 'Add Host', + action: function() { changePath($location.path() + '/hosts'); } + }, + variables: { + label: 'Inventory variables', + action: function(obj) { }, + "_disabled": true, + separator_before: true + } + } + } + else { + return { + addGroup: { + label: 'Add Subgroup', + action: function(obj) { + LoadBreadCrumbs({ path: '/groups/' + $(obj).attr('id'), title: $(obj).attr('name') }); + changePath($location.path() + '/groups/' + $(obj).attr('id') + '/children'); + } + }, + addHost: { + label: 'Add Host', + action: function(obj) { + LoadBreadCrumbs({ path: '/groups/' + $(obj).attr('id'), title: $(obj).attr('name') }); + changePath($location.path() + '/groups/' + $(obj).attr('id') + '/hosts'); + } + }, + edit: { + label: 'Edit Group', + action: function(obj) { changePath($location.path() + '/groups/' + $(obj).attr('id')); }, + separator_before: true + }, + delete: { + label: 'Delete Group', + action: function(obj) { + var action_to_take = function() { + var url = defaultUrl + $routeParams.id + '/groups/'; + Rest.setUrl(url); + Rest.post({ id: $(obj).attr('id'), disassociate: 1 }) + .success( function(data, status, headers, config) { + $('#prompt-modal').modal('hide'); + $('#tree-view').jstree("delete_node",obj); + }) + .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 }); + }); + }; + //Force binds to work. Not working usual way. + var parent = $.jstree._reference('#tree-view')._get_parent(obj); + $('#prompt-header').text('Delete Group'); + $('#prompt-body').text('Are you sure you want to remove group ' + $(obj).attr('name') + + ' from ' + $(parent).attr('name') + '?'); + $('#prompt-action-btn').addClass('btn-danger'); + scope.promptAction = action_to_take; // for some reason this binds? + $('#prompt-modal').modal({ + backdrop: 'static', + keyboard: true, + show: true + }); + } + }, + variables: { + label: 'Group variables', + action: function(obj) { }, + "_disabled": true, + separator_before: true + } + } + } } } InventoriesEdit.$inject = [ '$scope', '$rootScope', '$compile', '$location', '$log', '$routeParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert', 'ProcessErrors', 'LoadBreadCrumbs', 'RelatedSearchInit', - 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpOrganizationInit', 'Prompt', - 'TreeInit' ]; + 'RelatedPaginateInit', 'ReturnToCaller', 'ClearScope', 'LookUpInit', 'Prompt', + 'OrganizationList', 'TreeInit', 'GetBasePath']; \ No newline at end of file diff --git a/lib/ui/static/js/controllers/Users.js b/lib/ui/static/js/controllers/Users.js index 549bc49bb8..fa0d0a4b44 100644 --- a/lib/ui/static/js/controllers/Users.js +++ b/lib/ui/static/js/controllers/Users.js @@ -79,13 +79,6 @@ function UsersList ($scope, $rootScope, $location, $log, $routeParams, Rest, for (var i=0; i < scope.queue.length; i++) { if (scope.queue[i].result == 'error') { errors++; - // there is no way to know which user raised the error. no data comes - // back from the api call. - // $('td.username-column').each(function(index) { - // if ($(this).text() == scope.queue[i].username) { - // $(this).addClass("error"); - // } - // }); } } if (errors > 0) { diff --git a/lib/ui/static/js/forms/Groups.js b/lib/ui/static/js/forms/Groups.js index 549de8a61d..30fa153f72 100644 --- a/lib/ui/static/js/forms/Groups.js +++ b/lib/ui/static/js/forms/Groups.js @@ -30,12 +30,9 @@ angular.module('GroupFormDefinition', []) }, inventory: { label: 'Inventory', - type: 'lookup', - sourceModel: 'inventory', - sourceField: 'name', - addRequired: true, - editRequired: true, - ngClick: 'lookUpInventory()' + type: 'hidden', + includeOnEdit: true, + includeOnAdd: true } }, diff --git a/lib/ui/static/js/forms/Hosts.js b/lib/ui/static/js/forms/Hosts.js index 1b8c3de10b..f303a1e08e 100644 --- a/lib/ui/static/js/forms/Hosts.js +++ b/lib/ui/static/js/forms/Hosts.js @@ -29,14 +29,19 @@ angular.module('HostFormDefinition', []) editRequired: false }, inventory: { - label: 'Inventory', - type: 'lookup', - sourceModel: 'inventory', - sourceField: 'name', - addRequired: true, - editRequired: true, - ngClick: 'lookUpInventory()' + type: 'hidden', + includeOnEdit: true, + includeOnAdd: true } + // inventory: { + // label: 'Inventory', + // type: 'lookup', + // sourceModel: 'inventory', + // sourceField: 'name', + // addRequired: true, + // editRequired: true, + // ngClick: 'lookUpInventory()' + // } }, buttons: { //for now always generates \n"; } - if (this.has('buttons')) { - html += "\n"; - html += "\n"; - } - html += "\n"; if ( this.has('well') ) { html += "\n"; } - if (options.related && this.form.related) { + if ((!this.modal) && options.related && this.form.related) { html += this.buildCollections(); } - console.log(html); + //console.log(html); return html; @@ -371,9 +397,30 @@ angular.module('FormGenerator', ['GeneratorHelpers']) } html += "\">\n"; html += "
\n"; - + + if (form.related[itm].instructions) { + html += "
\n"; + html += "\n"; + html += "Hint: " + form.related[itm].instructions + "\n"; + html += "
\n" + } + if (form.related[itm].type == 'tree') { - html += "
\n"; + html += "
\n"; + // Add actions(s) + if (form.related[itm].actions && form.related[itm].actions.length > 0) { + html += "
\n"; + for (var act in form.related[itm].actions) { + var action = form.related[itm].actions[act]; + html += "
\n"; + } + html += "
\n"; + html += "
\n"; } else { html += "
\n"; diff --git a/lib/ui/static/lib/ansible/list-generator.js b/lib/ui/static/lib/ansible/list-generator.js index fad4b99f05..d3a6997b37 100644 --- a/lib/ui/static/lib/ansible/list-generator.js +++ b/lib/ui/static/lib/ansible/list-generator.js @@ -129,7 +129,6 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) if (options.mode != 'lookup') { //actions base = $location.path().replace(/^\//,'').split('/')[0]; - console.log('base: ' + base); html += "
\n"; for (action in list.actions) { if (list.actions[action].mode == 'all' || list.actions[action].mode == options.mode) { @@ -227,7 +226,6 @@ angular.module('ListGenerator', ['GeneratorHelpers',]) else { html += PaginateWidget({ set: list.name, iterator: list.iterator, mini: true }); } - return html; } diff --git a/lib/ui/static/lib/ansible/prompt-dialog.js b/lib/ui/static/lib/ansible/prompt-dialog.js index bc90c1726d..83aca806aa 100644 --- a/lib/ui/static/lib/ansible/prompt-dialog.js +++ b/lib/ui/static/lib/ansible/prompt-dialog.js @@ -26,8 +26,8 @@ angular.module('PromptDialog', []) var cls = (params.class == null || params.class == undefined) ? 'btn-danger' : params.class; $('#prompt-action-btn').addClass(cls); //Use jquery because django template engine conflicts with Angular's // use of {{...}} - scope.id = params.id; - scope.url = params.url; + //scope.id = params.id; + //scope.url = params.url; scope.promptAction = params.action; $(dialog).modal({ backdrop: 'static', diff --git a/lib/ui/static/lib/ansible/rest-services.js b/lib/ui/static/lib/ansible/rest-services.js index 2f03e6ca9b..a6c3660d2e 100644 --- a/lib/ui/static/lib/ansible/rest-services.js +++ b/lib/ui/static/lib/ansible/rest-services.js @@ -30,12 +30,12 @@ angular.module('RestServices',['ngCookies','AuthService']) get: function(args) { args = (args) ? args : {}; - this.params = (args.params != null || args.params != undefined) ? args.params : null; + this.params = (args.params) ? args.params : null; this.pReplace(); return $http({method: 'GET', url: this.url, headers: this.auth, - params: this.params, + params: this.params }); }, post: function(data) { diff --git a/lib/ui/templates/ui/index.html b/lib/ui/templates/ui/index.html index abc513dbba..3352fdd6eb 100644 --- a/lib/ui/templates/ui/index.html +++ b/lib/ui/templates/ui/index.html @@ -55,8 +55,6 @@ - - @@ -108,18 +106,18 @@
- +
- +