diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 88ed9b01b0..c94efd2ee1 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -1,21 +1,19 @@ -const DEFAULT_ORGANIZATION_PLACEHOLDER = 'SELECT AN ORGANIZATION'; - -function AddCredentialsController (models, $state) { +function AddCredentialsController (models, $state, strings) { let vm = this || {}; let me = models.me; let credential = models.credential; let credentialType = models.credentialType; + let organization = models.organization; - vm.panelTitle = 'NEW CREDENTIAL'; + vm.mode = 'add'; + vm.strings = strings.credentials; + + vm.panelTitle = vm.strings[vm.mode].PANEL_TITLE; vm.tab = { - details: { - _active: true - }, - permissions:{ - _disabled: true - } + details: { _active: true }, + permissions:{ _disabled: true } }; vm.form = credential.createFormSchema('post', { @@ -24,9 +22,13 @@ function AddCredentialsController (models, $state) { vm.form.organization._resource = 'organization'; vm.form.organization._route = 'credentials.add.organization'; - + vm.form.organization._model = organization; + vm.form.organization._placeholder = vm.strings.inputs.ORGANIZATION_PLACEHOLDER; + vm.form.credential_type._resource = 'credential_type'; vm.form.credential_type._route = 'credentials.add.credentialType'; + vm.form.credential_type._model = credentialType; + vm.form.credential_type._placeholder = vm.strings.inputs.CREDENTIAL_TYPE_PLACEHOLDER; vm.form.inputs = { _get: id => { @@ -52,7 +54,8 @@ function AddCredentialsController (models, $state) { AddCredentialsController.$inject = [ 'resolvedModels', - '$state' + '$state', + 'CredentialsStrings' ]; 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 a49b5dbed0..5625be6dd1 100644 --- a/awx/ui/client/features/credentials/add-edit-credentials.view.html +++ b/awx/ui/client/features/credentials/add-edit-credentials.view.html @@ -2,8 +2,8 @@ {{ vm.panelTitle }} - Details - Permissions + {{ vm.strings.tab.DETAILS }} + {{ vm.strings.tab.PERMISSIONS }} @@ -17,7 +17,7 @@ - Type Details + {{ vm.strings.inputs.GROUP_TITLE }} @@ -29,11 +29,11 @@ - CREDENTIALS PERMISSIONS + {{ vm.strings.permissions.TITLE }} - Details - Permissions + {{ vm.strings.tab.DETAILS }} + {{ vm.strings.tab.PERMISSIONS }} diff --git a/awx/ui/client/features/credentials/credentials.strings.js b/awx/ui/client/features/credentials/credentials.strings.js new file mode 100644 index 0000000000..16cd98a840 --- /dev/null +++ b/awx/ui/client/features/credentials/credentials.strings.js @@ -0,0 +1,34 @@ +function CredentialsStrings (BaseString) { + BaseString.call(this, 'credentials'); + + let t = this.t; + let ns = this.credentials; + + ns.state = { + ADD_BREADCRUMB_LABEL: t('CREATE CREDENTIAL'), + EDIT_BREADCRUMB_LABEL: t('EDIT CREDENTIAL') + }; + + ns.tab = { + DETAILS: t('Details'), + PERMISSIONS: t('Permissions') + }; + + ns.inputs = { + GROUP_TITLE: t('Type Details'), + ORGANIZATION_PLACEHOLDER: t('SELECT AN ORGANIZATION'), + CREDENTIAL_TYPE_PLACEHOLDER: t('SELECT A CREDENTIAL TYPE') + }; + + ns.add = { + PANEL_TITLE: t('NEW CREDENTIAL') + }; + + ns.permissions = { + TITLE: t('CREDENTIALS PERMISSIONS') + }; +} + +CredentialsStrings.$inject = ['BaseStringService']; + +export default CredentialsStrings; diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js index 69126a5988..5412e6f544 100644 --- a/awx/ui/client/features/credentials/edit-credentials.controller.js +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -1,12 +1,15 @@ -const DEFAULT_ORGANIZATION_PLACEHOLDER = 'SELECT AN ORGANIZATION'; - -function EditCredentialsController (models, $state, $scope) { +function EditCredentialsController (models, $state, $scope, strings) { let vm = this || {}; let me = models.me; let credential = models.credential; let credentialType = models.credentialType; - let selectedCredentialType = credentialType.getById(credential.get('credential_type')); + let organization = models.organization; + let selectedCredentialType = models.selectedCredentialType; + + vm.mode = 'edit'; + vm.strings = strings.credentials; + vm.panelTitle = credential.get('name'); vm.tab = { details: { @@ -33,21 +36,23 @@ function EditCredentialsController (models, $state, $scope) { // Only exists for permissions compatibility $scope.credential_obj = credential.get(); - vm.panelTitle = credential.get('name'); - vm.form = credential.createFormSchema('put', { omit: ['user', 'team', 'inputs'] }); vm.form.organization._resource = 'organization'; + vm.form.organization._model = organization; vm.form.organization._route = 'credentials.edit.organization'; vm.form.organization._value = credential.get('summary_fields.organization.id'); vm.form.organization._displayValue = credential.get('summary_fields.organization.name'); + vm.form.organization._placeholder = vm.strings.inputs.ORGANIZATION_PLACEHOLDER; vm.form.credential_type._resource = 'credential_type'; + vm.form.credential_type._model = credentialType; vm.form.credential_type._route = 'credentials.edit.credentialType'; - vm.form.credential_type._value = selectedCredentialType.id; - vm.form.credential_type._displayValue = selectedCredentialType.name; + vm.form.credential_type._value = selectedCredentialType.get('id'); + vm.form.credential_type._displayValue = selectedCredentialType.get('name'); + vm.form.credential_type._placeholder = vm.strings.inputs.CREDENTIAL_TYPE_PLACEHOLDER; vm.form.inputs = { _get (id) { @@ -80,7 +85,8 @@ function EditCredentialsController (models, $state, $scope) { EditCredentialsController.$inject = [ 'resolvedModels', '$state', - '$scope' + '$scope', + 'CredentialsStrings' ]; export default EditCredentialsController; diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index 0eb1e02233..f203ff471f 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -1,23 +1,38 @@ import LegacyCredentials from './legacy.credentials'; -import AddController from './add-credentials.controller.js'; -import EditController from './edit-credentials.controller.js'; -import { N_ } from '../../src/i18n'; +import AddController from './add-credentials.controller'; +import EditController from './edit-credentials.controller'; +import CredentialsStrings from './credentials.strings' -function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType) { +function CredentialsResolve ($q, $stateParams, Me, Credential, CredentialType, Organization) { let id = $stateParams.credential_id; + let models; let promises = { me: new Me('get'), - credentialType: new CredentialType('get') + credentialType: new CredentialType('get'), + organization: new Organization('get') }; - if (id) { - promises.credential = new Credential(['get', 'options'], [id, id]); - } else { + if (!id) { promises.credential = new Credential('options'); + + return $q.all(promises) } - return $q.all(promises); + promises.credential = new Credential(['get', 'options'], [id, id]); + + return $q.all(promises) + .then(_models_ => { + models = _models_; + let credentialTypeId = models.credential.get('credential_type'); + + return models.credentialType.graft(credentialTypeId); + }) + .then(selectedCredentialType => { + models.selectedCredentialType = selectedCredentialType; + + return models; + }); } CredentialsResolve.$inject = [ @@ -25,19 +40,23 @@ CredentialsResolve.$inject = [ '$stateParams', 'MeModel', 'CredentialModel', - 'CredentialTypeModel' + 'CredentialTypeModel', + 'OrganizationModel' ]; -function CredentialsConfig ($stateExtenderProvider, legacyProvider, pathProvider) { +function CredentialsConfig ($stateExtenderProvider, legacyProvider, pathProvider, stringProvider) { let path = pathProvider.$get(); let stateExtender = $stateExtenderProvider.$get(); let legacy = legacyProvider.$get(); + let strings = stringProvider.$get(); + + strings = strings.credentials.state; stateExtender.addState({ name: 'credentials.add', route: '/add', ncyBreadcrumb: { - label: N_('CREATE CREDENTIALS') + label: strings.ADD_BREADCRUMB_LABEL }, views: { 'add@credentials': { @@ -55,7 +74,7 @@ function CredentialsConfig ($stateExtenderProvider, legacyProvider, pathProvider name: 'credentials.edit', route: '/:credential_id', ncyBreadcrumb: { - label: N_('EDIT') + label: strings.EDIT_BREADCRUMB_LABEL }, views: { 'edit@credentials': { @@ -81,7 +100,8 @@ function CredentialsConfig ($stateExtenderProvider, legacyProvider, pathProvider CredentialsConfig.$inject = [ '$stateExtenderProvider', 'LegacyCredentialsServiceProvider', - 'PathServiceProvider' + 'PathServiceProvider', + 'CredentialsStringsProvider' ]; angular @@ -89,4 +109,5 @@ angular .config(CredentialsConfig) .controller('AddController', AddController) .controller('EditController', EditController) - .service('LegacyCredentialsService', LegacyCredentials); + .service('LegacyCredentialsService', LegacyCredentials) + .service('CredentialsStrings', CredentialsStrings); diff --git a/awx/ui/client/features/credentials/legacy.credentials.js b/awx/ui/client/features/credentials/legacy.credentials.js index c0a55d559c..95cda7e94a 100644 --- a/awx/ui/client/features/credentials/legacy.credentials.js +++ b/awx/ui/client/features/credentials/legacy.credentials.js @@ -113,7 +113,7 @@ function LegacyCredentialsService (pathService) { }, ncyBreadcrumb: { parent: 'credentials.edit', - label: 'PERMISSIONS' + label: N_('PERMISSIONS') }, views: { 'related': { @@ -336,7 +336,7 @@ function LegacyCredentialsService (pathService) { return this.credentialType; default: - throw new Error(`Legacy state configuration for ${name} does not exist`); + throw new Error(N_(`Legacy state configuration for ${name} does not exist`)); }; }; } diff --git a/awx/ui/client/lib/components/components.strings.js b/awx/ui/client/lib/components/components.strings.js new file mode 100644 index 0000000000..804dfd81ce --- /dev/null +++ b/awx/ui/client/lib/components/components.strings.js @@ -0,0 +1,49 @@ +function ComponentsStrings (BaseString) { + BaseString.call(this, 'components'); + + let t = this.t; + let ns = this.components; + + ns.REPLACE = t('REPLACE'); + ns.REVERT = t('REVERT'); + ns.ENCRYPTED = t('ENCRYPTED'); + ns.OPTIONS = t('OPTIONS'); + ns.SHOW = t('SHOW'); + ns.HIDE = t('HIDE'); + + ns.message = { + REQUIRED_INPUT_MISSING: t('Please enter a value.'), + INVALID_INPUT: t('Invalid input for this type.') + }; + + ns.form = { + SUBMISSION_ERROR_TITLE: t('Unable to Submit'), + SUBMISSION_ERROR_MESSAGE:t('Unexpected server error. View the console for more information'), + SUBMISSION_ERROR_PREFACE: t('Unexpected Error') + }; + + ns.group = { + UNSUPPORTED_ERROR_PREFACE: t('Unsupported input type') + }; + + ns.label = { + PROMPT_ON_LAUNCH: t('Prompt on launch') + }; + + ns.select = { + UNSUPPORTED_TYPE_ERROR: t('Unsupported display model type'), + EMPTY_PLACEHOLDER: t('NO OPTIONS AVAILABLE') + }; + + ns.textarea = { + SSH_KEY_HINT: t('HINT: Drag and drop an SSH private key file on the field below.') + }; + + ns.lookup = { + NOT_FOUND: t('That value was not found. Please enter or select a valid value.') + }; +} + +ComponentsStrings.$inject = ['BaseStringService']; + +export default ComponentsStrings; diff --git a/awx/ui/client/lib/components/form/action.directive.js b/awx/ui/client/lib/components/form/action.directive.js index 1cc9c4ded2..33db4182c7 100644 --- a/awx/ui/client/lib/components/form/action.directive.js +++ b/awx/ui/client/lib/components/form/action.directive.js @@ -5,7 +5,7 @@ function link (scope, element, attrs, controllers) { actionController.init(formController, element, scope); } -function atFormActionController ($state) { +function atFormActionController ($state, strings) { let vm = this || {}; let element; @@ -36,21 +36,21 @@ function atFormActionController ($state) { }; vm.setCancelDefaults = () => { - scope.text = 'CANCEL'; + scope.text = strings.CANCEL; scope.fill = 'Hollow'; scope.color = 'default'; scope.action = () => $state.go(scope.to || '^'); }; vm.setSaveDefaults = () => { - scope.text = 'SAVE'; + scope.text = strings.SAVE; scope.fill = ''; scope.color = 'success'; scope.action = () => form.submit(); }; } -atFormAction.$inject = ['$state']; +atFormActionController.$inject = ['$state', 'ComponentsStrings']; function atFormAction (pathService) { return { diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index d85ca856fd..75d3aa596a 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -8,13 +8,15 @@ function atFormLink (scope, el, attrs, controllers) { formController.init(scope, form); } -function AtFormController (eventService) { +function AtFormController (eventService, strings) { let vm = this || {}; let scope; let modal; let form; + strings = strings.components.forms; + vm.components = []; vm.state = { isValid: false, @@ -99,6 +101,8 @@ function AtFormController (eventService) { if (!handled) { let message; + let title = strings.SUBMISSION_ERROR_TITLE; + let preface = strings.SUBMISSION_ERROR_PREFACE; if (typeof err.data === 'object') { message = JSON.stringify(err.data); @@ -106,13 +110,13 @@ function AtFormController (eventService) { message = err.data; } - modal.show('Unable to Submit', `Unexpected Error: ${message}`); + modal.show(title, `${preface}: ${message}`) } }; vm.handleUnexpectedError = err => { - let title = 'Unable to Submit'; - let message = 'Unexpected server error. View the console for more information'; + let title = strings.SUBMISSION_ERROR_TITLE; + let message = strings.SUBMISSION_ERROR_MESSAGE; modal.show(title, message); @@ -190,7 +194,7 @@ function AtFormController (eventService) { }; } -AtFormController.$inject = ['EventService']; +AtFormController.$inject = ['EventService', 'ComponentsStrings']; function atForm (pathService) { return { diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index e9a79c8d4c..fa8b5d6d0b 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -8,6 +8,7 @@ import inputLabel from './input/label.directive'; import inputLookup from './input/lookup.directive'; import inputMessage from './input/message.directive'; import inputSecret from './input/secret.directive'; +import inputSelect from './input/select.directive'; import inputText from './input/text.directive'; import inputTextarea from './input/textarea.directive'; import inputTextareaSecret from './input/textarea-secret.directive'; @@ -20,6 +21,7 @@ import tab from './tabs/tab.directive'; import tabGroup from './tabs/group.directive'; import BaseInputController from './input/base.controller'; +import ComponentsStrings from './components.strings'; angular .module('at.lib.components', []) @@ -33,6 +35,7 @@ angular .directive('atInputLookup', inputLookup) .directive('atInputMessage', inputMessage) .directive('atInputSecret', inputSecret) + .directive('atInputSelect', inputSelect) .directive('atInputText', inputText) .directive('atInputTextarea', inputTextarea) .directive('atInputTextareaSecret', inputTextareaSecret) @@ -43,6 +46,7 @@ angular .directive('atPopover', popover) .directive('atTab', tab) .directive('atTabGroup', tabGroup) - .service('BaseInputController', BaseInputController); + .service('BaseInputController', BaseInputController) + .service('ComponentsStrings', ComponentsStrings); diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js index 71591ca129..1b27b159cd 100644 --- a/awx/ui/client/lib/components/input/base.controller.js +++ b/awx/ui/client/lib/components/input/base.controller.js @@ -1,17 +1,19 @@ -const REQUIRED_INPUT_MISSING_MESSAGE = 'Please enter a value.'; -const DEFAULT_INVALID_INPUT_MESSAGE = 'Invalid input for this type.'; -const PROMPT_ON_LAUNCH_VALUE = 'ASK'; -const ENCRYPTED_VALUE = '$encrypted$'; +function BaseInputController (strings) { + // Default values are universal. Don't translate. + const PROMPT_ON_LAUNCH_VALUE = 'ASK'; + const ENCRYPTED_VALUE = '$encrypted$'; -function BaseInputController () { return function extend (type, scope, element, form) { let vm = this; + vm.strings = strings; + scope.state = scope.state || {}; + scope.state._touched = false; scope.state._required = scope.state.required || false; - scope.state._isValid = scope.state.isValid || false; - scope.state._disabled = scope.state.disabled || false; + scope.state._isValid = scope.state._isValid || false; + scope.state._disabled = scope.state._disabled || false; scope.state._activeModel = '_value'; if (scope.state.ask_at_runtime) { @@ -43,17 +45,19 @@ function BaseInputController () { let isValid = true; let message = ''; - if (scope.state._required && !scope.state._value) { - isValid = false; - message = REQUIRED_INPUT_MISSING_MESSAGE; + if (scope.state._value || scope.state._displayValue) { + scope.state._touched = true; } - if (scope.state.validate) { + if (scope.state._required && !scope.state._value && !scope.state._displayValue) { + isValid = false; + message = vm.strings.components.message.REQUIRED_INPUT_MISSING; + } else if (scope.state._validate) { let result = scope.state._validate(scope.state._value); if (!result.isValid) { isValid = false; - message = result.message || DEFAULT_INVALID_INPUT_MESSAGE; + message = result.message || vm.strings.components.message.INVALID_INPUT; } } @@ -66,7 +70,7 @@ function BaseInputController () { vm.check = () => { let result = vm.validate(); - if (result.isValid !== scope.state._isValid) { + if (scope.state._touched || !scope.state._required) { scope.state._rejected = !result.isValid; scope.state._isValid = result.isValid; scope.state._message = result.message; @@ -79,14 +83,14 @@ function BaseInputController () { scope.state._isBeingReplaced = !scope.state._isBeingReplaced; if (!scope.state._isBeingReplaced) { - scope.state._buttonText = 'REPLACE'; + scope.state._buttonText = vm.strings.components.REPLACE; scope.state._disabled = true; scope.state._enableToggle = true; scope.state._value = scope.state._preEditValue; scope.state._activeModel = '_displayValue'; - scope.state._placeholder = 'ENCRYPTED'; + scope.state._placeholder = vm.strings.components.ENCRYPTED; } else { - scope.state._buttonText = 'REVERT'; + scope.state._buttonText = vm.strings.components.REVERT; scope.state._disabled = false; scope.state._enableToggle = false; scope.state._activeModel = '_value'; @@ -118,4 +122,6 @@ function BaseInputController () { }; } +BaseInputController.$inject = ['ComponentsStrings']; + export default BaseInputController; diff --git a/awx/ui/client/lib/components/input/checkbox.directive.js b/awx/ui/client/lib/components/input/checkbox.directive.js index 9380eae846..99728cb744 100644 --- a/awx/ui/client/lib/components/input/checkbox.directive.js +++ b/awx/ui/client/lib/components/input/checkbox.directive.js @@ -15,7 +15,7 @@ function AtInputCheckboxController (baseInputController) { vm.init = (scope, element, form) => { baseInputController.call(vm, 'input', scope, element, form); scope.label = scope.state.label; - scope.state.label = 'OPTIONS'; + scope.state.label = vm.strings.components.OPTIONS; vm.check(); }; diff --git a/awx/ui/client/lib/components/input/group.directive.js b/awx/ui/client/lib/components/input/group.directive.js index 550cd1c0ef..865a54611a 100644 --- a/awx/ui/client/lib/components/input/group.directive.js +++ b/awx/ui/client/lib/components/input/group.directive.js @@ -34,14 +34,14 @@ function AtInputGroupController ($scope, $compile) { }; vm.update = () => { - if (!vm.isValidSource()) { - return; - } - if (state._group) { vm.clear(); } + if (!vm.isValidSource()) { + return; + } + state._value = source._value; let inputs = state._get(source._value); @@ -101,7 +101,8 @@ function AtInputGroupController ($scope, $compile) { config._data = input.choices; config._exp = 'index as choice for (index, choice) in state._data'; } else { - throw new Error('Unsupported input type: ' + input.type) + let preface = vm.strings.components.UNSUPPORTED_ERROR_PREFACE; + throw new Error(`${preface}: ${input.type}`) } return config; @@ -158,6 +159,8 @@ function AtInputGroupController ($scope, $compile) { vm.clear = () => { form.deregisterInputGroup(state._group); element.innerHTML = ''; + state._group = undefined; + state._value = undefined; }; } diff --git a/awx/ui/client/lib/components/input/group.partial.html b/awx/ui/client/lib/components/input/group.partial.html index 6d20836d6a..45d4a845e0 100644 --- a/awx/ui/client/lib/components/input/group.partial.html +++ b/awx/ui/client/lib/components/input/group.partial.html @@ -1,4 +1,4 @@ -
+
diff --git a/awx/ui/client/lib/components/input/label.partial.html b/awx/ui/client/lib/components/input/label.partial.html index d53a4a7a25..2a9a61690f 100644 --- a/awx/ui/client/lib/components/input/label.partial.html +++ b/awx/ui/client/lib/components/input/label.partial.html @@ -8,7 +8,7 @@ -

Prompt on launch

+

{{ vm.strings.components.label.PROMPT_ON_LAUNCH }}

diff --git a/awx/ui/client/lib/components/input/lookup.directive.js b/awx/ui/client/lib/components/input/lookup.directive.js index 50e7ddef88..f8845ddd19 100644 --- a/awx/ui/client/lib/components/input/lookup.directive.js +++ b/awx/ui/client/lib/components/input/lookup.directive.js @@ -20,12 +20,13 @@ function AtInputLookupController (baseInputController, $state, $stateParams) { scope = _scope_; scope.$watch(scope.state._resource, vm.watchResource); + scope.state._validate = vm.checkOnInput; vm.check(); }; vm.watchResource = () => { - if (scope[scope.state._resource]) { + if (scope[scope.state._resource] !== scope.state._value) { scope.state._value = scope[scope.state._resource]; scope.state._displayValue = scope[`${scope.state._resource}_name`]; @@ -33,15 +34,43 @@ function AtInputLookupController (baseInputController, $state, $stateParams) { } }; - vm.search = () => { + vm.lookup = () => { let params = {}; - if (scope.state._value) { + if (scope.state._value && scope.state._isValid) { params.selected = scope.state._value; } $state.go(scope.state._route, params); }; + + vm.reset = () => { + scope.state._value = undefined; + scope[scope.state._resource] = undefined; + }; + + vm.checkOnInput = () => { + if (!scope.state._touched) { + return { isValid: true }; + } + + let result = scope.state._model.match('get', 'name', scope.state._displayValue); + + if (result) { + scope[scope.state._resource] = result.id; + scope.state._value = result.id; + scope.state._displayValue = result.name; + + return { isValid: true }; + } + + vm.reset(); + + return { + isValid: false, + message: vm.strings.components.lookup.NOT_FOUND + }; + }; } AtInputLookupController.$inject = [ diff --git a/awx/ui/client/lib/components/input/lookup.partial.html b/awx/ui/client/lib/components/input/lookup.partial.html index 6f4d7a0a68..39900afcc1 100644 --- a/awx/ui/client/lib/components/input/lookup.partial.html +++ b/awx/ui/client/lib/components/input/lookup.partial.html @@ -6,13 +6,13 @@ { if (scope.type === 'password') { scope.type = 'text'; - scope.state._buttonText = 'HIDE'; + scope.state._buttonText = vm.strings.components.HIDE; } else { scope.type = 'password'; - scope.state._buttonText = 'SHOW'; + scope.state._buttonText = vm.strings.components.SHOW; } }; } diff --git a/awx/ui/client/lib/components/input/select.directive.js b/awx/ui/client/lib/components/input/select.directive.js index cbc6c2b0fa..d61c13f33d 100644 --- a/awx/ui/client/lib/components/input/select.directive.js +++ b/awx/ui/client/lib/components/input/select.directive.js @@ -1,5 +1,3 @@ -const DEFAULT_EMPTY_PLACEHOLDER = 'NO OPTIONS AVAILABLE'; - function atInputSelectLink (scope, element, attrs, controllers) { let formController = controllers[0]; let inputController = controllers[1]; @@ -11,7 +9,7 @@ function atInputSelectLink (scope, element, attrs, controllers) { inputController.init(scope, element, formController); } -function AtInputSelectController (baseInputController, eventService) { +function AtInputSelectController (baseInputController, eventService) { let vm = this || {}; let scope; @@ -29,7 +27,7 @@ function AtInputSelectController (baseInputController, eventService) { if (!scope.state._data || scope.state._data.length === 0) { scope.state._disabled = true; - scope.state._placeholder = DEFAULT_EMPTY_PLACEHOLDER; + scope.state._placeholder = vm.strings.components.EMPTY_PLACEHOLDER; } vm.setListeners(); @@ -67,7 +65,7 @@ function AtInputSelectController (baseInputController, eventService) { } else if (scope.state._format === 'grouped-object') { scope.displayModel = scope.state._value[scope.state._display]; } else { - throw new Error('Unsupported display model type'); + throw new Error(vm.strings.components.UNSUPPORTED_TYPE_ERROR); } }; } diff --git a/awx/ui/client/lib/components/input/textarea-secret.directive.js b/awx/ui/client/lib/components/input/textarea-secret.directive.js index a22491b3de..ffe15c2741 100644 --- a/awx/ui/client/lib/components/input/textarea-secret.directive.js +++ b/awx/ui/client/lib/components/input/textarea-secret.directive.js @@ -1,5 +1,3 @@ -const DEFAULT_HINT = 'HINT: Drag and drop an SSH private key file on the field below.'; - function atInputTextareaSecretLink (scope, element, attrs, controllers) { let formController = controllers[0]; let inputController = controllers[1]; @@ -28,13 +26,13 @@ function AtInputTextareaSecretController (baseInputController, eventService) { if (scope.state.format === 'ssh_private_key') { scope.ssh = true; - scope.state._hint = scope.state._hint || DEFAULT_HINT; + scope.state._hint = scope.state._hint || vm.strings.components.textarea.SSH_KEY_HINT; input = element.find('input')[0]; } if (scope.state._value) { - scope.state._buttonText = 'REPLACE'; - scope.state._placeholder = 'ENCRYPTED'; + scope.state._buttonText = vm.strings.components.REPLACE; + scope.state._placeholder = vm.strings.components.ENCRYPTED; } else { if (scope.state.format === 'ssh_private_key') { vm.listeners = vm.setFileListeners(textarea, input); @@ -54,7 +52,7 @@ function AtInputTextareaSecretController (baseInputController, eventService) { vm.listeners = vm.setFileListeners(textarea, input); } else { scope.state._displayHint = false; - scope.state._placeholder = 'ENCRYPTED'; + scope.state._placeholder = vm.strings.components.ENCRYPTED; eventService.remove(vm.listeners); } }; @@ -92,7 +90,11 @@ function AtInputTextareaSecretController (baseInputController, eventService) { }; } -AtInputTextareaSecretController.$inject = ['BaseInputController', 'EventService']; +AtInputTextareaSecretController.$inject = [ + 'BaseInputController', + 'EventService', + 'ComponentsStrings' +]; function atInputTextareaSecret (pathService) { return { diff --git a/awx/ui/client/lib/components/modal/modal.partial.html b/awx/ui/client/lib/components/modal/modal.partial.html index 66db4d9d49..4ff2f44a99 100644 --- a/awx/ui/client/lib/components/modal/modal.partial.html +++ b/awx/ui/client/lib/components/modal/modal.partial.html @@ -23,7 +23,7 @@
diff --git a/awx/ui/client/lib/components/tabs/group.directive.js b/awx/ui/client/lib/components/tabs/group.directive.js index a78da714cf..907d8853fa 100644 --- a/awx/ui/client/lib/components/tabs/group.directive.js +++ b/awx/ui/client/lib/components/tabs/group.directive.js @@ -18,16 +18,8 @@ function AtTabGroupController ($state) { }; vm.register = tab => { - tab.active = true; -/* - * if (vm.tabs.length === 0) { - * tab.active = true; - * } else { - * tab.disabled = true; - * } - * - */ + vm.tabs.push(tab); }; } diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index 8f4e04cbc2..d1c8fd2936 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -12,8 +12,10 @@ function request (method, resource) { } function httpGet (resource) { + this.method = this.method || 'GET'; + let req = { - method: 'GET', + method: this.method, url: this.path }; @@ -85,6 +87,26 @@ function get (keys) { return this.find('get', keys); } +function match (method, key, value) { + let model = this.model[method.toUpperCase()]; + + if (!model) { + return null; + } + + if (!model.results) { + if (model[key] === value) { + return model; + } + + return null; + } + + let result = model.results.filter(result => result[key] === value); + + return result.length === 0 ? null : result[0]; +} + function find (method, keys) { let value = this.model[method.toUpperCase()]; @@ -138,6 +160,7 @@ function BaseModel (path) { this.get = get; this.options = options; this.find = find; + this.match = match; this.normalizePath = normalizePath; this.getById = getById; this.request = request; diff --git a/awx/ui/client/lib/models/CredentialType.js b/awx/ui/client/lib/models/CredentialType.js index 2679e191d7..f0ab0d6f0f 100644 --- a/awx/ui/client/lib/models/CredentialType.js +++ b/awx/ui/client/lib/models/CredentialType.js @@ -26,11 +26,18 @@ function mergeInputProperties (type) { }); } +function graft (id) { + let data = this.getById(id); + + return new CredentialTypeModel('get', data); +} + function CredentialTypeModel (method, id) { BaseModel.call(this, 'credential_types'); this.categorizeByKind = categorizeByKind.bind(this); this.mergeInputProperties = mergeInputProperties.bind(this); + this.graft = graft.bind(this); return this.request(method, id) .then(() => this); diff --git a/awx/ui/client/lib/services/base-string.service.js b/awx/ui/client/lib/services/base-string.service.js new file mode 100644 index 0000000000..7226a921a9 --- /dev/null +++ b/awx/ui/client/lib/services/base-string.service.js @@ -0,0 +1,23 @@ +let i18n; + +function BaseStringService (namespace) { + let t = i18n._; + + this.t = t; + this[namespace] = {}; + + this.CANCEL = t('CANCEL'); + this.SAVE = t('SAVE'); + this.OK = t('OK'); +} + + +function BaseStringServiceLoader (_i18n_) { + i18n = _i18n_; + + return BaseStringService; +} + +BaseStringServiceLoader.$inject = ['i18n']; + +export default BaseStringServiceLoader; diff --git a/awx/ui/client/lib/services/index.js b/awx/ui/client/lib/services/index.js index d6a256b0fc..f82bc49616 100644 --- a/awx/ui/client/lib/services/index.js +++ b/awx/ui/client/lib/services/index.js @@ -1,7 +1,9 @@ import EventService from './event.service'; import PathService from './path.service'; +import BaseStringService from './base-string.service'; angular .module('at.lib.services', []) .service('EventService', EventService) - .service('PathService', PathService); + .service('PathService', PathService) + .service('BaseStringService', BaseStringService); diff --git a/awx/ui/client/src/i18n.js b/awx/ui/client/src/i18n.js index 2b24a09822..062c2455ea 100644 --- a/awx/ui/client/src/i18n.js +++ b/awx/ui/client/src/i18n.js @@ -1,6 +1,7 @@ /* jshint ignore:start */ var sprintf = require('sprintf-js').sprintf; +let defaultLanguage = 'en_US'; /** * @ngdoc method @@ -24,7 +25,12 @@ export default $window.navigator.userLanguage || ''; var langUrl = langInfo.replace('-', '_'); - //gettextCatalog.debug = true; + + if (langUrl === defaultLanguage) { + return; + } + + // gettextCatalog.debug = true; gettextCatalog.setCurrentLanguage(langInfo); gettextCatalog.loadRemote('/static/languages/' + langUrl + '.json'); }; diff --git a/awx/ui/grunt-tasks/nggettext_extract.js b/awx/ui/grunt-tasks/nggettext_extract.js index de297658e3..b264b6ada8 100644 --- a/awx/ui/grunt-tasks/nggettext_extract.js +++ b/awx/ui/grunt-tasks/nggettext_extract.js @@ -1,11 +1,19 @@ +let source = [ + 'client/features/**/*.js', + 'client/features/**/*.html', + 'client/lib/**/*.js', + 'client/lib/**/*.html', + 'client/src/**/*.js', + 'client/src/**/*.html' +]; + module.exports = { all: { options: { - markerNames: ['_', 'N_'] + markerNames: ['_', 'N_', 't'] }, files: { - 'po/ansible-tower-ui.pot': ['client/src/**/*.js', - 'client/src/**/*.html'] + 'po/ansible-tower-ui.pot': source } - }, + } };