diff --git a/awx/ui/client/legacy-styles/ansible-ui.less b/awx/ui/client/legacy-styles/ansible-ui.less index 4a7471c060..7310f1072f 100644 --- a/awx/ui/client/legacy-styles/ansible-ui.less +++ b/awx/ui/client/legacy-styles/ansible-ui.less @@ -2348,3 +2348,7 @@ input[disabled].ui-spinner-input { .CodeMirror-lines { margin-bottom: 20px; } + +.select2-search--dropdown.select2-search--hide { + display: block; +} 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..748fb37e18 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 @@ -6,12 +6,12 @@ export default ['$state', '$stateParams', '$scope', 'SourcesFormDefinition', 'ParseTypeChange', 'GenerateForm', 'inventoryData', 'GroupManageService', - 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', - 'rbacUiControlService', 'ToJSON', 'SourcesService', + 'GetChoices', 'GetBasePath', 'CreateSelect2', 'GetSourceTypeOptions', 'Empty', + 'rbacUiControlService', 'ToJSON', 'SourcesService', 'Wait', 'Rest', function($state, $stateParams, $scope, SourcesFormDefinition, ParseTypeChange, GenerateForm, inventoryData, GroupManageService, GetChoices, - GetBasePath, CreateSelect2, GetSourceTypeOptions, rbacUiControlService, - ToJSON, SourcesService) { + GetBasePath, CreateSelect2, GetSourceTypeOptions, Empty, rbacUiControlService, + ToJSON, SourcesService, Wait, Rest) { let form = SourcesFormDefinition; init(); @@ -20,82 +20,157 @@ 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 (ret,status_code) { + 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 = "
" + i18n._("Select the inventory file to be synced by this source. You can select from the dropdown or enter a file within the input.") + "
", + dataTitle: i18n._('Inventory File'), + dataPlacement: 'right', + dataContainer: "body", + includeInventoryFileNotFoundError: true + }, source_regions: { label: 'Regions', type: 'select', @@ -162,7 +193,7 @@ return { custom_variables: { id: 'custom_variables', label: 'Environment Variables', //"{{vars_label}}" , - ngShow: "source && source.value=='custom' ", + ngShow: "source && source.value=='custom' || source.value === 'scm'", type: 'textarea', class: 'Form-textAreaLabel Form-formGroup--fullWidth', rows: 6, @@ -249,6 +280,20 @@ return { 'View YAML examples at docs.ansible.com
', dataContainer: 'body' }, + verbosity: { + label: i18n._('Verbosity'), + type: 'select', + ngOptions: 'v.label for v in verbosity_options track by v.value', + ngShow: "source && (source.value !== '' && source.value !== null)", + "default": 0, + required: true, + column: 1, + awPopOver: "" + i18n._("Control the level of output ansible will produce for inventory source update jobs.") + "
", + dataTitle: i18n._('Verbosity'), + dataPlacement: 'right', + dataContainer: "body", + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)', + }, checkbox_group: { label: 'Update Options', type: 'checkbox_group', @@ -268,7 +313,7 @@ return { dataContainer: 'body', dataPlacement: 'right', labelClass: 'checkbox-options', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: "(!(group_obj.summary_fields.user_capabilities.edit || canAdd))" }, { name: 'overwrite_vars', label: 'Overwrite Variables', @@ -283,7 +328,7 @@ return { dataContainer: 'body', dataPlacement: 'right', labelClass: 'checkbox-options', - ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + ngDisabled: "(!(group_obj.summary_fields.user_capabilities.edit || canAdd) || source.value === 'scm')" }, { name: 'update_on_launch', label: 'Update on Launch', @@ -296,6 +341,17 @@ return { dataPlacement: 'right', labelClass: 'checkbox-options', ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' + }, { + name: 'update_on_project_update', + label: 'Update on Project Update', + type: 'checkbox', + ngShow: "source.value === 'scm'", + awPopOver: 'TODO
', + dataTitle: 'Update on Project Update', + dataContainer: 'body', + dataPlacement: 'right', + labelClass: 'checkbox-options', + ngDisabled: '!(group_obj.summary_fields.user_capabilities.edit || canAdd)' }] }, update_cache_timeout: { diff --git a/awx/ui/client/src/inventories/sources/sources.list.js b/awx/ui/client/src/inventories/sources/sources.list.js index 38288bf04e..c1037d2a9c 100644 --- a/awx/ui/client/src/inventories/sources/sources.list.js +++ b/awx/ui/client/src/inventories/sources/sources.list.js @@ -62,7 +62,7 @@ export default { columnClass: 'col-lg-6 col-md-6 col-sm-6 col-xs-6 text-right', - group_update: { + source_update: { //label: 'Sync', mode: 'all', ngClick: 'updateSource(inventory_source)', @@ -97,7 +97,7 @@ export default { awToolTip: "{{ inventory_source.group_schedule_tooltip }}", ngClass: "inventory_source.scm_type_class", dataPlacement: 'top', - ngShow: "!(inventory_source.summary_fields.inventory_source.source === '')" + ngShow: "!(inventory_source.summary_fields.inventory_source.source === '') && inventory_source.summary_fields.user_capabilities.schedule" }, edit: { //label: 'Edit', diff --git a/awx/ui/client/src/projects/projects.list.js b/awx/ui/client/src/projects/projects.list.js index 9d03e69d3a..52dae13757 100644 --- a/awx/ui/client/src/projects/projects.list.js +++ b/awx/ui/client/src/projects/projects.list.js @@ -17,6 +17,7 @@ export default ['i18n', function(i18n) { 'Select button, located bottom right.Create a new project by clicking the button.
', index: false, hover: true, + emptyListText: i18n._('No Projects Have Been Created'), fields: { status: { diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index 5a49d1e8ff..73ab69cbdd 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -618,9 +618,13 @@ angular.module('Utilities', ['RestServices', 'Utilities']) var element = params.element, options = params.opts, multiple = (params.multiple !== undefined) ? params.multiple : true, + createNew = (params.createNew !== undefined) ? params.createNew : false, placeholder = params.placeholder, customDropdownAdapter = (params.customDropdownAdapter !== undefined) ? params.customDropdownAdapter : true, - addNew = params.addNew; + addNew = params.addNew, + scope = params.scope, + options = params.options, + model = params.model; $.fn.select2.amd.require([ 'select2/utils', @@ -658,6 +662,10 @@ angular.module('Utilities', ['RestServices', 'Utilities']) if (addNew) { config.tags = true; config.tokenSeparators = []; + + if (!multiple) { + scope["original_" + options] = scope[options]; + } } $(element).select2(config); @@ -677,6 +685,18 @@ angular.module('Utilities', ['RestServices', 'Utilities']) } }).on('select2:unselecting', (e) => { $(e.target).data('select2-unselecting', true); + }) + } + + if (addNew && !multiple) { + $(element).on('select2:select', (e) => { + var opt = $(e.target).find("[data-select2-tag='true']"); + if (opt.length) { + scope[model] = e.params.data.id; + scope[options] = scope["original_" + options]; + scope[options].push($(opt[0]).attr("value")); + } + $(element).trigger('change'); }); } diff --git a/awx/ui/client/src/shared/directives.js b/awx/ui/client/src/shared/directives.js index dc7c9a4011..6589d3b555 100644 --- a/awx/ui/client/src/shared/directives.js +++ b/awx/ui/client/src/shared/directives.js @@ -515,6 +515,10 @@ function(ConfigurationUtils, i18n, $rootScope) { _doAutoPopulate(); } }); + + if (attrs.watchbasepath === 'projectBasePath') { + _doAutoPopulate(); + } } function _doAutoPopulate() { @@ -522,7 +526,11 @@ function(ConfigurationUtils, i18n, $rootScope) { if (attrs.watchbasepath !== undefined && scope[attrs.watchbasepath] !== undefined) { basePath = scope[attrs.watchbasepath]; - query = '&role_level=use_role'; + if (attrs.watchbasepath !== "projectBasePath") { + query = '&role_level=use_role'; + } else { + query = ''; + } } else { basePath = GetBasePath(elm.attr('data-basePath')) || elm.attr('data-basePath'); diff --git a/awx/ui/client/src/shared/generator-helpers.js b/awx/ui/client/src/shared/generator-helpers.js index d6c8f03c6d..b46ef824e5 100644 --- a/awx/ui/client/src/shared/generator-helpers.js +++ b/awx/ui/client/src/shared/generator-helpers.js @@ -132,6 +132,7 @@ angular.module('GeneratorHelpers', [systemStatus.name]) icon = "fa-trash-o"; break; case 'group_update': + case 'source_update': icon = 'fa-refresh'; break; case 'scm_update':