From c436dcf8759aa6da45bff9be338f782c86e5bc20 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Tue, 26 Feb 2019 15:17:53 -0500 Subject: [PATCH] add input source prompting and plugin testing --- .../credentials/add-credentials.controller.js | 243 +++++++++++++++-- .../add-edit-credentials.view.html | 19 +- .../edit-credentials.controller.js | 248 ++++++++++++++++-- .../credentials/external-test.component.js | 37 +++ .../credentials/external-test.partial.html | 41 +++ awx/ui/client/features/credentials/index.js | 7 +- .../input-source-lookup.component.js | 92 +++++++ .../input-source-lookup.partial.html | 174 ++++++++++++ .../lib/components/form/form.directive.js | 7 +- .../lib/components/input/group.directive.js | 7 +- .../lib/components/input/group.partial.html | 4 +- .../lib/components/input/text.directive.js | 13 +- .../lib/components/input/text.partial.html | 43 ++- .../lib/components/tabs/tab.partial.html | 2 +- awx/ui/client/lib/components/tag/_index.less | 4 + awx/ui/client/lib/models/CredentialType.js | 10 +- .../lib/services/base-string.service.js | 2 + .../smart-search/smart-search.controller.js | 3 +- .../src/templates/prompt/prompt.block.less | 11 + 19 files changed, 879 insertions(+), 88 deletions(-) create mode 100644 awx/ui/client/features/credentials/external-test.component.js create mode 100644 awx/ui/client/features/credentials/external-test.partial.html create mode 100644 awx/ui/client/features/credentials/input-source-lookup.component.js create mode 100644 awx/ui/client/features/credentials/input-source-lookup.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 9193712f80..2acad45ae9 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -1,3 +1,5 @@ +/* eslint camelcase: 0 */ +/* eslint arrow-body-style: 0 */ function AddCredentialsController ( models, $state, @@ -6,7 +8,9 @@ function AddCredentialsController ( componentsStrings, ConfigService, ngToast, - $filter + Wait, + $filter, + CredentialType, ) { const vm = this || {}; @@ -40,6 +44,44 @@ function AddCredentialsController ( vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); vm.isTestable = credentialType.get('kind') === 'external'; + vm.inputSources = { + field: null, + credentialId: null, + credentialTypeId: null, + credentialTypeName: null, + tabs: { + credential: { + _active: true, + _disabled: false, + }, + metadata: { + _active: false, + _disabled: false, + } + }, + metadata: {}, + form: { + inputs: { + _get: () => vm.inputSources.metadata, + _reference: 'vm.form.inputs', + _key: 'inputs', + _source: { _value: {} }, + } + }, + items: [], + }; + vm.externalTest = { + metadata: null, + form: { + inputs: { + _get: () => vm.externalTest.metadata, + _reference: 'vm.form.inputs', + _key: 'inputs', + _source: { _value: {} }, + } + }, + }; + const gceFileInputSchema = { id: 'gce_service_account_key', type: 'file', @@ -50,10 +92,10 @@ function AddCredentialsController ( let gceFileInputPreEditValues; vm.form.inputs = { - _get: () => { + _get: ({ getSubmitData }) => { credentialType.mergeInputProperties(); - const fields = credentialType.get('inputs.fields'); + let fields = credentialType.get('inputs.fields'); if (credentialType.get('name') === 'Google Compute Engine') { fields.splice(2, 0, gceFileInputSchema); @@ -64,55 +106,196 @@ function AddCredentialsController ( become._isDynamic = true; become._choices = Array.from(apiConfig.become_methods, method => method[0]); } - vm.isTestable = credentialType.get('kind') === 'external'; + vm.getSubmitData = getSubmitData; + + fields = fields.map((field) => { + if (credentialType.get('kind') !== 'external') { + field.tagMode = true; + } + return field; + }); return fields; }, + _onRemoveTag ({ id }) { + vm.onInputSourceClear(id); + }, + _onInputLookup ({ id }) { + vm.onInputSourceOpen(id); + }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', _key: 'inputs' }; - vm.form.secondary = ({ inputs }) => { - const name = $filter('sanitize')(credentialType.get('name')); - const endpoint = `${credentialType.get('id')}/test/`; + vm.onInputSourceClear = (field) => { + vm.form[field].tagMode = true; + vm.form[field].asTag = false; + }; - return credentialType.http.post({ url: endpoint, data: { inputs }, replace: false }) + vm.setTab = (name) => { + const metaIsActive = name === 'metadata'; + vm.inputSources.tabs.credential._active = !metaIsActive; + vm.inputSources.tabs.credential._disabled = false; + vm.inputSources.tabs.metadata._active = metaIsActive; + vm.inputSources.tabs.metadata._disabled = false; + }; + + vm.unsetTabs = () => { + vm.inputSources.tabs.credential._active = false; + vm.inputSources.tabs.credential._disabled = false; + vm.inputSources.tabs.metadata._active = false; + vm.inputSources.tabs.metadata._disabled = false; + }; + + vm.onInputSourceOpen = (field) => { + vm.inputSources.field = field; + vm.setTab('credential'); + const sourceItem = vm.inputSources.items + .find(({ input_field_name }) => input_field_name === field); + if (sourceItem) { + const { source_credential, summary_fields } = sourceItem; + const { source_credential: { credential_type_id } } = summary_fields; + vm.inputSources.credentialId = source_credential; + vm.inputSources.credentialTypeId = credential_type_id; + vm.inputSources._value = credential_type_id; + } + }; + + vm.onInputSourceClose = () => { + vm.inputSources.field = null; + vm.inputSources.metadata = null; + vm.unsetTabs(); + }; + + vm.onInputSourceNext = () => { + const { field, credentialId, credentialTypeId } = vm.inputSources; + Wait('start'); + new CredentialType('get', credentialTypeId) + .then(model => { + model.mergeInputProperties('metadata'); + vm.inputSources.metadata = model.get('inputs.metadata'); + vm.inputSources.credentialTypeName = model.get('name'); + const [metavals] = vm.inputSources.items + .filter(({ input_field_name }) => input_field_name === field) + .filter(({ source_credential }) => source_credential === credentialId) + .map(({ metadata }) => metadata); + Object.keys(metavals || {}).forEach(key => { + const obj = vm.inputSources.metadata.find(o => o.id === key); + if (obj) obj._value = metavals[key]; + }); + vm.setTab('metadata'); + }) + .finally(() => Wait('stop')); + }; + + vm.onInputSourceSelect = () => { + const { field, credentialId } = vm.inputSources; + vm.inputSources.items = vm.inputSources.items + .filter(({ input_field_name }) => input_field_name !== field) + .concat([{ + input_field_name: field, + source_credential: credentialId, + target_credential: credential.get('id'), + }]); + vm.inputSources.field = null; + vm.inputSources.metadata = null; + vm.unsetTabs(); + }; + + vm.onInputSourceTabSelect = (name) => { + if (name === 'metadata') { + vm.onInputSourceNext(); + } else { + vm.setTab('credential'); + } + }; + + vm.onInputSourceRowClick = ({ id, credential_type }) => { + vm.inputSources.credentialId = id; + vm.inputSources.credentialTypeId = credential_type; + vm.inputSources._value = credential_type; + }; + + vm.onInputSourceTest = () => { + const metadata = Object.assign({}, ...vm.inputSources.form.inputs._group + .filter(({ _value }) => _value !== undefined) + .map(({ id, _value }) => ({ [id]: _value }))); + const name = $filter('sanitize')(vm.inputSources.credentialTypeName); + const endpoint = `${vm.inputSources.credentialId}/test/`; + + return vm.runTest({ name, model: credential, endpoint, data: { metadata } }); + }; + + vm.onExternalTestClick = () => { + credentialType.mergeInputProperties('metadata'); + vm.externalTest.metadata = credentialType.get('inputs.metadata'); + }; + + vm.onExternalTestClose = () => { + vm.externalTest.metadata = null; + }; + + vm.onExternalTest = () => { + const name = $filter('sanitize')(credentialType.get('name')); + const { inputs } = vm.getSubmitData(); + const metadata = Object.assign({}, ...vm.externalTest.form.inputs._group + .filter(({ _value }) => _value !== undefined) + .map(({ id, _value }) => ({ [id]: _value }))); + + let model; + if (credential.get('credential_type') !== credentialType.get('id')) { + model = credentialType; + } else { + model = credential; + } + + const endpoint = `${model.get('id')}/test/`; + return vm.runTest({ name, model, endpoint, data: { inputs, metadata } }); + }; + vm.form.secondary = vm.onExternalTestClick; + + vm.runTest = ({ name, model, endpoint, data: { inputs, metadata } }) => { + return model.http.post({ url: endpoint, data: { inputs, metadata }, replace: false }) .then(() => { ngToast.success({ - content: ` -
-
- -
-
- ${name}: ${strings.get('edit.TEST_PASSED')} -
-
`, + content: vm.buildTestNotificationContent({ + name, + icon: 'fa-check-circle', + msg: strings.get('edit.TEST_PASSED'), + }), dismissButton: false, dismissOnTimeout: true }); }) .catch(({ data }) => { - const msg = data.inputs ? `${$filter('sanitize')(data.inputs)}` : strings.get('edit.TEST_FAILED'); - + const msg = data.inputs + ? `${$filter('sanitize')(data.inputs)}` + : strings.get('edit.TEST_FAILED'); ngToast.danger({ - content: ` -
-
- -
-
- ${name}: ${msg} -
-
`, + content: vm.buildTestNotificationContent({ + name, + msg, + icon: 'fa-exclamation-triangle' + }), dismissButton: false, dismissOnTimeout: true }); }); }; + vm.buildTestNotificationContent = ({ name, msg, icon }) => ( + `
+
+ +
+
+ ${name}: ${msg} +
+
` + ); + vm.form.save = data => { data.user = me.get('id'); @@ -195,7 +378,9 @@ AddCredentialsController.$inject = [ 'ComponentsStrings', 'ConfigService', 'ngToast', - '$filter' + 'Wait', + '$filter', + 'CredentialTypeModel', ]; export default AddCredentialsController; diff --git a/awx/ui/client/features/credentials/add-edit-credentials.view.html b/awx/ui/client/features/credentials/add-edit-credentials.view.html index fae203e152..914c98dcfa 100644 --- a/awx/ui/client/features/credentials/add-edit-credentials.view.html +++ b/awx/ui/client/features/credentials/add-edit-credentials.view.html @@ -43,5 +43,22 @@
- + +
diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 1516b71d96..d3f5bf56f7 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -1,3 +1,5 @@ +/* eslint camelcase: 0 */ +/* eslint arrow-body-style: 0 */ function EditCredentialsController ( models, $state, @@ -8,10 +10,16 @@ function EditCredentialsController ( ngToast, Wait, $filter, + CredentialType, ) { const vm = this || {}; - - const { me, credential, credentialType, organization, isOrgCredAdmin } = models; + const { + me, + credential, + credentialType, + organization, + isOrgCredAdmin, + } = models; const omit = ['user', 'team', 'inputs']; const isEditable = credential.isEditable(); @@ -88,6 +96,44 @@ function EditCredentialsController ( vm.form.credential_type._placeholder = strings.get('inputs.CREDENTIAL_TYPE_PLACEHOLDER'); vm.isTestable = (isEditable && credentialType.get('kind') === 'external'); + vm.inputSources = { + field: null, + credentialId: null, + credentialTypeId: null, + credentialTypeName: null, + tabs: { + credential: { + _active: true, + _disabled: false, + }, + metadata: { + _active: false, + _disabled: false, + } + }, + metadata: {}, + form: { + inputs: { + _get: () => vm.inputSources.metadata, + _reference: 'vm.form.inputs', + _key: 'inputs', + _source: { _value: {} }, + } + }, + items: credential.get('related.input_sources.results'), + }; + vm.externalTest = { + metadata: null, + form: { + inputs: { + _get: () => vm.externalTest.metadata, + _reference: 'vm.form.inputs', + _key: 'inputs', + _source: { _value: {} }, + } + }, + }; + const gceFileInputSchema = { id: 'gce_service_account_key', type: 'file', @@ -98,7 +144,7 @@ function EditCredentialsController ( let gceFileInputPreEditValues; vm.form.inputs = { - _get () { + _get ({ getSubmitData }) { let fields; credentialType.mergeInputProperties(); @@ -128,54 +174,199 @@ function EditCredentialsController ( } } } + + fields = fields.map((field) => { + if (isEditable && credentialType.get('kind') !== 'external') { + field.tagMode = true; + } + return field; + }); + vm.isTestable = (isEditable && credentialType.get('kind') === 'external'); + vm.getSubmitData = getSubmitData; return fields; }, + _onRemoveTag ({ id }) { + vm.onInputSourceClear(id); + }, + _onInputLookup ({ id }) { + vm.onInputSourceOpen(id); + }, _source: vm.form.credential_type, _reference: 'vm.form.inputs', - _key: 'inputs' + _key: 'inputs', + border: true, + title: true, }; - vm.form.secondary = ({ inputs }) => { - const name = $filter('sanitize')(credentialType.get('name')); - const endpoint = `${credential.get('id')}/test/`; + vm.onInputSourceClear = (field) => { + vm.form[field].tagMode = true; + vm.form[field].asTag = false; + }; - return credential.http.post({ url: endpoint, data: { inputs }, replace: false }) + vm.setTab = (name) => { + const metaIsActive = name === 'metadata'; + vm.inputSources.tabs.credential._active = !metaIsActive; + vm.inputSources.tabs.credential._disabled = false; + vm.inputSources.tabs.metadata._active = metaIsActive; + vm.inputSources.tabs.metadata._disabled = false; + }; + + vm.unsetTabs = () => { + vm.inputSources.tabs.credential._active = false; + vm.inputSources.tabs.credential._disabled = false; + vm.inputSources.tabs.metadata._active = false; + vm.inputSources.tabs.metadata._disabled = false; + }; + + vm.onInputSourceOpen = (field) => { + vm.inputSources.field = field; + vm.setTab('credential'); + const sourceItem = vm.inputSources.items + .find(({ input_field_name }) => input_field_name === field); + if (sourceItem) { + const { source_credential, summary_fields } = sourceItem; + const { source_credential: { credential_type_id } } = summary_fields; + vm.inputSources.credentialId = source_credential; + vm.inputSources.credentialTypeId = credential_type_id; + vm.inputSources._value = credential_type_id; + } + }; + + vm.onInputSourceClose = () => { + vm.inputSources.field = null; + vm.inputSources.metadata = null; + vm.unsetTabs(); + }; + + vm.onInputSourceNext = () => { + const { field, credentialId, credentialTypeId } = vm.inputSources; + Wait('start'); + new CredentialType('get', credentialTypeId) + .then(model => { + model.mergeInputProperties('metadata'); + vm.inputSources.metadata = model.get('inputs.metadata'); + vm.inputSources.credentialTypeName = model.get('name'); + const [metavals] = vm.inputSources.items + .filter(({ input_field_name }) => input_field_name === field) + .filter(({ source_credential }) => source_credential === credentialId) + .map(({ metadata }) => metadata); + Object.keys(metavals || {}).forEach(key => { + const obj = vm.inputSources.metadata.find(o => o.id === key); + if (obj) obj._value = metavals[key]; + }); + vm.setTab('metadata'); + }) + .finally(() => Wait('stop')); + }; + + vm.onInputSourceSelect = () => { + const { field, credentialId } = vm.inputSources; + vm.inputSources.items = vm.inputSources.items + .filter(({ input_field_name }) => input_field_name !== field) + .concat([{ + input_field_name: field, + source_credential: credentialId, + target_credential: credential.get('id'), + }]); + vm.inputSources.field = null; + vm.inputSources.metadata = null; + vm.unsetTabs(); + }; + + vm.onInputSourceTabSelect = (name) => { + if (name === 'metadata') { + vm.onInputSourceNext(); + } else { + vm.setTab('credential'); + } + }; + + vm.onInputSourceRowClick = ({ id, credential_type }) => { + vm.inputSources.credentialId = id; + vm.inputSources.credentialTypeId = credential_type; + vm.inputSources._value = credential_type; + }; + + vm.onInputSourceTest = () => { + const metadata = Object.assign({}, ...vm.inputSources.form.inputs._group + .filter(({ _value }) => _value !== undefined) + .map(({ id, _value }) => ({ [id]: _value }))); + const name = $filter('sanitize')(vm.inputSources.credentialTypeName); + const endpoint = `${vm.inputSources.credentialId}/test/`; + + return vm.runTest({ name, model: credential, endpoint, data: { metadata } }); + }; + + vm.onExternalTestClick = () => { + credentialType.mergeInputProperties('metadata'); + vm.externalTest.metadata = credentialType.get('inputs.metadata'); + }; + + vm.onExternalTestClose = () => { + vm.externalTest.metadata = null; + }; + + vm.onExternalTest = () => { + const name = $filter('sanitize')(credentialType.get('name')); + const { inputs } = vm.getSubmitData(); + const metadata = Object.assign({}, ...vm.externalTest.form.inputs._group + .filter(({ _value }) => _value !== undefined) + .map(({ id, _value }) => ({ [id]: _value }))); + + let model; + if (credential.get('credential_type') !== credentialType.get('id')) { + model = credentialType; + } else { + model = credential; + } + + const endpoint = `${model.get('id')}/test/`; + return vm.runTest({ name, model, endpoint, data: { inputs, metadata } }); + }; + vm.form.secondary = vm.onExternalTestClick; + + vm.runTest = ({ name, model, endpoint, data: { inputs, metadata } }) => { + return model.http.post({ url: endpoint, data: { inputs, metadata }, replace: false }) .then(() => { ngToast.success({ - content: ` -
-
- -
-
- ${name}: ${strings.get('edit.TEST_PASSED')} -
-
`, + content: vm.buildTestNotificationContent({ + name, + icon: 'fa-check-circle', + msg: strings.get('edit.TEST_PASSED'), + }), dismissButton: false, dismissOnTimeout: true }); }) .catch(({ data }) => { - const msg = data.inputs ? `${$filter('sanitize')(data.inputs)}` : strings.get('edit.TEST_FAILED'); - + const msg = data.inputs + ? `${$filter('sanitize')(data.inputs)}` + : strings.get('edit.TEST_FAILED'); ngToast.danger({ - content: ` -
-
- -
-
- ${name}: ${msg} -
-
`, + content: vm.buildTestNotificationContent({ + name, + msg, + icon: 'fa-exclamation-triangle' + }), dismissButton: false, dismissOnTimeout: true }); }); }; + vm.buildTestNotificationContent = ({ name, msg, icon }) => ( + `
+
+ +
+
+ ${name}: ${msg} +
+
` + ); + /** * If a credential's `credential_type` is changed while editing, the inputs associated with * the old type need to be cleared before saving the inputs associated with the new type. @@ -258,6 +449,7 @@ EditCredentialsController.$inject = [ 'ngToast', 'Wait', '$filter', + 'CredentialTypeModel', ]; export default EditCredentialsController; diff --git a/awx/ui/client/features/credentials/external-test.component.js b/awx/ui/client/features/credentials/external-test.component.js new file mode 100644 index 0000000000..0c4fda3659 --- /dev/null +++ b/awx/ui/client/features/credentials/external-test.component.js @@ -0,0 +1,37 @@ +const templateUrl = require('~features/credentials/external-test.partial.html'); + +function ExternalTestModalController ($scope, $element, strings) { + const vm = this || {}; + let overlay; + + vm.strings = strings; + vm.title = 'Test External Credential'; + + vm.$onInit = () => { + const [el] = $element; + overlay = el.querySelector('#external-test-modal'); + vm.show(); + }; + + vm.show = () => { + overlay.style.display = 'block'; + overlay.style.opacity = 1; + }; +} + +ExternalTestModalController.$inject = [ + '$scope', + '$element', + 'CredentialsStrings', +]; + +export default { + templateUrl, + controller: ExternalTestModalController, + controllerAs: 'vm', + bindings: { + onClose: '=', + onSubmit: '=', + form: '=', + }, +}; diff --git a/awx/ui/client/features/credentials/external-test.partial.html b/awx/ui/client/features/credentials/external-test.partial.html new file mode 100644 index 0000000000..3d866c42f1 --- /dev/null +++ b/awx/ui/client/features/credentials/external-test.partial.html @@ -0,0 +1,41 @@ + diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index 38e291ef8e..2c13f645ed 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -2,6 +2,8 @@ import LegacyCredentials from './legacy.credentials'; import AddController from './add-credentials.controller'; import EditController from './edit-credentials.controller'; import CredentialsStrings from './credentials.strings'; +import InputSourceLookupComponent from './input-source-lookup.component'; +import ExternalTestComponent from './external-test.component'; const MODULE_NAME = 'at.features.credentials'; @@ -40,7 +42,8 @@ function CredentialsResolve ( const dependents = { credentialType: new CredentialType('get', typeId), - organization: new Organization('get', orgId) + organization: new Organization('get', orgId), + credentialInputSources: models.credential.extend('GET', 'input_sources') }; dependents.isOrgCredAdmin = dependents.organization.then((org) => org.search({ role_level: 'credential_admin_role' })); @@ -139,6 +142,8 @@ angular .controller('EditController', EditController) .service('LegacyCredentialsService', LegacyCredentials) .service('CredentialsStrings', CredentialsStrings) + .component('atInputSourceLookup', InputSourceLookupComponent) + .component('atExternalCredentialTest', ExternalTestComponent) .run(CredentialsRun); export default MODULE_NAME; diff --git a/awx/ui/client/features/credentials/input-source-lookup.component.js b/awx/ui/client/features/credentials/input-source-lookup.component.js new file mode 100644 index 0000000000..1ac781734c --- /dev/null +++ b/awx/ui/client/features/credentials/input-source-lookup.component.js @@ -0,0 +1,92 @@ +const templateUrl = require('~features/credentials/input-source-lookup.partial.html'); + +function InputSourceLookupController ($scope, $element, $http, GetBasePath, qs, strings) { + const vm = this || {}; + let overlay; + + vm.strings = strings; + vm.name = 'credential'; + vm.title = 'Set Input Source'; + + vm.$onInit = () => { + const [el] = $element; + overlay = el.querySelector('#input-source-lookup'); + + const defaultParams = { + order_by: 'name', + credential_type__kind: 'external', + page_size: 5 + }; + vm.setDefaultParams(defaultParams); + vm.setData({ results: [], count: 0 }); + $http({ method: 'GET', url: GetBasePath(`${vm.name}s`), params: defaultParams }) + .then(({ data }) => { + vm.setData(data); + vm.show(); + }); + }; + + vm.show = () => { + overlay.style.display = 'block'; + overlay.style.opacity = 1; + }; + + vm.close = () => { + vm.onClose(); + }; + + vm.next = () => { + vm.onNext(); + }; + + vm.select = () => { + vm.onSelect(); + }; + + vm.test = () => { + vm.onTest(); + }; + + vm.setData = ({ results, count }) => { + vm.dataset = { results, count }; + vm.collection = vm.dataset.results; + }; + + vm.setDefaultParams = (params) => { + vm.list = { name: vm.name, iterator: vm.name }; + vm.defaultParams = params; + vm.queryset = params; + }; + + vm.toggle_row = (obj) => { + vm.onRowClick(obj); + }; + + vm.onCredentialTabClick = () => vm.onTabSelect('credential'); +} + +InputSourceLookupController.$inject = [ + '$scope', + '$element', + '$http', + 'GetBasePath', + 'QuerySet', + 'CredentialsStrings', +]; + +export default { + templateUrl, + controller: InputSourceLookupController, + controllerAs: 'vm', + bindings: { + tabs: '=', + onClose: '=', + onNext: '=', + onSelect: '=', + onTabSelect: '=', + onRowClick: '=', + onTest: '=', + selectedId: '=', + form: '=', + }, +}; diff --git a/awx/ui/client/features/credentials/input-source-lookup.partial.html b/awx/ui/client/features/credentials/input-source-lookup.partial.html new file mode 100644 index 0000000000..4998201715 --- /dev/null +++ b/awx/ui/client/features/credentials/input-source-lookup.partial.html @@ -0,0 +1,174 @@ + diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index 9c008b19e6..7aaa90aa1a 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -87,13 +87,8 @@ function AtFormController (eventService, strings) { if (!vm.state.isValid) { return; } - - vm.state.disabled = true; - const data = vm.getSubmitData(); - - scope.state.secondary(data) - .finally(() => { vm.state.disabled = false; }); + scope.state.secondary(data); }; vm.submit = () => { diff --git a/awx/ui/client/lib/components/input/group.directive.js b/awx/ui/client/lib/components/input/group.directive.js index 291b534ac9..3e62023255 100644 --- a/awx/ui/client/lib/components/input/group.directive.js +++ b/awx/ui/client/lib/components/input/group.directive.js @@ -48,7 +48,7 @@ function AtInputGroupController ($scope, $compile) { state._value = source._value; - const inputs = state._get(source._value); + const inputs = state._get(form); const group = vm.createComponentConfigs(inputs); vm.insert(group); @@ -66,7 +66,9 @@ function AtInputGroupController ($scope, $compile) { _element: vm.createComponent(input, i), _key: 'inputs', _group: true, - _groupIndex: i + _groupIndex: i, + _onInputLookup: state._onInputLookup, + _onRemoveTag: state._onRemoveTag, }, input)); }); } @@ -160,7 +162,6 @@ function AtInputGroupController ($scope, $compile) { `); $compile(component)(scope.$parent); - return component; }; diff --git a/awx/ui/client/lib/components/input/group.partial.html b/awx/ui/client/lib/components/input/group.partial.html index 45d4a845e0..c32d29bb1e 100644 --- a/awx/ui/client/lib/components/input/group.partial.html +++ b/awx/ui/client/lib/components/input/group.partial.html @@ -1,7 +1,7 @@
-
+
-
+

diff --git a/awx/ui/client/lib/components/input/text.directive.js b/awx/ui/client/lib/components/input/text.directive.js index d28f4c2640..c24c807cd4 100644 --- a/awx/ui/client/lib/components/input/text.directive.js +++ b/awx/ui/client/lib/components/input/text.directive.js @@ -5,7 +5,10 @@ function atInputTextLink (scope, element, attrs, controllers) { const inputController = controllers[1]; if (scope.tab === '1') { - element.find('input')[0].focus(); + const el = element.find('input')[0]; + if (el) { + el.focus(); + } } inputController.init(scope, element, formController); @@ -20,9 +23,15 @@ function AtInputTextController (baseInputController) { baseInputController.call(vm, 'input', _scope_, element, form); scope = _scope_; - vm.check(); scope.$watch('state._value', () => vm.check()); }; + + vm.onLookupClick = () => { + if (scope.state._onInputLookup) { + const { id, label, required, type } = scope.state; + scope.state._onInputLookup({ id, label, required, type }); + } + }; } AtInputTextController.$inject = ['BaseInputController']; diff --git a/awx/ui/client/lib/components/input/text.partial.html b/awx/ui/client/lib/components/input/text.partial.html index 8a1a22137e..45d5c20340 100644 --- a/awx/ui/client/lib/components/input/text.partial.html +++ b/awx/ui/client/lib/components/input/text.partial.html @@ -1,15 +1,40 @@
- - - +
+ + + + + + + +
+
diff --git a/awx/ui/client/lib/components/tabs/tab.partial.html b/awx/ui/client/lib/components/tabs/tab.partial.html index 747e470571..eb007feb01 100644 --- a/awx/ui/client/lib/components/tabs/tab.partial.html +++ b/awx/ui/client/lib/components/tabs/tab.partial.html @@ -2,6 +2,6 @@ ng-attr-disabled="{{ state._disabled || undefined }}" ng-class="{ 'at-Tab--active': state._active, 'at-Tab--disabled': state._disabled }" ng-hide="{{ state._hide }}" - ng-click="vm.go()"> + ng-click="state._go && vm.go();"> diff --git a/awx/ui/client/lib/components/tag/_index.less b/awx/ui/client/lib/components/tag/_index.less index 4d13212442..b91830f51f 100644 --- a/awx/ui/client/lib/components/tag/_index.less +++ b/awx/ui/client/lib/components/tag/_index.less @@ -61,6 +61,10 @@ &--vault:before { content: '\f187'; } + + &--external:before { + content: '\f14c' + } } .TagComponent-button { diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js index 0607350ad8..38c44cd845 100644 --- a/awx/ui/client/lib/models/CredentialType.js +++ b/awx/ui/client/lib/models/CredentialType.js @@ -15,18 +15,18 @@ function categorizeByKind () { })); } -function mergeInputProperties () { - if (!this.has('inputs.fields')) { +function mergeInputProperties (key = 'fields') { + if (!this.has(`inputs.${key}`)) { return undefined; } const required = this.get('inputs.required'); - return this.get('inputs.fields').forEach((field, i) => { + return this.get(`inputs.${key}`).forEach((field, i) => { if (!required || required.indexOf(field.id) === -1) { - this.set(`inputs.fields[${i}].required`, false); + this.set(`inputs.${key}[${i}].required`, false); } else { - this.set(`inputs.fields[${i}].required`, true); + this.set(`inputs.${key}[${i}].required`, true); } }); } diff --git a/awx/ui/client/lib/services/base-string.service.js b/awx/ui/client/lib/services/base-string.service.js index 83a860eeb1..6db4249b49 100644 --- a/awx/ui/client/lib/services/base-string.service.js +++ b/awx/ui/client/lib/services/base-string.service.js @@ -61,7 +61,9 @@ function BaseStringService (namespace) { this.CANCEL = t.s('CANCEL'); this.CLOSE = t.s('CLOSE'); this.SAVE = t.s('SAVE'); + this.SELECT = t.s('SELECT'); this.OK = t.s('OK'); + this.RUN = t.s('RUN'); this.NEXT = t.s('NEXT'); this.SHOW = t.s('SHOW'); this.HIDE = t.s('HIDE'); diff --git a/awx/ui/client/src/shared/smart-search/smart-search.controller.js b/awx/ui/client/src/shared/smart-search/smart-search.controller.js index 2294fa474f..2109b1e03e 100644 --- a/awx/ui/client/src/shared/smart-search/smart-search.controller.js +++ b/awx/ui/client/src/shared/smart-search/smart-search.controller.js @@ -102,8 +102,9 @@ function SmartSearchController ( const rootField = termParts[0].split('.')[0].replace(/^-/, ''); const listName = $scope.list.name; const baseRelatedTypePath = `models.${listName}.base.${rootField}.type`; + const relatedTypePath = `models.${listName}.related`; - const isRelatedSearchTermField = (_.includes($scope.models[listName].related, rootField)); + const isRelatedSearchTermField = (_.includes(_.get($scope, relatedTypePath), rootField)); const isBaseModelRelatedSearchTermField = (_.get($scope, baseRelatedTypePath) === 'field'); return (isRelatedSearchTermField || isBaseModelRelatedSearchTermField); diff --git a/awx/ui/client/src/templates/prompt/prompt.block.less b/awx/ui/client/src/templates/prompt/prompt.block.less index c5670874ed..d9f7e3b6fd 100644 --- a/awx/ui/client/src/templates/prompt/prompt.block.less +++ b/awx/ui/client/src/templates/prompt/prompt.block.less @@ -44,6 +44,17 @@ height: 30px; min-width: 85px; } + +.Prompt-infoButton{ + .at-mixin-ButtonColor('at-color-info', 'at-color-default'); + text-transform: uppercase; + border-radius: 5px; + padding-left:15px; + padding-right: 15px; + height: 30px; + min-width: 85px; + margin-right: 20px; +} .Prompt-defaultButton:hover{ background-color: @btn-bg-hov; color: @btn-txt;