mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	UI: Fix enabling replication capabilities bug (#28371)
* add capabilities service to replication engine * fix capabilities paths in route file * pass updated capabilities using getters * add changelog * fix logic so default is based on undefined capabilities (not no mode)
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/28371.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/28371.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| ```release-note:bug | ||||
| ui: Fix UI improperly checking capabilities for enabling performance and dr replication | ||||
| ``` | ||||
| @@ -27,6 +27,7 @@ export default class App extends Application { | ||||
|       dependencies: { | ||||
|         services: [ | ||||
|           'auth', | ||||
|           'capabilities', | ||||
|           'flash-messages', | ||||
|           'namespace', | ||||
|           'replication-mode', | ||||
|   | ||||
| @@ -19,16 +19,15 @@ import { waitFor } from '@ember/test-waiters'; | ||||
|  * but otherwise it handles the rest of the form inputs. On success it will clear the form and call the onSuccess callback. | ||||
|  * | ||||
|  * @example | ||||
|  * ```js | ||||
|  * <EnableReplicationForm @replicationMode="dr" @canEnablePrimary={{true}} @canEnableSecondary={{false}} @performanceReplicationDisabled={{false}} @onSuccess={{this.reloadCluster}} /> | ||||
|  *    @param {string} replicationMode - should be one of "dr" or "performance" | ||||
|  *    @param {boolean} canEnablePrimary - if the capabilities allow the user to enable a primary cluster | ||||
|  *    @param {boolean} canEnableSecondary - if the capabilities allow the user to enable a secondary cluster | ||||
|  *    @param {boolean} performanceMode - should be "primary", "secondary", or "disabled". If enabled, form will show a warning when attempting to enable DR secondary | ||||
|  *    @param {Promise} onSuccess - (optional) callback called after successful replication enablement. Must be a promise. | ||||
|  *    @param {boolean} doTransition - (optional) if provided, passed to onSuccess callback to determine if a transition should be done | ||||
|  *  /> | ||||
|  * ``` | ||||
|  * | ||||
|  * @param {string} replicationMode - should be one of "dr" or "performance" | ||||
|  * @param {boolean} canEnablePrimary - if the capabilities allow the user to enable a primary cluster, parent getter returns capabilities based on type (i.e. "dr" or "performance") | ||||
|  * @param {boolean} canEnableSecondary - if the capabilities allow the user to enable a secondary cluster, parent getter returns capabilities based on type (i.e. "dr" or "performance") | ||||
|  * @param {boolean} performanceMode - should be "primary", "secondary", or "disabled". If enabled, form will show a warning when attempting to enable DR secondary | ||||
|  * @param {Promise} onSuccess - (optional) callback called after successful replication enablement. Must be a promise. | ||||
|  * @param {boolean} doTransition - (optional) if provided, passed to onSuccess callback to determine if a transition should be done | ||||
|  * | ||||
|  */ | ||||
| export default class EnableReplicationFormComponent extends Component { | ||||
|   @service version; | ||||
|   | ||||
| @@ -45,8 +45,8 @@ | ||||
|   </div> | ||||
|   <EnableReplicationForm | ||||
|     @replicationMode={{@replicationMode}} | ||||
|     @canEnablePrimary={{@cluster.canEnablePrimary}} | ||||
|     @canEnableSecondary={{@cluster.canEnableSecondary}} | ||||
|     @canEnablePrimary={{this.canEnable "Primary"}} | ||||
|     @canEnableSecondary={{this.canEnable "Secondary"}} | ||||
|     @performanceReplicationDisabled={{@cluster.performance.replicationDisabled}} | ||||
|     @performanceMode={{if @cluster.performance.replicationDisabled "disabled" @cluster.performance.modeForUrl}} | ||||
|     @onSuccess={{@onEnableSuccess}} | ||||
|   | ||||
							
								
								
									
										40
									
								
								ui/lib/replication/addon/components/page/mode-index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								ui/lib/replication/addon/components/page/mode-index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| /** | ||||
|  * Copyright (c) HashiCorp, Inc. | ||||
|  * SPDX-License-Identifier: BUSL-1.1 | ||||
|  */ | ||||
|  | ||||
| import Component from '@glimmer/component'; | ||||
|  | ||||
| /** | ||||
|  * @module PageModeIndex | ||||
|  * | ||||
|  * @example | ||||
|  * <Page::ModeIndex | ||||
|  *  @cluster={{this.model}} | ||||
|  *  @onEnableSuccess={{this.onEnableSuccess}} | ||||
|  *  @replicationDisabled={{this.replicationForMode.replicationDisabled} | ||||
|  *  @replicationMode={{this.replicationMode}} | ||||
|  * /> | ||||
|  * | ||||
|  * @param {model} cluster - cluster route model | ||||
|  * @param {function} onEnableSuccess - callback after enabling is successful, handles transition if enabled from the top-level index route | ||||
|  * @param {boolean} replicationDisabled - whether or not replication is enabled | ||||
|  * @param {string} replicationMode - should be "dr" or "performance" | ||||
|  */ | ||||
| export default class PageModeIndex extends Component { | ||||
|   canEnable = (type) => { | ||||
|     const { cluster, replicationMode } = this.args; | ||||
|     let perm; | ||||
|     if (replicationMode === 'dr') { | ||||
|       // returns canEnablePrimaryDr or canEnableSecondaryDr | ||||
|       perm = `canEnable${type}Dr`; | ||||
|     } | ||||
|     if (replicationMode === 'performance') { | ||||
|       // returns canEnablePrimaryPerformance or canEnableSecondaryPerformance | ||||
|       perm = `canEnable${type}Performance`; | ||||
|     } | ||||
|     // if there's a problem checking capabilities, default to true | ||||
|     // since the backend can gate as a fallback | ||||
|     return cluster[perm] ?? true; | ||||
|   }; | ||||
| } | ||||
| @@ -8,4 +8,24 @@ import { tracked } from '@glimmer/tracking'; | ||||
|  | ||||
| export default class ReplicationIndexController extends ReplicationModeBaseController { | ||||
|   @tracked modeSelection = 'dr'; | ||||
|  | ||||
|   getPerm(type) { | ||||
|     if (this.modeSelection === 'dr') { | ||||
|       // returns canEnablePrimaryDr or canEnableSecondaryDr | ||||
|       return `canEnable${type}Dr`; | ||||
|     } | ||||
|     if (this.modeSelection === 'performance') { | ||||
|       // returns canEnablePrimaryPerformance or canEnableSecondaryPerformance | ||||
|       return `canEnable${type}Performance`; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   // if there's a problem checking capabilities, default to true | ||||
|   // since the backend will gate as a fallback | ||||
|   get canEnablePrimary() { | ||||
|     return this.model[this.getPerm('Primary')] ?? true; | ||||
|   } | ||||
|   get canEnableSecondary() { | ||||
|     return this.model[this.getPerm('Secondary')] ?? true; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,7 @@ const Eng = Engine.extend({ | ||||
|   dependencies: { | ||||
|     services: [ | ||||
|       'auth', | ||||
|       'capabilities', | ||||
|       'flash-messages', | ||||
|       'namespace', | ||||
|       'replication-mode', | ||||
|   | ||||
| @@ -5,7 +5,6 @@ | ||||
|  | ||||
| import { service } from '@ember/service'; | ||||
| import { setProperties } from '@ember/object'; | ||||
| import { hash } from 'rsvp'; | ||||
| import Route from '@ember/routing/route'; | ||||
| import ClusterRoute from 'vault/mixins/cluster-route'; | ||||
|  | ||||
| @@ -14,6 +13,23 @@ export default Route.extend(ClusterRoute, { | ||||
|   store: service(), | ||||
|   auth: service(), | ||||
|   router: service(), | ||||
|   capabilities: service(), | ||||
|  | ||||
|   async fetchCapabilities() { | ||||
|     const enablePath = (type, cluster) => `sys/replication/${type}/${cluster}/enable`; | ||||
|     const perms = await this.capabilities.fetchMultiplePaths([ | ||||
|       enablePath('dr', 'primary'), | ||||
|       enablePath('dr', 'primary'), | ||||
|       enablePath('performance', 'secondary'), | ||||
|       enablePath('performance', 'secondary'), | ||||
|     ]); | ||||
|     return { | ||||
|       canEnablePrimaryDr: perms[enablePath('dr', 'primary')].canUpdate, | ||||
|       canEnableSecondaryDr: perms[enablePath('dr', 'primary')].canUpdate, | ||||
|       canEnablePrimaryPerformance: perms[enablePath('performance', 'secondary')].canUpdate, | ||||
|       canEnableSecondaryPerformance: perms[enablePath('performance', 'secondary')].canUpdate, | ||||
|     }; | ||||
|   }, | ||||
|  | ||||
|   beforeModel() { | ||||
|     if (this.auth.activeCluster.replicationRedacted) { | ||||
| @@ -29,21 +45,21 @@ export default Route.extend(ClusterRoute, { | ||||
|     return this.auth.activeCluster; | ||||
|   }, | ||||
|  | ||||
|   afterModel(model) { | ||||
|     return hash({ | ||||
|       canEnablePrimary: this.store | ||||
|         .findRecord('capabilities', 'sys/replication/primary/enable') | ||||
|         .then((c) => c.canUpdate), | ||||
|       canEnableSecondary: this.store | ||||
|         .findRecord('capabilities', 'sys/replication/secondary/enable') | ||||
|         .then((c) => c.canUpdate), | ||||
|     }).then(({ canEnablePrimary, canEnableSecondary }) => { | ||||
|       setProperties(model, { | ||||
|         canEnablePrimary, | ||||
|         canEnableSecondary, | ||||
|       }); | ||||
|       return model; | ||||
|   async afterModel(model) { | ||||
|     const { | ||||
|       canEnablePrimaryDr, | ||||
|       canEnableSecondaryDr, | ||||
|       canEnablePrimaryPerformance, | ||||
|       canEnableSecondaryPerformance, | ||||
|     } = await this.fetchCapabilities(); | ||||
|  | ||||
|     setProperties(model, { | ||||
|       canEnablePrimaryDr, | ||||
|       canEnableSecondaryDr, | ||||
|       canEnablePrimaryPerformance, | ||||
|       canEnableSecondaryPerformance, | ||||
|     }); | ||||
|     return model; | ||||
|   }, | ||||
|   actions: { | ||||
|     refresh() { | ||||
|   | ||||
| @@ -92,8 +92,8 @@ | ||||
|       </div> | ||||
|       <EnableReplicationForm | ||||
|         @replicationMode={{this.modeSelection}} | ||||
|         @canEnablePrimary={{this.model.canEnablePrimary}} | ||||
|         @canEnableSecondary={{this.model.canEnableSecondary}} | ||||
|         @canEnablePrimary={{this.canEnablePrimary}} | ||||
|         @canEnableSecondary={{this.canEnableSecondary}} | ||||
|         @performanceReplicationDisabled={{this.model.performance.replicationDisabled}} | ||||
|         @performanceMode={{if this.model.performance.replicationDisabled "disabled" this.model.performance.modeForUrl}} | ||||
|         @onSuccess={{this.onEnableSuccess}} | ||||
|   | ||||
| @@ -13,7 +13,9 @@ const S = { | ||||
|   title: 'h1', | ||||
|   subtitle: 'h2', | ||||
|   enableForm: '[data-test-replication-enable-form]', | ||||
|   enableBtn: '[data-test-replication-enable]', | ||||
|   summary: '[data-test-replication-summary]', | ||||
|   notAllowed: '[data-test-not-allowed]', | ||||
| }; | ||||
| module('Integration | Component | replication page/mode-index', function (hooks) { | ||||
|   setupRenderingTest(hooks); | ||||
| @@ -43,6 +45,8 @@ module('Integration | Component | replication page/mode-index', function (hooks) | ||||
|  | ||||
|       assert.dom(S.title).hasText('Enable Disaster Recovery Replication'); | ||||
|       assert.dom(S.enableForm).exists(); | ||||
|       assert.dom(S.notAllowed).doesNotExist(); | ||||
|       assert.dom(S.enableBtn).exists('Enable button shows by default if no permissions available'); | ||||
|     }); | ||||
|     test('it renders correctly when replication enabled', async function (assert) { | ||||
|       this.replicationDisabled = false; | ||||
| @@ -51,6 +55,24 @@ module('Integration | Component | replication page/mode-index', function (hooks) | ||||
|       assert.dom(S.enableForm).doesNotExist(); | ||||
|       assert.dom(S.summary).exists(); | ||||
|     }); | ||||
|  | ||||
|     test('it hides enable button if no permissions', async function (assert) { | ||||
|       this.clusterModel.canEnablePrimaryDr = false; | ||||
|       await this.renderComponent(); | ||||
|  | ||||
|       assert.dom(S.enableForm).exists(); | ||||
|       assert.dom(S.notAllowed).exists(); | ||||
|       assert.dom(S.enableBtn).doesNotExist(); | ||||
|     }); | ||||
|  | ||||
|     test('it shows enable button if has permissions', async function (assert) { | ||||
|       this.clusterModel.canEnablePrimaryDr = true; | ||||
|       await this.renderComponent(); | ||||
|  | ||||
|       assert.dom(S.enableForm).exists(); | ||||
|       assert.dom(S.notAllowed).doesNotExist(); | ||||
|       assert.dom(S.enableBtn).exists(); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   module('Performance mode', function (hooks) { | ||||
| @@ -62,6 +84,8 @@ module('Integration | Component | replication page/mode-index', function (hooks) | ||||
|  | ||||
|       assert.dom(S.title).hasText('Enable Performance Replication'); | ||||
|       assert.dom(S.enableForm).exists(); | ||||
|       assert.dom(S.notAllowed).doesNotExist(); | ||||
|       assert.dom(S.enableBtn).exists('Enable button shows by default if no permissions available'); | ||||
|     }); | ||||
|     test('it renders correctly when replication enabled', async function (assert) { | ||||
|       this.replicationDisabled = false; | ||||
| @@ -70,5 +94,23 @@ module('Integration | Component | replication page/mode-index', function (hooks) | ||||
|       assert.dom(S.enableForm).doesNotExist(); | ||||
|       assert.dom(S.summary).exists(); | ||||
|     }); | ||||
|  | ||||
|     test('it hides enable button if no permissions', async function (assert) { | ||||
|       this.clusterModel.canEnablePrimaryPerformance = false; | ||||
|       await this.renderComponent(); | ||||
|  | ||||
|       assert.dom(S.enableForm).exists(); | ||||
|       assert.dom(S.notAllowed).exists(); | ||||
|       assert.dom(S.enableBtn).doesNotExist(); | ||||
|     }); | ||||
|  | ||||
|     test('it shows enable button if has permissions', async function (assert) { | ||||
|       this.clusterModel.canEnablePrimaryPerformance = true; | ||||
|       await this.renderComponent(); | ||||
|  | ||||
|       assert.dom(S.enableForm).exists(); | ||||
|       assert.dom(S.notAllowed).doesNotExist(); | ||||
|       assert.dom(S.enableBtn).exists(); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 claire bontempo
					claire bontempo