{{#if (eq this.selectedLoadMethod this.loadMethods.AUTOMATED)}}
@@ -76,7 +74,13 @@
>
Snapshot configuration name
- Name of the configuration that created the snapshot. Existing automated snapshots should be configured via /sys
+ Name of the configuration that created the snapshot. Existing automated snapshots should be configured via the
+ automated snapshots config
endpoint.
{{F.options}}
@@ -94,7 +98,13 @@
>
Snapshot configuration name
- Name of the configuration that created the snapshot. Existing automated snapshots should be configured via /sys
+ Name of the configuration that created the snapshot. Existing automated snapshots should be configured via the
+ automated snapshots config
endpoint.
{{#if this.configError}}
@@ -123,7 +133,7 @@
{{else}}
-
Recover or read data
+
Recover or read data
{{#if this.recoveryData}}
Success
@@ -82,6 +82,7 @@
{{/if}}
{{! Allow for manual entry of mount paths if no mounts are returned (such as when the user does not have LIST permissions) }}
+ {{!-- {{#if (or this.mountOptions this.fetchMounts.isRunning)}} --}}
{{#if this.mountOptions}}
;
@@ -19,24 +19,26 @@ export default class RecoverySnapshotsRoute extends Route {
@service declare readonly api: ApiService;
@service declare readonly capabilities: Capabilities;
@service declare readonly router: RouterService;
+ @service declare readonly version: VersionService;
async model() {
- const { canUpdate } = await this.capabilities.fetchPathCapabilities(
- 'sys/storage/raft/snapshot/snapshot-load'
- );
+ if (this.version.isEnterprise) {
+ const { canUpdate } = await this.capabilities.fetchPathCapabilities(
+ 'sys/storage/raft/snapshot/snapshot-load'
+ );
- const snapshots = await this.fetchSnapshots();
+ const snapshots = await this.fetchSnapshots();
- return {
- snapshots,
- canLoadSnapshot: canUpdate,
- };
+ return {
+ snapshots,
+ canLoadSnapshot: canUpdate,
+ };
+ }
+ return { snapshots: [], showCommunityMessage: true };
}
- afterModel(model: SnapshotsRouteModel, transition: Transition) {
- // Don't redirect if we're already on details route
- const toRoute = transition.to?.name;
- if (model.snapshots.length === 1 && toRoute !== 'vault.cluster.recovery.snapshots.snapshot.details') {
+ redirect(model: SnapshotsRouteModel) {
+ if (model.snapshots.length === 1) {
const snapshot_id = model.snapshots[0];
this.router.transitionTo('vault.cluster.recovery.snapshots.snapshot.manage', snapshot_id);
}
diff --git a/ui/app/routes/vault/cluster/recovery/snapshots/index.ts b/ui/app/routes/vault/cluster/recovery/snapshots/index.ts
index e009c08924..7cd46f07ca 100644
--- a/ui/app/routes/vault/cluster/recovery/snapshots/index.ts
+++ b/ui/app/routes/vault/cluster/recovery/snapshots/index.ts
@@ -7,17 +7,13 @@ import Route from '@ember/routing/route';
import { service } from '@ember/service';
import type RouterService from '@ember/routing/router-service';
-import type { SnapshotsRouteModel } from '../snapshots';
-
export default class RecoverySnapshotsIndexRoute extends Route {
@service declare readonly router: RouterService;
- beforeModel() {
- const parentModel = this.modelFor('vault.cluster.recovery.snapshots') as SnapshotsRouteModel;
-
- if (parentModel.snapshots.length === 1) {
- const snapshot_id = parentModel.snapshots[0];
- this.router.transitionTo('vault.cluster.recovery.snapshots.snapshot.manage', snapshot_id);
- }
+ // There is not a recovery.snapshots.index view because currently only one snapshot can be loaded at a time.
+ // Redirect to the parent route so we can reuse its logic and send users to "recovery.snapshots.snapshot.manage"
+ // if a snapshot is loaded.
+ redirect() {
+ this.router.transitionTo('vault.cluster.recovery.snapshots');
}
}
diff --git a/ui/app/templates/vault/cluster/recovery/error.hbs b/ui/app/templates/vault/cluster/recovery/error.hbs
index b64675c169..7e6a3297ee 100644
--- a/ui/app/templates/vault/cluster/recovery/error.hbs
+++ b/ui/app/templates/vault/cluster/recovery/error.hbs
@@ -12,9 +12,9 @@
{{#if (eq this.model.message "raft storage is not in use")}}
-
-
-
+
+
+
this.server.create('configuration', 'withRaft'));
-
return login();
});
- test('it renders empty state when no snapshots are loaded', async function (assert) {
+ test('enterprise: it renders empty state when raft storage is not in use', async function (assert) {
this.server.get('/sys/storage/raft/snapshot-load', () => {
- return new Response(404, { 'Content-Type': 'application/json' }, JSON.stringify({ errors: [] }));
+ return overrideResponse(400, JSON.stringify({ errors: ['raft storage is not in use'] }));
+ });
+ await visit('/vault/recovery/snapshots');
+ assert.strictEqual(currentURL(), '/vault/recovery/snapshots');
+ assert.dom('header').exists('it renders header despite route throwing an error');
+ assert.dom(GENERAL.emptyStateTitle).hasText('Raft storage required');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText('Raft storage must be used in order to recover data from a snapshot.');
+ assert.dom(GENERAL.emptyStateActions).hasText('Snapshot management');
+ });
+
+ test('it renders promo for community versions', async function (assert) {
+ const version = this.owner.lookup('service:version');
+ version.type = 'community';
+ this.server.get('/sys/storage/raft/snapshot-load', () => {
+ // This assertion is intentionally setup to fail if a request is made to this endpoint
+ // because community versions should NOT request the snapshot-load endpoint
+ assert.true(false, 'it does not make a request to snapshot-load on CE versions');
});
await visit('/vault/recovery/snapshots');
-
+ assert
+ .dom(`${GENERAL.navLink('Secrets Recovery')} .hds-badge`)
+ .hasText('Enterprise', 'side nav link renders "Enterprise" badge');
assert.strictEqual(currentURL(), '/vault/recovery/snapshots');
-
- assert.dom(GENERAL.emptyStateTitle).hasText('Upload a snapshot to get started');
- assert.dom(GENERAL.emptyStateActions).hasText('Upload snapshot');
+ assert.dom(GENERAL.emptyStateTitle).hasText('Secrets Recovery is an enterprise feature');
+ assert
+ .dom(GENERAL.emptyStateMessage)
+ .hasText(
+ 'Secrets Recovery allows you to restore accidentally deleted or lost secrets from a snapshot. The snapshots can be provided via upload or loaded from external storage.'
+ );
+ assert.dom(GENERAL.emptyStateActions).hasText('Learn more about upgrading');
+ assert.dom(GENERAL.badge('enterprise')).exists();
});
- test('it redirects to snapshot route when a snapshot is loaded', async function (assert) {
- this.server.get('/sys/storage/raft/snapshot-load', () => {
- return { data: { keys: ['1234'] } };
+ module('enterprise: with raft configured', function (hooks) {
+ hooks.beforeEach(function () {
+ this.server.get('/sys/storage/raft/configuration', () =>
+ this.server.create('configuration', 'withRaft')
+ );
});
- this.server.get('/sys/storage/raft/snapshot-load/1234', () => {
- return {
- data: {
- status: 'ready',
- expires_at: new Date(),
- snapshot_id: '1234',
- },
- };
+ test('it renders empty state when no snapshots are loaded', async function (assert) {
+ this.server.get('/sys/storage/raft/snapshot-load', () => {
+ return new Response(404, { 'Content-Type': 'application/json' }, JSON.stringify({ errors: [] }));
+ });
+
+ await visit('/vault/recovery/snapshots');
+
+ assert.strictEqual(currentURL(), '/vault/recovery/snapshots');
+
+ assert.dom(GENERAL.emptyStateTitle).hasText('Upload a snapshot to get started');
+ assert.dom(GENERAL.emptyStateActions).hasText('Upload snapshot');
});
- await visit('vault/recovery/snapshots');
+ test('it redirects to snapshot route when a snapshot is loaded', async function (assert) {
+ this.server.get('/sys/storage/raft/snapshot-load', () => {
+ return { data: { keys: ['1234'] } };
+ });
- assert.strictEqual(currentURL(), '/vault/recovery/snapshots/1234/manage');
- assert.strictEqual(currentRouteName(), 'vault.cluster.recovery.snapshots.snapshot.manage');
+ this.server.get('/sys/storage/raft/snapshot-load/1234', () => {
+ return {
+ data: {
+ status: 'ready',
+ expires_at: addDays(new Date(), 3).toISOString(),
+ snapshot_id: '1234',
+ },
+ };
+ });
+
+ await click(GENERAL.navLink('Secrets Recovery'));
+ assert.strictEqual(currentURL(), '/vault/recovery/snapshots/1234/manage');
+ assert.strictEqual(currentRouteName(), 'vault.cluster.recovery.snapshots.snapshot.manage');
+ });
});
});
diff --git a/ui/tests/helpers/general-selectors.ts b/ui/tests/helpers/general-selectors.ts
index 189365641f..625f301f1e 100644
--- a/ui/tests/helpers/general-selectors.ts
+++ b/ui/tests/helpers/general-selectors.ts
@@ -170,4 +170,5 @@ export const GENERAL = {
/* ────── Misc ────── */
icon: (name: string) => (name ? `[data-test-icon="${name}"]` : '[data-test-icon]'),
+ badge: (name: string) => (name ? `[data-test-badge="${name}"]` : '[data-test-badge]'),
};
diff --git a/ui/tests/integration/components/recovery/page/snapshot-manage-test.js b/ui/tests/integration/components/recovery/page/snapshot-manage-test.js
index a0c37a3fb4..bec7b55cf9 100644
--- a/ui/tests/integration/components/recovery/page/snapshot-manage-test.js
+++ b/ui/tests/integration/components/recovery/page/snapshot-manage-test.js
@@ -12,10 +12,6 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import recoveryHandler from 'vault/mirage/handlers/recovery';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
-const SELECTORS = {
- badge: (name) => `[data-test-badge="${name}"]`,
-};
-
module('Integration | Component | recovery/snapshots/snapshot-manage', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
@@ -45,7 +41,7 @@ module('Integration | Component | recovery/snapshots/snapshot-manage', function
test('it displays loaded snapshot card', async function (assert) {
await render(hbs``);
- assert.dom(SELECTORS.badge('status')).hasText('Ready', 'status badge renders');
+ assert.dom(GENERAL.badge('status')).hasText('Ready', 'status badge renders');
});
test('it displays namespace selector for root namespace', async function (assert) {
diff --git a/ui/tests/integration/components/recovery/page/snapshots-test.js b/ui/tests/integration/components/recovery/page/snapshots-test.js
index c75f7fa970..4c405c47ed 100644
--- a/ui/tests/integration/components/recovery/page/snapshots-test.js
+++ b/ui/tests/integration/components/recovery/page/snapshots-test.js
@@ -12,10 +12,6 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
-const SELECTORS = {
- badge: (name) => `[data-test-badge="${name}"]`,
-};
-
module('Integration | Component | recovery/snapshots', function (hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
@@ -25,12 +21,12 @@ module('Integration | Component | recovery/snapshots', function (hooks) {
snapshots: [],
canLoadSnapshot: false,
};
- this.version = this.owner.lookup('service:version');
- this.version.type = 'enterprise';
+ this.renderComponent = () => render(hbs``);
});
test('it displays empty state in CE', async function (assert) {
- this.version.type = 'community';
+ this.model = { snapshots: [], showCommunityMessage: true };
+
await render(hbs``);
assert
.dom(GENERAL.emptyStateTitle)
@@ -45,14 +41,14 @@ module('Integration | Component | recovery/snapshots', function (hooks) {
.dom(GENERAL.emptyStateActions)
.hasText('Learn more about upgrading', 'CE empty state action renders');
- assert.dom(SELECTORS.badge('enterprise')).exists();
+ assert.dom(GENERAL.badge('enterprise')).exists();
});
test('it displays empty state in non root namespace', async function (assert) {
const nsService = this.owner.lookup('service:namespace');
nsService.setNamespace('test-ns');
- await render(hbs``);
+ await this.renderComponent();
assert
.dom(GENERAL.emptyStateTitle)
@@ -69,7 +65,7 @@ module('Integration | Component | recovery/snapshots', function (hooks) {
});
test('it displays empty state when user cannot load snapshot', async function (assert) {
- await render(hbs``);
+ await this.renderComponent();
assert.dom(GENERAL.emptyStateTitle).hasText('No snapshot available', 'empty state title renders');
assert
.dom(GENERAL.emptyStateMessage)
@@ -84,7 +80,7 @@ module('Integration | Component | recovery/snapshots', function (hooks) {
test('it displays empty state when user can load snapshot', async function (assert) {
this.model.canLoadSnapshot = true;
- await render(hbs``);
+ await this.renderComponent();
assert
.dom(GENERAL.emptyStateTitle)
.hasText('Upload a snapshot to get started', 'empty state title renders');