diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index e207eab59b..2a3367805c 100644
--- a/awx/ui/client/src/app.js
+++ b/awx/ui/client/src/app.js
@@ -26,6 +26,7 @@ import {CredentialsAdd, CredentialsEdit, CredentialsList} from './controllers/Cr
import {JobsListController} from './controllers/Jobs';
import {PortalController} from './controllers/Portal';
import systemTracking from './system-tracking/main';
+import inventory from './inventory/main';
import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/main';
import permissions from './permissions/main';
@@ -54,7 +55,10 @@ import {ProjectsList, ProjectsAdd, ProjectsEdit} from './controllers/Projects';
import OrganizationsList from './organizations/list/organizations-list.controller';
import OrganizationsAdd from './organizations/add/organizations-add.controller';
import OrganizationsEdit from './organizations/edit/organizations-edit.controller';
-import {InventoriesList, InventoriesAdd, InventoriesEdit, InventoriesManage} from './controllers/Inventories';
+import InventoriesAdd from './inventory/inventory-add.controller';
+import InventoriesEdit from './inventory/inventory-edit.controller';
+import InventoriesList from './inventory/inventory-list.controller';
+import InventoriesManage from './inventory/inventory-manage.controller';
import {AdminsList} from './controllers/Admins';
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
diff --git a/awx/ui/client/src/controllers/Inventories.js b/awx/ui/client/src/controllers/Inventories.js
deleted file mode 100644
index 62dfb5b03b..0000000000
--- a/awx/ui/client/src/controllers/Inventories.js
+++ /dev/null
@@ -1,1296 +0,0 @@
-/*************************************************
- * Copyright (c) 2015 Ansible, Inc.
- *
- * All Rights Reserved
- *************************************************/
-
-/**
- * @ngdoc function
- * @name controllers.function:Inventories
- * @description This controller's for the Inventory page
-*/
-
-import '../job-templates/main';
-
-export 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');
- };
-}
-
-InventoriesList.$inject = ['$scope', '$rootScope', '$location', '$log', '$stateParams', '$compile', '$filter', 'sanitizeFilter', 'Rest', 'Alert', 'InventoryList', 'generateList',
- 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'ProcessErrors',
- 'GetBasePath', 'Wait', 'EditInventoryProperties', 'Find', 'Empty', '$state'
-];
-
-
-export 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');
- };
-}
-
-InventoriesAdd.$inject = ['$scope', '$rootScope', '$compile', '$location',
- '$log', '$stateParams', 'InventoryForm', 'GenerateForm', 'Rest', 'Alert',
- 'ProcessErrors', 'ReturnToCaller', 'ClearScope', 'generateList',
- 'OrganizationList', 'SearchInit', 'PaginateInit', 'LookUpInit',
- 'GetBasePath', 'ParseTypeChange', 'Wait', 'ToJSON', '$state'
-];
-
-export 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'
- });
-
- };
-
-}
-
-InventoriesEdit.$inject = ['$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'
-];
-
-
-
-export 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();
- }
- );
-}
-
-
-InventoriesManage.$inject = ['$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'
-];
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', [
+])