From 1a619de91ff64e271396ec2d07489694dc27a119 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 5 Feb 2019 11:31:01 -0500 Subject: [PATCH 1/2] Makes priv escalation method a dynamic select element --- .../credentials/add-credentials.controller.js | 17 ++++++- .../edit-credentials.controller.js | 25 +++++++++- awx/ui/client/lib/components/index.js | 2 + .../input/dynamic-select.directive.js | 49 +++++++++++++++++++ .../input/dynamic-select.partial.html | 16 ++++++ .../lib/components/input/group.directive.js | 12 ++++- awx/ui/client/src/shared/Utilities.js | 13 +++-- 7 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 awx/ui/client/lib/components/input/dynamic-select.directive.js create mode 100644 awx/ui/client/lib/components/input/dynamic-select.partial.html 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/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..c316aa9451 --- /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); }); From 334f571ad3af23d884ebba649c7d5a792263a916 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 13 Feb 2019 11:48:43 -0500 Subject: [PATCH 2/2] Fixes bug where extra blank option was being added to select input --- awx/ui/client/legacy/styles/forms.less | 1 + .../client/lib/components/input/dynamic-select.partial.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) 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/input/dynamic-select.partial.html b/awx/ui/client/lib/components/input/dynamic-select.partial.html index c316aa9451..b1b5eaf616 100644 --- a/awx/ui/client/lib/components/input/dynamic-select.partial.html +++ b/awx/ui/client/lib/components/input/dynamic-select.partial.html @@ -6,9 +6,9 @@ ng-model="state._value" ng-attr-tabindex="{{ tab || undefined }}" ng-disabled="state._disabled || form.disabled" - ng-options="{{ state._exp }}" id="{{ state._formId }}_{{ state.id }}_dynamic_select"> - + +