diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index 55753b9f40..aabeec0f4c 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -373,7 +373,6 @@ var tower = angular.module('Tower', [
}
}).
-<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35
state('inventories', {
url: '/inventories',
templateUrl: urlPrefix + 'partials/inventories.html',
@@ -384,22 +383,6 @@ var tower = angular.module('Tower', [
},
ncyBreadcrumb: {
label: "INVENTORIES"
-=======
- state('organizations', {
- url: '/organizations',
- templateUrl: urlPrefix + 'partials/organizations.html',
- controller: OrganizationsList,
- data: {
- activityStream: true,
- activityStreamTarget: 'organization'
- },
- ncyBreadcrumb: {
- parent: function($scope) {
- $scope.$parent.$emit("ReloadOrgListView");
- return "setup";
- },
- label: "ORGANIZATIONS"
->>>>>>> Further modularization.
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
@@ -408,7 +391,6 @@ var tower = angular.module('Tower', [
}
}).
-<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35
state('inventories.add', {
url: '/add',
templateUrl: urlPrefix + 'partials/inventories.html',
@@ -416,15 +398,6 @@ var tower = angular.module('Tower', [
ncyBreadcrumb: {
parent: "inventories",
label: "CREATE INVENTORY"
-=======
- state('organizations.add', {
- url: '/add',
- templateUrl: urlPrefix + 'partials/organizations.crud.html',
- controller: OrganizationsAdd,
- ncyBreadcrumb: {
- parent: "organizations",
- label: "CREATE ORGANIZATION"
->>>>>>> Further modularization.
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
@@ -433,7 +406,6 @@ var tower = angular.module('Tower', [
}
}).
-<<<<<<< 3e32787490a4faf3899b1a5d125475e73521ef35
state('inventories.edit', {
url: '/:inventory_id',
templateUrl: urlPrefix + 'partials/inventories.html',
@@ -456,18 +428,6 @@ var tower = angular.module('Tower', [
activityStream: true,
activityStreamTarget: 'inventory',
activityStreamId: 'inventory_id'
-=======
- state('organizations.edit', {
- url: '/:organization_id',
- templateUrl: urlPrefix + 'partials/organizations.crud.html',
- controller: OrganizationsEdit,
- data: {
- activityStreamId: 'organization_id'
- },
- ncyBreadcrumb: {
- parent: "organizations",
- label: "{{name}}"
->>>>>>> Further modularization.
},
resolve: {
features: ['FeaturesService', function(FeaturesService) {
diff --git a/awx/ui/client/src/inventory/inventory-add.controller.js b/awx/ui/client/src/inventory/inventory-add.controller.js
new file mode 100644
index 0000000000..a0903f4fab
--- /dev/null
+++ b/awx/ui/client/src/inventory/inventory-add.controller.js
@@ -0,0 +1,98 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+import '../job-templates/main';
+
+function InventoriesAdd($scope, $rootScope, $compile, $location, $log,
+ $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors,
+ ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
+ PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
+ $state) {
+
+ ClearScope();
+
+ // Inject dynamic view
+ var defaultUrl = GetBasePath('inventory'),
+ form = InventoryForm(),
+ generator = GenerateForm;
+
+ form.well = true;
+ form.formLabelSize = null;
+ form.formFieldSize = null;
+
+ generator.inject(form, { mode: 'add', related: false, scope: $scope });
+
+ generator.reset();
+
+ $scope.parseType = 'yaml';
+ ParseTypeChange({
+ scope: $scope,
+ variable: 'variables',
+ parse_variable: 'parseType',
+ field_id: 'inventory_variables'
+ });
+
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: ($stateParams.organization_id) ? $stateParams.organization_id : null,
+ list: OrganizationList,
+ field: 'organization',
+ input_type: 'radio'
+ });
+
+ // Save
+ $scope.formSave = function () {
+ generator.clearApiErrors();
+ Wait('start');
+ try {
+ var fld, json_data, data;
+
+ json_data = ToJSON($scope.parseType, $scope.variables, true);
+
+ data = {};
+ for (fld in form.fields) {
+ if (form.fields[fld].realName) {
+ data[form.fields[fld].realName] = $scope[fld];
+ } else {
+ data[fld] = $scope[fld];
+ }
+ }
+
+ Rest.setUrl(defaultUrl);
+ Rest.post(data)
+ .success(function (data) {
+ var inventory_id = data.id;
+ Wait('stop');
+ $location.path('/inventories/' + inventory_id + '/manage');
+ })
+ .error(function (data, status) {
+ ProcessErrors( $scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to add new inventory. Post returned status: ' + status });
+ });
+ } catch (err) {
+ Wait('stop');
+ Alert("Error", "Error parsing inventory variables. Parser returned: " + err);
+ }
+
+ };
+
+ $scope.formCancel = function () {
+ $state.transitionTo('inventories');
+ };
+}
+
+export default['$scope', '$rootScope', '$compile', '$location',
+ '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
+ 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
+ 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
+ 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state', InventoriesAdd]
diff --git a/awx/ui/client/src/inventory/inventory-edit.controller.js b/awx/ui/client/src/inventory/inventory-edit.controller.js
new file mode 100644
index 0000000000..dd45a566ac
--- /dev/null
+++ b/awx/ui/client/src/inventory/inventory-edit.controller.js
@@ -0,0 +1,332 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+import '../job-templates/main';
+
+function InventoriesEdit($scope, $rootScope, $compile, $location,
+ $log, $stateParams, InventoryForm, GenerateForm, Rest, Alert, ProcessErrors,
+ ReturnToCaller, ClearScope, generateList, OrganizationList, SearchInit,
+ PaginateInit, LookUpInit, GetBasePath, ParseTypeChange, Wait, ToJSON,
+ ParseVariableString, RelatedSearchInit, RelatedPaginateInit,
+ Prompt, PlaybookRun, CreateDialog, deleteJobTemplate, $state) {
+
+ ClearScope();
+
+ // Inject dynamic view
+ var defaultUrl = GetBasePath('inventory'),
+ form = InventoryForm(),
+ generator = GenerateForm,
+ inventory_id = $stateParams.inventory_id,
+ master = {},
+ fld, json_data, data,
+ relatedSets = {};
+
+ form.well = true;
+ form.formLabelSize = null;
+ form.formFieldSize = null;
+ $scope.inventory_id = inventory_id;
+ generator.inject(form, { mode: 'edit', related: true, scope: $scope });
+
+ generator.reset();
+
+
+ // After the project is loaded, retrieve each related set
+ if ($scope.inventoryLoadedRemove) {
+ $scope.inventoryLoadedRemove();
+ }
+ $scope.projectLoadedRemove = $scope.$on('inventoryLoaded', function () {
+ var set;
+ for (set in relatedSets) {
+ $scope.search(relatedSets[set].iterator);
+ }
+ });
+
+ Wait('start');
+ Rest.setUrl(GetBasePath('inventory') + inventory_id + '/');
+ Rest.get()
+ .success(function (data) {
+ var fld;
+ for (fld in form.fields) {
+ if (fld === 'variables') {
+ $scope.variables = ParseVariableString(data.variables);
+ master.variables = $scope.variables;
+ } else if (fld === 'inventory_name') {
+ $scope[fld] = data.name;
+ master[fld] = $scope[fld];
+ } else if (fld === 'inventory_description') {
+ $scope[fld] = data.description;
+ master[fld] = $scope[fld];
+ } else if (data[fld]) {
+ $scope[fld] = data[fld];
+ master[fld] = $scope[fld];
+ }
+ if (form.fields[fld].sourceModel && data.summary_fields &&
+ 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] =
+ data.summary_fields[form.fields[fld].sourceModel][form.fields[fld].sourceField];
+ }
+ }
+ relatedSets = form.relatedSets(data.related);
+
+ // 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
+ });
+
+ Wait('stop');
+ $scope.parseType = 'yaml';
+ ParseTypeChange({
+ scope: $scope,
+ variable: 'variables',
+ parse_variable: 'parseType',
+ field_id: 'inventory_variables'
+ });
+ LookUpInit({
+ scope: $scope,
+ form: form,
+ current_item: $scope.organization,
+ list: OrganizationList,
+ field: 'organization',
+ input_type: 'radio'
+ });
+ $scope.$emit('inventoryLoaded');
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Failed to get inventory: ' + inventory_id + '. GET returned: ' + status });
+ });
+ // Save
+ $scope.formSave = function () {
+ Wait('start');
+
+ // Make sure we have valid variable data
+ json_data = ToJSON($scope.parseType, $scope.variables);
+
+ data = {};
+ for (fld in form.fields) {
+ if (form.fields[fld].realName) {
+ data[form.fields[fld].realName] = $scope[fld];
+ } else {
+ data[fld] = $scope[fld];
+ }
+ }
+
+ Rest.setUrl(defaultUrl + inventory_id + '/');
+ Rest.put(data)
+ .success(function () {
+ Wait('stop');
+ $location.path('/inventories/');
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, form, { hdr: 'Error!',
+ msg: 'Failed to update inventory. PUT returned status: ' + status });
+ });
+ };
+
+ $scope.manageInventory = function(){
+ $location.path($location.path() + '/manage');
+ };
+
+ $scope.formCancel = function () {
+ $state.transitionTo('inventories');
+ };
+
+ $scope.addScanJob = function(){
+ $location.path($location.path()+'/job_templates/add');
+ };
+
+ $scope.launchScanJob = function(){
+ PlaybookRun({ scope: $scope, id: this.scan_job_template.id });
+ };
+
+ $scope.scheduleScanJob = function(){
+ $location.path('/job_templates/'+this.scan_job_template.id+'/schedules');
+ };
+
+ $scope.editScanJob = function(){
+ $location.path($location.path()+'/job_templates/'+this.scan_job_template.id);
+ };
+
+ $scope.copyScanJobTemplate = function(){
+ var id = this.scan_job_template.id,
+ name = this.scan_job_template.name,
+ element,
+ buttons = [{
+ "label": "Cancel",
+ "onClick": function() {
+ $(this).dialog('close');
+ },
+ "icon": "fa-times",
+ "class": "btn btn-default",
+ "id": "copy-close-button"
+ },{
+ "label": "Copy",
+ "onClick": function() {
+ copyAction();
+ },
+ "icon": "fa-copy",
+ "class": "btn btn-primary",
+ "id": "job-copy-button"
+ }],
+ copyAction = function () {
+ // retrieve the copy of the job template object from the api, then overwrite the name and throw away the id
+ Wait('start');
+ var url = GetBasePath('job_templates')+id;
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function (data) {
+ data.name = $scope.new_copy_name;
+ delete data.id;
+ $scope.$emit('GoToCopy', data);
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ };
+
+
+ CreateDialog({
+ id: 'copy-job-modal' ,
+ title: "Copy",
+ scope: $scope,
+ buttons: buttons,
+ width: 500,
+ height: 300,
+ minWidth: 200,
+ callback: 'CopyDialogReady'
+ });
+
+ $('#job_name').text(name);
+ $('#copy-job-modal').show();
+
+
+ if ($scope.removeCopyDialogReady) {
+ $scope.removeCopyDialogReady();
+ }
+ $scope.removeCopyDialogReady = $scope.$on('CopyDialogReady', function() {
+ //clear any old remaining text
+ $scope.new_copy_name = "" ;
+ $scope.copy_form.$setPristine();
+ $('#copy-job-modal').dialog('open');
+ $('#job-copy-button').attr('ng-disabled', "!copy_form.$valid");
+ element = angular.element(document.getElementById('job-copy-button'));
+ $compile(element)($scope);
+
+ });
+
+ if ($scope.removeGoToCopy) {
+ $scope.removeGoToCopy();
+ }
+ $scope.removeGoToCopy = $scope.$on('GoToCopy', function(e, data) {
+ var url = GetBasePath('job_templates'),
+ old_survey_url = (data.related.survey_spec) ? data.related.survey_spec : "" ;
+ Rest.setUrl(url);
+ Rest.post(data)
+ .success(function (data) {
+ if(data.survey_enabled===true){
+ $scope.$emit("CopySurvey", data, old_survey_url);
+ }
+ else {
+ $('#copy-job-modal').dialog('close');
+ Wait('stop');
+ $location.path($location.path() + '/job_templates/' + data.id);
+ }
+
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. DELETE returned status: ' + status });
+ });
+ });
+
+ if ($scope.removeCopySurvey) {
+ $scope.removeCopySurvey();
+ }
+ $scope.removeCopySurvey = $scope.$on('CopySurvey', function(e, new_data, old_url) {
+ // var url = data.related.survey_spec;
+ Rest.setUrl(old_url);
+ Rest.get()
+ .success(function (survey_data) {
+
+ Rest.setUrl(new_data.related.survey_spec);
+ Rest.post(survey_data)
+ .success(function () {
+ $('#copy-job-modal').dialog('close');
+ Wait('stop');
+ $location.path($location.path() + '/job_templates/' + new_data.id);
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + new_data.related.survey_spec + ' failed. DELETE returned status: ' + status });
+ });
+
+
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + old_url + ' failed. DELETE returned status: ' + status });
+ });
+
+ });
+
+ };
+
+ $scope.deleteScanJob = function () {
+ var id = this.scan_job_template.id ,
+ action = function () {
+ $('#prompt-modal').modal('hide');
+ Wait('start');
+ deleteJobTemplate(id)
+ .success(function () {
+ $('#prompt-modal').modal('hide');
+ $scope.search(form.related.scan_job_templates.iterator);
+ })
+ .error(function (data) {
+ Wait('stop');
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!',
+ msg: 'DELETE returned status: ' + status });
+ });
+ };
+
+ Prompt({
+ hdr: 'Delete',
+ body: '
Are you sure you want to delete the job template below?
' + this.scan_job_template.name + '
',
+ action: action,
+ actionText: 'DELETE'
+ });
+
+ };
+
+}
+
+export default ['$scope', '$rootScope', '$compile', '$location',
+ '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
+ 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
+ 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
+ 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', 'ParseVariableString',
+ 'RelatedSearchInit', 'RelatedPaginateInit', 'Prompt',
+ 'PlaybookRun', 'CreateDialog', 'deleteJobTemplate', '$state',
+ InventoriesEdit,
+];
diff --git a/awx/ui/client/src/inventory/inventory-list.controller.js b/awx/ui/client/src/inventory/inventory-list.controller.js
new file mode 100644
index 0000000000..98f02df918
--- /dev/null
+++ b/awx/ui/client/src/inventory/inventory-list.controller.js
@@ -0,0 +1,371 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+import '../job-templates/main';
+
+function InventoriesList($scope, $rootScope, $location, $log,
+ $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
+ generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
+ ClearScope, ProcessErrors, GetBasePath, Wait,
+ EditInventoryProperties, Find, Empty, $state) {
+
+ var list = InventoryList,
+ defaultUrl = GetBasePath('inventory'),
+ view = generateList,
+ paths = $location.path().replace(/^\//, '').split('/'),
+ mode = (paths[0] === 'inventories') ? 'edit' : 'select';
+
+ function ellipsis(a) {
+ if (a.length > 20) {
+ return a.substr(0,20) + '...';
+ }
+ return a;
+ }
+
+ function attachElem(event, html, title) {
+ var elem = $(event.target).parent();
+ try {
+ elem.tooltip('hide');
+ elem.popover('destroy');
+ }
+ catch(err) {
+ //ignore
+ }
+ $('.popover').each(function() {
+ // remove lingering popover . Seems to be a bug in TB3 RC1
+ $(this).remove();
+ });
+ $('.tooltip').each( function() {
+ // close any lingering tool tipss
+ $(this).hide();
+ });
+ elem.attr({
+ "aw-pop-over": html,
+ "data-popover-title": title,
+ "data-placement": "right" });
+ $compile(elem)($scope);
+ elem.on('shown.bs.popover', function() {
+ $('.popover').each(function() {
+ $compile($(this))($scope); //make nested directives work!
+ });
+ $('.popover-content, .popover-title').click(function() {
+ elem.popover('hide');
+ });
+ });
+ elem.popover('show');
+ }
+
+ view.inject(InventoryList, { mode: mode, scope: $scope });
+ $rootScope.flashMessage = null;
+
+ SearchInit({
+ scope: $scope,
+ set: 'inventories',
+ list: list,
+ url: defaultUrl
+ });
+
+ PaginateInit({
+ scope: $scope,
+ list: list,
+ url: defaultUrl
+ });
+
+ if ($stateParams.name) {
+ $scope[InventoryList.iterator + 'InputDisable'] = false;
+ $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.name;
+ $scope[InventoryList.iterator + 'SearchField'] = 'name';
+ $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.name.label;
+ $scope[InventoryList.iterator + 'SearchSelectValue'] = null;
+ }
+
+ if ($stateParams.has_active_failures) {
+ $scope[InventoryList.iterator + 'InputDisable'] = true;
+ $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_active_failures;
+ $scope[InventoryList.iterator + 'SearchField'] = 'has_active_failures';
+ $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_active_failures.label;
+ $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_active_failures === 'true') ? {
+ value: 1
+ } : {
+ value: 0
+ };
+ }
+
+ if ($stateParams.has_inventory_sources) {
+ $scope[InventoryList.iterator + 'InputDisable'] = true;
+ $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.has_inventory_sources;
+ $scope[InventoryList.iterator + 'SearchField'] = 'has_inventory_sources';
+ $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.has_inventory_sources.label;
+ $scope[InventoryList.iterator + 'SearchSelectValue'] = ($stateParams.has_inventory_sources === 'true') ? {
+ value: 1
+ } : {
+ value: 0
+ };
+ }
+
+ if ($stateParams.inventory_sources_with_failures) {
+ // pass a value of true, however this field actually contains an integer value
+ $scope[InventoryList.iterator + 'InputDisable'] = true;
+ $scope[InventoryList.iterator + 'SearchValue'] = $stateParams.inventory_sources_with_failures;
+ $scope[InventoryList.iterator + 'SearchField'] = 'inventory_sources_with_failures';
+ $scope[InventoryList.iterator + 'SearchFieldLabel'] = InventoryList.fields.inventory_sources_with_failures.label;
+ $scope[InventoryList.iterator + 'SearchType'] = 'gtzero';
+ }
+
+ $scope.search(list.iterator);
+
+ if ($scope.removePostRefresh) {
+ $scope.removePostRefresh();
+ }
+ $scope.removePostRefresh = $scope.$on('PostRefresh', function () {
+ //If we got here by deleting an inventory, stop the spinner and cleanup events
+ Wait('stop');
+ try {
+ $('#prompt-modal').modal('hide');
+ }
+ catch(e) {
+ // ignore
+ }
+ $scope.inventories.forEach(function(inventory, idx) {
+ $scope.inventories[idx].launch_class = "";
+ if (inventory.has_inventory_sources) {
+ if (inventory.inventory_sources_with_failures > 0) {
+ $scope.inventories[idx].syncStatus = 'error';
+ $scope.inventories[idx].syncTip = inventory.inventory_sources_with_failures + ' groups with sync failures. Click for details';
+ }
+ else {
+ $scope.inventories[idx].syncStatus = 'successful';
+ $scope.inventories[idx].syncTip = 'No inventory sync failures. Click for details.';
+ }
+ }
+ else {
+ $scope.inventories[idx].syncStatus = 'na';
+ $scope.inventories[idx].syncTip = 'Not configured for inventory sync.';
+ $scope.inventories[idx].launch_class = "btn-disabled";
+ }
+ if (inventory.has_active_failures) {
+ $scope.inventories[idx].hostsStatus = 'error';
+ $scope.inventories[idx].hostsTip = inventory.hosts_with_active_failures + ' hosts with failures. Click for details.';
+ }
+ else if (inventory.total_hosts) {
+ $scope.inventories[idx].hostsStatus = 'successful';
+ $scope.inventories[idx].hostsTip = 'No hosts with failures. Click for details.';
+ }
+ else {
+ $scope.inventories[idx].hostsStatus = 'none';
+ $scope.inventories[idx].hostsTip = 'Inventory contains 0 hosts.';
+ }
+ });
+ });
+
+ if ($scope.removeRefreshInventories) {
+ $scope.removeRefreshInventories();
+ }
+ $scope.removeRefreshInventories = $scope.$on('RefreshInventories', function () {
+ // Reflect changes after inventory properties edit completes
+ $scope.search(list.iterator);
+ });
+
+ if ($scope.removeHostSummaryReady) {
+ $scope.removeHostSummaryReady();
+ }
+ $scope.removeHostSummaryReady = $scope.$on('HostSummaryReady', function(e, event, data) {
+
+ var html, title = "Recent Jobs";
+ Wait('stop');
+ if (data.count > 0) {
+ html = "
\n";
+ html += "\n";
+ html += "";
+ html += "| Status | ";
+ html += "Finished | ";
+ html += "Name | ";
+ html += "
\n";
+ html += "\n";
+ html += "\n";
+
+ data.results.forEach(function(row) {
+ html += "\n";
+ html += " | \n";
+ html += "" + ($filter('longDate')(row.finished)).replace(/ /,' ') + " | ";
+ html += "" + ellipsis(row.name) + " | ";
+ html += "
\n";
+ });
+ html += "\n";
+ html += "
\n";
+ }
+ else {
+ html = "
No recent job data available for this inventory.
\n";
+ }
+ attachElem(event, html, title);
+ });
+
+ if ($scope.removeGroupSummaryReady) {
+ $scope.removeGroupSummaryReady();
+ }
+ $scope.removeGroupSummaryReady = $scope.$on('GroupSummaryReady', function(e, event, inventory, data) {
+ var html, title;
+
+ Wait('stop');
+
+ // Build the html for our popover
+ html = "
\n";
+ html += "\n";
+ html += "";
+ html += "| Status | ";
+ html += "Last Sync | ";
+ html += "Group | ";
+ html += "
";
+ html += "\n";
+ html += "\n";
+ data.results.forEach( function(row) {
+ if (row.related.last_update) {
+ html += "";
+ html += " | ";
+ html += "" + ($filter('longDate')(row.last_updated)).replace(/ /,' ') + " | ";
+ html += "" + ellipsis(row.summary_fields.group.name) + " | ";
+ html += "
\n";
+ }
+ else {
+ html += "";
+ html += " | ";
+ html += "NA | ";
+ html += "" + ellipsis(row.summary_fields.group.name) + " | ";
+ html += "
\n";
+ }
+ });
+ html += "\n";
+ html += "
\n";
+ title = "Sync Status";
+ attachElem(event, html, title);
+ });
+
+ $scope.showGroupSummary = function(event, id) {
+ var inventory;
+ if (!Empty(id)) {
+ inventory = Find({ list: $scope.inventories, key: 'id', val: id });
+ if (inventory.syncStatus !== 'na') {
+ Wait('start');
+ Rest.setUrl(inventory.related.inventory_sources + '?or__source=ec2&or__source=rax&order_by=-last_job_run&page_size=5');
+ Rest.get()
+ .success(function(data) {
+ $scope.$emit('GroupSummaryReady', event, inventory, data);
+ })
+ .error(function(data, status) {
+ ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + inventory.related.inventory_sources + ' failed. GET returned status: ' + status
+ });
+ });
+ }
+ }
+ };
+
+ $scope.showHostSummary = function(event, id) {
+ var url, inventory;
+ if (!Empty(id)) {
+ inventory = Find({ list: $scope.inventories, key: 'id', val: id });
+ if (inventory.total_hosts > 0) {
+ Wait('start');
+ url = GetBasePath('jobs') + "?type=job&inventory=" + id + "&failed=";
+ url += (inventory.has_active_failures) ? 'true' : "false";
+ url += "&order_by=-finished&page_size=5";
+ Rest.setUrl(url);
+ Rest.get()
+ .success( function(data) {
+ $scope.$emit('HostSummaryReady', event, data);
+ })
+ .error( function(data, status) {
+ ProcessErrors( $scope, data, status, null, { hdr: 'Error!',
+ msg: 'Call to ' + url + ' failed. GET returned: ' + status
+ });
+ });
+ }
+ }
+ };
+
+ $scope.viewJob = function(url) {
+
+ // Pull the id out of the URL
+ var id = url.replace(/^\//, '').split('/')[3];
+
+ $state.go('inventorySyncStdout', {id: id});
+
+ };
+
+ $scope.editInventoryProperties = function (inventory_id) {
+ EditInventoryProperties({ scope: $scope, inventory_id: inventory_id });
+ };
+
+ $scope.addInventory = function () {
+ $state.go('inventories.add');
+ };
+
+ $scope.editInventory = function (id) {
+ $state.go('inventories.edit', {inventory_id: id});
+ };
+
+ $scope.manageInventory = function(id){
+ $location.path($location.path() + '/' + id + '/manage');
+ };
+
+ $scope.deleteInventory = function (id, name) {
+
+ var action = function () {
+ var url = defaultUrl + id + '/';
+ Wait('start');
+ $('#prompt-modal').modal('hide');
+ Rest.setUrl(url);
+ Rest.destroy()
+ .success(function () {
+ $scope.search(list.iterator);
+ })
+ .error(function (data, status) {
+ 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 delete the inventory below?
' + $filter('sanitize')(name) + '
',
+ action: action,
+ actionText: 'DELETE'
+ });
+ };
+
+ $scope.lookupOrganization = function (organization_id) {
+ Rest.setUrl(GetBasePath('organizations') + organization_id + '/');
+ Rest.get()
+ .success(function (data) {
+ return data.name;
+ });
+ };
+
+
+ // Failed jobs link. Go to the jobs tabs, find all jobs for the inventory and sort by status
+ $scope.viewJobs = function (id) {
+ $location.url('/jobs/?inventory__int=' + id);
+ };
+
+ $scope.viewFailedJobs = function (id) {
+ $location.url('/jobs/?inventory__int=' + id + '&status=failed');
+ };
+}
+
+export default ['$scope', '$rootScope', '$location', '$log',
+ '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList',
+ 'generateList', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller',
+ 'ClearScope', 'ProcessErrors', 'GetBasePath', 'Wait',
+ 'EditInventoryProperties', 'Find', 'Empty', '$state', InventoriesList];
diff --git a/awx/ui/client/src/inventory/inventory-manage.controller.js b/awx/ui/client/src/inventory/inventory-manage.controller.js
new file mode 100644
index 0000000000..651547894e
--- /dev/null
+++ b/awx/ui/client/src/inventory/inventory-manage.controller.js
@@ -0,0 +1,532 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+import '../job-templates/main';
+
+function InventoriesManage($log, $scope, $rootScope, $location,
+ $state, $compile, generateList, ClearScope, Empty, Wait, Rest, Alert,
+ GetBasePath, ProcessErrors, InventoryGroups,
+ InjectHosts, Find, HostsReload, SearchInit, PaginateInit, GetSyncStatusMsg,
+ GetHostsStatusMsg, GroupsEdit, InventoryUpdate, GroupsCancelUpdate,
+ ViewUpdateStatus, GroupsDelete, Store, HostsEdit, HostsDelete,
+ EditInventoryProperties, ToggleHostEnabled, ShowJobSummary,
+ InventoryGroupsHelp, HelpDialog,
+ GroupsCopy, HostsCopy, $stateParams) {
+
+ var PreviousSearchParams,
+ url,
+ hostScope = $scope.$new();
+
+ ClearScope();
+
+ // TODO: only display adhoc button if the user has permission to use it.
+ // TODO: figure out how to get the action-list partial to update so that
+ // the tooltip can be changed based off things being selected or not.
+ $scope.adhocButtonTipContents = "Launch adhoc command for the inventory";
+
+ // watcher for the group list checkbox changes
+ $scope.$on('multiSelectList.selectionChanged', function(e, selection) {
+ if (selection.length > 0) {
+ $scope.groupsSelected = true;
+ // $scope.adhocButtonTipContents = "Launch adhoc command for the "
+ // + "selected groups and hosts.";
+ } else {
+ $scope.groupsSelected = false;
+ // $scope.adhocButtonTipContents = "Launch adhoc command for the "
+ // + "inventory.";
+ }
+ $scope.groupsSelectedItems = selection.selectedItems;
+ });
+
+ // watcher for the host list checkbox changes
+ hostScope.$on('multiSelectList.selectionChanged', function(e, selection) {
+ // you need this so that the event doesn't bubble to the watcher above
+ // for the host list
+ e.stopPropagation();
+ if (selection.length === 0) {
+ $scope.hostsSelected = false;
+ } else if (selection.length === 1) {
+ $scope.systemTrackingTooltip = "Compare host over time";
+ $scope.hostsSelected = true;
+ $scope.systemTrackingDisabled = false;
+ } else if (selection.length === 2) {
+ $scope.systemTrackingTooltip = "Compare hosts against each other";
+ $scope.hostsSelected = true;
+ $scope.systemTrackingDisabled = false;
+ } else {
+ $scope.hostsSelected = true;
+ $scope.systemTrackingDisabled = true;
+ }
+ $scope.hostsSelectedItems = selection.selectedItems;
+ });
+
+ $scope.systemTracking = function() {
+ var hostIds = _.map($scope.hostsSelectedItems, function(x){
+ return x.id;
+ });
+ $state.transitionTo('systemTracking',
+ { inventory: $scope.inventory,
+ inventoryId: $scope.inventory.id,
+ hosts: $scope.hostsSelectedItems,
+ hostIds: hostIds
+ });
+ };
+
+ // populates host patterns based on selected hosts/groups
+ $scope.populateAdhocForm = function() {
+ var host_patterns = "all";
+ if ($scope.hostsSelected || $scope.groupsSelected) {
+ var allSelectedItems = [];
+ if ($scope.groupsSelectedItems) {
+ allSelectedItems = allSelectedItems.concat($scope.groupsSelectedItems);
+ }
+ if ($scope.hostsSelectedItems) {
+ allSelectedItems = allSelectedItems.concat($scope.hostsSelectedItems);
+ }
+ if (allSelectedItems) {
+ host_patterns = _.pluck(allSelectedItems, "name").join(":");
+ }
+ }
+ $rootScope.hostPatterns = host_patterns;
+ $state.go('inventoryManage.adhoc');
+ };
+
+ $scope.refreshHostsOnGroupRefresh = false;
+ $scope.selected_group_id = null;
+
+ Wait('start');
+
+
+ if ($scope.removeHostReloadComplete) {
+ $scope.removeHostReloadComplete();
+ }
+ $scope.removeHostReloadComplete = $scope.$on('HostReloadComplete', function() {
+ if ($scope.initial_height) {
+ var host_height = $('#hosts-container .well').height(),
+ group_height = $('#group-list-container .well').height(),
+ new_height;
+
+ if (host_height > group_height) {
+ new_height = host_height - (host_height - group_height);
+ }
+ else if (host_height < group_height) {
+ new_height = host_height + (group_height - host_height);
+ }
+ if (new_height) {
+ $('#hosts-container .well').height(new_height);
+ }
+ $scope.initial_height = null;
+ }
+ });
+
+ if ($scope.removeRowCountReady) {
+ $scope.removeRowCountReady();
+ }
+ $scope.removeRowCountReady = $scope.$on('RowCountReady', function(e, rows) {
+ // Add hosts view
+ $scope.show_failures = false;
+ InjectHosts({
+ group_scope: $scope,
+ host_scope: hostScope,
+ inventory_id: $scope.inventory.id,
+ tree_id: null,
+ group_id: null,
+ pageSize: rows
+ });
+
+ SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: $scope.inventory.related.root_groups });
+ PaginateInit({ scope: $scope, list: InventoryGroups , url: $scope.inventory.related.root_groups, pageSize: rows });
+ $scope.search(InventoryGroups.iterator, null, true);
+ });
+
+ if ($scope.removeInventoryLoaded) {
+ $scope.removeInventoryLoaded();
+ }
+ $scope.removeInventoryLoaded = $scope.$on('InventoryLoaded', function() {
+ var rows;
+
+ // Add groups view
+ generateList.inject(InventoryGroups, {
+ mode: 'edit',
+ id: 'group-list-container',
+ searchSize: 'col-lg-6 col-md-6 col-sm-6 col-xs-12',
+ scope: $scope
+ });
+
+ rows = 20;
+ hostScope.host_page_size = rows;
+ $scope.group_page_size = rows;
+
+ $scope.show_failures = false;
+ InjectHosts({
+ group_scope: $scope,
+ host_scope: hostScope,
+ inventory_id: $scope.inventory.id,
+ tree_id: null,
+ group_id: null,
+ pageSize: rows
+ });
+
+ // Load data
+ SearchInit({
+ scope: $scope,
+ set: 'groups',
+ list: InventoryGroups,
+ url: $scope.inventory.related.root_groups
+ });
+
+ PaginateInit({
+ scope: $scope,
+ list: InventoryGroups ,
+ url: $scope.inventory.related.root_groups,
+ pageSize: rows
+ });
+
+ $scope.search(InventoryGroups.iterator, null, true);
+
+ $scope.$emit('WatchUpdateStatus'); // init socket io conneciton and start watching for status updates
+ });
+
+ if ($scope.removePostRefresh) {
+ $scope.removePostRefresh();
+ }
+ $scope.removePostRefresh = $scope.$on('PostRefresh', function(e, set) {
+ if (set === 'groups') {
+ $scope.groups.forEach( function(group, idx) {
+ var stat, hosts_status;
+ stat = GetSyncStatusMsg({
+ status: group.summary_fields.inventory_source.status,
+ has_inventory_sources: group.has_inventory_sources,
+ source: ( (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null )
+ }); // from helpers/Groups.js
+ $scope.groups[idx].status_class = stat['class'];
+ $scope.groups[idx].status_tooltip = stat.tooltip;
+ $scope.groups[idx].launch_tooltip = stat.launch_tip;
+ $scope.groups[idx].launch_class = stat.launch_class;
+ hosts_status = GetHostsStatusMsg({
+ active_failures: group.hosts_with_active_failures,
+ total_hosts: group.total_hosts,
+ inventory_id: $scope.inventory.id,
+ group_id: group.id
+ }); // from helpers/Groups.js
+ $scope.groups[idx].hosts_status_tip = hosts_status.tooltip;
+ $scope.groups[idx].show_failures = hosts_status.failures;
+ $scope.groups[idx].hosts_status_class = hosts_status['class'];
+
+ $scope.groups[idx].source = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.source : null;
+ $scope.groups[idx].status = (group.summary_fields.inventory_source) ? group.summary_fields.inventory_source.status : null;
+
+ });
+ if ($scope.refreshHostsOnGroupRefresh) {
+ $scope.refreshHostsOnGroupRefresh = false;
+ HostsReload({
+ scope: hostScope,
+ group_id: $scope.selected_group_id,
+ inventory_id: $scope.inventory.id,
+ pageSize: hostScope.host_page_size
+ });
+ }
+ else {
+ Wait('stop');
+ }
+ }
+ });
+
+ // Load Inventory
+ url = GetBasePath('inventory') + $stateParams.inventory_id + '/';
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function (data) {
+ $scope.inventory = data;
+ $scope.$emit('InventoryLoaded');
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory: ' + $stateParams.inventory_id +
+ ' GET returned status: ' + status });
+ });
+
+ // start watching for real-time updates
+ if ($rootScope.removeWatchUpdateStatus) {
+ $rootScope.removeWatchUpdateStatus();
+ }
+ $rootScope.removeWatchUpdateStatus = $rootScope.$on('JobStatusChange-inventory', function(e, data) {
+ var stat, group;
+ if (data.group_id) {
+ group = Find({ list: $scope.groups, key: 'id', val: data.group_id });
+ if (data.status === "failed" || data.status === "successful") {
+ if (data.group_id === $scope.selected_group_id || group) {
+ // job completed, fefresh all groups
+ $log.debug('Update completed. Refreshing the tree.');
+ $scope.refreshGroups();
+ }
+ }
+ else if (group) {
+ // incremental update, just update
+ $log.debug('Status of group: ' + data.group_id + ' changed to: ' + data.status);
+ stat = GetSyncStatusMsg({
+ status: data.status,
+ has_inventory_sources: group.has_inventory_sources,
+ source: group.source
+ });
+ $log.debug('changing tooltip to: ' + stat.tooltip);
+ group.status = data.status;
+ group.status_class = stat['class'];
+ group.status_tooltip = stat.tooltip;
+ group.launch_tooltip = stat.launch_tip;
+ group.launch_class = stat.launch_class;
+ }
+ }
+ });
+
+ // Load group on selection
+ function loadGroups(url) {
+ SearchInit({ scope: $scope, set: 'groups', list: InventoryGroups, url: url });
+ PaginateInit({ scope: $scope, list: InventoryGroups , url: url, pageSize: $scope.group_page_size });
+ $scope.search(InventoryGroups.iterator, null, true, false, true);
+ }
+
+ $scope.refreshHosts = function() {
+ HostsReload({
+ scope: hostScope,
+ group_id: $scope.selected_group_id,
+ inventory_id: $scope.inventory.id,
+ pageSize: hostScope.host_page_size
+ });
+ };
+
+ $scope.refreshGroups = function() {
+ $scope.refreshHostsOnGroupRefresh = true;
+ $scope.search(InventoryGroups.iterator, null, true, false, true);
+ };
+
+ $scope.restoreSearch = function() {
+ // Restore search params and related stuff, plus refresh
+ // groups and hosts lists
+ SearchInit({
+ scope: $scope,
+ set: PreviousSearchParams.set,
+ list: PreviousSearchParams.list,
+ url: PreviousSearchParams.defaultUrl,
+ iterator: PreviousSearchParams.iterator,
+ sort_order: PreviousSearchParams.sort_order,
+ setWidgets: false
+ });
+ $scope.refreshHostsOnGroupRefresh = true;
+ $scope.search(InventoryGroups.iterator, null, true, false, true);
+ };
+
+ $scope.groupSelect = function(id) {
+ var groups = [], group = Find({ list: $scope.groups, key: 'id', val: id });
+ if($state.params.groups){
+ groups.push($state.params.groups);
+ }
+ groups.push(group.id);
+ groups = groups.join();
+ $state.transitionTo('inventoryManage', {inventory_id: $state.params.inventory_id, groups: groups}, { notify: false });
+ loadGroups(group.related.children, group.id);
+ };
+
+ $scope.createGroup = function () {
+ PreviousSearchParams = Store('group_current_search_params');
+ GroupsEdit({
+ scope: $scope,
+ inventory_id: $scope.inventory.id,
+ group_id: $scope.selected_group_id,
+ mode: 'add'
+ });
+ };
+
+ $scope.editGroup = function (id) {
+ PreviousSearchParams = Store('group_current_search_params');
+ GroupsEdit({
+ scope: $scope,
+ inventory_id: $scope.inventory.id,
+ group_id: id,
+ mode: 'edit'
+ });
+ };
+
+ // Launch inventory sync
+ $scope.updateGroup = function (id) {
+ var group = Find({ list: $scope.groups, key: 'id', val: id });
+ if (group) {
+ if (Empty(group.source)) {
+ // if no source, do nothing.
+ } else if (group.status === 'updating') {
+ Alert('Update in Progress', 'The inventory update process is currently running for group
' +
+ group.name + ' Click the
button to monitor the status.', 'alert-info', null, null, null, null, true);
+ } else {
+ Wait('start');
+ Rest.setUrl(group.related.inventory_source);
+ Rest.get()
+ .success(function (data) {
+ InventoryUpdate({
+ scope: $scope,
+ url: data.related.update,
+ group_name: data.summary_fields.group.name,
+ group_source: data.source,
+ group_id: group.id,
+ });
+ })
+ .error(function (data, status) {
+ ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to retrieve inventory source: ' +
+ group.related.inventory_source + ' GET returned status: ' + status });
+ });
+ }
+ }
+ };
+
+ $scope.cancelUpdate = function (id) {
+ GroupsCancelUpdate({ scope: $scope, id: id });
+ };
+
+ $scope.viewUpdateStatus = function (id) {
+ ViewUpdateStatus({
+ scope: $scope,
+ group_id: id
+ });
+ };
+
+ $scope.copyGroup = function(id) {
+ PreviousSearchParams = Store('group_current_search_params');
+ GroupsCopy({
+ scope: $scope,
+ group_id: id
+ });
+ };
+
+ $scope.deleteGroup = function (id) {
+ GroupsDelete({
+ scope: $scope,
+ group_id: id,
+ inventory_id: $scope.inventory.id
+ });
+ };
+
+ $scope.editInventoryProperties = function () {
+ // EditInventoryProperties({ scope: $scope, inventory_id: $scope.inventory.id });
+ $location.path('/inventories/' + $scope.inventory.id + '/');
+ };
+
+ hostScope.createHost = function () {
+ HostsEdit({
+ host_scope: hostScope,
+ group_scope: $scope,
+ mode: 'add',
+ host_id: null,
+ selected_group_id: $scope.selected_group_id,
+ inventory_id: $scope.inventory.id
+ });
+ };
+
+ hostScope.editHost = function (host_id) {
+ HostsEdit({
+ host_scope: hostScope,
+ group_scope: $scope,
+ mode: 'edit',
+ host_id: host_id,
+ inventory_id: $scope.inventory.id
+ });
+ };
+
+ hostScope.deleteHost = function (host_id, host_name) {
+ HostsDelete({
+ parent_scope: $scope,
+ host_scope: hostScope,
+ host_id: host_id,
+ host_name: host_name
+ });
+ };
+
+ hostScope.copyHost = function(id) {
+ PreviousSearchParams = Store('group_current_search_params');
+ HostsCopy({
+ group_scope: $scope,
+ host_scope: hostScope,
+ host_id: id
+ });
+ };
+
+ /*hostScope.restoreSearch = function() {
+ SearchInit({
+ scope: hostScope,
+ set: PreviousSearchParams.set,
+ list: PreviousSearchParams.list,
+ url: PreviousSearchParams.defaultUrl,
+ iterator: PreviousSearchParams.iterator,
+ sort_order: PreviousSearchParams.sort_order,
+ setWidgets: false
+ });
+ hostScope.search('host');
+ };*/
+
+ hostScope.toggleHostEnabled = function (host_id, external_source) {
+ ToggleHostEnabled({
+ parent_scope: $scope,
+ host_scope: hostScope,
+ host_id: host_id,
+ external_source: external_source
+ });
+ };
+
+ hostScope.showJobSummary = function (job_id) {
+ ShowJobSummary({
+ job_id: job_id
+ });
+ };
+
+ $scope.showGroupHelp = function (params) {
+ var opts = {
+ defn: InventoryGroupsHelp
+ };
+ if (params) {
+ opts.autoShow = params.autoShow || false;
+ }
+ HelpDialog(opts);
+ }
+;
+ $scope.showHosts = function (group_id, show_failures) {
+ // Clicked on group
+ if (group_id !== null) {
+ Wait('start');
+ hostScope.show_failures = show_failures;
+ $scope.groupSelect(group_id);
+ hostScope.hosts = [];
+ $scope.show_failures = show_failures; // turn on failed hosts
+ // filter in hosts view
+ } else {
+ Wait('stop');
+ }
+ };
+
+ if ($scope.removeGroupDeleteCompleted) {
+ $scope.removeGroupDeleteCompleted();
+ }
+ $scope.removeGroupDeleteCompleted = $scope.$on('GroupDeleteCompleted',
+ function() {
+ $scope.refreshGroups();
+ }
+ );
+}
+
+export default [
+ '$log', '$scope', '$rootScope', '$location',
+ '$state', '$compile', 'generateList', 'ClearScope', 'Empty', 'Wait',
+ 'Rest', 'Alert', 'GetBasePath', 'ProcessErrors',
+ 'InventoryGroups', 'InjectHosts', 'Find', 'HostsReload',
+ 'SearchInit', 'PaginateInit', 'GetSyncStatusMsg', 'GetHostsStatusMsg',
+ 'GroupsEdit', 'InventoryUpdate', 'GroupsCancelUpdate', 'ViewUpdateStatus',
+ 'GroupsDelete', 'Store', 'HostsEdit', 'HostsDelete',
+ 'EditInventoryProperties', 'ToggleHostEnabled', 'ShowJobSummary',
+ 'InventoryGroupsHelp', 'HelpDialog', 'GroupsCopy',
+ 'HostsCopy', '$stateParams', InventoriesManage,
+];
diff --git a/awx/ui/client/src/inventory/main.js b/awx/ui/client/src/inventory/main.js
new file mode 100644
index 0000000000..0689a2d726
--- /dev/null
+++ b/awx/ui/client/src/inventory/main.js
@@ -0,0 +1,10 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+
+export default
+angular.module('inventory', [
+])