diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 27b3eaee4a..36428d50a8 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -1,4 +1,11 @@ -function AddCredentialsController (models, $state, $scope, strings, componentsStrings) { +function AddCredentialsController ( + models, + $state, + $scope, + strings, + componentsStrings, + ConfigService +) { const vm = this || {}; const { me, credential, credentialType, organization } = models; @@ -48,6 +55,11 @@ function AddCredentialsController (models, $state, $scope, strings, componentsSt if (credentialType.get('name') === 'Google Compute Engine') { fields.splice(2, 0, gceFileInputSchema); $scope.$watch(`vm.form.${gceFileInputSchema.id}._value`, vm.gceOnFileInputChanged); + } else if (credentialType.get('name') === 'Machine') { + const apiConfig = ConfigService.get(); + const become = fields.find((field) => field.id === 'become_method'); + become._isDynamic = true; + become._choices = Array.from(apiConfig.become_methods, method => method[0]); } return fields; @@ -136,7 +148,8 @@ AddCredentialsController.$inject = [ '$state', '$scope', 'CredentialsStrings', - 'ComponentsStrings' + 'ComponentsStrings', + 'ConfigService' ]; export default AddCredentialsController; diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 560b2605a6..67f8590f95 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -1,4 +1,11 @@ -function EditCredentialsController (models, $state, $scope, strings, componentsStrings) { +function EditCredentialsController ( + models, + $state, + $scope, + strings, + componentsStrings, + ConfigService +) { const vm = this || {}; const { me, credential, credentialType, organization, isOrgCredAdmin } = models; @@ -103,6 +110,19 @@ function EditCredentialsController (models, $state, $scope, strings, componentsS $scope.$watch(`vm.form.${gceFileInputSchema.id}._value`, vm.gceOnFileInputChanged); $scope.$watch('vm.form.ssh_key_data._isBeingReplaced', vm.gceOnReplaceKeyChanged); + } else if (credentialType.get('name') === 'Machine') { + const apiConfig = ConfigService.get(); + const become = fields.find((field) => field.id === 'become_method'); + become._isDynamic = true; + become._choices = Array.from(apiConfig.become_methods, method => method[0]); + // Add the value to the choices if it doesn't exist in the preset list + if (become._value && become._value !== '') { + const optionMatches = become._choices + .findIndex((option) => option === become._value); + if (optionMatches === -1) { + become._choices.push(become._value); + } + } } return fields; @@ -189,7 +209,8 @@ EditCredentialsController.$inject = [ '$state', '$scope', 'CredentialsStrings', - 'ComponentsStrings' + 'ComponentsStrings', + 'ConfigService' ]; export default EditCredentialsController; diff --git a/awx/ui/client/legacy/styles/forms.less b/awx/ui/client/legacy/styles/forms.less index e166bde095..9270be7c19 100644 --- a/awx/ui/client/legacy/styles/forms.less +++ b/awx/ui/client/legacy/styles/forms.less @@ -377,6 +377,7 @@ .select2-results__option { color: @field-label !important; + min-height: 33px; } .select2-container--default .select2-results__option--highlighted[aria-selected] { diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index c9e5ba93ee..078bd16a06 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -2,6 +2,7 @@ import atLibServices from '~services'; import actionGroup from '~components/action/action-group.directive'; import divider from '~components/utility/divider.directive'; +import dynamicSelect from '~components/input/dynamic-select.directive'; import form from '~components/form/form.directive'; import formAction from '~components/form/action.directive'; import inputCheckbox from '~components/input/checkbox.directive'; @@ -53,6 +54,7 @@ angular ]) .directive('atActionGroup', actionGroup) .directive('atDivider', divider) + .directive('atDynamicSelect', dynamicSelect) .directive('atForm', form) .directive('atFormAction', formAction) .directive('atInputCheckbox', inputCheckbox) diff --git a/awx/ui/client/lib/components/input/dynamic-select.directive.js b/awx/ui/client/lib/components/input/dynamic-select.directive.js new file mode 100644 index 0000000000..ee3bca40ed --- /dev/null +++ b/awx/ui/client/lib/components/input/dynamic-select.directive.js @@ -0,0 +1,49 @@ +const templateUrl = require('~components/input/dynamic-select.partial.html'); + +function atDynamicSelectLink (scope, element, attrs, controllers) { + const [formController, inputController] = controllers; + + inputController.init(scope, element, formController); +} + +function AtDynamicSelectController (baseInputController, CreateSelect2) { + const vm = this || {}; + + let scope; + + vm.init = (_scope_, _element_, form) => { + baseInputController.call(vm, 'input', _scope_, _element_, form); + scope = _scope_; + CreateSelect2({ + element: `#${scope.state._formId}_${scope.state.id}_dynamic_select`, + model: 'state._value', + multiple: false, + addNew: true, + scope, + options: 'state._data' + }); + vm.check(); + }; +} + +AtDynamicSelectController.$inject = ['BaseInputController', 'CreateSelect2']; + +function atDynamicSelect () { + return { + restrict: 'E', + transclude: true, + replace: true, + require: ['^^at-form', 'atDynamicSelect'], + templateUrl, + controller: AtDynamicSelectController, + controllerAs: 'vm', + link: atDynamicSelectLink, + scope: { + state: '=', + col: '@', + tab: '@' + } + }; +} + +export default atDynamicSelect; diff --git a/awx/ui/client/lib/components/input/dynamic-select.partial.html b/awx/ui/client/lib/components/input/dynamic-select.partial.html new file mode 100644 index 0000000000..b1b5eaf616 --- /dev/null +++ b/awx/ui/client/lib/components/input/dynamic-select.partial.html @@ -0,0 +1,16 @@ +
+
+ +
+ +
+ +
+
diff --git a/awx/ui/client/lib/components/input/group.directive.js b/awx/ui/client/lib/components/input/group.directive.js index 01054f47c2..291b534ac9 100644 --- a/awx/ui/client/lib/components/input/group.directive.js +++ b/awx/ui/client/lib/components/input/group.directive.js @@ -75,10 +75,18 @@ function AtInputGroupController ($scope, $compile) { }; vm.getComponentType = input => { - const config = {}; + const config = { + _formId: formId + }; if (input.type === 'string') { - if (!input.multiline) { + if (input._isDynamic) { + config._component = 'at-dynamic-select'; + config._format = 'array'; + config._data = input._choices; + config._exp = 'choice for (index, choice) in state._data'; + config._isDynamic = true; + } else if (!input.multiline) { if (input.secret) { config._component = 'at-input-secret'; } else { diff --git a/awx/ui/client/src/shared/Utilities.js b/awx/ui/client/src/shared/Utilities.js index fdfb0a118e..4c31bd459f 100644 --- a/awx/ui/client/src/shared/Utilities.js +++ b/awx/ui/client/src/shared/Utilities.js @@ -596,7 +596,7 @@ angular.module('Utilities', ['RestServices', 'Utilities']) minimumResultsForSearch = params.minimumResultsForSearch ? params.minimumResultsForSearch : Infinity; if (scope && selectOptions) { - original_options = _.cloneDeep(scope[selectOptions]); + original_options = _.get(scope, selectOptions); } $.fn.select2.amd.require([ @@ -675,13 +675,16 @@ angular.module('Utilities', ['RestServices', 'Utilities']) if (addNew && !multiple) { $(element).on('select2:select', (e) => { - scope[model] = e.params.data.text; - scope[selectOptions] = _.cloneDeep(original_options); + _.set(scope, model, e.params.data.text); + const optionsClone = _.clone(original_options); + _.set(scope, selectOptions, optionsClone); if (e.params.data.id === "") { return; } - if (scope[selectOptions].indexOf(e.params.data.text) === -1) { - scope[selectOptions].push(e.params.data.text); + const optionMatches = original_options.findIndex((option) => option === e.params.data.text); + if (optionMatches === -1) { + optionsClone.push(e.params.data.text); + _.set(scope, selectOptions, optionsClone); } $(element).select2(config); });