mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
UI: Update replication nav (#24283)
* replication gets its own subnav * glimmerize replication-summary-card * Simplify replication-summary-card * update replication subnav + tests * replication action block uses HDS card * add/update test selectors * test coverage * Add changelog * Update defaults on replication-summary-card * test that the view updates between replication types * typo
This commit is contained in:
3
changelog/24283.txt
Normal file
3
changelog/24283.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:change
|
||||
ui: add subnav for replication items
|
||||
```
|
||||
@@ -20,9 +20,19 @@ export default class App extends Application {
|
||||
},
|
||||
replication: {
|
||||
dependencies: {
|
||||
services: ['auth', 'flash-messages', 'namespace', 'replication-mode', 'router', 'store', 'version'],
|
||||
services: [
|
||||
'auth',
|
||||
'flash-messages',
|
||||
'namespace',
|
||||
'replication-mode',
|
||||
'router',
|
||||
'store',
|
||||
'version',
|
||||
'-portal',
|
||||
],
|
||||
externalRoutes: {
|
||||
replication: 'vault.cluster.replication.index',
|
||||
vault: 'vault.cluster',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -42,32 +42,6 @@
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if
|
||||
(and
|
||||
this.version.isEnterprise
|
||||
this.namespace.inRootNamespace
|
||||
this.cluster.anyReplicationEnabled
|
||||
(has-permission "status" routeParams="replication")
|
||||
)
|
||||
}}
|
||||
<Nav.Title data-test-sidebar-nav-heading="Replication">Replication</Nav.Title>
|
||||
<Nav.Link
|
||||
@route="vault.cluster.replication.mode.index"
|
||||
@model="dr"
|
||||
@text="Disaster Recovery"
|
||||
data-test-sidebar-nav-link="Disaster Recovery"
|
||||
/>
|
||||
|
||||
{{#if (has-feature "Performance Replication")}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.replication.mode.index"
|
||||
@model="performance"
|
||||
@text="Performance"
|
||||
data-test-sidebar-nav-link="Performance"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if
|
||||
(or
|
||||
(and
|
||||
@@ -79,7 +53,12 @@
|
||||
<Nav.Title data-test-sidebar-nav-heading="Monitoring">Monitoring</Nav.Title>
|
||||
{{/if}}
|
||||
{{#if (and this.version.isEnterprise this.namespace.inRootNamespace (has-permission "status" routeParams="replication"))}}
|
||||
<Nav.Link @route="vault.cluster.replication.index" @text="Replication" data-test-sidebar-nav-link="Replication" />
|
||||
<Nav.Link
|
||||
@route="vault.cluster.replication.index"
|
||||
@text="Replication"
|
||||
data-test-sidebar-nav-link="Replication"
|
||||
@hasSubItems={{true}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and this.cluster.usingRaft this.namespace.inRootNamespace (has-permission "status" routeParams="raft"))}}
|
||||
<Nav.Link
|
||||
|
||||
@@ -76,7 +76,9 @@ export default class ClusterModel extends Model {
|
||||
|
||||
//replication mode - will only ever be 'unsupported'
|
||||
//otherwise the particular mode will have the relevant mode attr through replication-attributes
|
||||
@attr('string') mode;
|
||||
// eg dr.mode or performance.mode
|
||||
@attr('string')
|
||||
mode;
|
||||
get allReplicationDisabled() {
|
||||
return this.dr?.replicationDisabled && this.performance?.replicationDisabled;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
margin-bottom: $spacing-24;
|
||||
}
|
||||
|
||||
.action-block-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.action-block {
|
||||
box-shadow: 0 0 0 1px rgba($grey-dark, 0.3);
|
||||
grid-template-columns: 2fr 1fr;
|
||||
display: grid;
|
||||
padding: $spacing-16 $spacing-24;
|
||||
|
||||
40
ui/lib/core/addon/components/replication-summary-card.hbs
Normal file
40
ui/lib/core/addon/components/replication-summary-card.hbs
Normal file
@@ -0,0 +1,40 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<Hds::Card::Container
|
||||
@hasBorder={{true}}
|
||||
class="replication-summary-card card-container summary"
|
||||
data-test-replication-summary-card
|
||||
>
|
||||
<h3 class="title is-5 grid-item-top-left card-title">{{@title}}</h3>
|
||||
<div class="grid-item-top-right">
|
||||
<Hds::Button
|
||||
@route="mode.index"
|
||||
@model={{this.key}}
|
||||
@text="Details"
|
||||
@color="tertiary"
|
||||
@icon="chevron-right"
|
||||
@iconPosition="trailing"
|
||||
data-test-manage-link={{@title}}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid-item-left">
|
||||
<p class="title is-6">last_dr_wal</p>
|
||||
<p class="helper-text is-label has-text-grey">
|
||||
Index of last WAL entry written on local storage. Updates every ten seconds.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item-right">
|
||||
<p class="title is-6">known_secondaries</p>
|
||||
<p class="helper-text is-label has-text-grey">Number of secondaries connected to this primary.</p>
|
||||
</div>
|
||||
<p class="title is-3 grid-item-bottom-left" data-test-lastWAL>{{format-number this.lastWAL}}</p>
|
||||
<p class="title is-3 grid-item-bottom-right" data-test-known-secondaries>{{format-number this.knownSecondariesCount}}</p>
|
||||
<div class="grid-item-bottom-row">
|
||||
<p class="title is-6">merkle_root</p>
|
||||
<p class="helper-text is-label has-text-grey">A snapshot of the merkle tree's root hash.</p>
|
||||
<div><code class="is-word-break has-text-black" data-test-merkle-root>{{this.merkleRoot}}</code></div>
|
||||
</div>
|
||||
</Hds::Card::Container>
|
||||
@@ -3,9 +3,8 @@
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import layout from '../templates/components/replication-summary-card';
|
||||
import { get } from '@ember/object';
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
/**
|
||||
* @module ReplicationSummaryCard
|
||||
@@ -22,28 +21,17 @@ import layout from '../templates/components/replication-summary-card';
|
||||
* @param {Object} replicationDetails=null - An Ember data object computed off the Ember Model. It combines the Model.dr and Model.performance objects into one and contains details specific to the mode replication.
|
||||
*/
|
||||
|
||||
export default Component.extend({
|
||||
layout,
|
||||
title: null,
|
||||
replicationDetails: null,
|
||||
lastDrWAL: computed('replicationDetails.dr.lastWAL', function () {
|
||||
return this.replicationDetails.dr.lastWAL || 0;
|
||||
}),
|
||||
lastPerformanceWAL: computed('replicationDetails.performance.lastWAL', function () {
|
||||
return this.replicationDetails.performance.lastWAL || 0;
|
||||
}),
|
||||
merkleRootDr: computed('replicationDetails.dr.merkleRoot', function () {
|
||||
return this.replicationDetails.dr.merkleRoot || '';
|
||||
}),
|
||||
merkleRootPerformance: computed('replicationDetails.performance.merkleRoot', function () {
|
||||
return this.replicationDetails.performance.merkleRoot || '';
|
||||
}),
|
||||
knownSecondariesDr: computed('replicationDetails.dr.knownSecondaries', function () {
|
||||
const knownSecondaries = this.replicationDetails.dr.knownSecondaries;
|
||||
return knownSecondaries.length;
|
||||
}),
|
||||
knownSecondariesPerformance: computed('replicationDetails.performance.knownSecondaries', function () {
|
||||
const knownSecondaries = this.replicationDetails.performance.knownSecondaries;
|
||||
return knownSecondaries.length;
|
||||
}),
|
||||
});
|
||||
export default class ReplicationSummaryCard extends Component {
|
||||
get key() {
|
||||
return this.args.title === 'Performance' ? 'performance' : 'dr';
|
||||
}
|
||||
get lastWAL() {
|
||||
return get(this.args.replicationDetails, `${this.key}.lastWAL`) || 0;
|
||||
}
|
||||
get merkleRoot() {
|
||||
return get(this.args.replicationDetails, `${this.key}.merkleRoot`) || 'no hash found';
|
||||
}
|
||||
get knownSecondariesCount() {
|
||||
return get(this.args.replicationDetails, `${this.key}.knownSecondaries.length`) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
as |replicationAction|
|
||||
}}
|
||||
<div class="replication-actions-grid-item">
|
||||
{{component
|
||||
(concat "replication-action-" replicationAction)
|
||||
onSubmit=(action "onSubmit")
|
||||
replicationMode=this.replicationMode
|
||||
model=this.model
|
||||
}}
|
||||
<Hds::Card::Container class="action-block-width" @hasBorder={{true}}>
|
||||
{{component
|
||||
(concat "replication-action-" replicationAction)
|
||||
onSubmit=(action "onSubmit")
|
||||
replicationMode=this.replicationMode
|
||||
model=this.model
|
||||
}}
|
||||
</Hds::Card::Container>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
{{/unless}}
|
||||
</p.top>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
<h1 class="title is-3" data-test-replication-title>
|
||||
{{this.title}}
|
||||
{{#if this.data.anyReplicationEnabled}}
|
||||
<span class="tag is-light has-text-grey-dark" data-test-mode>
|
||||
|
||||
@@ -110,16 +110,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
{{#if this.replicationDisabled}}
|
||||
<Hds::Button
|
||||
@text="Enable"
|
||||
@route="mode.index"
|
||||
@models={{array this.cluster.name this.mode}}
|
||||
data-test-replication-promote-secondary
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::Button @text="Details" @color="secondary" @route="mode.index" @models={{array this.cluster.name this.mode}} />
|
||||
{{/if}}
|
||||
<Hds::Button
|
||||
@route="mode.index"
|
||||
@models={{array this.cluster.name this.mode}}
|
||||
@color={{if this.replicationDisabled "primary" "secondary"}}
|
||||
@text={{if this.replicationDisabled "Enable" "Details"}}
|
||||
data-test-replication-details-link={{this.mode}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -1,63 +0,0 @@
|
||||
{{!
|
||||
Copyright (c) HashiCorp, Inc.
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<Hds::Card::Container
|
||||
@hasBorder={{true}}
|
||||
class="replication-summary-card card-container summary {{if this.hasErrorClass 'has-error-border'}}"
|
||||
data-test-replication-summary-card
|
||||
>
|
||||
{{! Check if DR or Performance Card }}
|
||||
{{#if (eq this.title "Disaster Recovery")}}
|
||||
<h3 class="title is-5 grid-item-top-left card-title">{{this.title}}</h3>
|
||||
<div class="grid-item-top-right">
|
||||
<ToolbarLink @route="mode.index" @model="dr" data-test-manage-link={{this.title}}>
|
||||
Details
|
||||
</ToolbarLink>
|
||||
</div>
|
||||
<div class="grid-item-left">
|
||||
<h6 class="title is-6">last_dr_wal</h6>
|
||||
<p class="helper-text is-label has-text-grey">
|
||||
Index of last WAL entry written on local storage. Updates every ten seconds.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item-right">
|
||||
<h6 class="title is-6">known_secondaries</h6>
|
||||
<p class="helper-text is-label has-text-grey">Number of secondaries connected to this primary.</p>
|
||||
</div>
|
||||
<h2 class="title is-3 grid-item-bottom-left" data-test-lastWAL>{{format-number this.lastDrWAL}}</h2>
|
||||
<h2 class="title is-3 grid-item-bottom-right" data-test-known-secondaries>{{format-number this.knownSecondariesDr}}</h2>
|
||||
<div class="grid-item-bottom-row">
|
||||
<h6 class="title is-6">merkle_root</h6>
|
||||
<p class="helper-text is-label has-text-grey">A snapshot of the merkle tree's root hash.</p>
|
||||
<div><code class="is-word-break has-text-black">{{this.merkleRootDr}}</code></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<h3 class="title is-5 grid-item-top-left card-title">{{this.title}}</h3>
|
||||
<div class="grid-item-top-right">
|
||||
<ToolbarLink @route="mode.index" @model="performance" data-test-manage-link={{this.title}}>
|
||||
Details
|
||||
</ToolbarLink>
|
||||
</div>
|
||||
<div class="grid-item-left">
|
||||
<h6 class="title is-6">last_performance_wal</h6>
|
||||
<p class="helper-text is-label has-text-grey">
|
||||
Index of last WAL entry written on local storage. Updates every ten seconds.
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid-item-right">
|
||||
<h6 class="title is-6">known_secondaries</h6>
|
||||
<p class="helper-text is-label has-text-grey">Number of secondaries connected to this primary.</p>
|
||||
</div>
|
||||
<h2 class="title is-3 grid-item-bottom-left" data-test-lastWAL>{{format-number this.lastPerformanceWAL}}</h2>
|
||||
<h2 class="title is-3 grid-item-bottom-right" data-test-known-secondaries>
|
||||
{{format-number this.knownSecondariesPerformance}}
|
||||
</h2>
|
||||
<div class="grid-item-bottom-row">
|
||||
<h6 class="title is-6">merkle_root</h6>
|
||||
<p class="helper-text is-label has-text-grey">A snapshot of the merkle tree's root hash.</p>
|
||||
<div><code class="is-word-break has-text-black">{{this.merkleRootPerformance}}</code></div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</Hds::Card::Container>
|
||||
@@ -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'],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,4 +3,32 @@
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
~}}
|
||||
|
||||
<Hds::SideNav::Portal @ariaLabel="Replication Navigation Links" data-test-sidebar-nav-panel="Replication" as |Nav|>
|
||||
<Nav.BackLink
|
||||
@route="vault"
|
||||
@current-when={{false}}
|
||||
@isRouteExternal={{true}}
|
||||
@icon="arrow-left"
|
||||
@text="Back to main navigation"
|
||||
data-test-sidebar-nav-link="Back to main navigation"
|
||||
/>
|
||||
|
||||
<Nav.Title data-test-sidebar-nav-heading="Replication">Replication</Nav.Title>
|
||||
<Nav.Link
|
||||
@route="replication"
|
||||
@current-when="replication"
|
||||
@isRouteExternal={{true}}
|
||||
@text="Overview"
|
||||
data-test-sidebar-nav-link="Overview"
|
||||
/>
|
||||
{{#if (not-eq this.model.mode "unsupported")}}
|
||||
{{#if (has-feature "DR Replication")}}
|
||||
<Nav.Link @route="mode" @model="dr" @text="Disaster Recovery" data-test-sidebar-nav-link="Disaster Recovery" />
|
||||
{{/if}}
|
||||
{{#if (has-feature "Performance Replication")}}
|
||||
<Nav.Link @route="mode" @model="performance" @text="Performance" data-test-sidebar-nav-link="Performance" />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</Hds::SideNav::Portal>
|
||||
|
||||
{{outlet}}
|
||||
@@ -8,7 +8,7 @@
|
||||
{{else if (or this.cluster.allReplicationDisabled this.cluster.replicationAttrs.replicationDisabled)}}
|
||||
<PageHeader as |p|>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
<h1 class="title is-3" data-test-replication-title>
|
||||
{{#if this.initialReplicationMode}}
|
||||
{{#if (eq this.initialReplicationMode "dr")}}
|
||||
Enable Disaster Recovery Replication
|
||||
@@ -36,6 +36,7 @@
|
||||
replicationMode=this.replicationMode
|
||||
)
|
||||
}}
|
||||
data-test-replication-enable-form
|
||||
>
|
||||
<div class="box is-sideless is-fullwidth is-marginless">
|
||||
<MessageError @errors={{this.errors}} />
|
||||
@@ -291,7 +292,7 @@
|
||||
{{#if (not (and this.cluster.dr.replicationEnabled this.cluster.performance.replicationEnabled))}}
|
||||
<PageHeader as |p|>
|
||||
<p.levelLeft>
|
||||
<h1 class="title is-3">
|
||||
<h1 class="title is-3" data-test-replication-title>
|
||||
Replication
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
|
||||
130
ui/tests/acceptance/enterprise-replication-modes-test.js
Normal file
130
ui/tests/acceptance/enterprise-replication-modes-test.js
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
|
||||
@@ -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',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`<ReplicationSummaryCard @replicationDetails={{this.replicationDetails}} @title={{this.title}} />`
|
||||
);
|
||||
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`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user