mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	UI - identity details (#4502)
* add popups * add ability to disable entity and banner when entity is disabled * re-add alias-popup template * add accpetance tests for creating entities * add more entity creation acceptance tests * add delete to edit-form * add more identity tests and associated selectors * add onSuccess hook and use UnloadModel route mixins * add ability to toggle entity disabling from the popover * fix store list cache because unloadAll isn't synchronous * fill out tests for identity items and aliases * add ability to enable entity from the detail page * toArray on the peekAll * fix other tests/behavior that relied on a RecordArray * adjust layout for disabled entity and label for disabling an entity on the edit form * add item-details integration tests * move disable field on the entity form * use ghost buttons for delete in identity and policy edit forms * adding computed macros for lazy capability fetching and using them in the identity models
This commit is contained in:
		| @@ -16,6 +16,7 @@ export default Ember.Component.extend({ | |||||||
|         class={{buttonClasses}} |         class={{buttonClasses}} | ||||||
|         type="button" |         type="button" | ||||||
|         disabled={{disabled}} |         disabled={{disabled}} | ||||||
|  |         data-test-confirm-action-trigger=true | ||||||
|         {{action 'toggleConfirm'}} |         {{action 'toggleConfirm'}} | ||||||
|       > |       > | ||||||
|         {{yield}} |         {{yield}} | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								ui/app/components/identity/_popup-base.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ui/app/components/identity/_popup-base.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | import Ember from 'ember'; | ||||||
|  | const { assert, inject, Component } = Ember; | ||||||
|  |  | ||||||
|  | export default Component.extend({ | ||||||
|  |   tagName: '', | ||||||
|  |   flashMessages: inject.service(), | ||||||
|  |   params: null, | ||||||
|  |   successMessage() { | ||||||
|  |     return 'Save was successful'; | ||||||
|  |   }, | ||||||
|  |   errorMessage() { | ||||||
|  |     return 'There was an error saving'; | ||||||
|  |   }, | ||||||
|  |   onError(model) { | ||||||
|  |     if (model && model.rollbackAttributes) { | ||||||
|  |       model.rollbackAttributes(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   onSuccess(){}, | ||||||
|  |   // override and return a promise | ||||||
|  |   transaction() { | ||||||
|  |     assert('override transaction call in an extension of popup-base', false); | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   actions: { | ||||||
|  |     performTransaction() { | ||||||
|  |       let args = [...arguments]; | ||||||
|  |       let messageArgs = this.messageArgs(...args); | ||||||
|  |       return this.transaction(...args) | ||||||
|  |         .then(() => { | ||||||
|  |           this.get('onSuccess')(); | ||||||
|  |           this.get('flashMessages').success(this.successMessage(...messageArgs)); | ||||||
|  |         }) | ||||||
|  |         .catch(e => { | ||||||
|  |           this.onError(...messageArgs); | ||||||
|  |           this.get('flashMessages').success(this.errorMessage(e, ...messageArgs)); | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -2,20 +2,23 @@ import Ember from 'ember'; | |||||||
| import { task } from 'ember-concurrency'; | import { task } from 'ember-concurrency'; | ||||||
| import { humanize } from 'vault/helpers/humanize'; | import { humanize } from 'vault/helpers/humanize'; | ||||||
|  |  | ||||||
| const { computed } = Ember; | const { computed, inject } = Ember; | ||||||
| export default Ember.Component.extend({ | export default Ember.Component.extend({ | ||||||
|  |   flashMessages: inject.service(), | ||||||
|  |   'data-test-component': 'identity-edit-form', | ||||||
|   model: null, |   model: null, | ||||||
|  |  | ||||||
|  |   // 'create', 'edit', 'merge' | ||||||
|   mode: 'create', |   mode: 'create', | ||||||
|   /* |   /* | ||||||
|    * @param Function |    * @param Function | ||||||
|    * @public |    * @public | ||||||
|    * |    * | ||||||
|    * Optional param to call a function upon successfully mounting a backend |    * Optional param to call a function upon successfully saving an entity | ||||||
|    * |  | ||||||
|    */ |    */ | ||||||
|   onSave: () => {}, |   onSave: () => {}, | ||||||
|  |  | ||||||
|   cancelLink: computed('mode', 'model', function() { |   cancelLink: computed('mode', 'model.identityType', function() { | ||||||
|     let { model, mode } = this.getProperties('model', 'mode'); |     let { model, mode } = this.getProperties('model', 'mode'); | ||||||
|     let key = `${mode}-${model.get('identityType')}`; |     let key = `${mode}-${model.get('identityType')}`; | ||||||
|     let routes = { |     let routes = { | ||||||
| @@ -33,16 +36,17 @@ export default Ember.Component.extend({ | |||||||
|     return routes[key]; |     return routes[key]; | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   getMessage(model) { |   getMessage(model, isDelete = false) { | ||||||
|     let mode = this.get('mode'); |     let mode = this.get('mode'); | ||||||
|     let typeDisplay = humanize([model.get('identityType')]); |     let typeDisplay = humanize([model.get('identityType')]); | ||||||
|  |     let action = isDelete ? 'deleted' : 'saved'; | ||||||
|     if (mode === 'merge') { |     if (mode === 'merge') { | ||||||
|       return 'Successfully merged entities'; |       return 'Successfully merged entities'; | ||||||
|     } |     } | ||||||
|     if (model.get('id')) { |     if (model.get('id')) { | ||||||
|       return `Successfully saved ${typeDisplay} ${model.id}.`; |       return `Successfully ${action} ${typeDisplay} ${model.id}.`; | ||||||
|     } |     } | ||||||
|     return `Successfully saved ${typeDisplay}.`; |     return `Successfully ${action} ${typeDisplay}.`; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   save: task(function*() { |   save: task(function*() { | ||||||
| @@ -56,13 +60,26 @@ export default Ember.Component.extend({ | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this.get('flashMessages').success(message); |     this.get('flashMessages').success(message); | ||||||
|     yield this.get('onSave')(model); |     yield this.get('onSave')({saveType: 'save', model}); | ||||||
|   }).drop(), |   }).drop(), | ||||||
|  |  | ||||||
|   willDestroy() { |   willDestroy() { | ||||||
|     let model = this.get('model'); |     let model = this.get('model'); | ||||||
|     if (!model.isDestroyed || !model.isDestroying) { |     if ((model.get('isDirty') && !model.isDestroyed) || !model.isDestroying) { | ||||||
|       model.rollbackAttributes(); |       model.rollbackAttributes(); | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   actions: { | ||||||
|  |     deleteItem(model) { | ||||||
|  |       let message = this.getMessage(model, true); | ||||||
|  |       let flash = this.get('flashMessages'); | ||||||
|  |       model | ||||||
|  |         .destroyRecord() | ||||||
|  |         .then(() => { | ||||||
|  |           flash.success(message); | ||||||
|  |           return this.get('onSave')({saveType: 'delete', model}); | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								ui/app/components/identity/item-details.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ui/app/components/identity/item-details.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import Ember from 'ember'; | ||||||
|  |  | ||||||
|  | const { inject } = Ember; | ||||||
|  |  | ||||||
|  | export default Ember.Component.extend({ | ||||||
|  |   flashMessages: inject.service(), | ||||||
|  |  | ||||||
|  |   actions: { | ||||||
|  |     enable(model) { | ||||||
|  |       model.set('disabled', false); | ||||||
|  |  | ||||||
|  |       model.save(). | ||||||
|  |         then(() => { | ||||||
|  |           this.get('flashMessages').success(`Successfully enabled entity: ${model.id}`); | ||||||
|  |         }) | ||||||
|  |         .catch(e => { | ||||||
|  |           this.get('flashMessages').success( | ||||||
|  |             `There was a problem enabling the entity: ${model.id} - ${e.error.join(' ') || e.message}` | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
							
								
								
									
										22
									
								
								ui/app/components/identity/popup-alias.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								ui/app/components/identity/popup-alias.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import Base from './_popup-base'; | ||||||
|  |  | ||||||
|  | export default Base.extend({ | ||||||
|  |   messageArgs(model) { | ||||||
|  |     let type = model.get('identityType'); | ||||||
|  |     let id = model.id; | ||||||
|  |     return [type, id]; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   successMessage(type, id) { | ||||||
|  |     return `Successfully deleted ${type}: ${id}`; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   errorMessage(e, type, id) { | ||||||
|  |     let error = e.errors ? e.errors.join(' ') : e.message; | ||||||
|  |     return `There was a problem deleting ${type}: ${id} - ${error}`; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   transaction(model) { | ||||||
|  |     return model.destroyRecord(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										34
									
								
								ui/app/components/identity/popup-members.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								ui/app/components/identity/popup-members.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import Base from './_popup-base'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  | const { computed } = Ember; | ||||||
|  |  | ||||||
|  | export default Base.extend({ | ||||||
|  |   model: computed.alias('params.firstObject'), | ||||||
|  |  | ||||||
|  |   groupArray: computed('params', function() { | ||||||
|  |     return this.get('params').objectAt(1); | ||||||
|  |   }), | ||||||
|  |  | ||||||
|  |   memberId: computed('params', function() { | ||||||
|  |     return this.get('params').objectAt(2); | ||||||
|  |   }), | ||||||
|  |  | ||||||
|  |   messageArgs(/*model, groupArray, memberId*/) { | ||||||
|  |     return [...arguments]; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   successMessage(model, groupArray, memberId) { | ||||||
|  |     return `Successfully removed '${memberId}' from the group`; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   errorMessage(e, model, groupArray, memberId) { | ||||||
|  |     let error = e.errors ? e.errors.join(' ') : e.message; | ||||||
|  |     return `There was a problem removing '${memberId}' from the group - ${error}`; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   transaction(model, groupArray, memberId) { | ||||||
|  |     let members = model.get(groupArray); | ||||||
|  |     model.set(groupArray, members.without(memberId)); | ||||||
|  |     return model.save(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										29
									
								
								ui/app/components/identity/popup-metadata.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								ui/app/components/identity/popup-metadata.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | import Base from './_popup-base'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  | const { computed } = Ember; | ||||||
|  |  | ||||||
|  | export default Base.extend({ | ||||||
|  |   model: computed.alias('params.firstObject'), | ||||||
|  |   key: computed('params', function() { | ||||||
|  |     return this.get('params').objectAt(1); | ||||||
|  |   }), | ||||||
|  |  | ||||||
|  |   messageArgs(model, key) { | ||||||
|  |     return [model, key]; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   successMessage(model, key) { | ||||||
|  |     return `Successfully removed '${key}' from metadata`; | ||||||
|  |   }, | ||||||
|  |   errorMessage(e, model, key) { | ||||||
|  |     let error = e.errors ? e.errors.join(' ') : e.message; | ||||||
|  |     return `There was a problem removing '${key}' from the metadata - ${error}`; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   transaction(model, key) { | ||||||
|  |     let metadata = model.get('metadata'); | ||||||
|  |     delete metadata[key]; | ||||||
|  |     model.set('metadata', { ...metadata }); | ||||||
|  |     return model.save(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
							
								
								
									
										29
									
								
								ui/app/components/identity/popup-policy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								ui/app/components/identity/popup-policy.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | import Base from './_popup-base'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  | const { computed } = Ember; | ||||||
|  |  | ||||||
|  | export default Base.extend({ | ||||||
|  |   model: computed.alias('params.firstObject'), | ||||||
|  |   policyName: computed('params', function() { | ||||||
|  |     return this.get('params').objectAt(1); | ||||||
|  |   }), | ||||||
|  |  | ||||||
|  |   messageArgs(model, policyName) { | ||||||
|  |     return [model, policyName]; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   successMessage(model, policyName) { | ||||||
|  |     return `Successfully removed '${policyName}' policy from ${model.id} `; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   errorMessage(e, model, policyName) { | ||||||
|  |     let error = e.errors ? e.errors.join(' ') : e.message; | ||||||
|  |     return `There was a problem removing '${policyName}' policy - ${error}`; | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   transaction(model, policyName) { | ||||||
|  |     let policies = model.get('policies'); | ||||||
|  |     model.set('policies', policies.without(policyName)); | ||||||
|  |     return model.save(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
|  |  | ||||||
| export default Ember.Component.extend({ | export default Ember.Component.extend({ | ||||||
|  |   'data-test-component': 'info-table-row', | ||||||
|   classNames: ['info-table-row'], |   classNames: ['info-table-row'], | ||||||
|   isVisible: Ember.computed.or('alwaysRender', 'value'), |   isVisible: Ember.computed.or('alwaysRender', 'value'), | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,8 @@ const { computed } = Ember; | |||||||
| export default Ember.Component.extend({ | export default Ember.Component.extend({ | ||||||
|   type: null, |   type: null, | ||||||
|  |  | ||||||
|  |   yieldWithoutColumn: false, | ||||||
|  |  | ||||||
|   classNameBindings: ['containerClass'], |   classNameBindings: ['containerClass'], | ||||||
|  |  | ||||||
|   containerClass: computed('type', function() { |   containerClass: computed('type', function() { | ||||||
|   | |||||||
| @@ -9,6 +9,4 @@ export default Ember.Component.extend({ | |||||||
|   baseKey: null, |   baseKey: null, | ||||||
|   backendCrumb: null, |   backendCrumb: null, | ||||||
|   model: null, |   model: null, | ||||||
|  |  | ||||||
|  |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ export default Ember.Component.extend(DEFAULTS, { | |||||||
|  |  | ||||||
|   handleSuccess(resp, action) { |   handleSuccess(resp, action) { | ||||||
|     let props = {}; |     let props = {}; | ||||||
|     let secret = resp && resp.data || resp.auth; |     let secret = (resp && resp.data) || resp.auth; | ||||||
|     if (secret && action === 'unwrap') { |     if (secret && action === 'unwrap') { | ||||||
|       props = Ember.assign({}, props, { unwrap_data: secret }); |       props = Ember.assign({}, props, { unwrap_data: secret }); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,4 +1,10 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
| import ListController from 'vault/mixins/list-controller'; | import ListController from 'vault/mixins/list-controller'; | ||||||
|  |  | ||||||
| export default Ember.Controller.extend(ListController); | export default Ember.Controller.extend(ListController, { | ||||||
|  |   actions: { | ||||||
|  |     onDelete() { | ||||||
|  |       this.send('reload'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -4,7 +4,26 @@ import { task } from 'ember-concurrency'; | |||||||
| export default Ember.Controller.extend({ | export default Ember.Controller.extend({ | ||||||
|   showRoute: 'vault.cluster.access.identity.show', |   showRoute: 'vault.cluster.access.identity.show', | ||||||
|   showTab: 'details', |   showTab: 'details', | ||||||
|   navToShow: task(function*(model) { |   navAfterSave: task(function*({saveType, model}) { | ||||||
|     yield this.transitionToRoute(this.get('showRoute'), model.id, this.get('showTab')); |     let isDelete = saveType === 'delete'; | ||||||
|  |     let type = model.get('identityType'); | ||||||
|  |     let listRoutes= { | ||||||
|  |       'entity-alias': 'vault.cluster.access.identity.aliases.index', | ||||||
|  |       'group-alias': 'vault.cluster.access.identity.aliases.index', | ||||||
|  |       'group': 'vault.cluster.access.identity.index', | ||||||
|  |       'entity': 'vault.cluster.access.identity.index', | ||||||
|  |     }; | ||||||
|  |     let routeName = listRoutes[type] | ||||||
|  |     if (!isDelete) { | ||||||
|  |       yield this.transitionToRoute( | ||||||
|  |         this.get('showRoute'), | ||||||
|  |         model.id, | ||||||
|  |         this.get('showTab') | ||||||
|  |       ); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     yield this.transitionToRoute( | ||||||
|  |       routeName | ||||||
|  |     ); | ||||||
|   }), |   }), | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,4 +1,46 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
| import ListController from 'vault/mixins/list-controller'; | import ListController from 'vault/mixins/list-controller'; | ||||||
|  |  | ||||||
| export default Ember.Controller.extend(ListController); | const { inject } = Ember; | ||||||
|  |  | ||||||
|  | export default Ember.Controller.extend(ListController, { | ||||||
|  |   flashMessages: inject.service(), | ||||||
|  |  | ||||||
|  |   actions: { | ||||||
|  |     delete(model) { | ||||||
|  |       let type = model.get('identityType'); | ||||||
|  |       let id = model.id; | ||||||
|  |       return model | ||||||
|  |         .destroyRecord() | ||||||
|  |         .then(() => { | ||||||
|  |           this.send('reload'); | ||||||
|  |           this.get('flashMessages').success(`Successfully deleted ${type}: ${id}`); | ||||||
|  |         }) | ||||||
|  |         .catch(e => { | ||||||
|  |           this.get('flashMessages').success( | ||||||
|  |             `There was a problem deleting ${type}: ${id} - ${e.error.join(' ') || e.message}` | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     toggleDisabled(model) { | ||||||
|  |       let action = model.get('disabled') ? ['enabled', 'enabling'] : ['disabled', 'disabling']; | ||||||
|  |       let type = model.get('identityType'); | ||||||
|  |       let id = model.id; | ||||||
|  |       model.toggleProperty('disabled'); | ||||||
|  |  | ||||||
|  |       model.save(). | ||||||
|  |         then(() => { | ||||||
|  |           this.get('flashMessages').success(`Successfully ${action[0]} ${type}: ${id}`); | ||||||
|  |         }) | ||||||
|  |         .catch(e => { | ||||||
|  |           this.get('flashMessages').success( | ||||||
|  |             `There was a problem ${action[1]} ${type}: ${id} - ${e.error.join(' ') || e.message}` | ||||||
|  |           ); | ||||||
|  |         }); | ||||||
|  |     }, | ||||||
|  |     reloadRecord(model) { | ||||||
|  |       model.reload(); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -1,9 +1,11 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
| import utils from 'vault/lib/key-utils'; | import utils from 'vault/lib/key-utils'; | ||||||
|  |  | ||||||
| export default Ember.Controller.extend({ | const { inject, computed, Controller } = Ember; | ||||||
|   flashMessages: Ember.inject.service(), | export default Controller.extend({ | ||||||
|   clusterController: Ember.inject.controller('vault.cluster'), |   flashMessages: inject.service(), | ||||||
|  |   store: inject.service(), | ||||||
|  |   clusterController: inject.controller('vault.cluster'), | ||||||
|   queryParams: { |   queryParams: { | ||||||
|     page: 'page', |     page: 'page', | ||||||
|     pageFilter: 'pageFilter', |     pageFilter: 'pageFilter', | ||||||
| @@ -13,7 +15,7 @@ export default Ember.Controller.extend({ | |||||||
|   pageFilter: null, |   pageFilter: null, | ||||||
|   filter: null, |   filter: null, | ||||||
|  |  | ||||||
|   backendCrumb: Ember.computed(function() { |   backendCrumb: computed(function() { | ||||||
|     return { |     return { | ||||||
|       label: 'leases', |       label: 'leases', | ||||||
|       text: 'leases', |       text: 'leases', | ||||||
| @@ -24,13 +26,13 @@ export default Ember.Controller.extend({ | |||||||
|  |  | ||||||
|   isLoading: false, |   isLoading: false, | ||||||
|  |  | ||||||
|   filterMatchesKey: Ember.computed('filter', 'model', 'model.[]', function() { |   filterMatchesKey: computed('filter', 'model', 'model.[]', function() { | ||||||
|     var filter = this.get('filter'); |     var filter = this.get('filter'); | ||||||
|     var content = this.get('model'); |     var content = this.get('model'); | ||||||
|     return !!(content.length && content.findBy('id', filter)); |     return !!(content.length && content.findBy('id', filter)); | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   firstPartialMatch: Ember.computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() { |   firstPartialMatch: computed('filter', 'model', 'model.[]', 'filterMatchesKey', function() { | ||||||
|     var filter = this.get('filter'); |     var filter = this.get('filter'); | ||||||
|     var content = this.get('model'); |     var content = this.get('model'); | ||||||
|     var filterMatchesKey = this.get('filterMatchesKey'); |     var filterMatchesKey = this.get('filterMatchesKey'); | ||||||
| @@ -42,7 +44,7 @@ export default Ember.Controller.extend({ | |||||||
|         }); |         }); | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|   filterIsFolder: Ember.computed('filter', function() { |   filterIsFolder: computed('filter', function() { | ||||||
|     return !!utils.keyIsFolder(this.get('filter')); |     return !!utils.keyIsFolder(this.get('filter')); | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
| @@ -56,7 +58,7 @@ export default Ember.Controller.extend({ | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     revokePrefix(prefix, isForce) { |     revokePrefix(prefix, isForce) { | ||||||
|       const adapter = this.model.store.adapterFor('lease'); |       const adapter = this.get('store').adapterFor('lease'); | ||||||
|       const method = isForce ? 'forceRevokePrefix' : 'revokePrefix'; |       const method = isForce ? 'forceRevokePrefix' : 'revokePrefix'; | ||||||
|       const fn = adapter[method]; |       const fn = adapter[method]; | ||||||
|       fn |       fn | ||||||
|   | |||||||
| @@ -66,6 +66,7 @@ export default Ember.Controller.extend(BackendCrumbMixin, { | |||||||
|     delete(item) { |     delete(item) { | ||||||
|       const name = item.id; |       const name = item.id; | ||||||
|       item.destroyRecord().then(() => { |       item.destroyRecord().then(() => { | ||||||
|  |         this.send('reload'); | ||||||
|         this.get('flashMessages').success(`${name} was successfully deleted.`); |         this.get('flashMessages').success(`${name} was successfully deleted.`); | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								ui/app/macros/identity-capabilities.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								ui/app/macros/identity-capabilities.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | ||||||
|  |  | ||||||
|  | export default function() { | ||||||
|  |   return lazyCapabilities(apiPath`identity/${'identityType'}/id/${'id'}`, 'id', 'identityType'); | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								ui/app/macros/lazy-capabilities.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								ui/app/macros/lazy-capabilities.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | import { queryRecord } from 'ember-computed-query'; | ||||||
|  |  | ||||||
|  | export function apiPath(strings, ...keys) { | ||||||
|  |   return function(data) { | ||||||
|  |     let dict = data || {}; | ||||||
|  |     let result = [strings[0]]; | ||||||
|  |     keys.forEach((key, i) => { | ||||||
|  |       result.push(dict[key], strings[i + 1]); | ||||||
|  |     }); | ||||||
|  |     return result.join(''); | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default function() { | ||||||
|  |   let [templateFn, ...keys] = arguments; | ||||||
|  |   return queryRecord( | ||||||
|  |     'capabilities', | ||||||
|  |     context => { | ||||||
|  |       return { | ||||||
|  |         id: templateFn(context.getProperties(...keys)), | ||||||
|  |       }; | ||||||
|  |     }, | ||||||
|  |     ...keys | ||||||
|  |   ); | ||||||
|  | } | ||||||
| @@ -1,8 +1,12 @@ | |||||||
| import IdentityModel from './_base'; | import IdentityModel from './_base'; | ||||||
| import DS from 'ember-data'; | import DS from 'ember-data'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  | import identityCapabilities from 'vault/macros/identity-capabilities'; | ||||||
| const { attr, belongsTo } = DS; | const { attr, belongsTo } = DS; | ||||||
|  | const { computed } = Ember; | ||||||
|  |  | ||||||
| export default IdentityModel.extend({ | export default IdentityModel.extend({ | ||||||
|  |   parentType: 'entity', | ||||||
|   formFields: ['name', 'mountAccessor', 'metadata'], |   formFields: ['name', 'mountAccessor', 'metadata'], | ||||||
|   entity: belongsTo('identity/entity', { readOnly: true, async: false }), |   entity: belongsTo('identity/entity', { readOnly: true, async: false }), | ||||||
|  |  | ||||||
| @@ -12,7 +16,7 @@ export default IdentityModel.extend({ | |||||||
|     label: 'Auth Backend', |     label: 'Auth Backend', | ||||||
|     editType: 'mountAccessor', |     editType: 'mountAccessor', | ||||||
|   }), |   }), | ||||||
|   metadata: attr('object', { |   metadata: attr({ | ||||||
|     editType: 'kv', |     editType: 'kv', | ||||||
|   }), |   }), | ||||||
|   mountPath: attr('string', { |   mountPath: attr('string', { | ||||||
| @@ -28,4 +32,8 @@ export default IdentityModel.extend({ | |||||||
|     readOnly: true, |     readOnly: true, | ||||||
|   }), |   }), | ||||||
|   mergedFromCanonicalIds: attr(), |   mergedFromCanonicalIds: attr(), | ||||||
|  |  | ||||||
|  |   updatePath: identityCapabilities(), | ||||||
|  |   canDelete: computed.alias('updatePath.canDelete'), | ||||||
|  |   canEdit: computed.alias('updatePath.canUpdate'), | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,12 +1,23 @@ | |||||||
|  | import Ember from 'ember'; | ||||||
| import IdentityModel from './_base'; | import IdentityModel from './_base'; | ||||||
| import DS from 'ember-data'; | import DS from 'ember-data'; | ||||||
|  | import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | ||||||
|  | import identityCapabilities from 'vault/macros/identity-capabilities'; | ||||||
|  |  | ||||||
|  | const { computed } = Ember; | ||||||
|  |  | ||||||
| const { attr, hasMany } = DS; | const { attr, hasMany } = DS; | ||||||
|  |  | ||||||
| export default IdentityModel.extend({ | export default IdentityModel.extend({ | ||||||
|   formFields: ['name', 'policies', 'metadata'], |   formFields: ['name', 'disabled', 'policies', 'metadata'], | ||||||
|   name: attr('string'), |   name: attr('string'), | ||||||
|  |   disabled: attr('boolean', { | ||||||
|  |     defaultValue: false, | ||||||
|  |     label: 'Disable entity', | ||||||
|  |     helpText: 'All associated tokens cannot be used, but are not revoked.', | ||||||
|  |   }), | ||||||
|   mergedEntityIds: attr(), |   mergedEntityIds: attr(), | ||||||
|   metadata: attr('object', { |   metadata: attr({ | ||||||
|     editType: 'kv', |     editType: 'kv', | ||||||
|   }), |   }), | ||||||
|   policies: attr({ |   policies: attr({ | ||||||
| @@ -28,4 +39,11 @@ export default IdentityModel.extend({ | |||||||
|   inheritedGroupIds: attr({ |   inheritedGroupIds: attr({ | ||||||
|     readOnly: true, |     readOnly: true, | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|  |   updatePath: identityCapabilities(), | ||||||
|  |   canDelete: computed.alias('updatePath.canDelete'), | ||||||
|  |   canEdit: computed.alias('updatePath.canUpdate'), | ||||||
|  |  | ||||||
|  |   aliasPath: lazyCapabilities(apiPath`identity/entity-alias`), | ||||||
|  |   canAddAlias: computed.alias('aliasPath.canCreate'), | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,8 +1,13 @@ | |||||||
| import IdentityModel from './_base'; | import IdentityModel from './_base'; | ||||||
| import DS from 'ember-data'; | import DS from 'ember-data'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  | import identityCapabilities from 'vault/macros/identity-capabilities'; | ||||||
|  |  | ||||||
| const { attr, belongsTo } = DS; | const { attr, belongsTo } = DS; | ||||||
|  | const { computed } = Ember; | ||||||
|  |  | ||||||
| export default IdentityModel.extend({ | export default IdentityModel.extend({ | ||||||
|  |   parentType: 'group', | ||||||
|   formFields: ['name', 'mountAccessor'], |   formFields: ['name', 'mountAccessor'], | ||||||
|   group: belongsTo('identity/group', { readOnly: true, async: false }), |   group: belongsTo('identity/group', { readOnly: true, async: false }), | ||||||
|  |  | ||||||
| @@ -26,4 +31,9 @@ export default IdentityModel.extend({ | |||||||
|   lastUpdateTime: attr('string', { |   lastUpdateTime: attr('string', { | ||||||
|     readOnly: true, |     readOnly: true, | ||||||
|   }), |   }), | ||||||
|  |  | ||||||
|  |   updatePath: identityCapabilities(), | ||||||
|  |   canDelete: computed.alias('updatePath.canDelete'), | ||||||
|  |   canEdit: computed.alias('updatePath.canUpdate'), | ||||||
|  |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
| import IdentityModel from './_base'; | import IdentityModel from './_base'; | ||||||
| import DS from 'ember-data'; | import DS from 'ember-data'; | ||||||
|  | import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; | ||||||
|  | import identityCapabilities from 'vault/macros/identity-capabilities'; | ||||||
|  |  | ||||||
| const { computed } = Ember; | const { computed } = Ember; | ||||||
| const { attr, belongsTo } = DS; | const { attr, belongsTo } = DS; | ||||||
| @@ -52,4 +54,18 @@ export default IdentityModel.extend({ | |||||||
|   ), |   ), | ||||||
|  |  | ||||||
|   alias: belongsTo('identity/group-alias', { async: false, readOnly: true }), |   alias: belongsTo('identity/group-alias', { async: false, readOnly: true }), | ||||||
|  |   updatePath: identityCapabilities(), | ||||||
|  |   canDelete: computed.alias('updatePath.canDelete'), | ||||||
|  |   canEdit: computed.alias('updatePath.canUpdate'), | ||||||
|  |  | ||||||
|  |   aliasPath: lazyCapabilities(apiPath`identity/group-alias`), | ||||||
|  |   canAddAlias: computed('aliasPath.canCreate', 'type', 'alias', function() { | ||||||
|  |     let type = this.get('type'); | ||||||
|  |     let alias = this.get('alias'); | ||||||
|  |     // internal groups can't have aliases, and external groups can only have one | ||||||
|  |     if (type === 'internal' || alias) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return this.get('aliasPath.canCreate'); | ||||||
|  |   }), | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
|  | import UnloadModelRoute from 'vault/mixins/unload-model-route'; | ||||||
|  | import UnsavedModelRoute from 'vault/mixins/unsaved-model-route'; | ||||||
|  |  | ||||||
| export default Ember.Route.extend({ | export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, { | ||||||
|   model(params) { |   model(params) { | ||||||
|     let itemType = this.modelFor('vault.cluster.access.identity'); |     let itemType = this.modelFor('vault.cluster.access.identity'); | ||||||
|     let modelType = `identity/${itemType}-alias`; |     let modelType = `identity/${itemType}-alias`; | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
|  | import UnloadModelRoute from 'vault/mixins/unload-model-route'; | ||||||
|  | import UnsavedModelRoute from 'vault/mixins/unsaved-model-route'; | ||||||
|  |  | ||||||
| export default Ember.Route.extend({ | export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, { | ||||||
|   model(params) { |   model(params) { | ||||||
|     let itemType = this.modelFor('vault.cluster.access.identity'); |     let itemType = this.modelFor('vault.cluster.access.identity'); | ||||||
|     let modelType = `identity/${itemType}-alias`; |     let modelType = `identity/${itemType}-alias`; | ||||||
|   | |||||||
| @@ -27,10 +27,14 @@ export default Ember.Route.extend(ListRoute, { | |||||||
|   actions: { |   actions: { | ||||||
|     willTransition(transition) { |     willTransition(transition) { | ||||||
|       window.scrollTo(0, 0); |       window.scrollTo(0, 0); | ||||||
|       if (transition.targetName !== this.routeName) { |       if (!transition || transition.targetName !== this.routeName) { | ||||||
|         this.store.clearAllDatasets(); |         this.store.clearAllDatasets(); | ||||||
|       } |       } | ||||||
|       return true; |       return true; | ||||||
|     }, |     }, | ||||||
|  |     reload() { | ||||||
|  |       this.store.clearAllDatasets(); | ||||||
|  |       this.refresh(); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
|  | import UnloadModelRoute from 'vault/mixins/unload-model-route'; | ||||||
|  | import UnsavedModelRoute from 'vault/mixins/unsaved-model-route'; | ||||||
|  |  | ||||||
| export default Ember.Route.extend({ | export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, { | ||||||
|   model() { |   model() { | ||||||
|     let itemType = this.modelFor('vault.cluster.access.identity'); |     let itemType = this.modelFor('vault.cluster.access.identity'); | ||||||
|     let modelType = `identity/${itemType}`; |     let modelType = `identity/${itemType}`; | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| import Ember from 'ember'; | import Ember from 'ember'; | ||||||
|  | import UnloadModelRoute from 'vault/mixins/unload-model-route'; | ||||||
|  | import UnsavedModelRoute from 'vault/mixins/unsaved-model-route'; | ||||||
|  |  | ||||||
| export default Ember.Route.extend({ | export default Ember.Route.extend(UnloadModelRoute, UnsavedModelRoute, { | ||||||
|   model(params) { |   model(params) { | ||||||
|     let itemType = this.modelFor('vault.cluster.access.identity'); |     let itemType = this.modelFor('vault.cluster.access.identity'); | ||||||
|     let modelType = `identity/${itemType}`; |     let modelType = `identity/${itemType}`; | ||||||
|   | |||||||
| @@ -34,5 +34,9 @@ export default Ember.Route.extend(ListRoute, { | |||||||
|       } |       } | ||||||
|       return true; |       return true; | ||||||
|     }, |     }, | ||||||
|  |     reload() { | ||||||
|  |       this.store.clearAllDatasets(); | ||||||
|  |       this.refresh(); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -13,13 +13,36 @@ export default Ember.Route.extend({ | |||||||
|       Ember.set(error, 'httpStatus', 404); |       Ember.set(error, 'httpStatus', 404); | ||||||
|       throw error; |       throw error; | ||||||
|     } |     } | ||||||
|     // TODO peekRecord here to see if we have the record already |  | ||||||
|  |     // if the record is in the store use that | ||||||
|  |     let model = this.store.peekRecord(modelType, params.item_id); | ||||||
|  |  | ||||||
|  |     // if we don't have creationTime, we only have a partial model so reload | ||||||
|  |     if (model && !model.get('creationTime')) { | ||||||
|  |       model = model.reload(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // if there's no model, we need to fetch it | ||||||
|  |     if (!model) { | ||||||
|  |       model = this.store.findRecord(modelType, params.item_id); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return Ember.RSVP.hash({ |     return Ember.RSVP.hash({ | ||||||
|       model: this.store.findRecord(modelType, params.item_id), |       model, | ||||||
|       section, |       section, | ||||||
|     }); |     }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   activate() { | ||||||
|  |     // if we're just entering the route, and it's not a hard reload | ||||||
|  |     // reload to make sure we have the newest info | ||||||
|  |     if (this.currentModel) { | ||||||
|  |       Ember.run.next(() => { | ||||||
|  |         this.controller.get('model').reload(); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |  | ||||||
|   afterModel(resolvedModel) { |   afterModel(resolvedModel) { | ||||||
|     let { section, model } = resolvedModel; |     let { section, model } = resolvedModel; | ||||||
|     if (model.get('identityType') === 'group' && model.get('type') === 'internal' && section === 'aliases') { |     if (model.get('identityType') === 'group' && model.get('type') === 'internal' && section === 'aliases') { | ||||||
|   | |||||||
| @@ -159,5 +159,9 @@ export default Ember.Route.extend({ | |||||||
|       } |       } | ||||||
|       return true; |       return true; | ||||||
|     }, |     }, | ||||||
|  |     reload() { | ||||||
|  |       this.refresh(); | ||||||
|  |       this.store.clearAllDatasets(); | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -131,13 +131,25 @@ export default DS.Store.extend({ | |||||||
|   // pushes records into the store and returns the result |   // pushes records into the store and returns the result | ||||||
|   fetchPage(modelName, query) { |   fetchPage(modelName, query) { | ||||||
|     const response = this.constructResponse(modelName, query); |     const response = this.constructResponse(modelName, query); | ||||||
|     this.unloadAll(modelName); |     this.peekAll(modelName).forEach(record => { | ||||||
|  |       record.unloadRecord(); | ||||||
|  |     }); | ||||||
|  |     return new Ember.RSVP.Promise(resolve => { | ||||||
|  |       Ember.run.schedule('destroy', () => { | ||||||
|         this.push( |         this.push( | ||||||
|       this.serializerFor(modelName).normalizeResponse(this, this.modelFor(modelName), response, null, 'query') |           this.serializerFor(modelName).normalizeResponse( | ||||||
|  |             this, | ||||||
|  |             this.modelFor(modelName), | ||||||
|  |             response, | ||||||
|  |             null, | ||||||
|  |             'query' | ||||||
|  |           ) | ||||||
|         ); |         ); | ||||||
|     const model = this.peekAll(modelName); |         let model = this.peekAll(modelName).toArray(); | ||||||
|         model.set('meta', response.meta); |         model.set('meta', response.meta); | ||||||
|     return model; |         resolve(model); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   // get cached data |   // get cached data | ||||||
|   | |||||||
| @@ -49,6 +49,7 @@ | |||||||
|     height: auto; |     height: auto; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     text-align: left; |     text-align: left; | ||||||
|  |     text-decoration: none; | ||||||
|  |  | ||||||
|     &:hover { |     &:hover { | ||||||
|       background-color: $menu-item-hover-background-color; |       background-color: $menu-item-hover-background-color; | ||||||
|   | |||||||
| @@ -133,6 +133,17 @@ $button-box-shadow-standard: 0 3px 1px 0 rgba($black, 0.12); | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   &.is-orange { | ||||||
|  |     background-color: $orange; | ||||||
|  |     border-color: $orange; | ||||||
|  |     color: $white; | ||||||
|  |  | ||||||
|  |     &:hover, | ||||||
|  |     &.is-hovered { | ||||||
|  |       background-color: darken($orange, 5%); | ||||||
|  |       border-color: darken($orange, 5%); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   &.is-compact { |   &.is-compact { | ||||||
|     height: 2rem; |     height: 2rem; | ||||||
|     padding: $size-11 $size-8; |     padding: $size-11 $size-8; | ||||||
|   | |||||||
| @@ -10,7 +10,9 @@ | |||||||
|       {{form-field data-test-field attr=attr model=model}} |       {{form-field data-test-field attr=attr model=model}} | ||||||
|     {{/each}} |     {{/each}} | ||||||
|   </div> |   </div> | ||||||
|   <div class="field is-grouped box is-fullwidth is-bottomless"> |  | ||||||
|  |   <div class="field is-grouped is-grouped-split is-fullwidth box is-bottomless"> | ||||||
|  |     <div class="field is-grouped"> | ||||||
|       <div class="control"> |       <div class="control"> | ||||||
|         <button type="submit" data-test-identity-submit=true class="button is-primary {{if save.isRunning 'loading'}}" disabled={{save.isRunning}}> |         <button type="submit" data-test-identity-submit=true class="button is-primary {{if save.isRunning 'loading'}}" disabled={{save.isRunning}}> | ||||||
|           {{#if (eq mode "create")}} |           {{#if (eq mode "create")}} | ||||||
| @@ -20,14 +22,27 @@ | |||||||
|           {{/if}} |           {{/if}} | ||||||
|         </button> |         </button> | ||||||
|         {{#if (or (eq mode "merge") (eq mode "create" ))}} |         {{#if (or (eq mode "merge") (eq mode "create" ))}} | ||||||
|         <a href={{href-to cancelLink}} class="button"> |           <a href={{href-to cancelLink}} class="button" data-test-cancel-link> | ||||||
|             Cancel |             Cancel | ||||||
|           </a> |           </a> | ||||||
|         {{else}} |         {{else}} | ||||||
|         <a href={{href-to cancelLink model.id "details"}} class="button"> |           <a href={{href-to cancelLink model.id "details"}} class="button" data-test-cancel-link> | ||||||
|             Cancel |             Cancel | ||||||
|           </a> |           </a> | ||||||
|         {{/if}} |         {{/if}} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  |       {{#if (and (eq mode "edit") model.canDelete)}} | ||||||
|  |         {{#confirm-action | ||||||
|  |           buttonClasses="button is-ghost" | ||||||
|  |           onConfirmAction=(action "deleteItem" model) | ||||||
|  |           confirmMessage=(concat "Are you sure you want to delete " model.id "?") | ||||||
|  |           data-test-entity-item-delete=true | ||||||
|  |         }} | ||||||
|  |             Delete | ||||||
|  |         {{/confirm-action}} | ||||||
|  |       {{/if}} | ||||||
|  |      | ||||||
|  |   </div> | ||||||
| </form> | </form> | ||||||
|   | |||||||
| @@ -7,12 +7,12 @@ | |||||||
|     </div> |     </div> | ||||||
|     <div class="level-right"> |     <div class="level-right"> | ||||||
|       {{#if (eq identityType "entity")}} |       {{#if (eq identityType "entity")}} | ||||||
|         <a href="{{href-to 'vault.cluster.access.identity.merge'}}" class="button has-icon-right is-ghost is-compact" data-test-entity-merge-link=true> |         <a href="{{href-to 'vault.cluster.access.identity.merge' (pluralize identityType)}}" class="button has-icon-right is-ghost is-compact" data-test-entity-merge-link=true> | ||||||
|           Merge {{pluralize identityType}} |           Merge {{pluralize identityType}} | ||||||
|           {{i-con glyph="chevron-right" size=11}} |           {{i-con glyph="chevron-right" size=11}} | ||||||
|         </a> |         </a> | ||||||
|       {{/if}} |       {{/if}} | ||||||
|       <a href="{{href-to 'vault.cluster.access.identity.create'}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true> |       <a href="{{href-to 'vault.cluster.access.identity.create' (pluralize identityType)}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true> | ||||||
|         Create {{identityType}} |         Create {{identityType}} | ||||||
|         {{i-con glyph="chevron-right" size=11}} |         {{i-con glyph="chevron-right" size=11}} | ||||||
|       </a> |       </a> | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {{info-table-row label="Name" value=model.name }} | {{info-table-row label="Name" value=model.name data-test-alias-name=true}} | ||||||
| {{info-table-row label="ID" value=model.id }} | {{info-table-row label="ID" value=model.id }} | ||||||
| {{#info-table-row label=(if (eq model.identityType "entity-alias") "Entity ID" "Group ID") value=model.canonicalId}} | {{#info-table-row label=(if (eq model.identityType "entity-alias") "Entity ID" "Group ID") value=model.canonicalId}} | ||||||
|   <a href={{href-to 'vault.cluster.access.identity.show' (if (eq model.identityType "entity-alias") "entities" "groups") model.canonicalId "details"}} |   <a href={{href-to 'vault.cluster.access.identity.show' (if (eq model.identityType "entity-alias") "entities" "groups") model.canonicalId "details"}} | ||||||
|   | |||||||
| @@ -8,6 +8,9 @@ | |||||||
|         {{value}} |         {{value}} | ||||||
|       </div> |       </div> | ||||||
|       <div class="column has-text-right"> |       <div class="column has-text-right"> | ||||||
|  |         {{#if model.canEdit}} | ||||||
|  |           {{identity/popup-metadata params=(array model key)}} | ||||||
|  |         {{/if}} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ | |||||||
|         <code class="has-text-grey is-size-8">{{item.mountAccessor}}</code> |         <code class="has-text-grey is-size-8">{{item.mountAccessor}}</code> | ||||||
|       </div> |       </div> | ||||||
|       <div class="column has-text-right"> |       <div class="column has-text-right"> | ||||||
|  |         {{identity/popup-alias params=(array item)}} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   {{/linked-block}} |   {{/linked-block}} | ||||||
|   | |||||||
| @@ -1,4 +1,20 @@ | |||||||
| {{info-table-row label="Name" value=model.name }} | {{#if model.disabled}} | ||||||
|  |   <div class="box is-shadowless is-marginless"> | ||||||
|  |     {{#message-in-page type="warning" yieldWithoutColumn=true messageClass="message-body is-marginless" data-test-disabled-warning=true}} | ||||||
|  |       <div class="column"> | ||||||
|  |         <strong>Attention</strong> This {{model.identityType}} is disabled. All associated tokens cannot be used, but are not revoked. | ||||||
|  |       </div> | ||||||
|  |       {{#if model.canEdit}} | ||||||
|  |         <div class="column is-flex-v-centered is-narrow"> | ||||||
|  |           <button type="button" class="button is-orange box" {{action "enable" model}} data-test-enable=true> | ||||||
|  |             Enable | ||||||
|  |           </button> | ||||||
|  |         </div> | ||||||
|  |       {{/if}} | ||||||
|  |     {{/message-in-page}} | ||||||
|  |   </div> | ||||||
|  | {{/if}} | ||||||
|  | {{info-table-row label="Name" value=model.name data-test-identity-item-name=true}} | ||||||
| {{info-table-row label="Type" value=model.type }} | {{info-table-row label="Type" value=model.type }} | ||||||
| {{info-table-row label="ID" value=model.id }} | {{info-table-row label="ID" value=model.id }} | ||||||
| {{#info-table-row label="Merged Ids" value=model.mergedEntityIds }} | {{#info-table-row label="Merged Ids" value=model.mergedEntityIds }} | ||||||
|   | |||||||
| @@ -1,21 +1,56 @@ | |||||||
| {{#if model.hasMembers}} | {{#if model.hasMembers}} | ||||||
|   {{#each model.memberGroupIds as |gid|}} |   {{#each model.memberGroupIds as |gid|}} | ||||||
|     <a href={{href-to "vault.cluster.access.identity.show" "groups" gid "details" }} |  | ||||||
|  |   {{#linked-block | ||||||
|  |     "vault.cluster.access.identity.show" | ||||||
|  |     "groups" | ||||||
|  |     gid | ||||||
|  |     details | ||||||
|     class="box is-sideless is-marginless" |     class="box is-sideless is-marginless" | ||||||
|  |   }} | ||||||
|  |     <div class="columns is-mobile"> | ||||||
|  |       <div class="column is-10"> | ||||||
|  |         <a href={{href-to "vault.cluster.access.identity.show" "groups" gid "details" }} | ||||||
|  |           class="is-block has-text-black has-text-weight-semibold" | ||||||
|           >{{i-con |           >{{i-con | ||||||
|             glyph='folder' |             glyph='folder' | ||||||
|             size=14 |             size=14 | ||||||
|             class="has-text-grey-light" |             class="has-text-grey-light" | ||||||
|             }}{{gid}}</a> |             }}{{gid}}</a> | ||||||
|  |       </div> | ||||||
|  |       <div class="column has-text-right"> | ||||||
|  |         {{#if model.canEdit}} | ||||||
|  |           {{identity/popup-members params=(array model "memberGroupIds" gid)}} | ||||||
|  |         {{/if}} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |     {{/linked-block}} | ||||||
|   {{/each}} |   {{/each}} | ||||||
|   {{#each model.memberEntityIds as |gid|}} |   {{#each model.memberEntityIds as |gid|}} | ||||||
|     <a href={{href-to "vault.cluster.access.identity.show" "entities" gid "details" }} |     {{#linked-block | ||||||
|  |       "vault.cluster.access.identity.show" | ||||||
|  |       "groups" | ||||||
|  |       gid | ||||||
|  |       details | ||||||
|       class="box is-sideless is-marginless" |       class="box is-sideless is-marginless" | ||||||
|  |     }} | ||||||
|  |       <div class="columns"> | ||||||
|  |         <div class="column is-10"> | ||||||
|  |           <a href={{href-to "vault.cluster.access.identity.show" "entities" gid "details" }} | ||||||
|  |             class="is-block has-text-black has-text-weight-semibold" | ||||||
|             >{{i-con |             >{{i-con | ||||||
|               glyph='role' |               glyph='role' | ||||||
|               size=14 |               size=14 | ||||||
|               class="has-text-grey-light" |               class="has-text-grey-light" | ||||||
|               }}{{gid}}</a> |               }}{{gid}}</a> | ||||||
|  |         </div> | ||||||
|  |         <div class="column has-text-right"> | ||||||
|  |           {{#if model.canEdit}} | ||||||
|  |             {{identity/popup-members params=(array model "memberEntityIds" gid)}} | ||||||
|  |           {{/if}} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     {{/linked-block}} | ||||||
|   {{/each}} |   {{/each}} | ||||||
| {{else}} | {{else}} | ||||||
|   <div class="box is-bottomless has-background-white-bis"> |   <div class="box is-bottomless has-background-white-bis"> | ||||||
|   | |||||||
| @@ -8,6 +8,9 @@ | |||||||
|         {{value}} |         {{value}} | ||||||
|       </div> |       </div> | ||||||
|       <div class="column has-text-right"> |       <div class="column has-text-right"> | ||||||
|  |         {{#if model.canEdit}} | ||||||
|  |           {{identity/popup-metadata params=(array model key)}} | ||||||
|  |         {{/if}} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| {{#each model.policies as |item|}} | {{#each model.policies as |policyName|}} | ||||||
|   {{#linked-block |   {{#linked-block | ||||||
|     "vault.cluster.policy.show" |     "vault.cluster.policy.show" | ||||||
|     "acl" |     "acl" | ||||||
| @@ -7,12 +7,15 @@ | |||||||
|   }} |   }} | ||||||
|     <div class="columns is-mobile"> |     <div class="columns is-mobile"> | ||||||
|       <div class="column is-10"> |       <div class="column is-10"> | ||||||
|         <a href={{href-to "vault.cluster.policy.show" "acl" item}} |         <a href={{href-to "vault.cluster.policy.show" "acl" policyName}} | ||||||
|           class="has-text-black has-text-weight-semibold" |           class="is-block has-text-black has-text-weight-semibold" | ||||||
|           ><span class="is-underline">{{item}}</span> |           ><span class="is-underline">{{policyName}}</span> | ||||||
|         </a> |         </a> | ||||||
|       </div> |       </div> | ||||||
|       <div class="column has-text-right"> |       <div class="column has-text-right"> | ||||||
|  |         {{#if model.canEdit}} | ||||||
|  |           {{identity/popup-policy params=(array model policyName)}} | ||||||
|  |         {{/if}} | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   {{/linked-block}} |   {{/linked-block}} | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								ui/app/templates/components/identity/popup-alias.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								ui/app/templates/components/identity/popup-alias.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | {{#popup-menu name="alias-menu"}} | ||||||
|  |   {{#with params.firstObject as |item|}} | ||||||
|  |   <nav class="menu"> | ||||||
|  |     <ul class="menu-list"> | ||||||
|  |       <li class="action"> | ||||||
|  |         <a href={{href-to "vault.cluster.access.identity.aliases.show" (pluralize item.parentType) item.id "details" }}> | ||||||
|  |           Details | ||||||
|  |         </a> | ||||||
|  |       </li> | ||||||
|  |       {{#if item.updatePath.isPending}} | ||||||
|  |         <li class="action"> | ||||||
|  |           <button disabled=true type="button" class="link button is-loading is-transparent"> | ||||||
|  |             loading | ||||||
|  |           </button> | ||||||
|  |         </li> | ||||||
|  |       {{else}} | ||||||
|  |         {{#if item.canEdit}} | ||||||
|  |           <li class="action"> | ||||||
|  |             <a href={{href-to "vault.cluster.access.identity.aliases.edit" (pluralize item.parentType) item.id}}> | ||||||
|  |               Edit | ||||||
|  |             </a> | ||||||
|  |           </li> | ||||||
|  |         {{/if}} | ||||||
|  |         {{#if item.canDelete}} | ||||||
|  |           <li class="action"> | ||||||
|  |             {{#confirm-action | ||||||
|  |               data-test-item-delete=true | ||||||
|  |               confirmButtonClasses="button is-primary" | ||||||
|  |               buttonClasses="link" | ||||||
|  |               onConfirmAction=(action "performTransaction" item) | ||||||
|  |               confirmMessage=(concat "Are you sure you want to delete " item.id "?") | ||||||
|  |               showConfirm=(get this (concat "shouldDelete-" item.id)) | ||||||
|  |               class=(if (get this (concat "shouldDelete-" item.id)) "message is-block is-warning is-outline") | ||||||
|  |               containerClasses="message-body is-block" | ||||||
|  |               messageClasses="is-block" | ||||||
|  |             }} | ||||||
|  |               Delete | ||||||
|  |             {{/confirm-action}} | ||||||
|  |           </li> | ||||||
|  |         {{/if}} | ||||||
|  |       {{/if}} | ||||||
|  |     </ul> | ||||||
|  |   </nav> | ||||||
|  | {{/with}} | ||||||
|  | {{/popup-menu}} | ||||||
							
								
								
									
										21
									
								
								ui/app/templates/components/identity/popup-members.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								ui/app/templates/components/identity/popup-members.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | {{#popup-menu name="member-edit-menu"}} | ||||||
|  |   <nav class="menu"> | ||||||
|  |     <ul class="menu-list"> | ||||||
|  |       <li class="action"> | ||||||
|  |         {{#confirm-action | ||||||
|  |           confirmButtonClasses="button is-primary" | ||||||
|  |           confirmButtonText="Remove" | ||||||
|  |           buttonClasses="link" | ||||||
|  |           onConfirmAction=(action "performTransaction" model groupArray memberId) | ||||||
|  |           confirmMessage=(concat "Are you sure you want to remove " memberId "?") | ||||||
|  |           showConfirm=(get this (concat "shouldDelete-" memberId)) | ||||||
|  |           class=(if (get this (concat "shouldDelete-" memberId)) "message is-block is-warning is-outline") | ||||||
|  |           containerClasses="message-body is-block" | ||||||
|  |           messageClasses="is-block" | ||||||
|  |         }} | ||||||
|  |           Remove | ||||||
|  |         {{/confirm-action}} | ||||||
|  |       </li> | ||||||
|  |     </ul> | ||||||
|  |   </nav> | ||||||
|  | {{/popup-menu}} | ||||||
							
								
								
									
										21
									
								
								ui/app/templates/components/identity/popup-metadata.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								ui/app/templates/components/identity/popup-metadata.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | {{#popup-menu name="metadata-edit-menu"}} | ||||||
|  |   <nav class="menu"> | ||||||
|  |     <ul class="menu-list"> | ||||||
|  |       <li class="action"> | ||||||
|  |         {{#confirm-action | ||||||
|  |           confirmButtonClasses="button is-primary" | ||||||
|  |           confirmButtonText="Remove" | ||||||
|  |           buttonClasses="link" | ||||||
|  |           onConfirmAction=(action "performTransaction" model key) | ||||||
|  |           confirmMessage=(concat "Are you sure you want to remove " key "?") | ||||||
|  |           showConfirm=(get this (concat "shouldDelete-" key)) | ||||||
|  |           class=(if (get this (concat "shouldDelete-" key)) "message is-block is-warning is-outline") | ||||||
|  |           containerClasses="message-body is-block" | ||||||
|  |           messageClasses="is-block" | ||||||
|  |         }} | ||||||
|  |           Remove | ||||||
|  |         {{/confirm-action}} | ||||||
|  |       </li> | ||||||
|  |     </ul> | ||||||
|  |   </nav> | ||||||
|  | {{/popup-menu}} | ||||||
							
								
								
									
										31
									
								
								ui/app/templates/components/identity/popup-policy.hbs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ui/app/templates/components/identity/popup-policy.hbs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | {{#popup-menu name="policy-menu"}} | ||||||
|  |   <nav class="menu"> | ||||||
|  |     <ul class="menu-list"> | ||||||
|  |       <li class="action"> | ||||||
|  |         <a href={{href-to "vault.cluster.policy.show" "acl" policyName }}> | ||||||
|  |           View Policy | ||||||
|  |         </a> | ||||||
|  |       </li> | ||||||
|  |       <li class="action"> | ||||||
|  |         <a href={{href-to "vault.cluster.policy.edit" "acl" policyName }}> | ||||||
|  |           Edit Policy | ||||||
|  |         </a> | ||||||
|  |       </li> | ||||||
|  |       <li class="action"> | ||||||
|  |         {{#confirm-action | ||||||
|  |           confirmButtonClasses="button is-primary" | ||||||
|  |           confirmButtonText="Remove" | ||||||
|  |           buttonClasses="link" | ||||||
|  |           onConfirmAction=(action "performTransaction" model policyName) | ||||||
|  |           confirmMessage=(concat "Are you sure you want to remove " policyName "?") | ||||||
|  |           showConfirm=(get this (concat "shouldDelete-" policyName)) | ||||||
|  |           class=(if (get this (concat "shouldDelete-" policyName)) "message is-block is-warning is-outline") | ||||||
|  |           containerClasses="message-body is-block" | ||||||
|  |           messageClasses="is-block" | ||||||
|  |         }} | ||||||
|  |           Remove from {{model.identityType}} | ||||||
|  |         {{/confirm-action}} | ||||||
|  |       </li> | ||||||
|  |     </ul> | ||||||
|  |   </nav> | ||||||
|  | {{/popup-menu}} | ||||||
| @@ -8,11 +8,15 @@ | |||||||
|         excludeIconClass=true |         excludeIconClass=true | ||||||
|       }} |       }} | ||||||
|     </div> |     </div> | ||||||
|  |     {{#if yieldWithoutColumn}} | ||||||
|  |       {{yield}} | ||||||
|  |     {{else}} | ||||||
|       <div class="column"> |       <div class="column"> | ||||||
|         <p> |         <p> | ||||||
|         <strong>{{alertType.text}}</strong> |         <strong>{{alertType.text}}</strong> | ||||||
|         {{yield}} |         {{yield}} | ||||||
|         </p> |         </p> | ||||||
|       </div> |       </div> | ||||||
|  |     {{/if}} | ||||||
|   </div> |   </div> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -8,4 +8,4 @@ | |||||||
|   </div> |   </div> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
| {{identity/edit-form model=model onSave=(perform navToShow)}} | {{identity/edit-form model=model onSave=(perform navAfterSave)}} | ||||||
|   | |||||||
| @@ -8,4 +8,4 @@ | |||||||
|   </div> |   </div> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
| {{identity/edit-form mode="edit" model=model onSave=(perform navToShow)}} | {{identity/edit-form mode="edit" model=model onSave=(perform navAfterSave)}} | ||||||
|   | |||||||
| @@ -1,23 +1,33 @@ | |||||||
| {{identity/entity-nav identityType=identityType}} | {{identity/entity-nav identityType=identityType}} | ||||||
| {{#if model.meta.total}} | {{#if model.meta.total}} | ||||||
|   {{#each model as |item|}} |   {{#each model as |item|}} | ||||||
|  |     {{#linked-block | ||||||
|  |       "vault.cluster.access.identity.aliases.show" | ||||||
|  |       item.id | ||||||
|  |       "details" | ||||||
|  |       class="box is-sideless is-marginless" | ||||||
|  |       data-test-identity-row=true | ||||||
|  |       }} | ||||||
|  |       <div class="columns is-mobile"> | ||||||
|  |         <div class="column is-10"> | ||||||
|           <a href={{href-to |           <a href={{href-to | ||||||
|             "vault.cluster.access.identity.aliases.show" |             "vault.cluster.access.identity.aliases.show" | ||||||
|             item.id |             item.id | ||||||
|              "details" |              "details" | ||||||
|             }} |             }} | ||||||
|       class="is-flex box is-sideless is-marginless" |             class="is-block has-text-black has-text-weight-semibold" | ||||||
|       data-test-lease-link={{item.id}} |             data-test-identity-link={{item.id}} | ||||||
|     > |           >{{i-con | ||||||
|       {{i-con |  | ||||||
|               glyph="role" |               glyph="role" | ||||||
|               size=14 |               size=14 | ||||||
|               class="has-text-grey-light" |               class="has-text-grey-light" | ||||||
|       }} |             }}<span class="has-text-weight-semibold">{{item.id}}</span></a> | ||||||
|       <span class="has-text-weight-semibold"> |         </div> | ||||||
|         {{item.id}} |         <div class="column has-text-right"> | ||||||
|       </span> |           {{identity/popup-alias params=(array item) onSuccess=(action "onDelete")}} | ||||||
|     </a> |         </div> | ||||||
|  |       </div> | ||||||
|  |     {{/linked-block}} | ||||||
|   {{/each}} |   {{/each}} | ||||||
| {{else}} | {{else}} | ||||||
|   <div class="box is-bottomless has-background-white-bis"> |   <div class="box is-bottomless has-background-white-bis"> | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ | |||||||
|       </h1> |       </h1> | ||||||
|     </div> |     </div> | ||||||
|     <div class="level-right"> |     <div class="level-right"> | ||||||
|       <a href="{{href-to 'vault.cluster.access.identity.aliases.edit' model.id}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true> |       <a href="{{href-to 'vault.cluster.access.identity.aliases.edit' model.id}}" class="button has-icon-right is-ghost is-compact" data-test-alias-edit-link=true> | ||||||
|         Edit {{lowercase (humanize model.identityType)}} |         Edit {{lowercase (humanize model.identityType)}} | ||||||
|         {{i-con glyph="chevron-right" size=11}} |         {{i-con glyph="chevron-right" size=11}} | ||||||
|       </a> |       </a> | ||||||
|   | |||||||
| @@ -8,4 +8,4 @@ | |||||||
|   </div> |   </div> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
| {{identity/edit-form model=model onSave=(perform navToShow)}} | {{identity/edit-form model=model onSave=(perform navAfterSave)}} | ||||||
|   | |||||||
| @@ -8,4 +8,4 @@ | |||||||
|   </div> |   </div> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
| {{identity/edit-form mode="edit" model=model onSave=(perform navToShow)}} | {{identity/edit-form mode="edit" model=model onSave=(perform navAfterSave)}} | ||||||
|   | |||||||
| @@ -1,23 +1,103 @@ | |||||||
| {{identity/entity-nav identityType=identityType}} | {{identity/entity-nav identityType=identityType}} | ||||||
| {{#if model.meta.total}} | {{#if model.meta.total}} | ||||||
|   {{#each model as |item|}} |   {{#each model as |item|}} | ||||||
|  |     {{#linked-block | ||||||
|  |       "vault.cluster.access.identity.show" | ||||||
|  |       item.id | ||||||
|  |       "details" | ||||||
|  |       class="box is-sideless is-marginless" | ||||||
|  |       data-test-identity-row=true | ||||||
|  |       }} | ||||||
|  |       <div class="columns is-mobile"> | ||||||
|  |         <div class="column is-10"> | ||||||
|           <a href={{href-to |           <a href={{href-to | ||||||
|             "vault.cluster.access.identity.show" |             "vault.cluster.access.identity.show" | ||||||
|             item.id |             item.id | ||||||
|             "details" |             "details" | ||||||
|             }} |             }} | ||||||
|       class="is-flex box is-sideless is-marginless" |             class="is-block has-text-black has-text-weight-semibold" | ||||||
|       data-test-lease-link={{item.id}} |             data-test-identity-link=true | ||||||
|     > |           >{{i-con | ||||||
|       {{i-con |  | ||||||
|               glyph="role" |               glyph="role" | ||||||
|               size=14 |               size=14 | ||||||
|               class="has-text-grey-light" |               class="has-text-grey-light" | ||||||
|       }} |             }}<span class="has-text-weight-semibold">{{item.id}}</span></a> | ||||||
|       <span class="has-text-weight-semibold"> |         </div> | ||||||
|         {{item.id}} |         <div class="column has-text-right"> | ||||||
|       </span> |           {{#popup-menu name="identity-item" onOpen=(action "reloadRecord" item)}} | ||||||
|  |             <nav class="menu"> | ||||||
|  |               <ul class="menu-list"> | ||||||
|  |                 <li class="action"> | ||||||
|  |                   <a href={{href-to "vault.cluster.access.identity.show" item.id "details" }}> | ||||||
|  |                     Details | ||||||
|                   </a> |                   </a> | ||||||
|  |                 </li> | ||||||
|  |                 {{#if (or item.isReloading item.updatePath.isPending item.aliasPath.isPending)}} | ||||||
|  |                   <li class="action"> | ||||||
|  |                     <button disabled=true type="button" class="link button is-loading is-transparent"> | ||||||
|  |                       loading | ||||||
|  |                     </button> | ||||||
|  |                   </li> | ||||||
|  |                 {{else}} | ||||||
|  |                   {{#if item.canEdit}} | ||||||
|  |                     <li class="action"> | ||||||
|  |                       <a href={{href-to 'vault.cluster.access.identity.edit' item.id}}> | ||||||
|  |                         Edit | ||||||
|  |                       </a> | ||||||
|  |                     </li> | ||||||
|  |                     <li class="action"> | ||||||
|  |                       {{#if item.disabled}} | ||||||
|  |                         <button type="button" {{action "toggleDisabled" item}} class="link"> | ||||||
|  |                           Enable | ||||||
|  |                         </button> | ||||||
|  |                       {{else}} | ||||||
|  |                         {{#confirm-action | ||||||
|  |                           confirmButtonClasses="button is-primary" | ||||||
|  |                           confirmButtonText="Disable" | ||||||
|  |                           buttonClasses="link" | ||||||
|  |                           onConfirmAction=(action "toggleDisabled" item) | ||||||
|  |                           confirmMessage=(concat "Are you sure you want to disable " item.id "?") | ||||||
|  |                           showConfirm=(get this (concat "shouldDisable-" item.id)) | ||||||
|  |                           class=(if (get this (concat "shouldDisable-" item.id)) "message is-block is-warning is-outline") | ||||||
|  |                           containerClasses="message-body is-block" | ||||||
|  |                           messageClasses="is-block" | ||||||
|  |                         }} | ||||||
|  |                           Disable | ||||||
|  |                         {{/confirm-action}} | ||||||
|  |                       {{/if}} | ||||||
|  |                     </li> | ||||||
|  |                   {{/if}} | ||||||
|  |                   {{#if item.canAddAlias}} | ||||||
|  |                     <li class="action"> | ||||||
|  |                       <a href={{href-to 'vault.cluster.access.identity.aliases.add' (pluralize identityType) item.id}}> | ||||||
|  |                         Add alias | ||||||
|  |                       </a> | ||||||
|  |                     </li> | ||||||
|  |                   {{/if}} | ||||||
|  |                   {{#if item.canDelete}} | ||||||
|  |                     <li class="action"> | ||||||
|  |                       {{#confirm-action | ||||||
|  |                         data-test-item-delete=true | ||||||
|  |                         confirmButtonClasses="button is-primary" | ||||||
|  |                         buttonClasses="link" | ||||||
|  |                         onConfirmAction=(action "delete" item) | ||||||
|  |                         confirmMessage=(concat "Are you sure you want to delete " item.id "?") | ||||||
|  |                         showConfirm=(get this (concat "shouldDelete-" item.id)) | ||||||
|  |                         class=(if (get this (concat "shouldDelete-" item.id)) "message is-block is-warning is-outline") | ||||||
|  |                         containerClasses="message-body is-block" | ||||||
|  |                         messageClasses="is-block" | ||||||
|  |                       }} | ||||||
|  |                         Delete | ||||||
|  |                       {{/confirm-action}} | ||||||
|  |                     </li> | ||||||
|  |                   {{/if}} | ||||||
|  |                 {{/if}} | ||||||
|  |               </ul> | ||||||
|  |             </nav> | ||||||
|  |           {{/popup-menu}} | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     {{/linked-block}} | ||||||
|   {{/each}} |   {{/each}} | ||||||
| {{else}} | {{else}} | ||||||
|   <div class="box is-bottomless has-background-white-bis"> |   <div class="box is-bottomless has-background-white-bis"> | ||||||
|   | |||||||
| @@ -8,4 +8,4 @@ | |||||||
|   </div> |   </div> | ||||||
| </header> | </header> | ||||||
|  |  | ||||||
| {{identity/edit-form mode="merge" model=model onSave=(perform navToShow)}} | {{identity/edit-form mode="merge" model=model onSave=(perform navAfterSave)}} | ||||||
|   | |||||||
| @@ -17,12 +17,12 @@ | |||||||
|     </div> |     </div> | ||||||
|     <div class="level-right"> |     <div class="level-right"> | ||||||
|       {{#unless (or (and (eq model.identityType "group") (eq model.type "internal")) model.alias)}} |       {{#unless (or (and (eq model.identityType "group") (eq model.type "internal")) model.alias)}} | ||||||
|         <a href="{{href-to 'vault.cluster.access.identity.aliases.add' model.id}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true> |         <a href="{{href-to 'vault.cluster.access.identity.aliases.add' (pluralize model.identityType) model.id}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true> | ||||||
|           Add alias |           Add alias | ||||||
|           {{i-con glyph="chevron-right" size=11}} |           {{i-con glyph="chevron-right" size=11}} | ||||||
|         </a> |         </a> | ||||||
|       {{/unless}} |       {{/unless}} | ||||||
|       <a href="{{href-to 'vault.cluster.access.identity.edit' model.id}}" class="button has-icon-right is-ghost is-compact" data-test-entity-create-link=true> |       <a href="{{href-to 'vault.cluster.access.identity.edit' (pluralize model.identityType) model.id}}" class="button has-icon-right is-ghost is-compact" data-test-entity-edit-link=true> | ||||||
|         Edit {{model.identityType}} |         Edit {{model.identityType}} | ||||||
|         {{i-con glyph="chevron-right" size=11}} |         {{i-con glyph="chevron-right" size=11}} | ||||||
|       </a> |       </a> | ||||||
| @@ -34,7 +34,7 @@ | |||||||
|     <ul> |     <ul> | ||||||
|       {{#each (tabs-for-identity-show model.identityType model.type) as |tab|}} |       {{#each (tabs-for-identity-show model.identityType model.type) as |tab|}} | ||||||
|         {{#link-to "vault.cluster.access.identity.show" model.id tab tagName="li"}} |         {{#link-to "vault.cluster.access.identity.show" model.id tab tagName="li"}} | ||||||
|           <a href={{href-to "vault.cluster.access.identity.show" model.id tab }}> |           <a href={{href-to "vault.cluster.access.identity.show" (pluralize model.identityType) model.id tab }}> | ||||||
|             {{capitalize tab}} |             {{capitalize tab}} | ||||||
|           </a> |           </a> | ||||||
|         {{/link-to}} |         {{/link-to}} | ||||||
|   | |||||||
| @@ -92,7 +92,7 @@ | |||||||
|       </div> |       </div> | ||||||
|       {{#if (and (not-eq model.id "default") capabilities.canDelete)}} |       {{#if (and (not-eq model.id "default") capabilities.canDelete)}} | ||||||
|         {{#confirm-action |         {{#confirm-action | ||||||
|           buttonClasses="button is-link is-outlined is-inverted" |           buttonClasses="button is-ghost" | ||||||
|           onConfirmAction=(action "deletePolicy" model) |           onConfirmAction=(action "deletePolicy" model) | ||||||
|           confirmMessage=(concat "Are you sure you want to delete " model.id "?") |           confirmMessage=(concat "Are you sure you want to delete " model.id "?") | ||||||
|           data-test-policy-delete=true |           data-test-policy-delete=true | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								ui/tests/acceptance/access/identity/_shared-alias-tests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								ui/tests/acceptance/access/identity/_shared-alias-tests.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | import page from 'vault/tests/pages/access/identity/aliases/add'; | ||||||
|  | import aliasIndexPage from 'vault/tests/pages/access/identity/aliases/index'; | ||||||
|  | import aliasShowPage from 'vault/tests/pages/access/identity/aliases/show'; | ||||||
|  | import createItemPage from 'vault/tests/pages/access/identity/create'; | ||||||
|  | import showItemPage from 'vault/tests/pages/access/identity/show'; | ||||||
|  |  | ||||||
|  | export const testAliasCRUD = (name, itemType, assert) => { | ||||||
|  |   let itemID; | ||||||
|  |   let aliasID; | ||||||
|  |   if (itemType === 'groups') { | ||||||
|  |     createItemPage.createItem(itemType, 'external'); | ||||||
|  |   } else { | ||||||
|  |     createItemPage.createItem(itemType); | ||||||
|  |   } | ||||||
|  |   andThen(() => { | ||||||
|  |     let idRow = showItemPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0]; | ||||||
|  |     itemID = idRow.rowValue; | ||||||
|  |     page.visit({ item_type: itemType, id: itemID }); | ||||||
|  |   }); | ||||||
|  |   page.editForm.name(name).submit(); | ||||||
|  |   andThen(() => { | ||||||
|  |     let idRow = aliasShowPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0]; | ||||||
|  |     aliasID = idRow.rowValue; | ||||||
|  |     assert.equal( | ||||||
|  |       currentRouteName(), | ||||||
|  |       'vault.cluster.access.identity.aliases.show', | ||||||
|  |       'navigates to the correct route' | ||||||
|  |     ); | ||||||
|  |     assert.ok( | ||||||
|  |       aliasShowPage.flashMessage.latestMessage.startsWith('Successfully saved', `${itemType}: shows a flash message`) | ||||||
|  |     ); | ||||||
|  |     assert.ok(aliasShowPage.nameContains(name), `${itemType}: renders the name on the show page`); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   aliasIndexPage.visit({ item_type: itemType }); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(aliasIndexPage.items.filterBy('id', aliasID).length, 1, `${itemType}: lists the entity in the entity list`); | ||||||
|  |     aliasIndexPage.items.filterBy('id', aliasID)[0].menu(); | ||||||
|  |   }); | ||||||
|  |   aliasIndexPage.delete().confirmDelete(); | ||||||
|  |  | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(aliasIndexPage.items.filterBy('id', aliasID).length, 0, `${itemType}: the row is deleted`); | ||||||
|  |     aliasIndexPage.flashMessage.latestMessage.startsWith('Successfully deleted', `${itemType}: shows flash message`); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const testAliasDeleteFromForm = (name, itemType, assert) => { | ||||||
|  |   let itemID; | ||||||
|  |   let aliasID; | ||||||
|  |   if (itemType === 'groups') { | ||||||
|  |     createItemPage.createItem(itemType, 'external'); | ||||||
|  |   } else { | ||||||
|  |     createItemPage.createItem(itemType); | ||||||
|  |   } | ||||||
|  |   andThen(() => { | ||||||
|  |     let idRow = showItemPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0]; | ||||||
|  |     itemID = idRow.rowValue; | ||||||
|  |     page.visit({ item_type: itemType, id: itemID }); | ||||||
|  |   }); | ||||||
|  |   page.editForm.name(name).submit(); | ||||||
|  |   andThen(() => { | ||||||
|  |     let idRow = aliasShowPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0]; | ||||||
|  |     aliasID = idRow.rowValue; | ||||||
|  |   }); | ||||||
|  |   aliasShowPage.edit(); | ||||||
|  |  | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.aliases.edit', `${itemType}: navigates to edit on create`); | ||||||
|  |   }); | ||||||
|  |   page.editForm.delete().confirmDelete(); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.aliases.index', `${itemType}: navigates to list page on delete`); | ||||||
|  |     assert.equal(aliasIndexPage.items.filterBy('id', aliasID).length, 0, `${itemType}: the row does not show in the list`); | ||||||
|  |     aliasIndexPage.flashMessage.latestMessage.startsWith('Successfully deleted', `${itemType}: shows flash message`); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
							
								
								
									
										51
									
								
								ui/tests/acceptance/access/identity/_shared-tests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								ui/tests/acceptance/access/identity/_shared-tests.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | import page from 'vault/tests/pages/access/identity/create'; | ||||||
|  | import showPage from 'vault/tests/pages/access/identity/show'; | ||||||
|  | import indexPage from 'vault/tests/pages/access/identity/index'; | ||||||
|  |  | ||||||
|  | export const testCRUD = (name, itemType, assert) => { | ||||||
|  |   let id; | ||||||
|  |   page.visit({ item_type: itemType }); | ||||||
|  |   page.editForm.name(name).submit(); | ||||||
|  |   andThen(() => { | ||||||
|  |     let idRow = showPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0]; | ||||||
|  |     id = idRow.rowValue; | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.show', `${itemType}: navigates to show on create`); | ||||||
|  |     assert.ok( | ||||||
|  |       showPage.flashMessage.latestMessage.startsWith('Successfully saved', `${itemType}: shows a flash message`) | ||||||
|  |     ); | ||||||
|  |     assert.ok(showPage.nameContains(name), `${itemType}: renders the name on the show page`); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   indexPage.visit({ item_type: itemType }); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(indexPage.items.filterBy('id', id).length, 1, `${itemType}: lists the entity in the entity list`); | ||||||
|  |     indexPage.items.filterBy('id', id)[0].menu(); | ||||||
|  |   }); | ||||||
|  |   indexPage.delete().confirmDelete(); | ||||||
|  |  | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(indexPage.items.filterBy('id', id).length, 0, `${itemType}: the row is deleted`); | ||||||
|  |     indexPage.flashMessage.latestMessage.startsWith('Successfully deleted', `${itemType}: shows flash message`); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | export const testDeleteFromForm = (name, itemType, assert) => { | ||||||
|  |   let id; | ||||||
|  |   page.visit({ item_type: itemType }); | ||||||
|  |   page.editForm.name(name).submit(); | ||||||
|  |   andThen(() => { | ||||||
|  |     id = showPage.rows.filterBy('hasLabel').filterBy('rowLabel', 'ID')[0].rowValue | ||||||
|  |   }); | ||||||
|  |   showPage.edit(); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.edit', `${itemType}: navigates to edit on create`); | ||||||
|  |   }); | ||||||
|  |   page.editForm.delete().confirmDelete(); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.index', `${itemType}: navigates to list page on delete`); | ||||||
|  |     assert.equal(indexPage.items.filterBy('id', id).length, 0, `${itemType}: the row does not show in the list`); | ||||||
|  |     indexPage.flashMessage.latestMessage.startsWith('Successfully deleted', `${itemType}: shows flash message`); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | import { test } from 'qunit'; | ||||||
|  | import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance'; | ||||||
|  | import { testAliasCRUD, testAliasDeleteFromForm } from '../../_shared-alias-tests'; | ||||||
|  |  | ||||||
|  | moduleForAcceptance('Acceptance | /access/identity/entities/aliases/add', { | ||||||
|  |   beforeEach() { | ||||||
|  |     return authLogin(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | test('it allows create, list, delete of an entity alias', function(assert) { | ||||||
|  |   let name = `alias-${Date.now()}`; | ||||||
|  |   testAliasCRUD(name, 'entities', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it allows delete from the edit form', function(assert) { | ||||||
|  |   let name = `alias-${Date.now()}`; | ||||||
|  |   testAliasDeleteFromForm(name, 'entities', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								ui/tests/acceptance/access/identity/entities/create-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								ui/tests/acceptance/access/identity/entities/create-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | import { test } from 'qunit'; | ||||||
|  | import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance'; | ||||||
|  | import page from 'vault/tests/pages/access/identity/create'; | ||||||
|  | import { testCRUD, testDeleteFromForm } from '../_shared-tests'; | ||||||
|  |  | ||||||
|  | moduleForAcceptance('Acceptance | /access/identity/entities/create', { | ||||||
|  |   beforeEach() { | ||||||
|  |     return authLogin(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it visits the correct page', function(assert) { | ||||||
|  |   page.visit({ item_type: 'entities' }); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal( | ||||||
|  |       currentRouteName(), | ||||||
|  |       'vault.cluster.access.identity.create', | ||||||
|  |       'navigates to the correct route' | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it allows create, list, delete of an entity', function(assert) { | ||||||
|  |   let name = `entity-${Date.now()}`; | ||||||
|  |   testCRUD(name, 'entities', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it can be deleted from the edit form', function(assert) { | ||||||
|  |   let name = `entity-${Date.now()}`; | ||||||
|  |   testDeleteFromForm(name, 'entities', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
							
								
								
									
										23
									
								
								ui/tests/acceptance/access/identity/entities/index-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								ui/tests/acceptance/access/identity/entities/index-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | import { test } from 'qunit'; | ||||||
|  | import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance'; | ||||||
|  | import page from 'vault/tests/pages/access/identity/index'; | ||||||
|  |  | ||||||
|  | moduleForAcceptance('Acceptance | /access/identity/entities', { | ||||||
|  |   beforeEach() { | ||||||
|  |     return authLogin(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it renders the entities page', function(assert) { | ||||||
|  |   page.visit({ item_type: 'entities' }); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.index', 'navigates to the correct route'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it renders the groups page', function(assert) { | ||||||
|  |   page.visit({ item_type: 'groups' }); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal(currentRouteName(), 'vault.cluster.access.identity.index', 'navigates to the correct route'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | import { test } from 'qunit'; | ||||||
|  | import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance'; | ||||||
|  | import { testAliasCRUD, testAliasDeleteFromForm } from '../../_shared-alias-tests'; | ||||||
|  |  | ||||||
|  | moduleForAcceptance('Acceptance | /access/identity/groups/aliases/add', { | ||||||
|  |   beforeEach() { | ||||||
|  |     return authLogin(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | test('it allows create, list, delete of an entity alias', function(assert) { | ||||||
|  |   let name = `alias-${Date.now()}`; | ||||||
|  |   testAliasCRUD(name, 'groups', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it allows delete from the edit form', function(assert) { | ||||||
|  |   let name = `alias-${Date.now()}`; | ||||||
|  |   testAliasDeleteFromForm(name, 'groups', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								ui/tests/acceptance/access/identity/groups/create-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ui/tests/acceptance/access/identity/groups/create-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | import { test } from 'qunit'; | ||||||
|  | import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance'; | ||||||
|  | import page from 'vault/tests/pages/access/identity/create'; | ||||||
|  | import { testCRUD, testDeleteFromForm } from '../_shared-tests'; | ||||||
|  |  | ||||||
|  | moduleForAcceptance('Acceptance | /access/identity/groups/create', { | ||||||
|  |   beforeEach() { | ||||||
|  |     return authLogin(); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it visits the correct page', function(assert) { | ||||||
|  |   page.visit({ item_type: 'groups' }); | ||||||
|  |   andThen(() => { | ||||||
|  |     assert.equal( | ||||||
|  |       currentRouteName(), | ||||||
|  |       'vault.cluster.access.identity.create', | ||||||
|  |       'navigates to the correct route' | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it allows create, list, delete of an group', function(assert) { | ||||||
|  |   let name = `group-${Date.now()}`; | ||||||
|  |   testCRUD(name, 'groups', assert); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it can be deleted from the group edit form', function(assert) { | ||||||
|  |   let name = `group-${Date.now()}`; | ||||||
|  |   testDeleteFromForm(name, 'groups', assert); | ||||||
|  | }); | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| import { test } from 'qunit'; |  | ||||||
| import moduleForAcceptance from 'vault/tests/helpers/module-for-acceptance'; |  | ||||||
| import page from 'vault/tests/pages/access/identity/index'; |  | ||||||
|  |  | ||||||
| moduleForAcceptance('Acceptance | /access/identity/entities', { |  | ||||||
|   beforeEach() { |  | ||||||
|     return authLogin(); |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| test('it renders the page', function(assert) { |  | ||||||
|   page.visit({ item_type: 'entities' }); |  | ||||||
|   andThen(() => { |  | ||||||
|     assert.ok(currentRouteName(), 'vault.cluster.access.identity.index', 'navigates to the correct route'); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
| @@ -87,7 +87,9 @@ test('replication', function(assert) { | |||||||
|       find('[data-test-mount-config-mode]').text().trim().toLowerCase().includes(mode), |       find('[data-test-mount-config-mode]').text().trim().toLowerCase().includes(mode), | ||||||
|       'show page renders the correct mode' |       'show page renders the correct mode' | ||||||
|     ); |     ); | ||||||
|     assert.dom('[data-test-mount-config-paths]').hasText(mountPath, 'show page renders the correct mount path'); |     assert | ||||||
|  |       .dom('[data-test-mount-config-paths]') | ||||||
|  |       .hasText(mountPath, 'show page renders the correct mount path'); | ||||||
|   }); |   }); | ||||||
|   // click edit |   // click edit | ||||||
|  |  | ||||||
| @@ -101,7 +103,9 @@ test('replication', function(assert) { | |||||||
|       `/vault/replication/performance/secondaries`, |       `/vault/replication/performance/secondaries`, | ||||||
|       'redirects to the secondaries page' |       'redirects to the secondaries page' | ||||||
|     ); |     ); | ||||||
|     assert.dom('[data-test-flash-message-body]:contains(The performance mount filter)').hasText( |     assert | ||||||
|  |       .dom('[data-test-flash-message-body]:contains(The performance mount filter)') | ||||||
|  |       .hasText( | ||||||
|         `The performance mount filter config for the secondary ${secondaryName} was successfully deleted.`, |         `The performance mount filter config for the secondary ${secondaryName} was successfully deleted.`, | ||||||
|         'renders success flash upon deletion' |         'renders success flash upon deletion' | ||||||
|       ); |       ); | ||||||
| @@ -149,10 +153,9 @@ test('replication', function(assert) { | |||||||
|   }); |   }); | ||||||
|   click('[data-test-replication-link="secondaries"]'); |   click('[data-test-replication-link="secondaries"]'); | ||||||
|   andThen(() => { |   andThen(() => { | ||||||
|     assert.dom('[data-test-secondary-name]').hasText( |     assert | ||||||
|       secondaryName, |       .dom('[data-test-secondary-name]') | ||||||
|       'it displays the secondary in the list of known secondaries' |       .hasText(secondaryName, 'it displays the secondary in the list of known secondaries'); | ||||||
|     ); |  | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // disable dr replication |   // disable dr replication | ||||||
|   | |||||||
| @@ -51,7 +51,9 @@ test('it renders the show page', function(assert) { | |||||||
|       'vault.cluster.access.leases.show', |       'vault.cluster.access.leases.show', | ||||||
|       'a lease for the secret is in the list' |       'a lease for the secret is in the list' | ||||||
|     ); |     ); | ||||||
|     assert.dom('[data-test-lease-renew-picker]').doesNotExist('non-renewable lease does not render a renew picker'); |     assert | ||||||
|  |       .dom('[data-test-lease-renew-picker]') | ||||||
|  |       .doesNotExist('non-renewable lease does not render a renew picker'); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -65,7 +67,9 @@ skip('it renders the show page with a picker', function(assert) { | |||||||
|       'vault.cluster.access.leases.show', |       'vault.cluster.access.leases.show', | ||||||
|       'a lease for the secret is in the list' |       'a lease for the secret is in the list' | ||||||
|     ); |     ); | ||||||
|     assert.dom('[data-test-lease-renew-picker]').exists({ count: 1 }, 'renewable lease renders a renew picker'); |     assert | ||||||
|  |       .dom('[data-test-lease-renew-picker]') | ||||||
|  |       .exists({ count: 1 }, 'renewable lease renders a renew picker'); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -84,7 +88,9 @@ test('it removes leases upon revocation', function(assert) { | |||||||
|   click(`[data-test-lease-link="${this.enginePath}/"]`); |   click(`[data-test-lease-link="${this.enginePath}/"]`); | ||||||
|   click('[data-test-lease-link="data/"]'); |   click('[data-test-lease-link="data/"]'); | ||||||
|   andThen(() => { |   andThen(() => { | ||||||
|     assert.dom(`[data-test-lease-link="${this.enginePath}/data/${this.name}/"]`).doesNotExist('link to the lease was removed with revocation'); |     assert | ||||||
|  |       .dom(`[data-test-lease-link="${this.enginePath}/data/${this.name}/"]`) | ||||||
|  |       .doesNotExist('link to the lease was removed with revocation'); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -99,16 +105,17 @@ test('it removes branches when a prefix is revoked', function(assert) { | |||||||
|       'vault.cluster.access.leases.list-root', |       'vault.cluster.access.leases.list-root', | ||||||
|       'it navigates back to the leases root on revocation' |       'it navigates back to the leases root on revocation' | ||||||
|     ); |     ); | ||||||
|     assert.dom(`[data-test-lease-link="${this.enginePath}/"]`).doesNotExist('link to the prefix was removed with revocation'); |     assert | ||||||
|  |       .dom(`[data-test-lease-link="${this.enginePath}/"]`) | ||||||
|  |       .doesNotExist('link to the prefix was removed with revocation'); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| test('lease not found', function(assert) { | test('lease not found', function(assert) { | ||||||
|   visit('/vault/access/leases/show/not-found'); |   visit('/vault/access/leases/show/not-found'); | ||||||
|   andThen(() => { |   andThen(() => { | ||||||
|     assert.dom('[data-test-lease-error]').hasText( |     assert | ||||||
|       'not-found is not a valid lease ID', |       .dom('[data-test-lease-error]') | ||||||
|       'it shows an error when the lease is not found' |       .hasText('not-found is not a valid lease ID', 'it shows an error when the lease is not found'); | ||||||
|     ); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -46,7 +46,9 @@ test('policies', function(assert) { | |||||||
|   }); |   }); | ||||||
|   click('[data-test-policy-list-link]'); |   click('[data-test-policy-list-link]'); | ||||||
|   andThen(function() { |   andThen(function() { | ||||||
|     assert.dom(`[data-test-policy-link="${policyLower}"]`).exists({ count: 1 }, 'new policy shown in the list'); |     assert | ||||||
|  |       .dom(`[data-test-policy-link="${policyLower}"]`) | ||||||
|  |       .exists({ count: 1 }, 'new policy shown in the list'); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   // policy deletion |   // policy deletion | ||||||
| @@ -56,7 +58,9 @@ test('policies', function(assert) { | |||||||
|   click('[data-test-confirm-button]'); |   click('[data-test-confirm-button]'); | ||||||
|   andThen(function() { |   andThen(function() { | ||||||
|     assert.equal(currentURL(), `/vault/policies/acl`, 'navigates to policy list on successful deletion'); |     assert.equal(currentURL(), `/vault/policies/acl`, 'navigates to policy list on successful deletion'); | ||||||
|     assert.dom(`[data-test-policy-item="${policyLower}"]`).doesNotExist('deleted policy is not shown in the list'); |     assert | ||||||
|  |       .dom(`[data-test-policy-item="${policyLower}"]`) | ||||||
|  |       .doesNotExist('deleted policy is not shown in the list'); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -46,10 +46,6 @@ test('settings', function(assert) { | |||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   andThen(() => { |   andThen(() => { | ||||||
|     assert.ok( |     assert.ok(currentURL(), '/vault/secrets/${path}/configuration', 'navigates to the config page'); | ||||||
|       currentURL(), |  | ||||||
|       '/vault/secrets/${path}/configuration', |  | ||||||
|       'navigates to the config page' |  | ||||||
|     ); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -143,7 +143,9 @@ test('ssh backend', function(assert) { | |||||||
|     click(`[data-test-confirm-button]`); |     click(`[data-test-confirm-button]`); | ||||||
|  |  | ||||||
|     andThen(() => { |     andThen(() => { | ||||||
|       assert.dom(`[data-test-secret-link="${role.name}"]`).doesNotExist(`${role.type}: role is no longer in the list`); |       assert | ||||||
|  |         .dom(`[data-test-secret-link="${role.name}"]`) | ||||||
|  |         .doesNotExist(`${role.type}: role is no longer in the list`); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -118,42 +118,38 @@ test('tools functionality', function(assert) { | |||||||
|   click('[data-test-tools-b64-toggle="input"]'); |   click('[data-test-tools-b64-toggle="input"]'); | ||||||
|   click('[data-test-tools-submit]'); |   click('[data-test-tools-submit]'); | ||||||
|   andThen(() => { |   andThen(() => { | ||||||
|     assert.dom('[data-test-tools-input="sum"]').hasValue( |     assert | ||||||
|       'LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=', |       .dom('[data-test-tools-input="sum"]') | ||||||
|       'hashes the data, encodes input' |       .hasValue('LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=', 'hashes the data, encodes input'); | ||||||
|     ); |  | ||||||
|   }); |   }); | ||||||
|   click('[data-test-tools-back]'); |   click('[data-test-tools-back]'); | ||||||
|   fillIn('[data-test-tools-input="hash-input"]', 'e2RhdGE6ImZvbyJ9'); |   fillIn('[data-test-tools-input="hash-input"]', 'e2RhdGE6ImZvbyJ9'); | ||||||
|  |  | ||||||
|   click('[data-test-tools-submit]'); |   click('[data-test-tools-submit]'); | ||||||
|   andThen(() => { |   andThen(() => { | ||||||
|     assert.dom('[data-test-tools-input="sum"]').hasValue( |     assert | ||||||
|       'JmSi2Hhbgu2WYOrcOyTqqMdym7KT3sohCwAwaMonVrc=', |       .dom('[data-test-tools-input="sum"]') | ||||||
|       'hashes the data, passes b64 input through' |       .hasValue('JmSi2Hhbgu2WYOrcOyTqqMdym7KT3sohCwAwaMonVrc=', 'hashes the data, passes b64 input through'); | ||||||
|     ); |  | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const AUTH_RESPONSE = { | const AUTH_RESPONSE = { | ||||||
|   "request_id": "39802bc4-235c-2f0b-87f3-ccf38503ac3e", |   request_id: '39802bc4-235c-2f0b-87f3-ccf38503ac3e', | ||||||
|   "lease_id": "", |   lease_id: '', | ||||||
|   "renewable": false, |   renewable: false, | ||||||
|   "lease_duration": 0, |   lease_duration: 0, | ||||||
|   "data": null, |   data: null, | ||||||
|   "wrap_info": null, |   wrap_info: null, | ||||||
|   "warnings": null, |   warnings: null, | ||||||
|   "auth": { |   auth: { | ||||||
|     "client_token": "ecfc2758-588e-981d-50f4-a25883bbf03c", |     client_token: 'ecfc2758-588e-981d-50f4-a25883bbf03c', | ||||||
|     "accessor": "6299780b-f2b2-1a3f-7b83-9d3d67629249", |     accessor: '6299780b-f2b2-1a3f-7b83-9d3d67629249', | ||||||
|     "policies": [ |     policies: ['root'], | ||||||
|       "root" |     metadata: null, | ||||||
|     ], |     lease_duration: 0, | ||||||
|     "metadata": null, |     renewable: false, | ||||||
|     "lease_duration": 0, |     entity_id: '', | ||||||
|     "renewable": false, |   }, | ||||||
|     "entity_id": "" |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| test('ensure unwrap with auth block works properly', function(assert) { | test('ensure unwrap with auth block works properly', function(assert) { | ||||||
|   | |||||||
| @@ -101,14 +101,18 @@ const testEncryption = (assert, keyName) => { | |||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|       assertBeforeDecrypt: key => { |       assertBeforeDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="context"]').hasValue( |         assert | ||||||
|  |           .dom('[data-test-transit-input="context"]') | ||||||
|  |           .hasValue( | ||||||
|             'nqR8LiVgNh/lwO2rArJJE9F9DMhh0lKo4JX9DAAkCDw=', |             'nqR8LiVgNh/lwO2rArJJE9F9DMhh0lKo4JX9DAAkCDw=', | ||||||
|             `${key}: the ui shows the base64-encoded context` |             `${key}: the ui shows the base64-encoded context` | ||||||
|           ); |           ); | ||||||
|       }, |       }, | ||||||
|  |  | ||||||
|       assertAfterDecrypt: key => { |       assertAfterDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="plaintext"]').hasValue( |         assert | ||||||
|  |           .dom('[data-test-transit-input="plaintext"]') | ||||||
|  |           .hasValue( | ||||||
|             'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=', |             'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=', | ||||||
|             `${key}: the ui shows the base64-encoded plaintext` |             `${key}: the ui shows the base64-encoded plaintext` | ||||||
|           ); |           ); | ||||||
| @@ -128,10 +132,14 @@ const testEncryption = (assert, keyName) => { | |||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|       assertBeforeDecrypt: key => { |       assertBeforeDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="context"]').hasValue(encodeString('context'), `${key}: the ui shows the input context`); |         assert | ||||||
|  |           .dom('[data-test-transit-input="context"]') | ||||||
|  |           .hasValue(encodeString('context'), `${key}: the ui shows the input context`); | ||||||
|       }, |       }, | ||||||
|       assertAfterDecrypt: key => { |       assertAfterDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="plaintext"]').hasValue( |         assert | ||||||
|  |           .dom('[data-test-transit-input="plaintext"]') | ||||||
|  |           .hasValue( | ||||||
|             'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=', |             'NaXud2QW7KjyK6Me9ggh+zmnCeBGdG93LQED49PtoOI=', | ||||||
|             `${key}: the ui shows the base64-encoded plaintext` |             `${key}: the ui shows the base64-encoded plaintext` | ||||||
|           ); |           ); | ||||||
| @@ -151,10 +159,14 @@ const testEncryption = (assert, keyName) => { | |||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|       assertBeforeDecrypt: key => { |       assertBeforeDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="context"]').hasValue(encodeString('context'), `${key}: the ui shows the input context`); |         assert | ||||||
|  |           .dom('[data-test-transit-input="context"]') | ||||||
|  |           .hasValue(encodeString('context'), `${key}: the ui shows the input context`); | ||||||
|       }, |       }, | ||||||
|       assertAfterDecrypt: key => { |       assertAfterDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="plaintext"]').hasValue('This is the secret', `${key}: the ui decodes plaintext`); |         assert | ||||||
|  |           .dom('[data-test-transit-input="plaintext"]') | ||||||
|  |           .hasValue('This is the secret', `${key}: the ui decodes plaintext`); | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @@ -173,11 +185,15 @@ const testEncryption = (assert, keyName) => { | |||||||
|         ); |         ); | ||||||
|       }, |       }, | ||||||
|       assertBeforeDecrypt: key => { |       assertBeforeDecrypt: key => { | ||||||
|         assert.dom('[data-test-transit-input="context"]').hasValue(encodeString('secret 2'), `${key}: the ui shows the encoded context`); |         assert | ||||||
|  |           .dom('[data-test-transit-input="context"]') | ||||||
|  |           .hasValue(encodeString('secret 2'), `${key}: the ui shows the encoded context`); | ||||||
|       }, |       }, | ||||||
|       assertAfterDecrypt: key => { |       assertAfterDecrypt: key => { | ||||||
|         assert.ok(findWithAssert('[data-test-transit-input="plaintext"]'), `${key}: plaintext box shows`); |         assert.ok(findWithAssert('[data-test-transit-input="plaintext"]'), `${key}: plaintext box shows`); | ||||||
|         assert.dom('[data-test-transit-input="plaintext"]').hasValue('There are many secrets 🤐', `${key}: the ui decodes plaintext`); |         assert | ||||||
|  |           .dom('[data-test-transit-input="plaintext"]') | ||||||
|  |           .hasValue('There are many secrets 🤐', `${key}: the ui decodes plaintext`); | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|   ]; |   ]; | ||||||
| @@ -229,12 +245,16 @@ test('transit backend', function(assert) { | |||||||
|     if (index === 0) { |     if (index === 0) { | ||||||
|       click('[data-test-transit-link="versions"]'); |       click('[data-test-transit-link="versions"]'); | ||||||
|       andThen(() => { |       andThen(() => { | ||||||
|         assert.dom('[data-test-transit-key-version-row]').exists({ count: 1 }, `${key.name}: only one key version`); |         assert | ||||||
|  |           .dom('[data-test-transit-key-version-row]') | ||||||
|  |           .exists({ count: 1 }, `${key.name}: only one key version`); | ||||||
|       }); |       }); | ||||||
|       click('[data-test-transit-key-rotate] button'); |       click('[data-test-transit-key-rotate] button'); | ||||||
|       click('[data-test-confirm-button]'); |       click('[data-test-confirm-button]'); | ||||||
|       andThen(() => { |       andThen(() => { | ||||||
|         assert.dom('[data-test-transit-key-version-row]').exists({ count: 2 }, `${key.name}: two key versions after rotate`); |         assert | ||||||
|  |           .dom('[data-test-transit-key-version-row]') | ||||||
|  |           .exists({ count: 2 }, `${key.name}: two key versions after rotate`); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|     click('[data-test-transit-key-actions-link]'); |     click('[data-test-transit-key-actions-link]'); | ||||||
| @@ -256,7 +276,9 @@ test('transit backend', function(assert) { | |||||||
|           `${key.name}: exportable key has a link to export action` |           `${key.name}: exportable key has a link to export action` | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         assert.dom('[data-test-transit-action-link="export"]').doesNotExist(`${key.name}: non-exportable key does not link to export action`); |         assert | ||||||
|  |           .dom('[data-test-transit-action-link="export"]') | ||||||
|  |           .doesNotExist(`${key.name}: non-exportable key does not link to export action`); | ||||||
|       } |       } | ||||||
|       if (key.convergent && key.supportsEncryption) { |       if (key.convergent && key.supportsEncryption) { | ||||||
|         testEncryption(assert, key.name); |         testEncryption(assert, key.name); | ||||||
|   | |||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | import { moduleForComponent, test } from 'ember-qunit'; | ||||||
|  | import hbs from 'htmlbars-inline-precompile'; | ||||||
|  | import sinon from 'sinon'; | ||||||
|  | import { create } from 'ember-cli-page-object'; | ||||||
|  | import itemDetails from 'vault/tests/pages/components/identity/item-details'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  |  | ||||||
|  | const component = create(itemDetails); | ||||||
|  | const { getOwner } = Ember; | ||||||
|  |  | ||||||
|  | moduleForComponent('identity/item-details', 'Integration | Component | identity/item details', { | ||||||
|  |   integration: true, | ||||||
|  |   beforeEach() { | ||||||
|  |     component.setContext(this); | ||||||
|  |     getOwner(this).lookup('service:flash-messages').registerTypes(['success']); | ||||||
|  |   }, | ||||||
|  |   afterEach() { | ||||||
|  |     component.removeContext(); | ||||||
|  |   } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it renders the disabled warning', function(assert) { | ||||||
|  |   let model = Ember.Object.create({ | ||||||
|  |     save() { | ||||||
|  |       return Ember.RSVP.resolve(); | ||||||
|  |     }, | ||||||
|  |     disabled: true, | ||||||
|  |     canEdit: true | ||||||
|  |   }); | ||||||
|  |   sinon.spy(model, 'save'); | ||||||
|  |   this.set('model', model); | ||||||
|  |   this.render(hbs`{{identity/item-details model=model}}`); | ||||||
|  |   assert.dom('[data-test-disabled-warning]').exists(); | ||||||
|  |   component.enable(); | ||||||
|  |  | ||||||
|  |   assert.ok(model.save.calledOnce, 'clicking enable calls model save'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it does not render the button if canEdit is false', function(assert) { | ||||||
|  |   let model = Ember.Object.create({ | ||||||
|  |     disabled: true | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   this.set('model', model); | ||||||
|  |   this.render(hbs`{{identity/item-details model=model}}`); | ||||||
|  |   assert.dom('[data-test-disabled-warning]').exists('shows the warning banner'); | ||||||
|  |   assert.dom('[data-test-enable]').doesNotExist('does not show the enable button'); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | test('it does not render the banner when item is enabled', function(assert) { | ||||||
|  |   let model = Ember.Object.create(); | ||||||
|  |   this.set('model', model); | ||||||
|  |  | ||||||
|  |   this.render(hbs`{{identity/item-details model=model}}`); | ||||||
|  |   assert.dom('[data-test-disabled-warning]').doesNotExist('does not show the warning banner'); | ||||||
|  | }); | ||||||
							
								
								
									
										7
									
								
								ui/tests/pages/access/identity/aliases/add.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ui/tests/pages/access/identity/aliases/add.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | import { create, visitable } from 'ember-cli-page-object'; | ||||||
|  | import editForm from 'vault/tests/pages/components/identity/edit-form'; | ||||||
|  |  | ||||||
|  | export default create({ | ||||||
|  |   visit: visitable('/vault/access/identity/:item_type/aliases/add/:id'), | ||||||
|  |   editForm, | ||||||
|  | }); | ||||||
							
								
								
									
										13
									
								
								ui/tests/pages/access/identity/aliases/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								ui/tests/pages/access/identity/aliases/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import { create, clickable, text, visitable, collection } from 'ember-cli-page-object'; | ||||||
|  | import flashMessage from 'vault/tests/pages/components/flash-message'; | ||||||
|  |  | ||||||
|  | export default create({ | ||||||
|  |   visit: visitable('/vault/access/identity/:item_type/aliases'), | ||||||
|  |   flashMessage, | ||||||
|  |   items: collection('[data-test-identity-row]', { | ||||||
|  |     menu: clickable('[data-test-popup-menu-trigger]'), | ||||||
|  |     id: text('[data-test-identity-link]'), | ||||||
|  |   }), | ||||||
|  |   delete: clickable('[data-test-item-delete] [data-test-confirm-action-trigger]'), | ||||||
|  |   confirmDelete: clickable('[data-test-item-delete] [data-test-confirm-button]'), | ||||||
|  | }); | ||||||
							
								
								
									
										11
									
								
								ui/tests/pages/access/identity/aliases/show.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ui/tests/pages/access/identity/aliases/show.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import { create, clickable, collection, contains, visitable } from 'ember-cli-page-object'; | ||||||
|  | import flashMessage from 'vault/tests/pages/components/flash-message'; | ||||||
|  | import infoTableRow from 'vault/tests/pages/components/info-table-row'; | ||||||
|  |  | ||||||
|  | export default create({ | ||||||
|  |   visit: visitable('/vault/access/identity/:item_type/aliases/:alias_id'), | ||||||
|  |   flashMessage, | ||||||
|  |   nameContains: contains('[data-test-alias-name]'), | ||||||
|  |   rows: collection('[data-test-component="info-table-row"]', infoTableRow), | ||||||
|  |   edit: clickable('[data-test-alias-edit-link]') | ||||||
|  | }); | ||||||
							
								
								
									
										13
									
								
								ui/tests/pages/access/identity/create.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								ui/tests/pages/access/identity/create.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import { create, visitable } from 'ember-cli-page-object'; | ||||||
|  | import editForm from 'vault/tests/pages/components/identity/edit-form'; | ||||||
|  |  | ||||||
|  | export default create({ | ||||||
|  |   visit: visitable('/vault/access/identity/:item_type/create'), | ||||||
|  |   editForm, | ||||||
|  |   createItem(item_type, type) { | ||||||
|  |     if (type) { | ||||||
|  |       return this.visit({item_type}).editForm.type(type).submit(); | ||||||
|  |     } | ||||||
|  |     return this.visit({item_type}).editForm.submit(); | ||||||
|  |   } | ||||||
|  | }); | ||||||
| @@ -1,4 +1,13 @@ | |||||||
| import { create, visitable } from 'ember-cli-page-object'; | import { create, clickable, text, visitable, collection } from 'ember-cli-page-object'; | ||||||
|  | import flashMessage from 'vault/tests/pages/components/flash-message'; | ||||||
|  |  | ||||||
| export default create({ | export default create({ | ||||||
|   visit: visitable('/vault/access/identity/:item_type'), |   visit: visitable('/vault/access/identity/:item_type'), | ||||||
|  |   flashMessage, | ||||||
|  |   items: collection('[data-test-identity-row]', { | ||||||
|  |     menu: clickable('[data-test-popup-menu-trigger]'), | ||||||
|  |     id: text('[data-test-identity-link]'), | ||||||
|  |   }), | ||||||
|  |   delete: clickable('[data-test-item-delete] [data-test-confirm-action-trigger]'), | ||||||
|  |   confirmDelete: clickable('[data-test-item-delete] [data-test-confirm-button]'), | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								ui/tests/pages/access/identity/show.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								ui/tests/pages/access/identity/show.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import { create, clickable, collection, contains, visitable } from 'ember-cli-page-object'; | ||||||
|  | import flashMessage from 'vault/tests/pages/components/flash-message'; | ||||||
|  | import infoTableRow from 'vault/tests/pages/components/info-table-row'; | ||||||
|  |  | ||||||
|  | export default create({ | ||||||
|  |   visit: visitable('/vault/access/identity/:item_type/:item_id'), | ||||||
|  |   flashMessage, | ||||||
|  |   nameContains: contains('[data-test-identity-item-name]'), | ||||||
|  |   rows: collection('[data-test-component="info-table-row"]', infoTableRow), | ||||||
|  |   edit: clickable('[data-test-entity-edit-link]') | ||||||
|  | }); | ||||||
							
								
								
									
										14
									
								
								ui/tests/pages/components/identity/edit-form.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								ui/tests/pages/components/identity/edit-form.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | import { clickable, fillable, attribute } from 'ember-cli-page-object'; | ||||||
|  | import fields from '../form-field'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   ...fields, | ||||||
|  |   cancelLinkHref: attribute('href', '[data-test-cancel-link]'), | ||||||
|  |   cancelLink: clickable('[data-test-cancel-link]'), | ||||||
|  |   name: fillable('[data-test-input="name"]'), | ||||||
|  |   disabled: clickable('[data-test-input="disabled"]'), | ||||||
|  |   type: fillable('[data-test-input="type"]'), | ||||||
|  |   submit: clickable('[data-test-identity-submit]'), | ||||||
|  |   delete: clickable('[data-test-confirm-action-trigger]'), | ||||||
|  |   confirmDelete: clickable('[data-test-confirm-button]'), | ||||||
|  | }; | ||||||
							
								
								
									
										5
									
								
								ui/tests/pages/components/identity/item-details.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								ui/tests/pages/components/identity/item-details.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import { clickable } from 'ember-cli-page-object'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   enable: clickable('[data-test-enable]'), | ||||||
|  | }; | ||||||
							
								
								
									
										7
									
								
								ui/tests/pages/components/info-table-row.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								ui/tests/pages/components/info-table-row.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | import { text, isPresent } from 'ember-cli-page-object'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   hasLabel: isPresent('[data-test-row-label]'), | ||||||
|  |   rowLabel: text('[data-test-row-label]'), | ||||||
|  |   rowValue: text('[data-test-row-value]'), | ||||||
|  | }; | ||||||
							
								
								
									
										73
									
								
								ui/tests/unit/components/identity/edit-form-test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								ui/tests/unit/components/identity/edit-form-test.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | import { moduleForComponent, test } from 'ember-qunit'; | ||||||
|  | import sinon from 'sinon'; | ||||||
|  | import Ember from 'ember'; | ||||||
|  |  | ||||||
|  | moduleForComponent('identity/edit-form', 'Unit | Component | identity/edit-form', { | ||||||
|  |   unit: true, | ||||||
|  |   needs: ['service:auth', 'service:flash-messages'], | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | let testCases = [ | ||||||
|  |   { | ||||||
|  |     identityType: 'entity', | ||||||
|  |     mode: 'create', | ||||||
|  |     expected: 'vault.cluster.access.identity', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'entity', | ||||||
|  |     mode: 'edit', | ||||||
|  |     expected: 'vault.cluster.access.identity.show', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'entity-merge', | ||||||
|  |     mode: 'merge', | ||||||
|  |     expected: 'vault.cluster.access.identity', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'entity-alias', | ||||||
|  |     mode: 'create', | ||||||
|  |     expected: 'vault.cluster.access.identity.aliases', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'entity-alias', | ||||||
|  |     mode: 'edit', | ||||||
|  |     expected: 'vault.cluster.access.identity.aliases.show', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'group', | ||||||
|  |     mode: 'create', | ||||||
|  |     expected: 'vault.cluster.access.identity', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'group', | ||||||
|  |     mode: 'edit', | ||||||
|  |     expected: 'vault.cluster.access.identity.show', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'group-alias', | ||||||
|  |     mode: 'create', | ||||||
|  |     expected: 'vault.cluster.access.identity.aliases', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     identityType: 'group-alias', | ||||||
|  |     mode: 'edit', | ||||||
|  |     expected: 'vault.cluster.access.identity.aliases.show', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | testCases.forEach(function(testCase) { | ||||||
|  |   let model = Ember.Object.create({ | ||||||
|  |     identityType: testCase.identityType, | ||||||
|  |     rollbackAttributes: sinon.spy(), | ||||||
|  |   }); | ||||||
|  |   test(`it computes cancelLink properly: ${testCase.identityType} ${testCase.mode}`, function(assert) { | ||||||
|  |     let component = this.subject(); | ||||||
|  |  | ||||||
|  |     component.set('mode', testCase.mode); | ||||||
|  |     component.set('model', model); | ||||||
|  |     assert.equal( | ||||||
|  |       component.get('cancelLink'), | ||||||
|  |       testCase.expected, | ||||||
|  |       'cancel link is correct' | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -89,6 +89,7 @@ test('store.constructResponse', function(assert) { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| test('store.fetchPage', function(assert) { | test('store.fetchPage', function(assert) { | ||||||
|  |   let done = assert.async(4); | ||||||
|   const keys = ['zero', 'one', 'two', 'three', 'four', 'five', 'six']; |   const keys = ['zero', 'one', 'two', 'three', 'four', 'five', 'six']; | ||||||
|   const data = { |   const data = { | ||||||
|     data: { |     data: { | ||||||
| @@ -106,11 +107,14 @@ test('store.fetchPage', function(assert) { | |||||||
|  |  | ||||||
|   let result; |   let result; | ||||||
|   Ember.run(() => { |   Ember.run(() => { | ||||||
|     result = store.fetchPage('transit-key', query); |     store.fetchPage('transit-key', query).then(r => { | ||||||
|  |       result = r; | ||||||
|  |       done(); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   assert.ok(result.get('length'), pageSize, 'returns the correct number of items'); |   assert.ok(result.get('length'), pageSize, 'returns the correct number of items'); | ||||||
|   assert.deepEqual(result.toArray().mapBy('id'), keys.slice(0, pageSize), 'returns the first page of items'); |   assert.deepEqual(result.mapBy('id'), keys.slice(0, pageSize), 'returns the first page of items'); | ||||||
|   assert.deepEqual( |   assert.deepEqual( | ||||||
|     result.get('meta'), |     result.get('meta'), | ||||||
|     { |     { | ||||||
| @@ -125,44 +129,54 @@ test('store.fetchPage', function(assert) { | |||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   Ember.run(() => { |   Ember.run(() => { | ||||||
|     result = store.fetchPage('transit-key', { |     store.fetchPage('transit-key', { | ||||||
|       size: pageSize, |       size: pageSize, | ||||||
|       page: 3, |       page: 3, | ||||||
|       responsePath: 'data.keys', |       responsePath: 'data.keys', | ||||||
|  |     }).then(r => { | ||||||
|  |       result = r; | ||||||
|  |       done() | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   const pageThreeEnd = 3 * pageSize; |   const pageThreeEnd = 3 * pageSize; | ||||||
|   const pageThreeStart = pageThreeEnd - pageSize; |   const pageThreeStart = pageThreeEnd - pageSize; | ||||||
|   assert.deepEqual( |   assert.deepEqual( | ||||||
|     result.toArray().mapBy('id'), |     result.mapBy('id'), | ||||||
|     keys.slice(pageThreeStart, pageThreeEnd), |     keys.slice(pageThreeStart, pageThreeEnd), | ||||||
|     'returns the third page of items' |     'returns the third page of items' | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   Ember.run(() => { |   Ember.run(() => { | ||||||
|     result = store.fetchPage('transit-key', { |     store.fetchPage('transit-key', { | ||||||
|       size: pageSize, |       size: pageSize, | ||||||
|       page: 99, |       page: 99, | ||||||
|       responsePath: 'data.keys', |       responsePath: 'data.keys', | ||||||
|  |     }).then(r => { | ||||||
|  |  | ||||||
|  |       result = r; | ||||||
|  |       done(); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   assert.deepEqual( |   assert.deepEqual( | ||||||
|     result.toArray().mapBy('id'), |     result.mapBy('id'), | ||||||
|     keys.slice(keys.length - 1), |     keys.slice(keys.length - 1), | ||||||
|     'returns the last page when the page value is beyond the of bounds' |     'returns the last page when the page value is beyond the of bounds' | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   Ember.run(() => { |   Ember.run(() => { | ||||||
|     result = store.fetchPage('transit-key', { |     store.fetchPage('transit-key', { | ||||||
|       size: pageSize, |       size: pageSize, | ||||||
|       page: 0, |       page: 0, | ||||||
|       responsePath: 'data.keys', |       responsePath: 'data.keys', | ||||||
|  |     }).then(r => { | ||||||
|  |       result = r; | ||||||
|  |       done(); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|   assert.deepEqual( |   assert.deepEqual( | ||||||
|     result.toArray().mapBy('id'), |     result.mapBy('id'), | ||||||
|     keys.slice(0, pageSize), |     keys.slice(0, pageSize), | ||||||
|     'returns the first page when page value is under the bounds' |     'returns the first page when page value is under the bounds' | ||||||
|   ); |   ); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Matthew Irish
					Matthew Irish