diff --git a/awx/ui/client/src/inventories/sources/add/sources-add.controller.js b/awx/ui/client/src/inventories/sources/add/sources-add.controller.js index 806f850f4c..870125a87d 100644 --- a/awx/ui/client/src/inventories/sources/add/sources-add.controller.js +++ b/awx/ui/client/src/inventories/sources/add/sources-add.controller.js @@ -5,13 +5,12 @@ *************************************************/ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', - 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupManageService', - 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', - 'rbacUiControlService', 'ToJSON', 'SourcesService', + 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'Empty', + 'rbacUiControlService', 'ToJSON', 'SourcesService', 'Wait', 'Rest', 'Alert', 'ProcessErrors', function($state, $stateParams, $scope, SourcesFormDefinition, ParseTypeChange, - GenerateForm, inventoryData, GroupManageService, GetChoices, - GetBasePath, CreateSelect2, GetSourceTypeOptions, rbacUiControlService, - ToJSON, SourcesService) { + GenerateForm, inventoryData, GetChoices, + GetBasePath, CreateSelect2, GetSourceTypeOptions, Empty, rbacUiControlService, + ToJSON, SourcesService, Wait, Rest, Alert, ProcessErrors) { let form = SourcesFormDefinition; init(); @@ -20,82 +19,153 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', // apply form definition's default field values GenerateForm.applyDefaults(form, $scope); - rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/inventory_sources") - .then(function(canAdd) { - $scope.canAdd = canAdd; - }); - $scope.envParseType = 'yaml'; + rbacUiControlService.canAdd(GetBasePath('inventory') + $stateParams.inventory_id + "/inventory_sources") + .then(function(canAdd) { + $scope.canAdd = canAdd; + }); + initSources(); } + var getInventoryFiles = function (project) { + var url; + + if (!Empty(project)) { + url = GetBasePath('projects') + project + '/inventories/'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + $scope.inventory_files = data; + $scope.inventory_files.push("/ (project root)"); + sync_inventory_file_select2(); + Wait('stop'); + }) + .error(function () { + Alert('Cannot get inventory files', 'Unable to retrieve the list of inventory files for this project.', 'alert-info'); + Wait('stop'); + }); + } + }; + + // Detect and alert user to potential SCM status issues + var checkSCMStatus = function () { + if (!Empty($scope.project)) { + Rest.setUrl(GetBasePath('projects') + $scope.project + '/'); + Rest.get() + .success(function (data) { + var msg; + switch (data.status) { + case 'failed': + msg = "
The Project selected has a status of \"failed\". You must run a successful update before you can select an inventory file."; + break; + case 'never updated': + msg = "
The Project selected has a status of \"never updated\". You must run a successful update before you can select an inventory file."; + break; + case 'missing': + msg = '
The selected project has a status of \"missing\". Please check the server and make sure ' + + ' the directory exists and file permissions are set correctly.
'; + break; + } + if (msg) { + Alert('Warning', msg, 'alert-info alert-info--noTextTransform', null, null, null, null, true); + } + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, form, { hdr: 'Error!', + msg: 'Failed to get project ' + $scope.project + '. GET returned status: ' + status }); + }); + } + }; + + // Register a watcher on project_name + if ($scope.getInventoryFilesUnregister) { + $scope.getInventoryFilesUnregister(); + } + $scope.getInventoryFilesUnregister = $scope.$watch('project', function (newValue, oldValue) { + if (newValue !== oldValue) { + getInventoryFiles(newValue); + checkSCMStatus(); + } + }); + + function sync_inventory_file_select2() { + CreateSelect2({ + element:'#inventory-file-select', + addNew: true, + multiple: false, + scope: $scope, + options: 'inventory_files', + model: 'inventory_file' + }); + } + $scope.lookupCredential = function(){ - let kind = ($scope.source.value === "ec2") ? "aws" : $scope.source.value; $state.go('.credential', { credential_search: { - kind: kind, + // TODO: get kind sorting for credential properly implemented + // kind: kind, page_size: '5', page: '1' } }); }; - $scope.formCancel = function() { - $state.go('^'); - }; - - $scope.formSave = function() { - var params; - - params = { - name: $scope.name, - description: $scope.description, - inventory: inventoryData.id, - instance_filters: $scope.instance_filters, - source_script: $scope.inventory_script, - credential: $scope.credential, - overwrite: $scope.overwrite, - overwrite_vars: $scope.overwrite_vars, - update_on_launch: $scope.update_on_launch, - update_cache_timeout: $scope.update_cache_timeout || 0, - // comma-delimited strings - group_by: _.map($scope.group_by, 'value').join(','), - source_regions: _.map($scope.source_regions, 'value').join(',') - }; - - if ($scope.source) { - params.source_vars = $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables']; - params.source = $scope.source.value; - } else { - params.source = null; - } - SourcesService.post(params).then(function(res){ - let inventory_source_id = res.data.id; - $state.go('^.edit', {inventory_source_id: inventory_source_id}, {reload: true}); + $scope.lookupProject = function(){ + $state.go('.project', { + project_search: { + page_size: '5', + page: '1' + } }); }; + + $scope.projectBasePath = GetBasePath('projects'); + $scope.credentialBasePath = GetBasePath('credentials') + '?credential_type__kind__in=cloud,network'; + $scope.sourceChange = function(source) { - source = source.value; + if (source) { + source = source.value; + } else { + source = ""; + } + + $scope.credentialBasePath = GetBasePath('credentials') + '?credential_type__kind__in=cloud,network'; + if (source === 'custom'){ $scope.credentialBasePath = GetBasePath('inventory_script'); } - // equal to case 'ec2' || 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack' - else{ - $scope.credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source === '' ? '' : '?kind=' + (source)); - } - if (source === 'ec2' || source === 'custom' || source === 'vmware' || source === 'openstack') { + + if (source === 'ec2' || source === 'custom' || source === 'vmware' || source === 'openstack' || source === 'scm') { + $scope.envParseType = 'yaml'; + + var varName; + if (source === 'scm') { + varName = 'custom_variables'; + } else { + varName = source + '_variables'; + } + ParseTypeChange({ scope: $scope, - field_id: source + '_variables', - variable: source + '_variables', + field_id: varName, + variable: varName, parse_variable: 'envParseType' }); } + if (source === 'scm') { + $scope.overwrite_vars = true; + $scope.inventory_source_form.inventory_file.$setPristine(); + } else { + $scope.overwrite_vars = false; + } + // reset fields $scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null; // azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint $scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions']; - $scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false; + $scope.cloudCredentialRequired = source !== '' && source !== 'scm' && source !== 'custom' && source !== 'ec2' ? true : false; $scope.group_by = null; $scope.source_regions = null; $scope.credential = null; @@ -107,6 +177,10 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', initRegionSelect(); }); + $scope.$on('choicesReadyVerbosity', function() { + initVerbositySelect(); + }); + $scope.$on('sourceTypeOptionsReady', function() { initSourceSelect(); }); @@ -121,6 +195,7 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', multiple: true }); } + function initSourceSelect(){ CreateSelect2({ element: '#inventory_source_source', @@ -128,6 +203,15 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', }); } + function initVerbositySelect(){ + CreateSelect2({ + element: '#inventory_source_verbosity', + multiple: false + }); + + $scope.verbosity = $scope.verbosity_options[0]; + } + function initSources(){ GetChoices({ scope: $scope, @@ -174,11 +258,66 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', choice_name: 'ec2_group_by_choices', callback: 'choicesReadyGroup' }); + + GetChoices({ + scope: $scope, + url: GetBasePath('inventory_sources'), + field: 'verbosity', + variable: 'verbosity_options', + callback: 'choicesReadyVerbosity' + }); + GetSourceTypeOptions({ scope: $scope, variable: 'source_type_options', //callback: 'sourceTypeOptionsReady' this callback is hard-coded into GetSourceTypeOptions(), included for ref }); } + + $scope.formCancel = function() { + $state.go('^'); + }; + + $scope.formSave = function() { + var params; + + params = { + name: $scope.name, + description: $scope.description, + inventory: inventoryData.id, + instance_filters: $scope.instance_filters, + source_script: $scope.inventory_script, + credential: $scope.credential, + overwrite: $scope.overwrite, + overwrite_vars: $scope.overwrite_vars, + update_on_launch: $scope.update_on_launch, + verbosity: $scope.verbosity.value, + update_cache_timeout: $scope.update_cache_timeout || 0, + // comma-delimited strings + group_by: _.map($scope.group_by, 'value').join(','), + source_regions: _.map($scope.source_regions, 'value').join(',') + }; + + if ($scope.source) { + params.source_vars = $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables']; + params.source = $scope.source.value; + if ($scope.source.value === 'scm') { + params.update_on_project_update = $scope.update_on_project_update; + params.source_project = $scope.project; + + if ($scope.inventory_file === '/ (project root)') { + params.source_path = ""; + } else { + params.source_path = $scope.inventory_file; + } + } + } else { + params.source = null; + } + SourcesService.post(params).then(function(res){ + let inventory_source_id = res.data.id; + $state.go('^.edit', {inventory_source_id: inventory_source_id}, {reload: true}); + }); + }; } ]; diff --git a/awx/ui/client/src/inventories/sources/edit/sources-edit.controller.js b/awx/ui/client/src/inventories/sources/edit/sources-edit.controller.js index 98517f9e56..0c354958cb 100644 --- a/awx/ui/client/src/inventories/sources/edit/sources-edit.controller.js +++ b/awx/ui/client/src/inventories/sources/edit/sources-edit.controller.js @@ -5,13 +5,11 @@ *************************************************/ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', - 'rbacUiControlService', 'ToJSON', 'ParseTypeChange', 'GroupManageService', - 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', - 'inventorySourceData', 'SourcesService', 'inventoryData', + 'rbacUiControlService', 'ToJSON', 'ParseTypeChange', 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', + 'inventorySourceData', 'SourcesService', 'inventoryData', 'Empty', 'Wait', 'Rest', 'Alert', 'ProcessErrors', function($state, $stateParams, $scope, ParseVariableString, - rbacUiControlService, ToJSON,ParseTypeChange, GroupManageService, - GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, - inventorySourceData, SourcesService, inventoryData) { + rbacUiControlService, ToJSON,ParseTypeChange, GetChoices, GetBasePath, CreateSelect2, GetSourceTypeOptions, + inventorySourceData, SourcesService, inventoryData, Empty, Wait, Rest, Alert, ProcessErrors) { init(); @@ -20,8 +18,19 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', .then(function(canAdd) { $scope.canAdd = canAdd; }); + // instantiate expected $scope values from inventorySourceData - _.assign($scope, { credential: inventorySourceData.credential }, { overwrite: inventorySourceData.overwrite }, { overwrite_vars: inventorySourceData.overwrite_vars }, { update_on_launch: inventorySourceData.update_on_launch }, { update_cache_timeout: inventorySourceData.update_cache_timeout }, { instance_filters: inventorySourceData.instance_filters }, { inventory_script: inventorySourceData.source_script }); + _.assign($scope, + {credential: inventorySourceData.credential}, + {overwrite: inventorySourceData.overwrite}, + {overwrite_vars: inventorySourceData.overwrite_vars}, + {update_on_launch: inventorySourceData.update_on_launch}, + {update_cache_timeout: inventorySourceData.update_cache_timeout}, + {instance_filters: inventorySourceData.instance_filters}, + {inventory_script: inventorySourceData.source_script}, + {verbosity: inventorySourceData.verbosity}); + + $scope.inventory_source_obj = inventorySourceData; if (inventorySourceData.credential) { $scope.credential_name = inventorySourceData.summary_fields.credential.name; } @@ -41,6 +50,113 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', initSources(); } + var getInventoryFiles = function (project) { + var url; + + if (!Empty(project)) { + url = GetBasePath('projects') + project + '/inventories/'; + Wait('start'); + Rest.setUrl(url); + Rest.get() + .success(function (data) { + $scope.inventory_files = data; + $scope.inventory_files.push("/ (project root)"); + + if (inventorySourceData.source_path !== "") { + $scope.inventory_file = inventorySourceData.source_path; + if ($scope.inventory_files.indexOf($scope.inventory_file) < 0) { + $scope.inventory_files.push($scope.inventory_file); + } + } else { + $scope.inventory_file = "/ (project root)"; + } + sync_inventory_file_select2(); + Wait('stop'); + }) + .error(function () { + Alert('Cannot get inventory files', 'Unable to retrieve the list of inventory files for this project.', 'alert-info'); + Wait('stop'); + }); + } + }; + + // Detect and alert user to potential SCM status issues + var checkSCMStatus = function () { + if (!Empty($scope.project)) { + Rest.setUrl(GetBasePath('projects') + $scope.project + '/'); + Rest.get() + .success(function (data) { + var msg; + switch (data.status) { + case 'failed': + msg = "
The Project selected has a status of \"failed\". You must run a successful update before you can select an inventory file."; + break; + case 'never updated': + msg = "
The Project selected has a status of \"never updated\". You must run a successful update before you can select an inventory file."; + break; + case 'missing': + msg = '
The selected project has a status of \"missing\". Please check the server and make sure ' + + ' the directory exists and file permissions are set correctly.
'; + break; + } + if (msg) { + Alert('Warning', msg, 'alert-info alert-info--noTextTransform', null, null, null, null, true); + } + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to get project ' + $scope.project + '. GET returned status: ' + status }); + }); + } + }; + + // Register a watcher on project_name + if ($scope.getInventoryFilesUnregister) { + $scope.getInventoryFilesUnregister(); + } + $scope.getInventoryFilesUnregister = $scope.$watch('project', function (newValue, oldValue) { + if (newValue !== oldValue) { + getInventoryFiles(newValue); + checkSCMStatus(); + } + }); + + function sync_inventory_file_select2() { + CreateSelect2({ + element:'#inventory-file-select', + addNew: true, + multiple: false, + scope: $scope, + options: 'inventory_files', + model: 'inventory_file' + }); + + // TODO: figure out why the inventory file model is being set to + // dirty + } + + $scope.lookupCredential = function(){ + $state.go('.credential', { + credential_search: { + // TODO: get kind sorting for credential properly implemented + // kind: kind, + page_size: '5', + page: '1' + } + }); + }; + + $scope.lookupProject = function(){ + $state.go('.project', { + project_search: { + page_size: '5', + page: '1' + } + }); + }; + + $scope.projectBasePath = GetBasePath('projects'); + var initRegionSelect = function() { CreateSelect2({ element: '#inventory_source_source_regions', @@ -89,45 +205,49 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', if ($scope.source) { params.source_vars = $scope[$scope.source.value + '_variables'] === '---' || $scope[$scope.source.value + '_variables'] === '{}' ? null : $scope[$scope.source.value + '_variables']; params.source = $scope.source.value; + if ($scope.source.value === 'scm') { + params.update_on_project_update = $scope.update_on_project_update; + params.source_path = $scope.inventory_file; + } } else { params.source = null; } - // switch (source) { - // no inventory source set, just create a new group - // '' is the value supplied for Manual source type - // case null || '': - SourcesService.put(params).then(() => $state.go('.', null, { reload: true })); - // break; - // // create a new group and create/associate an inventory source - // // equal to case 'rax' || 'ec2' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack' || 'custom' - // default: - // GroupManageService.put(group) - // .then(() => GroupManageService.putInventorySource(params, groupData.related.inventory_source)) - // .then(() => $state.go($state.current, null, { reload: true })); - // break; - // } + + SourcesService + .put(params) + .then(() => $state.go('.', null, { reload: true })); }; $scope.sourceChange = function(source) { $scope.source = source; if (source.value === 'ec2' || source.value === 'custom' || - source.value === 'vmware' || source.value === 'openstack') { - $scope[source.value + '_variables'] = $scope[source.value + '_variables'] === (null || undefined) ? '---' : $scope[source.value + '_variables']; + source.value === 'vmware' || source.value === 'openstack' || + source.value === 'scm') { + + var varName; + if (source === 'scm') { + varName = 'custom_variables'; + } else { + varName = source + '_variables'; + } + + $scope[varName] = $scope[varName] === (null || undefined) ? '---' : $scope[varName]; ParseTypeChange({ scope: $scope, - field_id: source.value + '_variables', - variable: source.value + '_variables', + field_id: varName, + variable: varName, parse_variable: 'envParseType', }); } // reset fields // azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint $scope.source_region_choices = source.value === 'azure_rm' ? $scope.azure_regions : $scope[source.value + '_regions']; - $scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false; + $scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'scm' && source.value !== 'ec2' ? true : false; $scope.group_by = null; $scope.source_regions = null; $scope.credential = null; $scope.credential_name = null; + initRegionSelect(); }; @@ -222,6 +342,41 @@ export default ['$state', '$stateParams', '$scope', 'ParseVariableString', }); } + function initVerbositySelect(){ + CreateSelect2({ + element: '#inventory_source_verbosity', + multiple: false + }); + } + + function sync_verbosity_select2() { + CreateSelect2({ + element:'#inventory_source_verbosity', + multiple: false + }); + } + + $scope.$on('choicesReadyVerbosity', function() { + var i; + for (i = 0; i < $scope.verbosity_options.length; i++) { + if ($scope.verbosity_options[i].value === $scope.verbosity) { + $scope.verbosity = $scope.verbosity_options[i]; + } + } + + initVerbositySelect(); + }); + + $scope.$watch('verbosity', sync_verbosity_select2); + + GetChoices({ + scope: $scope, + url: GetBasePath('inventory_sources'), + field: 'verbosity', + variable: 'verbosity_options', + callback: 'choicesReadyVerbosity' + }); + // region / source options callback $scope.$on('choicesReadyGroup', function() { if (angular.isObject($scope.source)) { diff --git a/awx/ui/client/src/inventories/sources/factories/get-source-type-options.factory.js b/awx/ui/client/src/inventories/sources/factories/get-source-type-options.factory.js index befef8a499..659c1c112a 100644 --- a/awx/ui/client/src/inventories/sources/factories/get-source-type-options.factory.js +++ b/awx/ui/client/src/inventories/sources/factories/get-source-type-options.factory.js @@ -11,7 +11,7 @@ export default .success(function (data) { var i, choices = data.actions.GET.source.choices; for (i = 0; i < choices.length; i++) { - if (choices[i][0] !== 'file') { + if (choices[i][0] !== 'file' && choices[i][0] !== "") { scope[variable].push({ label: choices[i][1], value: choices[i][0] diff --git a/awx/ui/client/src/inventories/sources/list/sources-list.controller.js b/awx/ui/client/src/inventories/sources/list/sources-list.controller.js index 13a64a8305..0d9cc7c706 100644 --- a/awx/ui/client/src/inventories/sources/list/sources-list.controller.js +++ b/awx/ui/client/src/inventories/sources/list/sources-list.controller.js @@ -5,17 +5,18 @@ *************************************************/ export default ['$scope', '$rootScope', '$state', '$stateParams', 'SourcesListDefinition', - 'InventoryUpdate', 'GroupManageService', 'CancelSourceUpdate', + 'InventoryUpdate', 'CancelSourceUpdate', 'ViewUpdateStatus', 'rbacUiControlService', 'GetBasePath', 'GetSyncStatusMsg', 'Dataset', 'Find', 'QuerySet', 'inventoryData', '$filter', 'Prompt', 'Wait', 'SourcesService', 'inventorySourceOptions', function($scope, $rootScope, $state, $stateParams, SourcesListDefinition, - InventoryUpdate, GroupManageService, CancelSourceUpdate, + InventoryUpdate, CancelSourceUpdate, ViewUpdateStatus, rbacUiControlService, GetBasePath, GetSyncStatusMsg, Dataset, Find, qs, inventoryData, $filter, Prompt, Wait, SourcesService, inventorySourceOptions){ let list = SourcesListDefinition; + var inventory_source; init(); @@ -24,7 +25,7 @@ $scope.canAdhoc = inventoryData.summary_fields.user_capabilities.adhoc; $scope.canAdd = false; - rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/groups") + rbacUiControlService.canAdd(GetBasePath('inventory') + $scope.inventory_id + "/inventory_sources") .then(function(canAdd) { $scope.canAdd = canAdd; }); @@ -39,7 +40,7 @@ optionsRequestDataProcessing(); $scope.$on(`ws-jobs`, function(e, data){ - var inventory_source = Find({ list: $scope.inventory_sources, key: 'id', val: data.inventory_source_id }); + inventory_source = Find({ list: $scope.inventory_sources, key: 'id', val: data.inventory_source_id }); if (inventory_source === undefined || inventory_source === null) { inventory_source = {}; @@ -125,11 +126,11 @@ Wait('start'); SourcesService.delete(inventory_source.id).then(() => { $('#prompt-modal').modal('hide'); - // if (parseInt($state.params.source_id) === id) { - // $state.go("sources", null, {reload: true}); - // } else { + if (parseInt($state.params.source_id) === inventory_source) { + $state.go("sources", null, {reload: true}); + } else { $state.go($state.current.name, null, {reload: true}); - // } + } Wait('stop'); }); }; @@ -160,7 +161,7 @@ }); }; $scope.scheduleSource = function(id) { - // Add this group's id to the array of group id's so that it gets + // Add this inv source's id to the array of inv source id's so that it gets // added to the breadcrumb trail $state.go('inventories.edit.inventory_sources.edit.schedules', {inventory_source_id: id}, {reload: true}); }; diff --git a/awx/ui/client/src/inventories/sources/list/sources-list.partial.html b/awx/ui/client/src/inventories/sources/list/sources-list.partial.html index 1a02f3a515..03cc6ab1fd 100644 --- a/awx/ui/client/src/inventories/sources/list/sources-list.partial.html +++ b/awx/ui/client/src/inventories/sources/list/sources-list.partial.html @@ -5,7 +5,7 @@