diff --git a/ui/lib/core/addon/templates/components/replication-mode-summary.hbs b/ui/lib/core/addon/templates/components/replication-mode-summary.hbs
index e1b7fa65254a..bd2eec9950c7 100644
--- a/ui/lib/core/addon/templates/components/replication-mode-summary.hbs
+++ b/ui/lib/core/addon/templates/components/replication-mode-summary.hbs
@@ -110,16 +110,13 @@
- {{#if this.replicationDisabled}}
-
- {{else}}
-
- {{/if}}
+
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/core/addon/templates/components/replication-summary-card.hbs b/ui/lib/core/addon/templates/components/replication-summary-card.hbs
deleted file mode 100644
index f4562b523625..000000000000
--- a/ui/lib/core/addon/templates/components/replication-summary-card.hbs
+++ /dev/null
@@ -1,63 +0,0 @@
-{{!
- Copyright (c) HashiCorp, Inc.
- SPDX-License-Identifier: BUSL-1.1
-~}}
-
-
- {{! Check if DR or Performance Card }}
- {{#if (eq this.title "Disaster Recovery")}}
- {{this.title}}
-
-
- Details
-
-
-
-
last_dr_wal
-
- Index of last WAL entry written on local storage. Updates every ten seconds.
-
-
-
-
known_secondaries
-
Number of secondaries connected to this primary.
-
- {{format-number this.lastDrWAL}}
- {{format-number this.knownSecondariesDr}}
-
-
merkle_root
-
A snapshot of the merkle tree's root hash.
-
{{this.merkleRootDr}}
-
- {{else}}
- {{this.title}}
-
-
- Details
-
-
-
-
last_performance_wal
-
- Index of last WAL entry written on local storage. Updates every ten seconds.
-
-
-
-
known_secondaries
-
Number of secondaries connected to this primary.
-
- {{format-number this.lastPerformanceWAL}}
-
- {{format-number this.knownSecondariesPerformance}}
-
-
-
merkle_root
-
A snapshot of the merkle tree's root hash.
-
{{this.merkleRootPerformance}}
-
- {{/if}}
-
\ No newline at end of file
diff --git a/ui/lib/replication/addon/engine.js b/ui/lib/replication/addon/engine.js
index 2347738d9366..f3ddb560ea47 100644
--- a/ui/lib/replication/addon/engine.js
+++ b/ui/lib/replication/addon/engine.js
@@ -14,8 +14,17 @@ const Eng = Engine.extend({
modulePrefix,
Resolver,
dependencies: {
- services: ['auth', 'flash-messages', 'namespace', 'replication-mode', 'router', 'store', 'version'],
- externalRoutes: ['replication'],
+ services: [
+ 'auth',
+ 'flash-messages',
+ 'namespace',
+ 'replication-mode',
+ 'router',
+ 'store',
+ 'version',
+ '-portal',
+ ],
+ externalRoutes: ['replication', 'vault'],
},
});
diff --git a/ui/lib/replication/addon/templates/application.hbs b/ui/lib/replication/addon/templates/application.hbs
index 11a06fba0089..1e8c8ad0b3ff 100644
--- a/ui/lib/replication/addon/templates/application.hbs
+++ b/ui/lib/replication/addon/templates/application.hbs
@@ -3,4 +3,32 @@
SPDX-License-Identifier: BUSL-1.1
~}}
+
+
+
+ Replication
+
+ {{#if (not-eq this.model.mode "unsupported")}}
+ {{#if (has-feature "DR Replication")}}
+
+ {{/if}}
+ {{#if (has-feature "Performance Replication")}}
+
+ {{/if}}
+ {{/if}}
+
+
{{outlet}}
\ No newline at end of file
diff --git a/ui/lib/replication/addon/templates/components/replication-summary.hbs b/ui/lib/replication/addon/templates/components/replication-summary.hbs
index 3750ad2c5aea..7fa582ffcb1a 100644
--- a/ui/lib/replication/addon/templates/components/replication-summary.hbs
+++ b/ui/lib/replication/addon/templates/components/replication-summary.hbs
@@ -8,7 +8,7 @@
{{else if (or this.cluster.allReplicationDisabled this.cluster.replicationAttrs.replicationDisabled)}}
-
+
{{#if this.initialReplicationMode}}
{{#if (eq this.initialReplicationMode "dr")}}
Enable Disaster Recovery Replication
@@ -36,6 +36,7 @@
replicationMode=this.replicationMode
)
}}
+ data-test-replication-enable-form
>
@@ -291,7 +292,7 @@
{{#if (not (and this.cluster.dr.replicationEnabled this.cluster.performance.replicationEnabled))}}
-
+
Replication
diff --git a/ui/tests/acceptance/enterprise-replication-modes-test.js b/ui/tests/acceptance/enterprise-replication-modes-test.js
new file mode 100644
index 000000000000..c06c7181acd8
--- /dev/null
+++ b/ui/tests/acceptance/enterprise-replication-modes-test.js
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { module, test } from 'qunit';
+import { setupApplicationTest } from 'ember-qunit';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { click, visit } from '@ember/test-helpers';
+import authPage from 'vault/tests/pages/auth';
+import { STATUS_DISABLED_RESPONSE, mockReplicationBlock } from 'vault/tests/helpers/replication';
+
+const s = {
+ navLink: (title) => `[data-test-sidebar-nav-link="${title}"]`,
+ title: '[data-test-replication-title]',
+ detailLink: (mode) => `[data-test-replication-details-link="${mode}"]`,
+ summaryCard: '[data-test-replication-summary-card]',
+ dashboard: '[data-test-replication-dashboard]',
+ enableForm: '[data-test-replication-enable-form]',
+ knownSecondary: (name) => `[data-test-secondaries-node="${name}"]`,
+};
+
+module('Acceptance | Enterprise | replication modes', function (hooks) {
+ setupApplicationTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(async function () {
+ this.setupMocks = (payload) => {
+ this.server.get('sys/replication/status', () => ({
+ data: payload,
+ }));
+ return authPage.login();
+ };
+ });
+
+ test('replication page when unsupported', async function (assert) {
+ await this.setupMocks({
+ data: {
+ mode: 'unsupported',
+ },
+ });
+ await visit('/vault/replication');
+ assert.dom(s.title).hasText('Replication unsupported', 'it shows the unsupported view');
+
+ // Nav links
+ assert.dom(s.navLink('Performance')).doesNotExist('hides performance link');
+ assert.dom(s.navLink('Disaster Recovery')).doesNotExist('hides dr link');
+ });
+
+ test('replication page when disabled', async function (assert) {
+ await this.setupMocks(STATUS_DISABLED_RESPONSE);
+ await visit('/vault/replication');
+ assert.dom(s.title).hasText('Enable Replication', 'it shows the enable view');
+
+ // Nav links
+ assert.dom(s.navLink('Performance')).exists('shows performance link');
+ assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
+
+ await click(s.navLink('Performance'));
+ assert.dom(s.title).hasText('Enable Performance Replication', 'it shows the enable view for performance');
+
+ await click(s.navLink('Disaster Recovery'));
+ assert.dom(s.title).hasText('Enable Disaster Recovery Replication', 'it shows the enable view for dr');
+ });
+
+ ['primary', 'secondary'].forEach((mode) => {
+ test(`replication page when perf ${mode} only`, async function (assert) {
+ await this.setupMocks({
+ dr: mockReplicationBlock(),
+ performance: mockReplicationBlock(mode),
+ });
+ await visit('/vault/replication');
+
+ assert.dom(s.title).hasText('Replication', 'it shows default view');
+ assert.dom(s.detailLink('performance')).hasText('Details', 'CTA to see performance details');
+ assert.dom(s.detailLink('dr')).hasText('Enable', 'CTA to enable dr');
+
+ // Nav links
+ assert.dom(s.navLink('Performance')).exists('shows performance link');
+ assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
+
+ await click(s.navLink('Performance'));
+ assert.dom(s.title).hasText(`Performance ${mode}`, `it shows the performance title`);
+ assert.dom(s.dashboard).exists(`it shows the replication dashboard`);
+
+ await click(s.navLink('Disaster Recovery'));
+ assert.dom(s.title).hasText('Enable Disaster Recovery Replication', 'it shows the dr title');
+ assert.dom(s.enableForm).exists('it shows the enable view for dr');
+ });
+ });
+ // DR secondary mode is a whole other thing, test primary only here
+ test(`replication page when dr primary only`, async function (assert) {
+ await this.setupMocks({
+ dr: mockReplicationBlock('primary'),
+ performance: mockReplicationBlock(),
+ });
+ await visit('/vault/replication');
+ assert.dom(s.title).hasText('Replication', 'it shows default view');
+ assert.dom(s.detailLink('performance')).hasText('Enable', 'CTA to enable performance');
+ assert.dom(s.detailLink('dr')).hasText('Details', 'CTA to see dr details');
+
+ // Nav links
+ assert.dom(s.navLink('Performance')).exists('shows performance link');
+ assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
+
+ await click(s.navLink('Performance'));
+ assert.dom(s.title).hasText(`Enable Performance Replication`, `it shows the performance title`);
+ assert.dom(s.enableForm).exists('it shows the enable view for performance');
+
+ await click(s.navLink('Disaster Recovery'));
+ assert.dom(s.title).hasText(`Disaster Recovery primary`, 'it shows the dr title');
+ assert.dom(s.dashboard).exists(`it shows the replication dashboard`);
+ });
+
+ test(`replication page both primary`, async function (assert) {
+ await this.setupMocks({
+ dr: mockReplicationBlock('primary'),
+ performance: mockReplicationBlock('primary'),
+ });
+ await visit('/vault/replication');
+ assert.dom(s.title).hasText('Disaster Recovery & Performance primary', 'it shows primary view');
+ assert.dom(s.summaryCard).exists({ count: 2 }, 'shows 2 summary cards');
+
+ await click(s.navLink('Performance'));
+ assert.dom(s.title).hasText(`Performance primary`, `it shows the performance mode details`);
+
+ await click(s.navLink('Disaster Recovery'));
+ assert.dom(s.title).hasText(`Disaster Recovery primary`, 'it shows the dr mode details');
+ });
+});
diff --git a/ui/tests/acceptance/enterprise-replication-test.js b/ui/tests/acceptance/enterprise-replication-test.js
index 9a89204d587f..8d46ad0d9f9d 100644
--- a/ui/tests/acceptance/enterprise-replication-test.js
+++ b/ui/tests/acceptance/enterprise-replication-test.js
@@ -306,7 +306,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
.doesNotExist(`does not render replication summary card when both modes are not enabled as primary`);
// enable DR primary replication
- await click('[data-test-replication-promote-secondary]');
+ await click('[data-test-replication-details-link="dr"]');
await click('[data-test-replication-enable]');
await pollCluster(this.owner);
diff --git a/ui/tests/acceptance/enterprise-replication-unsupported-test.js b/ui/tests/acceptance/enterprise-replication-unsupported-test.js
deleted file mode 100644
index cfcb2a73c273..000000000000
--- a/ui/tests/acceptance/enterprise-replication-unsupported-test.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { module, test } from 'qunit';
-import { setupApplicationTest } from 'ember-qunit';
-import { setupMirage } from 'ember-cli-mirage/test-support';
-import authPage from 'vault/tests/pages/auth';
-import { visit } from '@ember/test-helpers';
-
-module('Acceptance | Enterprise | replication unsupported', function (hooks) {
- setupApplicationTest(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(async function () {
- this.server.get('/sys/replication/status', function () {
- return {
- data: {
- mode: 'unsupported',
- },
- };
- });
- return authPage.login();
- });
-
- test('replication page when unsupported', async function (assert) {
- await visit('/vault/replication');
- assert
- .dom('[data-test-replication-title]')
- .hasText('Replication unsupported', 'it shows the unsupported view');
- });
-});
diff --git a/ui/tests/acceptance/enterprise-sidebar-nav-test.js b/ui/tests/acceptance/enterprise-sidebar-nav-test.js
index 28e7671296dd..73246bea8291 100644
--- a/ui/tests/acceptance/enterprise-sidebar-nav-test.js
+++ b/ui/tests/acceptance/enterprise-sidebar-nav-test.js
@@ -5,7 +5,7 @@
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
-import { click, currentURL, fillIn } from '@ember/test-helpers';
+import { click, currentURL } from '@ember/test-helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
import authPage from 'vault/tests/pages/auth';
@@ -22,11 +22,15 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) {
// common links are tested in the sidebar-nav test and will not be covered here
test('it should render enterprise only navigation links', async function (assert) {
+ assert.expect(12);
assert.dom(panel('Cluster')).exists('Cluster nav panel renders');
await click(link('Replication'));
assert.strictEqual(currentURL(), '/vault/replication', 'Replication route renders');
- await click('[data-test-replication-enable]');
+ assert.dom(panel('Replication')).exists(`Replication nav panel renders`);
+ assert.dom(link('Overview')).hasClass('active', 'Overview link is active');
+ assert.dom(link('Performance')).exists('Performance link exists');
+ assert.dom(link('Disaster Recovery')).exists('DR link exists');
await click(link('Performance'));
assert.strictEqual(
@@ -37,11 +41,6 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) {
await click(link('Disaster Recovery'));
assert.strictEqual(currentURL(), '/vault/replication/dr', 'Replication dr route renders');
- // disable replication now that we have checked the links
- await click('[data-test-replication-link="manage"]');
- await click('[data-test-replication-action-trigger]');
- await fillIn('[data-test-confirmation-modal-input="Disable Replication?"]', 'Disaster Recovery');
- await click('[data-test-confirm-button="Disable Replication?"]');
await click(link('Client Count'));
assert.strictEqual(currentURL(), '/vault/clients/dashboard', 'Client counts route renders');
diff --git a/ui/tests/helpers/replication.js b/ui/tests/helpers/replication.js
index be3cc8ab0d32..3024ee9d0e82 100644
--- a/ui/tests/helpers/replication.js
+++ b/ui/tests/helpers/replication.js
@@ -34,3 +34,63 @@ export const disableReplication = async (type, assert) => {
await settled();
}
};
+
+export const STATUS_DISABLED_RESPONSE = {
+ dr: mockReplicationBlock(),
+ performance: mockReplicationBlock(),
+};
+
+/**
+ * Mock replication block returns the expected payload for a given replication type
+ * @param {string} mode disabled | primary | secondary
+ * @param {string} status connected | disconnected
+ * @returns expected object for a single replication type, eg dr or performance values
+ */
+export function mockReplicationBlock(mode = 'disabled', status = 'connected') {
+ switch (mode) {
+ case 'primary':
+ return {
+ cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
+ known_secondaries: ['4'],
+ last_wal: 455,
+ merkle_root: 'aaaaaabbbbbbbccccccccddddddd',
+ mode: 'primary',
+ primary_cluster_addr: '',
+ secondaries: [
+ {
+ api_address: 'https://127.0.0.1:49277',
+ cluster_address: 'https://127.0.0.1:49281',
+ connection_status: status,
+ last_heartbeat: '2020-06-10T15:40:46-07:00',
+ node_id: '4',
+ },
+ ],
+ state: 'stream-wals',
+ };
+ case 'secondary':
+ return {
+ cluster_id: '5f20f5ab-acea-0481-787e-71ec2ff5a60b',
+ known_primary_cluster_addrs: ['https://127.0.0.1:8201'],
+ last_remote_wal: 291,
+ merkle_root: 'aaaaaabbbbbbbccccccccddddddd',
+ corrupted_merkle_tree: false,
+ last_corruption_check_epoch: '1694456090',
+ mode: 'secondary',
+ primaries: [
+ {
+ api_address: 'https://127.0.0.1:49244',
+ cluster_address: 'https://127.0.0.1:8201',
+ connection_status: status,
+ last_heartbeat: '2020-06-10T15:40:46-07:00',
+ },
+ ],
+ primary_cluster_addr: 'https://127.0.0.1:8201',
+ secondary_id: '2',
+ state: 'stream-wals',
+ };
+ default:
+ return {
+ mode: 'disabled',
+ };
+ }
+}
diff --git a/ui/tests/integration/components/replication-summary-card-test.js b/ui/tests/integration/components/replication-summary-card-test.js
index a8ca8dafdc0b..86e98ed98cf2 100644
--- a/ui/tests/integration/components/replication-summary-card-test.js
+++ b/ui/tests/integration/components/replication-summary-card-test.js
@@ -5,7 +5,7 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
-import { render } from '@ember/test-helpers';
+import { render, settled } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
const TITLE = 'Disaster Recovery';
@@ -15,11 +15,13 @@ const REPLICATION_DETAILS = {
state: 'running',
lastWAL: 10,
knownSecondaries: ['https://127.0.0.1:8201', 'https://127.0.0.1:8202'],
+ merkleRoot: 'zzzzzzzyyyyyyyxxxxxxxwwwwww',
},
performance: {
state: 'running',
lastWAL: 20,
knownSecondaries: ['https://127.0.0.1:8201'],
+ merkleRoot: 'aaaaaabbbbbbbbccccccccdddddd',
},
};
@@ -44,6 +46,9 @@ module('Integration | Component | replication-summary-card', function (hooks) {
assert
.dom('[data-test-known-secondaries]')
.includesText(knownSecondaries, `shows the correct computed value of the known secondaries count`);
+ assert
+ .dom('[data-test-merkle-root]')
+ .includesText(REPLICATION_DETAILS.dr.merkleRoot, `shows the correct merkle root value`);
});
test('it shows the correct lastWAL and knownSecondaries when title is Performance', async function (assert) {
@@ -58,5 +63,38 @@ module('Integration | Component | replication-summary-card', function (hooks) {
assert
.dom('[data-test-known-secondaries]')
.includesText(knownSecondaries, `shows the correct computed value of the known secondaries count`);
+ assert
+ .dom('[data-test-merkle-root]')
+ .includesText(REPLICATION_DETAILS.performance.merkleRoot, `shows the correct merkle root value`);
+ });
+
+ test('it shows reasonable defaults', async function (assert) {
+ const data = {
+ dr: {
+ mode: 'disabled',
+ },
+ performance: {
+ mode: 'disabled',
+ },
+ };
+ this.set('replicationDetails', data);
+ await render(
+ hbs``
+ );
+ assert.dom('[data-test-lastWAL]').includesText('0', `shows the correct lastWAL value`);
+ assert
+ .dom('[data-test-known-secondaries]')
+ .includesText('0', `shows the correct default value of the known secondaries count`);
+ assert.dom('[data-test-merkle-root]').includesText('', `shows the correct merkle root value`);
+
+ await this.set('title', 'Performance');
+ await settled();
+ assert.dom('[data-test-lastWAL]').includesText('0', `shows the correct lastWAL value`);
+ assert
+ .dom('[data-test-known-secondaries]')
+ .includesText('0', `shows the correct default value of the known secondaries count`);
+ assert
+ .dom('[data-test-merkle-root]')
+ .includesText('no hash found', `shows the correct merkle root value`);
});
});
diff --git a/ui/tests/integration/components/sidebar/nav/cluster-test.js b/ui/tests/integration/components/sidebar/nav/cluster-test.js
index ccb9be3bcbf5..b0a477744629 100644
--- a/ui/tests/integration/components/sidebar/nav/cluster-test.js
+++ b/ui/tests/integration/components/sidebar/nav/cluster-test.js
@@ -21,7 +21,7 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
setupRenderingTest(hooks);
test('it should render nav headings', async function (assert) {
- const headings = ['Vault', 'Replication', 'Monitoring'];
+ const headings = ['Vault', 'Monitoring'];
stubFeaturesAndPermissions(this.owner, true, true);
await renderComponent();
@@ -52,8 +52,6 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
'Access',
'Policies',
'Tools',
- 'Disaster Recovery',
- 'Performance',
'Replication',
'Raft Storage',
'Client Count',