diff --git a/awx/ui/client/features/credentials/add-credentials.controller.js b/awx/ui/client/features/credentials/add-credentials.controller.js index 0dc60b46e5..b9d2c5ee7a 100644 --- a/awx/ui/client/features/credentials/add-credentials.controller.js +++ b/awx/ui/client/features/credentials/add-credentials.controller.js @@ -29,8 +29,8 @@ function AddCredentialsController (models, $state) { return credential.request('post', data); }; - vm.form.saveSuccess = res => { - $state.go('credentials.edit', { id: res.data.id }); + vm.form.onSaveSuccess = res => { + $state.go('credentials.edit', { credential: res.data }); }; } diff --git a/awx/ui/client/features/credentials/add-credentials.view.html b/awx/ui/client/features/credentials/add-edit-credentials.view.html similarity index 94% rename from awx/ui/client/features/credentials/add-credentials.view.html rename to awx/ui/client/features/credentials/add-edit-credentials.view.html index 284c5f44ad..3e62f42611 100644 --- a/awx/ui/client/features/credentials/add-credentials.view.html +++ b/awx/ui/client/features/credentials/add-edit-credentials.view.html @@ -4,7 +4,6 @@ Details Permissions - Notifications diff --git a/awx/ui/client/features/credentials/edit-credentials.controller.js b/awx/ui/client/features/credentials/edit-credentials.controller.js new file mode 100644 index 0000000000..0887669f5d --- /dev/null +++ b/awx/ui/client/features/credentials/edit-credentials.controller.js @@ -0,0 +1,44 @@ +function EditCredentialsController (models, $state) { + let vm = this || {}; + + let me = models.me; + let credential = models.credential; + let credentialType = models.credentialType; + let credentialOptions = models.credentialOptions; + + vm.form = credentialOptions.createFormSchema('put', { + omit: ['user', 'team', 'inputs'], + models + }); + + vm.form.credential_type._data = credentialType.get('results'); + vm.form.credential_type._placeholder = 'SELECT A TYPE'; + vm.form.credential_type._format = 'grouped-object'; + vm.form.credential_type._display = 'name'; + vm.form.credential_type._key = 'id'; + vm.form.credential_type._exp = 'type as type.name group by type.kind for type in state._data'; + + vm.form.inputs = { + _get: credentialType.mergeInputProperties, + _source: vm.form.credential_type, + _reference: 'vm.form.inputs', + _key: 'inputs' + }; + + vm.form.save = data => { + data.user = me.get('results[0].id'); + + return credential.request('post', data); + }; + + vm.form.onSaveSuccess = res => { + $state.go('credentials.edit', { credential: res.data }); + }; +} + +EditCredentialsController.$inject = [ + 'resolvedModels', + '$state' +]; + +export default EditCredentialsController; diff --git a/awx/ui/client/features/credentials/index.js b/awx/ui/client/features/credentials/index.js index 5846dc5fa1..c4bee3db68 100644 --- a/awx/ui/client/features/credentials/index.js +++ b/awx/ui/client/features/credentials/index.js @@ -1,6 +1,7 @@ import CredentialList from './credentials.list.js'; import ListController from './list/credentials-list.controller'; import AddController from './add-credentials.controller.js'; +import EditController from './edit-credentials.controller.js'; import { N_ } from '../../src/i18n'; function config ($stateExtenderProvider, pathServiceProvider) { @@ -41,32 +42,20 @@ function config ($stateExtenderProvider, pathServiceProvider) { }); function CredentialsResolve ($q, params, Me, Credential, CredentialType) { - let models; - let id = params.id; let promises = { me: new Me('get') }; - if (!id) { + if (params.credential) { + promises.credential = new Credential('get', params.credential); + promises.credentialOptions = new Credential('options'); + promises.credentialType = new CredentialType('get', params.credential.credential_type); + } else { promises.credential = new Credential('options'); promises.credentialType = new CredentialType('get'); - - return $q.all(promises).then(models => models); } - - promises.credential = new Credential('get', id); - return $q.all(promises) - .then(_models_ => { - models = _models_; - - return new CredentialType('get', models.credential.get('id')); - }) - .then(credentialType => { - models.credentialType = credentialType; - - return models; - }); + return $q.all(promises).then(models => models); } CredentialsResolve.$inject = [ @@ -85,7 +74,7 @@ function config ($stateExtenderProvider, pathServiceProvider) { }, views: { 'add@credentials': { - templateUrl: pathService.getViewPath('credentials/add-credentials'), + templateUrl: pathService.getViewPath('credentials/add-edit-credentials'), controller: AddController, controllerAs: 'vm' } @@ -97,14 +86,14 @@ function config ($stateExtenderProvider, pathServiceProvider) { stateExtender.addState({ name: 'credentials.edit', - route: '/edit/:id', + route: '/edit/:credential', ncyBreadcrumb: { label: N_('EDIT') }, views: { 'edit@credentials': { - templateUrl: pathService.getViewPath('credentials/add-credentials'), - controller: AddController, + templateUrl: pathService.getViewPath('credentials/add-edit-credentials'), + controller: EditController, controllerAs: 'vm' } }, @@ -124,4 +113,5 @@ angular .config(config) .factory('CredentialList', CredentialList) .controller('ListController', ListController) - .controller('AddController', AddController); + .controller('AddController', AddController) + .controller('EditController', EditController); diff --git a/awx/ui/client/lib/components/_index.less b/awx/ui/client/lib/components/_index.less index 943a2ecd20..4a313024b8 100644 --- a/awx/ui/client/lib/components/_index.less +++ b/awx/ui/client/lib/components/_index.less @@ -1,5 +1,6 @@ @import 'action/_index'; @import 'input/_index'; @import 'panel/_index'; +@import 'modal/_index'; @import 'popover/_index'; @import 'tabs/_index'; diff --git a/awx/ui/client/lib/components/form/form.directive.js b/awx/ui/client/lib/components/form/form.directive.js index f3d9b85ca0..041a4cc9d4 100644 --- a/awx/ui/client/lib/components/form/form.directive.js +++ b/awx/ui/client/lib/components/form/form.directive.js @@ -12,10 +12,11 @@ function AtFormController (eventService) { let form; vm.components = []; + vm.modal = {}; vm.state = { isValid: false, disabled: false, - value: {} + value: {}, }; vm.init = (_scope_, _form_) => { @@ -76,7 +77,7 @@ function AtFormController (eventService) { }, {}); scope.state.save(data) - .then(scope.state.created) + .then(scope.state.onSaveSuccess) .catch(err => vm.onSaveError(err)) .finally(() => vm.state.disabled = false); }; @@ -89,7 +90,15 @@ function AtFormController (eventService) { } if (!handled) { - // TODO: launch modal for unexpected error type + let message; + + if (typeof err.data === 'object') { + message = JSON.stringify(err.data); + } else { + message = err.data; + } + + vm.modal.show('Unable to Submit', `Unexpected Error: ${message}`); } }; diff --git a/awx/ui/client/lib/components/form/form.partial.html b/awx/ui/client/lib/components/form/form.partial.html index 3303ed535e..dd2d00b40e 100644 --- a/awx/ui/client/lib/components/form/form.partial.html +++ b/awx/ui/client/lib/components/form/form.partial.html @@ -1,5 +1,9 @@ -
-
- -
-
+
+
+
+ +
+
+ + +
diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index ecf1d729c9..fda8307819 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -10,6 +10,7 @@ import inputSecret from './input/secret.directive'; import inputText from './input/text.directive'; import inputTextarea from './input/textarea.directive'; import inputTextareaSecret from './input/textarea-secret.directive'; +import modal from './modal/modal.directive'; import panel from './panel/panel.directive'; import panelHeading from './panel/heading.directive'; import panelBody from './panel/body.directive'; @@ -33,6 +34,7 @@ angular .directive('atInputText', inputText) .directive('atInputTextarea', inputTextarea) .directive('atInputTextareaSecret', inputTextareaSecret) + .directive('atModal', modal) .directive('atPanel', panel) .directive('atPanelHeading', panelHeading) .directive('atPanelBody', panelBody) diff --git a/awx/ui/client/lib/components/input/select.directive.js b/awx/ui/client/lib/components/input/select.directive.js index b9e1b6588c..348480fc54 100644 --- a/awx/ui/client/lib/components/input/select.directive.js +++ b/awx/ui/client/lib/components/input/select.directive.js @@ -36,6 +36,7 @@ function AtInputSelectController (baseInputController, eventService) { [select, 'focus', () => input.classList.add('at-Input--focus')], [select, 'change', () => scope.$apply(() => { scope.open = false; + vm.updateDisplayModel(); vm.check(); })], [select, 'blur', () => { diff --git a/awx/ui/client/lib/components/input/select.partial.html b/awx/ui/client/lib/components/input/select.partial.html index 14b67acafa..d096c4baef 100644 --- a/awx/ui/client/lib/components/input/select.partial.html +++ b/awx/ui/client/lib/components/input/select.partial.html @@ -8,14 +8,12 @@ placeholder="{{state._placeholder || undefined }}" ng-class="{ 'at-Input--rejected': state._rejected }" ng-model="displayModel" - ng-disabled="state._disabled || form.disabled" - ng-change="vm.check()" /> + ng-disabled="state._disabled || form.disabled" /> diff --git a/awx/ui/client/lib/components/modal/_index.less b/awx/ui/client/lib/components/modal/_index.less new file mode 100644 index 0000000000..11e962e98b --- /dev/null +++ b/awx/ui/client/lib/components/modal/_index.less @@ -0,0 +1,10 @@ +.at-Modal-title { + margin: 0; + padding: 0; + + .at-mixin-Heading(@at-font-size-3x); +} + +.at-Modal-body { + font-size: @at-font-size; +} diff --git a/awx/ui/client/lib/components/modal/modal.directive.js b/awx/ui/client/lib/components/modal/modal.directive.js index e69de29bb2..10f18a0afc 100644 --- a/awx/ui/client/lib/components/modal/modal.directive.js +++ b/awx/ui/client/lib/components/modal/modal.directive.js @@ -0,0 +1,63 @@ +const DEFAULT_ANIMATION_DURATION = 150; + +function atModalLink (scope, el, attr, controllers) { + let modalController = controllers[0]; + let container = el[0]; + + modalController.init(scope, container); +} + +function AtModalController () { + let vm = this; + + let scope; + let container; + + vm.init = (_scope_, _container_) => { + scope = _scope_; + container = _container_; + + scope.state.show = vm.show; + scope.state.hide = vm.hide; + }; + + vm.show = (title, message) => { + scope.title = title; + scope.message = message; + + container.style.display = 'block'; + container.style.opacity = 1; + }; + + vm.hide = () => { + container.style.opacity = 0; + + setTimeout(() => { + container.style.display = 'none'; + scope.message = ''; + scope.title = ''; + }, DEFAULT_ANIMATION_DURATION); + }; +} + +function atModal (pathService) { + return { + restrict: 'E', + replace: true, + transclude: true, + require: ['atModal'], + templateUrl: pathService.getPartialPath('components/modal/modal'), + controller: AtModalController, + controllerAs: 'vm', + link: atModalLink, + scope: { + state: '=' + } + }; +} + +atModal.$inject = [ + 'PathService' +]; + +export default atModal; diff --git a/awx/ui/client/lib/components/modal/modal.partial.html b/awx/ui/client/lib/components/modal/modal.partial.html index e69de29bb2..69df2bc984 100644 --- a/awx/ui/client/lib/components/modal/modal.partial.html +++ b/awx/ui/client/lib/components/modal/modal.partial.html @@ -0,0 +1,20 @@ + diff --git a/awx/ui/client/lib/components/popover/popover.directive.js b/awx/ui/client/lib/components/popover/popover.directive.js index 296a6e49fb..66857f6e4c 100644 --- a/awx/ui/client/lib/components/popover/popover.directive.js +++ b/awx/ui/client/lib/components/popover/popover.directive.js @@ -17,7 +17,7 @@ function AtPopoverController () { vm.init = (scope, _container_, _icon_, _popover_) => { icon = _icon_; popover = _popover_; - scope.inline = scope.state._inline || true; + scope.inline = true; icon.addEventListener('click', vm.createDisplayListener()); }; diff --git a/awx/ui/client/lib/models/Base.js b/awx/ui/client/lib/models/Base.js index c01b924758..594a8087d9 100644 --- a/awx/ui/client/lib/models/Base.js +++ b/awx/ui/client/lib/models/Base.js @@ -1,9 +1,17 @@ let $http; +let $q; function request (method, ...args) { - method = method.toUpperCase(); + this.method = method.toUpperCase(); - switch (method) { + if (typeof args[0] === 'object') { + this.res = null; + this.model = args[0]; + + return $q.resolve(); + } + + switch (this.method) { case 'OPTIONS': return this.httpOptions(...args); case 'GET': @@ -112,12 +120,13 @@ function BaseModel (path) { this.path = this.normalizePath(path); }; -function BaseModelLoader (_$http_) { +function BaseModelLoader (_$http_, _$q_) { $http = _$http_; + $q = _$q_; return BaseModel; } -BaseModelLoader.$inject = ['$http']; +BaseModelLoader.$inject = ['$http', '$q']; export default BaseModelLoader; diff --git a/awx/ui/client/lib/models/Credential.js b/awx/ui/client/lib/models/Credential.js index 1f36c7c5c2..c99c0f0099 100644 --- a/awx/ui/client/lib/models/Credential.js +++ b/awx/ui/client/lib/models/Credential.js @@ -1,7 +1,9 @@ let BaseModel; -function createFormSchema (type, config) { - let schema = Object.assign({}, this.get('actions.POST')); +function createFormSchema (method, config) { + method = method.toUpperCase(); + + let schema = Object.assign({}, this.get(`actions.${method}`)); if (config && config.omit) { config.omit.forEach(key => {