diff --git a/awx/ui/client/src/app.js b/awx/ui/client/src/app.js
index 3c8cdce00e..b06df55335 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 inventories from './inventories/main';
import inventoryScripts from './inventory-scripts/main';
import organizations from './organizations/main';
import permissions from './permissions/main';
@@ -54,7 +55,7 @@ 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, InventoriesEdit, InventoriesList, InventoriesManage} from './inventories/main';
import {AdminsList} from './controllers/Admins';
import {UsersList, UsersAdd, UsersEdit} from './controllers/Users';
import {TeamsList, TeamsAdd, TeamsEdit} from './controllers/Teams';
@@ -87,6 +88,7 @@ var tower = angular.module('Tower', [
RestServices.name,
browserData.name,
systemTracking.name,
+ inventories.name,
inventoryScripts.name,
organizations.name,
permissions.name,
@@ -369,69 +371,6 @@ var tower = angular.module('Tower', [
}
}).
- state('inventories', {
- url: '/inventories',
- templateUrl: urlPrefix + 'partials/inventories.html',
- controller: InventoriesList,
- data: {
- activityStream: true,
- activityStreamTarget: 'inventory'
- },
- ncyBreadcrumb: {
- label: "INVENTORIES"
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- state('inventories.add', {
- url: '/add',
- templateUrl: urlPrefix + 'partials/inventories.html',
- controller: InventoriesAdd,
- ncyBreadcrumb: {
- parent: "inventories",
- label: "CREATE INVENTORY"
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- state('inventories.edit', {
- url: '/:inventory_id',
- templateUrl: urlPrefix + 'partials/inventories.html',
- controller: InventoriesEdit,
- data: {
- activityStreamId: 'inventory_id'
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
- state('inventoryManage', {
- url: '/inventories/:inventory_id/manage?groups',
- templateUrl: urlPrefix + 'partials/inventory-manage.html',
- controller: InventoriesManage,
- data: {
- activityStream: true,
- activityStreamTarget: 'inventory',
- activityStreamId: 'inventory_id'
- },
- resolve: {
- features: ['FeaturesService', function(FeaturesService) {
- return FeaturesService.get();
- }]
- }
- }).
-
state('organizationAdmins', {
url: '/organizations/:organization_id/admins',
templateUrl: urlPrefix + 'partials/organizations.html',
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/helpers/Hosts.js b/awx/ui/client/src/helpers/Hosts.js
index f55b1199d2..6a7b864e02 100644
--- a/awx/ui/client/src/helpers/Hosts.js
+++ b/awx/ui/client/src/helpers/Hosts.js
@@ -437,10 +437,10 @@ angular.module('HostsHelper', [ 'RestServices', 'Utilities', listGenerator.name,
.factory('HostsEdit', ['$rootScope', '$location', '$log', '$stateParams', 'Rest', 'Alert', 'HostForm', 'GenerateForm',
'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange', 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis',
- 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize',
+ 'ToJSON', 'ParseVariableString', 'CreateDialog', 'TextareaResize', 'ParamPass',
function($rootScope, $location, $log, $stateParams, Rest, Alert, HostForm, GenerateForm, Prompt, ProcessErrors,
GetBasePath, HostsReload, ParseTypeChange, Wait, Find, SetStatus, ApplyEllipsis, ToJSON,
- ParseVariableString, CreateDialog, TextareaResize) {
+ ParseVariableString, CreateDialog, TextareaResize, ParamPass) {
return function(params) {
var parent_scope = params.host_scope,
diff --git a/awx/ui/client/src/inventories/add/inventory-add.controller.js b/awx/ui/client/src/inventories/add/inventory-add.controller.js
new file mode 100644
index 0000000000..bd3cde3041
--- /dev/null
+++ b/awx/ui/client/src/inventories/add/inventory-add.controller.js
@@ -0,0 +1,95 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+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.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/inventories/add/inventory-add.route.js b/awx/ui/client/src/inventories/add/inventory-add.route.js
new file mode 100644
index 0000000000..50ba5b26a6
--- /dev/null
+++ b/awx/ui/client/src/inventories/add/inventory-add.route.js
@@ -0,0 +1,24 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+import InventoriesAdd from './inventory-add.controller';
+
+export default {
+ name: 'inventories.add',
+ route: '/add',
+ templateUrl: templateUrl('inventories/inventories'),
+ controller: InventoriesAdd,
+ ncyBreadcrumb: {
+ parent: "inventories",
+ label: "CREATE INVENTORY"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/add/main.js b/awx/ui/client/src/inventories/add/main.js
new file mode 100644
index 0000000000..e12ff940ac
--- /dev/null
+++ b/awx/ui/client/src/inventories/add/main.js
@@ -0,0 +1,14 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import route from './inventory-add.route';
+import controller from './inventory-add.controller';
+
+export default
+ angular.module('inventoryAdd', [])
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(route);
+ }]);
diff --git a/awx/ui/client/src/inventories/edit/inventory-edit.controller.js b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js
new file mode 100644
index 0000000000..f7cb6f2601
--- /dev/null
+++ b/awx/ui/client/src/inventories/edit/inventory-edit.controller.js
@@ -0,0 +1,329 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+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.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/inventories/edit/inventory-edit.route.js b/awx/ui/client/src/inventories/edit/inventory-edit.route.js
new file mode 100644
index 0000000000..d721ba92a4
--- /dev/null
+++ b/awx/ui/client/src/inventories/edit/inventory-edit.route.js
@@ -0,0 +1,26 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+import InventoriesEdit from './inventory-edit.controller';
+
+export default {
+ name: 'inventories.edit',
+ route: '/:inventory_id',
+ templateUrl: templateUrl('inventories/inventories'),
+ controller: InventoriesEdit,
+ data: {
+ activityStreamId: 'inventory_id'
+ },
+ ncyBreadcrumb: {
+ label: "INVENTORY EDIT"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/edit/main.js b/awx/ui/client/src/inventories/edit/main.js
new file mode 100644
index 0000000000..28c99819b7
--- /dev/null
+++ b/awx/ui/client/src/inventories/edit/main.js
@@ -0,0 +1,14 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import route from './inventory-edit.route';
+import controller from './inventory-edit.controller';
+
+export default
+ angular.module('inventoryEdit', [])
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(route);
+ }]);
diff --git a/awx/ui/client/src/partials/inventories.html b/awx/ui/client/src/inventories/inventories.partial.html
similarity index 100%
rename from awx/ui/client/src/partials/inventories.html
rename to awx/ui/client/src/inventories/inventories.partial.html
diff --git a/awx/ui/client/src/inventories/list/inventory-list.controller.js b/awx/ui/client/src/inventories/list/inventory-list.controller.js
new file mode 100644
index 0000000000..947b1c0341
--- /dev/null
+++ b/awx/ui/client/src/inventories/list/inventory-list.controller.js
@@ -0,0 +1,364 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+function InventoriesList($scope, $rootScope, $location, $log,
+ $stateParams, $compile, $filter, sanitizeFilter, Rest, Alert, InventoryList,
+ generateList, Prompt, SearchInit, PaginateInit, ReturnToCaller,
+ ClearScope, ProcessErrors, GetBasePath, Wait,
+ 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.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', 'Find', 'Empty', '$state', InventoriesList];
diff --git a/awx/ui/client/src/inventories/list/inventory-list.route.js b/awx/ui/client/src/inventories/list/inventory-list.route.js
new file mode 100644
index 0000000000..2804370249
--- /dev/null
+++ b/awx/ui/client/src/inventories/list/inventory-list.route.js
@@ -0,0 +1,27 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+import InventoriesList from './inventory-list.controller';
+
+export default {
+ name: 'inventories',
+ route: '/inventories',
+ templateUrl: templateUrl('inventories/inventories'),
+ controller: InventoriesList,
+ data: {
+ activityStream: true,
+ activityStreamTarget: 'inventory'
+ },
+ ncyBreadcrumb: {
+ label: "INVENTORIES"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/list/main.js b/awx/ui/client/src/inventories/list/main.js
new file mode 100644
index 0000000000..4d67816cd7
--- /dev/null
+++ b/awx/ui/client/src/inventories/list/main.js
@@ -0,0 +1,14 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import route from './inventory-list.route';
+import controller from './inventory-list.controller';
+
+export default
+ angular.module('inventoryList', [])
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(route);
+ }]);
diff --git a/awx/ui/client/src/inventories/main.js b/awx/ui/client/src/inventories/main.js
new file mode 100644
index 0000000000..52f9986ef3
--- /dev/null
+++ b/awx/ui/client/src/inventories/main.js
@@ -0,0 +1,18 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import inventoryAdd from './add/main';
+import inventoryEdit from './edit/main';
+import inventoryList from './list/main';
+import inventoryManage from './manage/main';
+
+export default
+angular.module('inventory', [
+ inventoryAdd.name,
+ inventoryEdit.name,
+ inventoryList.name,
+ inventoryManage.name,
+]);
diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.controller.js b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js
new file mode 100644
index 0000000000..508a74d4c2
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/inventory-manage.controller.js
@@ -0,0 +1,525 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/**
+ * @ngdoc function
+ * @name controllers.function:Inventories
+ * @description This controller's for the Inventory page
+ */
+
+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, ParamPass) {
+
+ 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');
+ var params = {
+ scope: $scope,
+ inventory_id: $scope.inventory.id,
+ group_id: $scope.selected_group_id,
+ mode: 'add'
+ }
+ ParamPass.set(params);
+ $state.go('inventoryManage.addGroup');
+ };
+
+ $scope.editGroup = function (id) {
+ PreviousSearchParams = Store('group_current_search_params');
+ var params = {
+ scope: $scope,
+ inventory_id: $scope.inventory.id,
+ group_id: id,
+ mode: 'edit'
+ }
+ ParamPass.set(params);
+ $state.go('inventoryManage.editGroup', {group_id: id});
+ };
+
+ // 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 () {
+ var params = {
+ host_scope: hostScope,
+ group_scope: $scope,
+ mode: 'add',
+ host_id: null,
+ selected_group_id: $scope.selected_group_id,
+ inventory_id: $scope.inventory.id
+ }
+ ParamPass.set(params);
+ $state.go('inventoryManage.addHost');
+ };
+
+ hostScope.editHost = function (host_id) {
+ var params = {
+ host_scope: hostScope,
+ group_scope: $scope,
+ mode: 'edit',
+ host_id: host_id,
+ inventory_id: $scope.inventory.id
+ }
+ ParamPass.set(params);
+ $state.go('inventoryManage.editHost', {host_id: host_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.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', 'ParamPass', InventoriesManage,
+];
diff --git a/awx/ui/client/src/partials/inventory-manage.html b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html
similarity index 99%
rename from awx/ui/client/src/partials/inventory-manage.html
rename to awx/ui/client/src/inventories/manage/inventory-manage.partial.html
index ecae801c20..f465ef47c0 100644
--- a/awx/ui/client/src/partials/inventory-manage.html
+++ b/awx/ui/client/src/inventories/manage/inventory-manage.partial.html
@@ -10,9 +10,6 @@
-
-
1. Copy or move ?
diff --git a/awx/ui/client/src/inventories/manage/inventory-manage.route.js b/awx/ui/client/src/inventories/manage/inventory-manage.route.js
new file mode 100644
index 0000000000..62306cd91b
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/inventory-manage.route.js
@@ -0,0 +1,28 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import {templateUrl} from '../../shared/template-url/template-url.factory';
+import InventoriesManage from './inventory-manage.controller';
+
+export default {
+ name: 'inventoryManage',
+ url: '/inventories/:inventory_id/manage?groups',
+ templateUrl: templateUrl('inventories/manage/inventory-manage'),
+ controller: InventoriesManage,
+ data: {
+ activityStream: true,
+ activityStreamTarget: 'inventory',
+ activityStreamId: 'inventory_id'
+ },
+ ncyBreadcrumb: {
+ label: "INVENTORY MANAGE"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+};
diff --git a/awx/ui/client/src/inventories/manage/main.js b/awx/ui/client/src/inventories/manage/main.js
new file mode 100644
index 0000000000..075ba36887
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/main.js
@@ -0,0 +1,19 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import route from './inventory-manage.route';
+
+import manageHosts from './manage-hosts/main';
+import manageGroups from './manage-groups/main';
+
+export default
+angular.module('inventoryManage', [
+ manageHosts.name,
+ manageGroups.name
+ ])
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(route);
+ }]);
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js
new file mode 100644
index 0000000000..158d6653ec
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.controller.js
@@ -0,0 +1,550 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+function manageGroupsDirectiveController($filter, $rootScope, $location, $log, $stateParams, $compile, $state, $scope, Rest, Alert, GroupForm, GenerateForm, Prompt, ProcessErrors,
+ GetBasePath, SetNodeName, ParseTypeChange, GetSourceTypeOptions, InventoryUpdate, LookUpInit, Empty, Wait,
+ GetChoices, UpdateGroup, SourceChange, Find, ParseVariableString, ToJSON, GroupsScheduleListInit,
+ SourceForm, SetSchedulesInnerDialogSize, CreateSelect2, ParamPass) {
+
+ var vm = this;
+ var params = ParamPass.get();
+ if(params === undefined) {
+ params = {};
+ params.scope = $scope.$new();
+ }
+ var parent_scope = params.scope,
+ group_id = $stateParams.group_id,
+ mode = $state.current.data.mode, // 'add' or 'edit'
+ inventory_id = $stateParams.inventory_id,
+ generator = GenerateForm,
+ group_created = false,
+ defaultUrl,
+ master = {},
+ choicesReady,
+ modal_scope = parent_scope.$new(),
+ properties_scope = parent_scope.$new(),
+ sources_scope = parent_scope.$new(),
+ elem, group,
+ schedules_url = '';
+
+ if (mode === 'edit') {
+ defaultUrl = GetBasePath('groups') + group_id + '/';
+ } else {
+ defaultUrl = (group_id !== undefined) ? GetBasePath('groups') + group_id + '/children/' :
+ GetBasePath('inventory') + inventory_id + '/groups/';
+ }
+
+ Rest.setUrl(defaultUrl);
+ Rest.get()
+ .success(function(data) {
+ group = data;
+ for (var fld in GroupForm.fields) {
+ if (data[fld]) {
+ properties_scope[fld] = data[fld];
+ master[fld] = properties_scope[fld];
+ }
+ }
+ if(mode === 'edit') {
+ schedules_url = data.related.inventory_source + 'schedules/';
+ properties_scope.variable_url = data.related.variable_data;
+ sources_scope.source_url = data.related.inventory_source;
+ modal_scope.$emit('LoadSourceData');
+ }
+ })
+ .error(function(data, status) {
+ ProcessErrors(modal_scope, data, status, {
+ hdr: 'Error!',
+ msg: 'Failed to retrieve group: ' + defaultUrl + '. GET status: ' + status
+ });
+ });
+
+
+ $('#properties-tab').empty();
+ $('#sources-tab').empty();
+
+ elem = document.getElementById('group-manage-panel');
+ $compile(elem)(modal_scope);
+
+ $scope.parseType = 'yaml';
+
+ var form_scope =
+ generator.inject(GroupForm, {
+ mode: mode,
+ id: 'properties-tab',
+ related: false,
+ scope: properties_scope,
+ cancelButton: false,
+ });
+ var source_form_scope =
+ generator.inject(SourceForm, {
+ mode: mode,
+ id: 'sources-tab',
+ related: false,
+ scope: sources_scope,
+ cancelButton: false
+ });
+
+ generator.reset();
+
+ GetSourceTypeOptions({
+ scope: sources_scope,
+ variable: 'source_type_options'
+ });
+ sources_scope.source = SourceForm.fields.source['default'];
+ sources_scope.sourcePathRequired = false;
+ sources_scope[SourceForm.fields.source_vars.parseTypeName] = 'yaml';
+ sources_scope.update_cache_timeout = 0;
+ properties_scope.parseType = 'yaml';
+
+ function waitStop() {
+ Wait('stop');
+ }
+
+ function initSourceChange() {
+ parent_scope.showSchedulesTab = (mode === 'edit' && sources_scope.source && sources_scope.source.value !== "manual") ? true : false;
+ SourceChange({
+ scope: sources_scope,
+ form: SourceForm
+ });
+ }
+
+ // JT -- this gets called after the properties & properties variables are loaded, and is emitted from (groupLoaded)
+ if (modal_scope.removeLoadSourceData) {
+ modal_scope.removeLoadSourceData();
+ }
+ modal_scope.removeLoadSourceData = modal_scope.$on('LoadSourceData', function() {
+ ParseTypeChange({
+ scope: form_scope,
+ variable: 'variables',
+ parse_variable: 'parseType',
+ field_id: 'group_variables'
+ });
+
+ if (sources_scope.source_url) {
+ // get source data
+ Rest.setUrl(sources_scope.source_url);
+ Rest.get()
+ .success(function(data) {
+ var fld, i, j, flag, found, set, opts, list, form;
+ form = SourceForm;
+ for (fld in form.fields) {
+ if (fld === 'checkbox_group') {
+ for (i = 0; i < form.fields[fld].fields.length; i++) {
+ flag = form.fields[fld].fields[i];
+ if (data[flag.name] !== undefined) {
+ sources_scope[flag.name] = data[flag.name];
+ master[flag.name] = sources_scope[flag.name];
+ }
+ }
+ }
+ if (fld === 'source') {
+ found = false;
+ data.source = (data.source === "") ? "manual" : data.source;
+ for (i = 0; i < sources_scope.source_type_options.length; i++) {
+ if (sources_scope.source_type_options[i].value === data.source) {
+ sources_scope.source = sources_scope.source_type_options[i];
+ found = true;
+ }
+ }
+ if (!found || sources_scope.source.value === "manual") {
+ sources_scope.groupUpdateHide = true;
+ } else {
+ sources_scope.groupUpdateHide = false;
+ }
+ master.source = sources_scope.source;
+ } else if (fld === 'source_vars') {
+ // Parse source_vars, converting to YAML.
+ sources_scope.source_vars = ParseVariableString(data.source_vars);
+ master.source_vars = sources_scope.variables;
+ } else if (fld === "inventory_script") {
+ // the API stores it as 'source_script', we call it inventory_script
+ data.summary_fields['inventory_script'] = data.summary_fields.source_script;
+ sources_scope.inventory_script = data.source_script;
+ master.inventory_script = sources_scope.inventory_script;
+ } else if (fld === "source_regions") {
+ if (data[fld] === "") {
+ sources_scope[fld] = data[fld];
+ master[fld] = sources_scope[fld];
+ } else {
+ sources_scope[fld] = data[fld].split(",");
+ master[fld] = sources_scope[fld];
+ }
+ } else if (data[fld] !== undefined) {
+ sources_scope[fld] = data[fld];
+ master[fld] = sources_scope[fld];
+ }
+
+ if (form.fields[fld].sourceModel && data.summary_fields &&
+ data.summary_fields[form.fields[fld].sourceModel]) {
+ sources_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];
+ }
+ }
+
+ initSourceChange();
+
+ if (data.source_regions) {
+ if (data.source === 'ec2' ||
+ data.source === 'rax' ||
+ data.source === 'gce' ||
+ data.source === 'azure') {
+ if (data.source === 'ec2') {
+ set = sources_scope.ec2_regions;
+ } else if (data.source === 'rax') {
+ set = sources_scope.rax_regions;
+ } else if (data.source === 'gce') {
+ set = sources_scope.gce_regions;
+ } else if (data.source === 'azure') {
+ set = sources_scope.azure_regions;
+ }
+ opts = [];
+ list = data.source_regions.split(',');
+ for (i = 0; i < list.length; i++) {
+ for (j = 0; j < set.length; j++) {
+ if (list[i] === set[j].value) {
+ opts.push({
+ id: set [j].value,
+ text: set [j].label
+ });
+ }
+ }
+ }
+ master.source_regions = opts;
+ CreateSelect2({
+ element: "#source_source_regions",
+ opts: opts
+ });
+
+ }
+ } else {
+ // If empty, default to all
+ master.source_regions = [{
+ id: 'all',
+ text: 'All'
+ }];
+ }
+ if (data.group_by && data.source === 'ec2') {
+ set = sources_scope.ec2_group_by;
+ opts = [];
+ list = data.group_by.split(',');
+ for (i = 0; i < list.length; i++) {
+ for (j = 0; j < set.length; j++) {
+ if (list[i] === set[j].value) {
+ opts.push({
+ id: set [j].value,
+ text: set [j].label
+ });
+ }
+ }
+ }
+ master.group_by = opts;
+ CreateSelect2({
+ element: "#source_group_by",
+ opts: opts
+ });
+ }
+
+ sources_scope.group_update_url = data.related.update;
+ })
+ .error(function(data, status) {
+ sources_scope.source = "";
+ ProcessErrors(modal_scope, data, status, null, {
+ hdr: 'Error!',
+ msg: 'Failed to retrieve inventory source. GET status: ' + status
+ });
+ });
+ }
+ });
+
+ if (sources_scope.removeScopeSourceTypeOptionsReady) {
+ sources_scope.removeScopeSourceTypeOptionsReady();
+ }
+ sources_scope.removeScopeSourceTypeOptionsReady = sources_scope.$on('sourceTypeOptionsReady', function() {
+ if (mode === 'add') {
+ sources_scope.source = Find({
+ list: sources_scope.source_type_options,
+ key: 'value',
+ val: ''
+ });
+ modal_scope.showSchedulesTab = false;
+ }
+ });
+
+ choicesReady = 0;
+
+ if (sources_scope.removeChoicesReady) {
+ sources_scope.removeChoicesReady();
+ }
+ sources_scope.removeChoicesReady = sources_scope.$on('choicesReadyGroup', function() {
+ CreateSelect2({
+ element: '#source_source',
+ multiple: false
+ });
+ modal_scope.$emit('LoadSourceData');
+
+ choicesReady++;
+ if (choicesReady === 5) {
+ if (mode !== 'edit') {
+ properties_scope.variables = "---";
+ master.variables = properties_scope.variables;
+ }
+ }
+ });
+
+ // Load options for source regions
+ GetChoices({
+ scope: sources_scope,
+ url: GetBasePath('inventory_sources'),
+ field: 'source_regions',
+ variable: 'rax_regions',
+ choice_name: 'rax_region_choices',
+ callback: 'choicesReadyGroup'
+ });
+
+ GetChoices({
+ scope: sources_scope,
+ url: GetBasePath('inventory_sources'),
+ field: 'source_regions',
+ variable: 'ec2_regions',
+ choice_name: 'ec2_region_choices',
+ callback: 'choicesReadyGroup'
+ });
+
+ GetChoices({
+ scope: sources_scope,
+ url: GetBasePath('inventory_sources'),
+ field: 'source_regions',
+ variable: 'gce_regions',
+ choice_name: 'gce_region_choices',
+ callback: 'choicesReadyGroup'
+ });
+
+ GetChoices({
+ scope: sources_scope,
+ url: GetBasePath('inventory_sources'),
+ field: 'source_regions',
+ variable: 'azure_regions',
+ choice_name: 'azure_region_choices',
+ callback: 'choicesReadyGroup'
+ });
+
+ // Load options for group_by
+ GetChoices({
+ scope: sources_scope,
+ url: GetBasePath('inventory_sources'),
+ field: 'group_by',
+ variable: 'ec2_group_by',
+ choice_name: 'ec2_group_by_choices',
+ callback: 'choicesReadyGroup'
+ });
+
+ //Wait('start');
+
+ if (parent_scope.removeAddTreeRefreshed) {
+ parent_scope.removeAddTreeRefreshed();
+ }
+ parent_scope.removeAddTreeRefreshed = parent_scope.$on('GroupTreeRefreshed', function() {
+ // Clean up
+ Wait('stop');
+
+ if (modal_scope.searchCleanUp) {
+ modal_scope.searchCleanup();
+ }
+ try {
+ //$('#group-modal-dialog').dialog('close');
+ } catch (e) {
+ // ignore
+ }
+ });
+
+ if (modal_scope.removeSaveComplete) {
+ modal_scope.removeSaveComplete();
+ }
+ modal_scope.removeSaveComplete = modal_scope.$on('SaveComplete', function(e, error) {
+ if (!error) {
+ modal_scope.cancelPanel();
+ }
+ });
+
+ if (modal_scope.removeFormSaveSuccess) {
+ modal_scope.removeFormSaveSuccess();
+ }
+ modal_scope.removeFormSaveSuccess = modal_scope.$on('formSaveSuccess', function() {
+
+ // Source data gets stored separately from the group. Validate and store Source
+ // related fields, then call SaveComplete to wrap things up.
+
+ var parseError = false,
+ regions, r, i,
+ group_by,
+ data = {
+ group: group_id,
+ source: ((sources_scope.source && sources_scope.source.value !== 'manual') ? sources_scope.source.value : ''),
+ source_path: sources_scope.source_path,
+ credential: sources_scope.credential,
+ overwrite: sources_scope.overwrite,
+ overwrite_vars: sources_scope.overwrite_vars,
+ source_script: sources_scope.inventory_script,
+ update_on_launch: sources_scope.update_on_launch,
+ update_cache_timeout: (sources_scope.update_cache_timeout || 0)
+ };
+
+ // Create a string out of selected list of regions
+ if (sources_scope.source_regions) {
+ regions = $('#source_source_regions').select2("data");
+ r = [];
+ for (i = 0; i < regions.length; i++) {
+ r.push(regions[i].id);
+ }
+ data.source_regions = r.join();
+ }
+
+ if (sources_scope.source && (sources_scope.source.value === 'ec2')) {
+ data.instance_filters = sources_scope.instance_filters;
+ // Create a string out of selected list of regions
+ group_by = $('#source_group_by').select2("data");
+ r = [];
+ for (i = 0; i < group_by.length; i++) {
+ r.push(group_by[i].id);
+ }
+ data.group_by = r.join();
+ }
+
+ if (sources_scope.source && (sources_scope.source.value === 'ec2')) {
+ // for ec2, validate variable data
+ data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.source_vars, true);
+ }
+
+ if (sources_scope.source && (sources_scope.source.value === 'custom')) {
+ data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.extra_vars, true);
+ }
+
+ if (sources_scope.source && (sources_scope.source.value === 'vmware' ||
+ sources_scope.source.value === 'openstack')) {
+ data.source_vars = ToJSON(sources_scope.envParseType, sources_scope.inventory_variables, true);
+ }
+
+ // the API doesn't expect the credential to be passed with a custom inv script
+ if (sources_scope.source && sources_scope.source.value === 'custom') {
+ delete(data.credential);
+ }
+
+ if (!parseError) {
+ Rest.setUrl(sources_scope.source_url);
+ Rest.put(data)
+ .success(function() {
+ modal_scope.$emit('SaveComplete', false);
+ })
+ .error(function(data, status) {
+ $('#group_tabs a:eq(1)').tab('show');
+ ProcessErrors(sources_scope, data, status, SourceForm, {
+ hdr: 'Error!',
+ msg: 'Failed to update group inventory source. PUT status: ' + status
+ });
+ });
+ }
+ });
+
+ // Cancel
+ modal_scope.cancelPanel = function() {
+ Wait('stop');
+ $state.go('inventoryManage', {}, {reload: true})
+ };
+
+ // Save
+ modal_scope.saveGroup = function() {
+ Wait('start');
+ var fld, data, json_data;
+
+ try {
+
+ json_data = ToJSON(properties_scope.parseType, properties_scope.variables, true);
+
+ data = {};
+ for (fld in GroupForm.fields) {
+ data[fld] = properties_scope[fld];
+ }
+
+ data.inventory = inventory_id;
+
+ Rest.setUrl(defaultUrl);
+ if (mode === 'edit' || (mode === 'add' && group_created)) {
+ Rest.put(data)
+ .success(function() {
+ modal_scope.$emit('formSaveSuccess');
+ })
+ .error(function(data, status) {
+ $('#group_tabs a:eq(0)').tab('show');
+ ProcessErrors(properties_scope, data, status, GroupForm, {
+ hdr: 'Error!',
+ msg: 'Failed to update group: ' + group_id + '. PUT status: ' + status
+ });
+ });
+ } else {
+ Rest.post(data)
+ .success(function(data) {
+ group_created = true;
+ group_id = data.id;
+ sources_scope.source_url = data.related.inventory_source;
+ modal_scope.$emit('formSaveSuccess');
+ })
+ .error(function(data, status) {
+ $('#group_tabs a:eq(0)').tab('show');
+ ProcessErrors(properties_scope, data, status, GroupForm, {
+ hdr: 'Error!',
+ msg: 'Failed to create group: ' + group_id + '. POST status: ' + status
+ });
+ });
+ }
+ } catch (e) {
+ // ignore. ToJSON will have already alerted the user
+ }
+ };
+
+ // Start the update process
+ modal_scope.updateGroup = function() {
+ if (sources_scope.source === "manual" || sources_scope.source === null) {
+ Alert('Missing Configuration', 'The selected group is not configured for updates. You must first edit the group, provide Source settings, ' +
+ 'and then run an update.', 'alert-info');
+ } else if (sources_scope.status === 'updating') {
+ Alert('Update in Progress', 'The inventory update process is currently running for group
' +
+ $filter('sanitize')(sources_scope.summary_fields.group.name) + '. Use the Refresh button to monitor the status.', 'alert-info', null, null, null, null, true);
+ } else {
+ InventoryUpdate({
+ scope: parent_scope,
+ group_id: group_id,
+ url: properties_scope.group_update_url,
+ group_name: properties_scope.name,
+ group_source: sources_scope.source.value
+ });
+ }
+ };
+
+ // Change the lookup and regions when the source changes
+ sources_scope.sourceChange = function() {
+ sources_scope.credential_name = "";
+ sources_scope.credential = "";
+ if (sources_scope.credential_name_api_error) {
+ delete sources_scope.credential_name_api_error;
+ }
+ initSourceChange();
+ };
+
+
+ angular.extend(vm, {
+ cancelPanel : modal_scope.cancelPanel,
+ saveGroup: modal_scope.saveGroup
+ })
+}
+
+export default ['$filter', '$rootScope', '$location', '$log', '$stateParams', '$compile', '$state', '$scope', 'Rest', 'Alert', 'GroupForm', 'GenerateForm',
+ 'Prompt', 'ProcessErrors', 'GetBasePath', 'SetNodeName', 'ParseTypeChange', 'GetSourceTypeOptions', 'InventoryUpdate',
+ 'LookUpInit', 'Empty', 'Wait', 'GetChoices', 'UpdateGroup', 'SourceChange', 'Find',
+ 'ParseVariableString', 'ToJSON', 'GroupsScheduleListInit', 'SourceForm', 'SetSchedulesInnerDialogSize', 'CreateSelect2', 'ParamPass',
+ manageGroupsDirectiveController
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js
new file mode 100644
index 0000000000..b4c3fbff73
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.js
@@ -0,0 +1,25 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/* jshint unused: vars */
+import manageGroupsDirectiveController from './manage-groups.directive.controller';
+
+export default ['templateUrl', 'ParamPass',
+ function(templateUrl, ParamPass) {
+ return {
+ restrict: 'EA',
+ scope: true,
+ replace: true,
+ templateUrl: templateUrl('inventories/manage/manage-groups/directive/manage-groups.directive'),
+ link: function(scope, element, attrs) {
+
+ },
+ controller: manageGroupsDirectiveController,
+ controllerAs: 'vm',
+ bindToController: true
+ };
+ }
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html
new file mode 100644
index 0000000000..eacbf795c9
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/directive/manage-groups.directive.partial.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/main.js b/awx/ui/client/src/inventories/manage/manage-groups/main.js
new file mode 100644
index 0000000000..b015b64628
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/main.js
@@ -0,0 +1,16 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import route from './manage-groups.route';
+import manageGroupsDirective from './directive/manage-groups.directive';
+
+export default
+ angular.module('manage-groups', [])
+ .directive('manageGroups', manageGroupsDirective)
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(route.edit);
+ $stateExtender.addState(route.add);
+ }]);
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html
new file mode 100644
index 0000000000..e39e0984ac
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.partial.html
@@ -0,0 +1,5 @@
+
diff --git a/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js
new file mode 100644
index 0000000000..e83c934ea9
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-groups/manage-groups.route.js
@@ -0,0 +1,46 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+import {
+ templateUrl
+} from '../../../shared/template-url/template-url.factory';
+
+export default {
+ edit: {
+ name: 'inventoryManage.editGroup',
+ route: '/:group_id/editGroup',
+ templateUrl: templateUrl('inventories/manage/manage-groups/manage-groups'),
+ data: {
+ group_id: 'group_id',
+ mode: 'edit'
+ },
+ ncyBreadcrumb: {
+ label: "INVENTORY EDIT GROUPS"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+ },
+
+ add: {
+ name: 'inventoryManage.addGroup',
+ route: '/addGroup',
+ templateUrl: templateUrl('inventories/manage/manage-groups/manage-groups'),
+ ncyBreadcrumb: {
+ label: "INVENTORY ADD GROUP"
+ },
+ data: {
+ mode: 'add'
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+ },
+
+};
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js
new file mode 100644
index 0000000000..90190143f3
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.controller.js
@@ -0,0 +1,192 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+function manageHostsDirectiveController($rootScope, $location, $log, $stateParams, $state, $scope, Rest, Alert, HostForm,
+ GenerateForm, Prompt, ProcessErrors, GetBasePath, HostsReload, ParseTypeChange, Wait,
+ Find, SetStatus, ApplyEllipsis, ToJSON, ParseVariableString, CreateDialog, TextareaResize, ParamPass) {
+
+ var vm = this;
+
+ var params = ParamPass.get();
+ if(params === undefined) {
+ params = {};
+ params.host_scope = $scope.$new();
+ params.group_scope = $scope.$new();
+ }
+ var parent_scope = params.host_scope,
+ group_scope = params.group_scope,
+ inventory_id = $stateParams.inventory_id,
+ mode = $state.current.data.mode, // 'add' or 'edit'
+ selected_group_id = params.selected_group_id,
+ generator = GenerateForm,
+ form = HostForm,
+ defaultUrl,
+ scope = parent_scope.$new(),
+ master = {},
+ relatedSets = {},
+ url, form_scope;
+
+ var host_id = $stateParams.host_id || undefined;
+
+ form_scope =
+ generator.inject(HostForm, {
+ mode: 'edit',
+ id: 'host-panel-form',
+ related: false,
+ scope: scope,
+ cancelButton: false
+ });
+ generator.reset();
+ console.info(angular.element(document.getElementById('host_variables')));
+
+ $scope.parseType = 'yaml';
+ ParseTypeChange({
+ scope: form_scope,
+ variable: 'variables',
+ parse_variable: 'parseType',
+ field_id: 'host_variables'
+ });
+
+
+ // Retrieve detail record and prepopulate the form
+ if (mode === 'edit') {
+ defaultUrl = GetBasePath('hosts') + host_id + '/';
+ Rest.setUrl(defaultUrl);
+ Rest.get()
+ .success(function(data) {
+ var set, fld, related;
+ for (fld in form.fields) {
+ if (data[fld]) {
+ scope[fld] = data[fld];
+ master[fld] = scope[fld];
+ }
+ }
+ related = data.related;
+ for (set in form.related) {
+ if (related[set]) {
+ relatedSets[set] = {
+ url: related[set],
+ iterator: form.related[set].iterator
+ };
+ }
+ }
+ scope.variable_url = data.related.variable_data;
+ scope.has_inventory_sources = data.has_inventory_sources;
+ })
+ .error(function(data, status) {
+ ProcessErrors(parent_scope, data, status, form, {
+ hdr: 'Error!',
+ msg: 'Failed to retrieve host: ' + host_id + '. GET returned status: ' + status
+ });
+ });
+ } else {
+ if (selected_group_id) {
+ // adding hosts to a group
+ url = GetBasePath('groups') + selected_group_id + '/';
+ } else {
+ // adding hosts to the top-level (inventory)
+ url = GetBasePath('inventory') + inventory_id + '/';
+ }
+ // Add mode
+ Rest.setUrl(url);
+ Rest.get()
+ .success(function(data) {
+ scope.has_inventory_sources = data.has_inventory_sources;
+ scope.enabled = true;
+ scope.variables = '---';
+ defaultUrl = data.related.hosts;
+ //scope.$emit('hostVariablesLoaded');
+ })
+ .error(function(data, status) {
+ ProcessErrors(parent_scope, data, status, form, {
+ hdr: 'Error!',
+ msg: 'Failed to retrieve group: ' + selected_group_id + '. GET returned status: ' + status
+ });
+ });
+ }
+
+ if (scope.removeSaveCompleted) {
+ scope.removeSaveCompleted();
+ }
+ scope.removeSaveCompleted = scope.$on('saveCompleted', function() {
+ Wait('stop');
+ try {
+ $('#host-modal-dialog').dialog('close');
+ } catch (err) {
+ // ignore
+ }
+ if (group_scope && group_scope.refreshHosts) {
+ group_scope.refreshHosts();
+ }
+ if (parent_scope.refreshHosts) {
+ parent_scope.refreshHosts();
+ }
+ scope.$destroy();
+ $state.go('inventoryManage', {}, {
+ reload: true
+ });
+ });
+
+ // Save changes to the parent
+ var saveHost = function() {
+ Wait('start');
+ var fld, data = {};
+
+ try {
+ data.variables = ToJSON(scope.parseType, scope.variables, true);
+ for (fld in form.fields) {
+ data[fld] = scope[fld];
+ }
+ data.inventory = inventory_id;
+ Rest.setUrl(defaultUrl);
+ if (mode === 'edit') {
+ Rest.put(data)
+ .success(function() {
+ scope.$emit('saveCompleted');
+ })
+ .error(function(data, status) {
+ ProcessErrors(scope, data, status, form, {
+ hdr: 'Error!',
+ msg: 'Failed to update host: ' + host_id + '. PUT returned status: ' + status
+ });
+ });
+ } else {
+ Rest.post(data)
+ .success(function() {
+ scope.$emit('saveCompleted');
+ })
+ .error(function(data, status) {
+ ProcessErrors(scope, data, status, form, {
+ hdr: 'Error!',
+ msg: 'Failed to create host. POST returned status: ' + status
+ });
+ });
+ }
+ } catch (e) {
+ // ignore. ToJSON will have already alerted the user
+ }
+ };
+
+ var cancelPanel = function() {
+ scope.$destroy();
+ if (scope.codeMirror) {
+ scope.codeMirror.destroy();
+ }
+ $state.go('inventoryManage');
+ };
+
+ angular.extend(vm, {
+ cancelPanel: cancelPanel,
+ saveHost: saveHost,
+ mode: mode
+ });
+}
+
+export default ['$rootScope', '$location', '$log', '$stateParams', '$state', '$scope', 'Rest', 'Alert', 'HostForm',
+ 'GenerateForm', 'Prompt', 'ProcessErrors', 'GetBasePath', 'HostsReload', 'ParseTypeChange',
+ 'Wait', 'Find', 'SetStatus', 'ApplyEllipsis', 'ToJSON', 'ParseVariableString',
+ 'CreateDialog', 'TextareaResize', 'ParamPass', manageHostsDirectiveController
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.js b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.js
new file mode 100644
index 0000000000..ac10ea1dfd
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.js
@@ -0,0 +1,25 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+/* jshint unused: vars */
+import manageHostsDirectiveController from './manage-hosts.directive.controller';
+
+export default ['templateUrl', 'ParamPass',
+ function(templateUrl, ParamPass) {
+ return {
+ restrict: 'EA',
+ scope: true,
+ replace: true,
+ templateUrl: templateUrl('inventories/manage/manage-hosts/directive/manage-hosts.directive'),
+ link: function(scope, element, attrs) {
+
+ },
+ controller: manageHostsDirectiveController,
+ controllerAs: 'vm',
+ bindToController: true
+ };
+ }
+];
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html
new file mode 100644
index 0000000000..31c2249234
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/directive/manage-hosts.directive.partial.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/main.js b/awx/ui/client/src/inventories/manage/manage-hosts/main.js
new file mode 100644
index 0000000000..e90954f2de
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/main.js
@@ -0,0 +1,16 @@
+/*************************************************
+ * Copyright (c) 2015 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+
+import route from './manage-hosts.route';
+import manageHostsDirective from './directive/manage-hosts.directive';
+
+export default
+ angular.module('manage-hosts', [])
+ .directive('manageHosts', manageHostsDirective)
+ .run(['$stateExtender', function($stateExtender) {
+ $stateExtender.addState(route.edit);
+ $stateExtender.addState(route.add);
+ }]);
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html
new file mode 100644
index 0000000000..f145702511
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.partial.html
@@ -0,0 +1,5 @@
+
diff --git a/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js
new file mode 100644
index 0000000000..8e97578aa5
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage-hosts/manage-hosts.route.js
@@ -0,0 +1,46 @@
+/*************************************************
+ * Copyright (c) 2016 Ansible, Inc.
+ *
+ * All Rights Reserved
+ *************************************************/
+import {
+ templateUrl
+} from '../../../shared/template-url/template-url.factory';
+
+export default {
+ edit: {
+ name: 'inventoryManage.editHost',
+ route: '/:host_id/editHost',
+ templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'),
+ data: {
+ host_id: 'host_id',
+ mode: 'edit'
+ },
+ ncyBreadcrumb: {
+ label: "INVENTORY EDIT HOSTS"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+ },
+
+ add: {
+ name: 'inventoryManage.addHost',
+ route: '/addHost',
+ templateUrl: templateUrl('inventories/manage/manage-hosts/manage-hosts'),
+ data: {
+ mode: 'add'
+ },
+ ncyBreadcrumb: {
+ label: "INVENTORY ADD HOST"
+ },
+ resolve: {
+ features: ['FeaturesService', function(FeaturesService) {
+ return FeaturesService.get();
+ }]
+ }
+ },
+
+};
diff --git a/awx/ui/client/src/inventories/manage/manage.block.less b/awx/ui/client/src/inventories/manage/manage.block.less
new file mode 100644
index 0000000000..772423c2e4
--- /dev/null
+++ b/awx/ui/client/src/inventories/manage/manage.block.less
@@ -0,0 +1,18 @@
+#Inventory-groupManage--panel,
+#Inventory-hostManage--panel {
+ .ui-dialog-buttonpane.ui-widget-content {
+ border: none;
+ text-align: right;
+ }
+
+ #host-panel-form,
+ #properties-tab {
+ .Form-header {
+ margin-top: -20px;
+ }
+ }
+
+ .Form-textArea {
+ width: 100%;
+ }
+}
diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js
index 434526cd7d..a662c7c139 100644
--- a/awx/ui/client/src/shared/Utilities.js
+++ b/awx/ui/client/src/shared/Utilities.js
@@ -871,4 +871,20 @@ angular.module('Utilities', ['RestServices', 'Utilities', 'sanitizeFilter'])
};
}
-]);
+])
+.factory('ParamPass', function() {
+ var savedData = undefined;
+
+ function set(data) {
+ savedData = data;
+ }
+
+ function get() {
+ return savedData;
+ }
+
+ return {
+ set: set,
+ get: get
+ }
+});
diff --git a/awx/ui/client/src/shared/form-generator.js b/awx/ui/client/src/shared/form-generator.js
index 693a090f68..5b45fff729 100644
--- a/awx/ui/client/src/shared/form-generator.js
+++ b/awx/ui/client/src/shared/form-generator.js
@@ -1394,13 +1394,18 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
"ng-if=is_superuser>Admin";
}
html += "
\n";
+ if(options.cancelButton !== undefined && options.cancelButton === false) {
+ html += "
";
+ html += "
";
+ } else {
+ html += "
";
+ html += "
\n";
+ }
+ html += "
\n"; //end of Form-header
- html += "