- {{#if @model.version.isEnterprise}}
+ {{#if (and @model.version.isEnterprise (or @model.license @model.isRootNamespace))}}
{{#if @model.license}}
diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js
index 6161b84e3cb2..f426d1f113dc 100644
--- a/ui/tests/acceptance/dashboard-test.js
+++ b/ui/tests/acceptance/dashboard-test.js
@@ -4,135 +4,89 @@
*/
import { module, test } from 'qunit';
-import { visit, currentURL } from '@ember/test-helpers';
+import {
+ visit,
+ currentURL,
+ settled,
+ fillIn,
+ click,
+ waitUntil,
+ find,
+ currentRouteName,
+} from '@ember/test-helpers';
import { setupApplicationTest } from 'vault/tests/helpers';
import { setupMirage } from 'ember-cli-mirage/test-support';
+import { create } from 'ember-cli-page-object';
+import { selectChoose } from 'ember-power-select/test-support/helpers';
+import { runCommands } from 'vault/tests/helpers/pki/pki-run-commands';
+import { deleteEngineCmd } from 'vault/tests/helpers/commands';
import authPage from 'vault/tests/pages/auth';
+import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
+import consoleClass from 'vault/tests/pages/components/console/ui-panel';
+import ENV from 'vault/config/environment';
+import { formatNumber } from 'core/helpers/format-number';
+import { pollCluster } from 'vault/tests/helpers/poll-cluster';
+import { disableReplication } from 'vault/tests/helpers/replication';
+import connectionPage from 'vault/tests/pages/secrets/backend/database/connection';
+
+// selectors
import SECRETS_ENGINE_SELECTORS from 'vault/tests/helpers/components/dashboard/secrets-engines-card';
import VAULT_CONFIGURATION_SELECTORS from 'vault/tests/helpers/components/dashboard/vault-configuration-details-card';
-import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
+import QUICK_ACTION_SELECTORS from 'vault/tests/helpers/components/dashboard/quick-actions-card';
+import REPLICATION_CARD_SELECTORS from 'vault/tests/helpers/components/dashboard/replication-card';
+
+const consoleComponent = create(consoleClass);
module('Acceptance | landing page dashboard', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
- hooks.beforeEach(function () {
- this.data = {
- api_addr: 'http://127.0.0.1:8200',
- cache_size: 0,
- cluster_addr: 'https://127.0.0.1:8201',
- cluster_cipher_suites: '',
- cluster_name: '',
- default_lease_ttl: 0,
- default_max_request_duration: 0,
- detect_deadlocks: '',
- disable_cache: false,
- disable_clustering: false,
- disable_indexing: false,
- disable_mlock: true,
- disable_performance_standby: false,
- disable_printable_check: false,
- disable_sealwrap: false,
- disable_sentinel_trace: false,
- enable_response_header_hostname: false,
- enable_response_header_raft_node_id: false,
- enable_ui: true,
- experiments: null,
- introspection_endpoint: false,
- listeners: [
- {
- config: {
- address: '0.0.0.0:8200',
- cluster_address: '0.0.0.0:8201',
- tls_disable: true,
- },
- type: 'tcp',
- },
- ],
- log_format: '',
- log_level: 'debug',
- log_requests_level: '',
- max_lease_ttl: '48h',
- pid_file: '',
- plugin_directory: '',
- plugin_file_permissions: 0,
- plugin_file_uid: 0,
- raw_storage_endpoint: true,
- seals: [
- {
- disabled: false,
- type: 'shamir',
- },
- ],
- storage: {
- cluster_addr: 'https://127.0.0.1:8201',
- disable_clustering: false,
- raft: {
- max_entry_size: '',
- },
- redirect_addr: 'http://127.0.0.1:8200',
- type: 'raft',
- },
- telemetry: {
- add_lease_metrics_namespace_labels: false,
- circonus_api_app: '',
- circonus_api_token: '',
- circonus_api_url: '',
- circonus_broker_id: '',
- circonus_broker_select_tag: '',
- circonus_check_display_name: '',
- circonus_check_force_metric_activation: '',
- circonus_check_id: '',
- circonus_check_instance_id: '',
- circonus_check_search_tag: '',
- circonus_check_tags: '',
- circonus_submission_interval: '',
- circonus_submission_url: '',
- disable_hostname: true,
- dogstatsd_addr: '',
- dogstatsd_tags: null,
- lease_metrics_epsilon: 3600000000000,
- maximum_gauge_cardinality: 500,
- metrics_prefix: '',
- num_lease_metrics_buckets: 168,
- prometheus_retention_time: 86400000000000,
- stackdriver_debug_logs: false,
- stackdriver_location: '',
- stackdriver_namespace: '',
- stackdriver_project_id: '',
- statsd_address: '',
- statsite_address: '',
- usage_gauge_period: 5000000000,
- },
- };
- return authPage.login();
- });
-
test('navigate to dashboard on login', async function (assert) {
+ await authPage.login();
assert.strictEqual(currentURL(), '/vault/dashboard');
});
test('display the version number for the title', async function (assert) {
+ await authPage.login();
await visit('/vault/dashboard');
- assert.dom('[data-test-dashboard-version-header]').hasText('Vault v1.9.0 root');
+ const version = this.owner.lookup('service:version');
+ const versionName = version.version;
+ const versionNameEnd = version.isEnterprise ? versionName.indexOf('+') : versionName.length;
+ assert
+ .dom('[data-test-dashboard-version-header]')
+ .hasText(`Vault v${versionName.slice(0, versionNameEnd)} root`);
});
- module('secrets engines card', function () {
+ module('secrets engines card', function (hooks) {
+ hooks.beforeEach(async function () {
+ await authPage.login();
+ });
+
test('shows a secrets engine card', async function (assert) {
await mountSecrets.enable('pki', 'pki');
+ await settled();
await visit('/vault/dashboard');
assert.dom(SECRETS_ENGINE_SELECTORS.cardTitle).hasText('Secrets engines');
- assert.dom(SECRETS_ENGINE_SELECTORS.getSecretEngineAccessor('pki')).exists();
+ assert.dom('[data-test-secrets-engines-card-show-all]').doesNotExist();
+ // cleanup engine mount
+ await consoleComponent.runCommands(deleteEngineCmd('pki'));
});
test('it adds disabled css styling to unsupported secret engines', async function (assert) {
await mountSecrets.enable('nomad', 'nomad');
+ await settled();
await visit('/vault/dashboard');
assert.dom('[data-test-secrets-engines-row="nomad"] [data-test-view]').doesNotExist();
+ assert.dom('[data-test-secrets-engines-card-show-all]').doesNotExist();
+ // cleanup engine mount
+ await consoleComponent.runCommands(deleteEngineCmd('nomad'));
});
});
- module('learn more card', function () {
+ module('learn more card', function (hooks) {
+ hooks.beforeEach(function () {
+ return authPage.login();
+ });
test('shows the learn more card', async function (assert) {
await visit('/vault/dashboard');
assert.dom('[data-test-learn-more-title]').hasText('Learn more');
@@ -144,11 +98,103 @@ module('Acceptance | landing page dashboard', function (hooks) {
assert.dom('[data-test-learn-more-links] a').exists({ count: 4 });
assert
.dom('[data-test-feedback-form]')
- .hasText("Don't see what you're looking for on this page? Let us know via our feedback form. ");
+ .hasText("Don't see what you're looking for on this page? Let us know via our feedback form .");
});
});
- module('configuration details card', function () {
+ module('configuration details card', function (hooks) {
+ hooks.beforeEach(async function () {
+ this.data = {
+ api_addr: 'http://127.0.0.1:8200',
+ cache_size: 0,
+ cluster_addr: 'https://127.0.0.1:8201',
+ cluster_cipher_suites: '',
+ cluster_name: '',
+ default_lease_ttl: 0,
+ default_max_request_duration: 0,
+ detect_deadlocks: '',
+ disable_cache: false,
+ disable_clustering: false,
+ disable_indexing: false,
+ disable_mlock: true,
+ disable_performance_standby: false,
+ disable_printable_check: false,
+ disable_sealwrap: false,
+ disable_sentinel_trace: false,
+ enable_response_header_hostname: false,
+ enable_response_header_raft_node_id: false,
+ enable_ui: true,
+ experiments: null,
+ introspection_endpoint: false,
+ listeners: [
+ {
+ config: {
+ address: '0.0.0.0:8200',
+ cluster_address: '0.0.0.0:8201',
+ tls_disable: true,
+ },
+ type: 'tcp',
+ },
+ ],
+ log_format: '',
+ log_level: 'debug',
+ log_requests_level: '',
+ max_lease_ttl: '48h',
+ pid_file: '',
+ plugin_directory: '',
+ plugin_file_permissions: 0,
+ plugin_file_uid: 0,
+ raw_storage_endpoint: true,
+ seals: [
+ {
+ disabled: false,
+ type: 'shamir',
+ },
+ ],
+ storage: {
+ cluster_addr: 'https://127.0.0.1:8201',
+ disable_clustering: false,
+ raft: {
+ max_entry_size: '',
+ },
+ redirect_addr: 'http://127.0.0.1:8200',
+ type: 'raft',
+ },
+ telemetry: {
+ add_lease_metrics_namespace_labels: false,
+ circonus_api_app: '',
+ circonus_api_token: '',
+ circonus_api_url: '',
+ circonus_broker_id: '',
+ circonus_broker_select_tag: '',
+ circonus_check_display_name: '',
+ circonus_check_force_metric_activation: '',
+ circonus_check_id: '',
+ circonus_check_instance_id: '',
+ circonus_check_search_tag: '',
+ circonus_check_tags: '',
+ circonus_submission_interval: '',
+ circonus_submission_url: '',
+ disable_hostname: true,
+ dogstatsd_addr: '',
+ dogstatsd_tags: null,
+ lease_metrics_epsilon: 3600000000000,
+ maximum_gauge_cardinality: 500,
+ metrics_prefix: '',
+ num_lease_metrics_buckets: 168,
+ prometheus_retention_time: 86400000000000,
+ stackdriver_debug_logs: false,
+ stackdriver_location: '',
+ stackdriver_namespace: '',
+ stackdriver_project_id: '',
+ statsd_address: '',
+ statsite_address: '',
+ usage_gauge_period: 5000000000,
+ },
+ };
+ await authPage.login();
+ });
+
test('shows the configuration details card', async function (assert) {
this.server.get('sys/config/state/sanitized', () => ({
data: this.data,
@@ -197,4 +243,216 @@ module('Acceptance | landing page dashboard', function (hooks) {
assert.dom(VAULT_CONFIGURATION_SELECTORS.tlsDisable).hasText('Disabled');
});
});
+
+ module('quick actions card', function (hooks) {
+ hooks.beforeEach(async function () {
+ await authPage.login();
+ });
+
+ test('shows the default state of the quick actions card', async function (assert) {
+ assert.dom(QUICK_ACTION_SELECTORS.emptyState).exists();
+ });
+
+ test('shows the correct actions and links associated with pki', async function (assert) {
+ await mountSecrets.enable('pki', 'pki');
+ await runCommands([
+ `write pki/roles/some-role \
+ issuer_ref="default" \
+ allowed_domains="example.com" \
+ allow_subdomains=true \
+ max_ttl="720h"`,
+ ]);
+ await runCommands([`write pki/root/generate/internal issuer_name="Hashicorp" common_name="Hello"`]);
+ await settled();
+ await visit('/vault/dashboard');
+ await selectChoose(QUICK_ACTION_SELECTORS.secretsEnginesSelect, 'pki');
+ await fillIn(QUICK_ACTION_SELECTORS.actionSelect, 'Issue certificate');
+ assert.dom(QUICK_ACTION_SELECTORS.emptyState).doesNotExist();
+ assert.dom(QUICK_ACTION_SELECTORS.paramsTitle).hasText('Role to use');
+
+ await selectChoose(QUICK_ACTION_SELECTORS.paramSelect, 'some-role');
+ assert.dom(QUICK_ACTION_SELECTORS.getActionButton('Issue leaf certificate')).exists({ count: 1 });
+ await click(QUICK_ACTION_SELECTORS.getActionButton('Issue leaf certificate'));
+ assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.roles.role.generate');
+
+ await visit('/vault/dashboard');
+
+ await selectChoose(QUICK_ACTION_SELECTORS.secretsEnginesSelect, 'pki');
+ await fillIn(QUICK_ACTION_SELECTORS.actionSelect, 'View certificate');
+ assert.dom(QUICK_ACTION_SELECTORS.emptyState).doesNotExist();
+ assert.dom(QUICK_ACTION_SELECTORS.paramsTitle).hasText('Certificate serial number');
+ assert.dom(QUICK_ACTION_SELECTORS.getActionButton('View certificate')).exists({ count: 1 });
+ await selectChoose(QUICK_ACTION_SELECTORS.paramSelect, '.ember-power-select-option', 0);
+ await click(QUICK_ACTION_SELECTORS.getActionButton('View certificate'));
+ assert.strictEqual(
+ currentRouteName(),
+ 'vault.cluster.secrets.backend.pki.certificates.certificate.details'
+ );
+
+ await visit('/vault/dashboard');
+
+ await selectChoose(QUICK_ACTION_SELECTORS.secretsEnginesSelect, 'pki');
+ await fillIn(QUICK_ACTION_SELECTORS.actionSelect, 'View issuer');
+ assert.dom(QUICK_ACTION_SELECTORS.emptyState).doesNotExist();
+ assert.dom(QUICK_ACTION_SELECTORS.paramsTitle).hasText('Issuer');
+ assert.dom(QUICK_ACTION_SELECTORS.getActionButton('View issuer')).exists({ count: 1 });
+ await selectChoose(QUICK_ACTION_SELECTORS.paramSelect, '.ember-power-select-option', 0);
+ await click(QUICK_ACTION_SELECTORS.getActionButton('View issuer'));
+ assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.issuers.issuer.details');
+
+ // cleanup engine mount
+ await consoleComponent.runCommands(deleteEngineCmd('pki'));
+ });
+
+ const newConnection = async (backend, plugin = 'mongodb-database-plugin') => {
+ const name = `connection-${Date.now()}`;
+ await connectionPage.visitCreate({ backend });
+ await connectionPage.dbPlugin(plugin);
+ await connectionPage.name(name);
+ await connectionPage.connectionUrl(`mongodb://127.0.0.1:4321/${name}`);
+ await connectionPage.toggleVerify();
+ await connectionPage.save();
+ await connectionPage.enable();
+ return name;
+ };
+
+ test('shows the correct actions and links associated with database', async function (assert) {
+ await mountSecrets.enable('database', 'database');
+ await newConnection('database');
+ await runCommands([
+ `write database/roles/my-role \
+ db_name=mongodb-database-plugin \
+ creation_statements='{ "db": "admin", "roles": [{ "role": "readWrite" }, {"role": "read", "db": "foo"}] }' \
+ default_ttl="1h" \
+ max_ttl="24h`,
+ ]);
+ await settled();
+ await visit('/vault/dashboard');
+ await selectChoose(QUICK_ACTION_SELECTORS.secretsEnginesSelect, 'database');
+ await fillIn(QUICK_ACTION_SELECTORS.actionSelect, 'Generate credentials for database');
+ assert.dom(QUICK_ACTION_SELECTORS.emptyState).doesNotExist();
+ assert.dom(QUICK_ACTION_SELECTORS.paramsTitle).hasText('Role to use');
+ assert.dom(QUICK_ACTION_SELECTORS.getActionButton('Generate credentials')).exists({ count: 1 });
+ await selectChoose(QUICK_ACTION_SELECTORS.paramSelect, '.ember-power-select-option', 0);
+ await click(QUICK_ACTION_SELECTORS.getActionButton('Generate credentials'));
+ assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.credentials');
+ await consoleComponent.runCommands(deleteEngineCmd('database'));
+ });
+
+ test('shows the correct actions and links associated with kv v1', async function (assert) {
+ await runCommands(['write sys/mounts/kv type=kv', 'write kv/foo bar=baz']);
+ await settled();
+ await visit('/vault/dashboard');
+ await selectChoose(QUICK_ACTION_SELECTORS.secretsEnginesSelect, 'kv');
+ await fillIn(QUICK_ACTION_SELECTORS.actionSelect, 'Find KV secrets');
+ assert.dom(QUICK_ACTION_SELECTORS.emptyState).doesNotExist();
+ assert.dom(QUICK_ACTION_SELECTORS.paramsTitle).hasText('Secret path');
+ assert.dom(QUICK_ACTION_SELECTORS.getActionButton('Read secrets')).exists({ count: 1 });
+ await consoleComponent.runCommands(deleteEngineCmd('kv'));
+ });
+ });
+
+ module('replication and client count card community version', function () {
+ test('hides replication card for community version', async function (assert) {
+ await visit('/vault/dashboard');
+ assert.dom('[data-test-replication-card]').doesNotExist();
+ });
+
+ test('hides the client count card in community version', async function (assert) {
+ assert.dom('[data-test-client-count-card]').doesNotExist();
+ });
+ });
+
+ module('client counts card enterprise', function (hooks) {
+ hooks.before(async function () {
+ ENV['ember-cli-mirage'].handler = 'clients';
+ });
+
+ hooks.beforeEach(async function () {
+ this.store = this.owner.lookup('service:store');
+
+ await authPage.login();
+ });
+
+ hooks.after(function () {
+ ENV['ember-cli-mirage'].handler = null;
+ });
+
+ test('shows the client count card for enterprise', async function (assert) {
+ const version = this.owner.lookup('service:version');
+ assert.true(version.isEnterprise, 'version is enterprise');
+ assert.strictEqual(currentURL(), '/vault/dashboard');
+ assert.dom('[data-test-client-count-card]').exists();
+ const response = await this.store.peekRecord('clients/activity', 'some-activity-id');
+ assert.dom('[data-test-client-count-title]').hasText('Client count');
+ assert.dom('[data-test-stat-text="total-clients"] .stat-label').hasText('Total');
+ assert
+ .dom('[data-test-stat-text="total-clients"] .stat-value')
+ .hasText(formatNumber([response.total.clients]));
+ assert.dom('[data-test-stat-text="new-clients"] .stat-label').hasText('New');
+ assert
+ .dom('[data-test-stat-text="new-clients"] .stat-text')
+ .hasText('The number of clients new to Vault in the current month.');
+ assert
+ .dom('[data-test-stat-text="new-clients"] .stat-value')
+ .hasText(formatNumber([response.byMonth.lastObject.new_clients.clients]));
+ });
+ });
+
+ module('replication card enterprise', function (hooks) {
+ hooks.beforeEach(async function () {
+ await authPage.login();
+ await settled();
+ await disableReplication('dr');
+ await settled();
+ await disableReplication('performance');
+ await settled();
+ });
+
+ test('shows the replication card empty state in enterprise version', async function (assert) {
+ await visit('/vault/dashboard');
+ const version = this.owner.lookup('service:version');
+ assert.true(version.isEnterprise, 'vault is enterprise');
+ assert.dom(REPLICATION_CARD_SELECTORS.replicationEmptyState).exists();
+ assert.dom(REPLICATION_CARD_SELECTORS.replicationEmptyStateTitle).hasText('Replication not set up');
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.replicationEmptyStateMessage)
+ .hasText('Data will be listed here. Enable a primary replication cluster to get started.');
+ assert.dom(REPLICATION_CARD_SELECTORS.replicationEmptyStateActions).hasText('Enable replication');
+ });
+
+ test('it should show replication status if both dr and performance replication are enabled as features in enterprise', async function (assert) {
+ const version = this.owner.lookup('service:version');
+ assert.true(version.isEnterprise, 'vault is enterprise');
+ await visit('/vault/replication');
+ assert.strictEqual(currentURL(), '/vault/replication');
+ await click('[data-test-replication-type-select="performance"]');
+ await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
+ await click('[data-test-replication-enable]');
+ await pollCluster(this.owner);
+ assert.ok(
+ await waitUntil(() => find('[data-test-replication-dashboard]')),
+ 'details dashboard is shown'
+ );
+ await visit('/vault/dashboard');
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.getReplicationTitle('dr-perf', 'DR primary'))
+ .hasText('DR primary');
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.getStateTooltipTitle('dr-perf', 'DR primary'))
+ .hasText('not set up');
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.getStateTooltipIcon('dr-perf', 'DR primary', 'x-circle'))
+ .exists();
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.getReplicationTitle('dr-perf', 'Perf primary'))
+ .hasText('Perf primary');
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.getStateTooltipTitle('dr-perf', 'Perf primary'))
+ .hasText('running');
+ assert
+ .dom(REPLICATION_CARD_SELECTORS.getStateTooltipIcon('dr-perf', 'Perf primary', 'check-circle'))
+ .exists();
+ });
+ });
});
diff --git a/ui/tests/acceptance/enterprise-replication-test.js b/ui/tests/acceptance/enterprise-replication-test.js
index 3115290ca9b0..abf2f826fbc5 100644
--- a/ui/tests/acceptance/enterprise-replication-test.js
+++ b/ui/tests/acceptance/enterprise-replication-test.js
@@ -12,40 +12,10 @@ import { pollCluster } from 'vault/tests/helpers/poll-cluster';
import { create } from 'ember-cli-page-object';
import flashMessage from 'vault/tests/pages/components/flash-message';
import ss from 'vault/tests/pages/components/search-select';
-
+import { disableReplication } from 'vault/tests/helpers/replication';
const searchSelect = create(ss);
const flash = create(flashMessage);
-const disableReplication = async (type, assert) => {
- // disable performance replication
- await visit(`/vault/replication/${type}`);
-
- if (findAll('[data-test-replication-link="manage"]').length) {
- await click('[data-test-replication-link="manage"]');
-
- await click('[data-test-disable-replication] button');
-
- const typeDisplay = type === 'dr' ? 'Disaster Recovery' : 'Performance';
- await fillIn('[data-test-confirmation-modal-input="Disable Replication?"]', typeDisplay);
- await click('[data-test-confirm-button]');
- await settled(); // eslint-disable-line
-
- if (assert) {
- // bypassing for now -- remove if tests pass reliably
- // assert.strictEqual(
- // flash.latestMessage,
- // 'This cluster is having replication disabled. Vault will be unavailable for a brief period and will resume service shortly.',
- // 'renders info flash when disabled'
- // );
- assert.ok(
- await waitUntil(() => currentURL() === '/vault/replication'),
- 'redirects to the replication page'
- );
- }
- await settled();
- }
-};
-
module('Acceptance | Enterprise | replication', function (hooks) {
setupApplicationTest(hooks);
diff --git a/ui/tests/helpers/components/dashboard/quick-actions-card.js b/ui/tests/helpers/components/dashboard/quick-actions-card.js
new file mode 100644
index 000000000000..f49d900d38df
--- /dev/null
+++ b/ui/tests/helpers/components/dashboard/quick-actions-card.js
@@ -0,0 +1,11 @@
+const SELECTORS = {
+ searchSelect: '.search-select',
+ secretsEnginesSelect: '[data-test-secrets-engines-select]',
+ actionSelect: '[data-test-select="action-select"]',
+ emptyState: '[data-test-no-mount-selected-empty]',
+ paramsTitle: '[data-test-search-select-params-title]',
+ paramSelect: '[data-test-param-select]',
+ getActionButton: (action) => `[data-test-button="${action}"]`,
+};
+
+export default SELECTORS;
diff --git a/ui/tests/helpers/components/dashboard/replication-card.js b/ui/tests/helpers/components/dashboard/replication-card.js
index c44a13583bca..76eb7da354cc 100644
--- a/ui/tests/helpers/components/dashboard/replication-card.js
+++ b/ui/tests/helpers/components/dashboard/replication-card.js
@@ -12,10 +12,15 @@ const SELECTORS = {
knownSecondariesLabel: '[data-test-stat-text="known secondaries"] .stat-label',
knownSecondariesSubtext: '[data-test-stat-text="known secondaries"] .stat-text',
knownSecondariesValue: '[data-test-stat-text="known secondaries"] .stat-value',
- replicationEmptyState: '[data-test-component="empty-state"]',
- replicationEmptyStateTitle: '[data-test-component="empty-state"] .empty-state-title',
- replicationEmptyStateMessage: '[data-test-component="empty-state"] .empty-state-message',
- replicationEmptyStateActions: '[data-test-component="empty-state"] .empty-state-actions',
+ replicationEmptyState: '[data-test-replication-card] [data-test-component="empty-state"]',
+ replicationEmptyStateTitle:
+ '[data-test-replication-card] [data-test-component="empty-state"] .empty-state-title',
+ replicationEmptyStateMessage:
+ '[data-test-replication-card] [data-test-component="empty-state"] .empty-state-message',
+ replicationEmptyStateActions:
+ '[data-test-replication-card] [data-test-component="empty-state"] .empty-state-actions',
+ replicationEmptyStateActionsLink:
+ '[data-test-replication-card] [data-test-component="empty-state"] .empty-state-actions a',
};
export default SELECTORS;
diff --git a/ui/tests/helpers/replication.js b/ui/tests/helpers/replication.js
new file mode 100644
index 000000000000..fca104cf627b
--- /dev/null
+++ b/ui/tests/helpers/replication.js
@@ -0,0 +1,31 @@
+import { click, fillIn, findAll, currentURL, visit, settled, waitUntil } from '@ember/test-helpers';
+
+export const disableReplication = async (type, assert) => {
+ // disable performance replication
+ await visit(`/vault/replication/${type}`);
+
+ if (findAll('[data-test-replication-link="manage"]').length) {
+ await click('[data-test-replication-link="manage"]');
+
+ await click('[data-test-disable-replication] button');
+
+ const typeDisplay = type === 'dr' ? 'Disaster Recovery' : 'Performance';
+ await fillIn('[data-test-confirmation-modal-input="Disable Replication?"]', typeDisplay);
+ await click('[data-test-confirm-button]');
+ await settled(); // eslint-disable-line
+
+ if (assert) {
+ // bypassing for now -- remove if tests pass reliably
+ // assert.strictEqual(
+ // flash.latestMessage,
+ // 'This cluster is having replication disabled. Vault will be unavailable for a brief period and will resume service shortly.',
+ // 'renders info flash when disabled'
+ // );
+ assert.ok(
+ await waitUntil(() => currentURL() === '/vault/replication'),
+ 'redirects to the replication page'
+ );
+ }
+ await settled();
+ }
+};
diff --git a/ui/tests/integration/components/dashboard/quick-actions-card-test.js b/ui/tests/integration/components/dashboard/quick-actions-card-test.js
index 1f95e610480d..3d021c7a0b14 100644
--- a/ui/tests/integration/components/dashboard/quick-actions-card-test.js
+++ b/ui/tests/integration/components/dashboard/quick-actions-card-test.js
@@ -8,10 +8,9 @@ import { setupRenderingTest } from 'vault/tests/helpers';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { fillIn } from '@ember/test-helpers';
-
import { selectChoose } from 'ember-power-select/test-support/helpers';
-// TODO LANDING PAGE: create SELECTORS for the data-test attributes
+import SELECTORS from 'vault/tests/helpers/components/dashboard/quick-actions-card';
module('Integration | Component | dashboard/quick-actions-card', function (hooks) {
setupRenderingTest(hooks);
@@ -94,39 +93,39 @@ module('Integration | Component | dashboard/quick-actions-card', function (hooks
test('it should show quick action empty state if no engine is selected', async function (assert) {
await this.renderComponent();
assert.dom('.title').hasText('Quick actions');
- assert.dom('[data-test-secrets-engines-select]').exists({ count: 1 });
- assert.dom('[data-test-component="empty-state"]').exists({ count: 1 });
+ assert.dom(SELECTORS.secretsEnginesSelect).exists({ count: 1 });
+ assert.dom(SELECTORS.emptyState).exists({ count: 1 });
});
test('it should show correct actions for pki', async function (assert) {
await this.renderComponent();
- await selectChoose('.search-select', 'pki-0-test');
- await fillIn('[data-test-select="action-select"]', 'Issue certificate');
- assert.dom('[data-test-component="empty-state"]').doesNotExist();
- await fillIn('[data-test-select="action-select"]', 'Issue certificate');
- assert.dom('[data-test-button="Issue leaf certificate"]').exists({ count: 1 });
- assert.dom('[data-test-search-select-params-title]').hasText('Role to use');
- await fillIn('[data-test-select="action-select"]', 'View certificate');
- assert.dom('[data-test-search-select-params-title]').hasText('Certificate serial number');
- assert.dom('[data-test-button="View certificate"]').exists({ count: 1 });
- await fillIn('[data-test-select="action-select"]', 'View issuer');
- assert.dom('[data-test-search-select-params-title]').hasText('Issuer');
- assert.dom('[data-test-button="View issuer"]').exists({ count: 1 });
+ await selectChoose(SELECTORS.secretsEnginesSelect, 'pki-0-test');
+ await fillIn(SELECTORS.actionSelect, 'Issue certificate');
+ assert.dom(SELECTORS.emptyState).doesNotExist();
+ await fillIn(SELECTORS.actionSelect, 'Issue certificate');
+ assert.dom(SELECTORS.getActionButton('Issue leaf certificate')).exists({ count: 1 });
+ assert.dom(SELECTORS.paramsTitle).hasText('Role to use');
+ await fillIn(SELECTORS.actionSelect, 'View certificate');
+ assert.dom(SELECTORS.paramsTitle).hasText('Certificate serial number');
+ assert.dom(SELECTORS.getActionButton('View certificate')).exists({ count: 1 });
+ await fillIn(SELECTORS.actionSelect, 'View issuer');
+ assert.dom(SELECTORS.paramsTitle).hasText('Issuer');
+ assert.dom(SELECTORS.getActionButton('View issuer')).exists({ count: 1 });
});
test('it should show correct actions for database', async function (assert) {
await this.renderComponent();
- await selectChoose('.search-select', 'database-test');
- assert.dom('[data-test-component="empty-state"]').doesNotExist();
- await fillIn('[data-test-select="action-select"]', 'Generate credentials for database');
- assert.dom('[data-test-search-select-params-title]').hasText('Role to use');
- assert.dom('[data-test-button="Generate credentials"]').exists({ count: 1 });
+ await selectChoose(SELECTORS.secretsEnginesSelect, 'database-test');
+ assert.dom(SELECTORS.emptyState).doesNotExist();
+ await fillIn(SELECTORS.actionSelect, 'Generate credentials for database');
+ assert.dom(SELECTORS.paramsTitle).hasText('Role to use');
+ assert.dom(SELECTORS.getActionButton('Generate credentials')).exists({ count: 1 });
});
test('it should show correct actions for kv', async function (assert) {
await this.renderComponent();
- await selectChoose('.search-select', 'secrets-1-test');
- assert.dom('[data-test-component="empty-state"]').doesNotExist();
- await fillIn('[data-test-select="action-select"]', 'Find KV secrets');
- assert.dom('[data-test-search-select-params-title]').hasText('Secret path');
- assert.dom('[data-test-button="Read secrets"]').exists({ count: 1 });
+ await selectChoose(SELECTORS.secretsEnginesSelect, 'secrets-1-test');
+ assert.dom(SELECTORS.emptyState).doesNotExist();
+ await fillIn(SELECTORS.actionSelect, 'Find KV secrets');
+ assert.dom(SELECTORS.paramsTitle).hasText('Secret path');
+ assert.dom(SELECTORS.getActionButton('Read secrets')).exists({ count: 1 });
});
});
diff --git a/ui/tests/integration/components/dashboard/replication-state-text.js b/ui/tests/integration/components/dashboard/replication-state-text.js
new file mode 100644
index 000000000000..956f2f0ea419
--- /dev/null
+++ b/ui/tests/integration/components/dashboard/replication-state-text.js
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'vault/tests/helpers';
+import { render } from '@ember/test-helpers';
+import { hbs } from 'ember-cli-htmlbars';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import SELECTORS from 'vault/tests/helpers/components/dashboard/replication-card';
+
+module('Integration | Component | dashboard/replication-state-text', function (hooks) {
+ setupRenderingTest(hooks);
+ setupMirage(hooks);
+
+ hooks.beforeEach(function () {
+ this.name = 'DR Primary';
+ this.clusterState = {
+ glyph: 'circle-check',
+ isOk: true,
+ };
+ });
+
+ test('it displays replication states', async function (assert) {
+ await render(
+ hbs`
+
+ `
+ );
+ assert.dom(SELECTORS.getReplicationTitle('dr-perf', 'DR primary')).hasText('DR primary');
+ assert.dom(SELECTORS.getStateTooltipTitle('dr-perf', 'DR primary')).hasText('running');
+ assert.dom(SELECTORS.getStateTooltipIcon('dr-perf', 'DR primary', 'check-circle')).exists();
+
+ this.name = 'DR Primary';
+ this.clusterState = {
+ glyph: 'x-circle',
+ isOk: false,
+ };
+ await render(
+ hbs`
+
+ `
+ );
+ assert.dom(SELECTORS.getReplicationTitle('dr-perf', 'Perf primary')).hasText('Perf primary');
+ assert.dom(SELECTORS.getStateTooltipTitle('dr-perf', 'Perf primary')).hasText('running');
+ assert.dom(SELECTORS.getStateTooltipIcon('dr-perf', 'Perf primary', 'x-circle')).exists();
+ assert
+ .dom(SELECTORS.getStateTooltipIcon('dr-perf', 'Perf primary', 'x-circle'))
+ .hasClass('has-text-danger');
+ });
+});
From b32962c0fabc4eed364d8fabf925576523b437a4 Mon Sep 17 00:00:00 2001
From: Kianna Quach
Date: Wed, 23 Aug 2023 23:04:14 -0700
Subject: [PATCH 16/21] Add changelog
---
changelog/21057.txt | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 changelog/21057.txt
diff --git a/changelog/21057.txt b/changelog/21057.txt
new file mode 100644
index 000000000000..7ca81cd37632
--- /dev/null
+++ b/changelog/21057.txt
@@ -0,0 +1,3 @@
+```release-note:feature
+**Dashboard UI**: Dashboard is now available in the UI as the new landing page.
+```
\ No newline at end of file
From 23c4ea244d29552ed05d54212a62673af2cdeba6 Mon Sep 17 00:00:00 2001
From: Kianna Quach
Date: Thu, 24 Aug 2023 10:35:18 -0700
Subject: [PATCH 17/21] Address feedback!
---
.../components/dashboard/learn-more-card.js | 1 +
.../dashboard/quick-actions-card.js | 2 +-
ui/app/routes/vault/cluster/dashboard.js | 13 ++---------
.../templates/components/clients/no-data.hbs | 2 +-
.../components/dashboard/learn-more-card.hbs | 22 +++++++++++++------
.../dashboard/vault-version-title.hbs | 4 +++-
ui/app/templates/vault/cluster/dashboard.hbs | 6 ++---
7 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/ui/app/components/dashboard/learn-more-card.js b/ui/app/components/dashboard/learn-more-card.js
index f4d7d01471a1..f195144f6d05 100644
--- a/ui/app/components/dashboard/learn-more-card.js
+++ b/ui/app/components/dashboard/learn-more-card.js
@@ -32,6 +32,7 @@ export default class DashboardLearnMoreCard extends Component {
link: 'https://developer.hashicorp.com/vault/tutorials/adp/transform',
icon: 'learn-link',
title: 'Advanced Data Protection: Transform engine',
+ requiredFeature: 'Transform Secrets Engine',
},
{
link: 'https://developer.hashicorp.com/vault/tutorials/secrets-management/pki-engine',
diff --git a/ui/app/components/dashboard/quick-actions-card.js b/ui/app/components/dashboard/quick-actions-card.js
index 07362bb1b543..1edfac8a5e2b 100644
--- a/ui/app/components/dashboard/quick-actions-card.js
+++ b/ui/app/components/dashboard/quick-actions-card.js
@@ -137,7 +137,7 @@ export default class DashboardQuickActionsCard extends Component {
// link to different route
if (this.selectedEngine.type === 'kv') {
searchSelectParamRoute =
- this.paramValue && this.paramValue[this.paramValue?.length - 1] === '/'
+ this.paramValue && this.paramValue?.endsWith('/')
? 'vault.cluster.secrets.backend.list'
: 'vault.cluster.secrets.backend.show';
}
diff --git a/ui/app/routes/vault/cluster/dashboard.js b/ui/app/routes/vault/cluster/dashboard.js
index 37e84b36f4fe..5b96ba33e47b 100644
--- a/ui/app/routes/vault/cluster/dashboard.js
+++ b/ui/app/routes/vault/cluster/dashboard.js
@@ -25,16 +25,7 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
}
}
- async getLicense() {
- try {
- return await this.store.queryRecord('license', {});
- } catch (e) {
- return null;
- }
- }
-
model() {
- const vaultConfiguration = this.getVaultConfiguration();
const clusterModel = this.modelFor('vault.cluster');
const replication = {
dr: clusterModel.dr,
@@ -42,12 +33,12 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
};
return hash({
- vaultConfiguration,
replication,
secretsEngines: this.store.query('secret-engine', {}),
+ license: this.store.queryRecord('license', {}).catch(() => null),
isRootNamespace: this.namespace.inRootNamespace,
version: this.version,
- license: this.getLicense(),
+ vaultConfiguration: this.getVaultConfiguration(),
});
}
diff --git a/ui/app/templates/components/clients/no-data.hbs b/ui/app/templates/components/clients/no-data.hbs
index 074677a74c0d..3c69c526f290 100644
--- a/ui/app/templates/components/clients/no-data.hbs
+++ b/ui/app/templates/components/clients/no-data.hbs
@@ -1,6 +1,6 @@
{{#if (eq @config.enabled "On")}}
{{else}}
diff --git a/ui/app/templates/components/dashboard/learn-more-card.hbs b/ui/app/templates/components/dashboard/learn-more-card.hbs
index 8a480b50c263..6af58d1968cc 100644
--- a/ui/app/templates/components/dashboard/learn-more-card.hbs
+++ b/ui/app/templates/components/dashboard/learn-more-card.hbs
@@ -5,13 +5,21 @@
{{#each this.learnMoreLinks as |learnMoreLink|}}
-
+ {{#if
+ (or
+ (and learnMoreLink.requiredFeature @isEnterprise (has-feature learnMoreLink.requiredFeature))
+ (not learnMoreLink.requiredFeature)
+ )
+ }}
+
+ {{/if}}
+
{{/each}}
\ No newline at end of file
diff --git a/ui/app/templates/components/dashboard/vault-version-title.hbs b/ui/app/templates/components/dashboard/vault-version-title.hbs
index 339a64602619..7879661a3ba4 100644
--- a/ui/app/templates/components/dashboard/vault-version-title.hbs
+++ b/ui/app/templates/components/dashboard/vault-version-title.hbs
@@ -2,7 +2,9 @@
{{this.versionHeader}}
-
+ {{#if @version.isEnterprise}}
+
+ {{/if}}
diff --git a/ui/app/templates/vault/cluster/dashboard.hbs b/ui/app/templates/vault/cluster/dashboard.hbs
index 5a2a234e2fb7..8cc24111de4f 100644
--- a/ui/app/templates/vault/cluster/dashboard.hbs
+++ b/ui/app/templates/vault/cluster/dashboard.hbs
@@ -1,4 +1,4 @@
-
+
@@ -25,7 +25,7 @@
{{/if}}
-
+
@@ -34,7 +34,7 @@
From b51e40e73ffc7fe25dff074aaf5e9475fb4945b5 Mon Sep 17 00:00:00 2001
From: Kianna Quach
Date: Thu, 24 Aug 2023 11:06:11 -0700
Subject: [PATCH 18/21] Fix learn more test since we are now hiding adp link
---
ui/tests/acceptance/dashboard-test.js | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/ui/tests/acceptance/dashboard-test.js b/ui/tests/acceptance/dashboard-test.js
index f426d1f113dc..d86f0082cef2 100644
--- a/ui/tests/acceptance/dashboard-test.js
+++ b/ui/tests/acceptance/dashboard-test.js
@@ -87,7 +87,20 @@ module('Acceptance | landing page dashboard', function (hooks) {
hooks.beforeEach(function () {
return authPage.login();
});
- test('shows the learn more card', async function (assert) {
+ test('shows the learn more card on community', async function (assert) {
+ await visit('/vault/dashboard');
+ assert.dom('[data-test-learn-more-title]').hasText('Learn more');
+ assert
+ .dom('[data-test-learn-more-subtext]')
+ .hasText(
+ 'Explore the features of Vault and learn advance practices with the following tutorials and documentation.'
+ );
+ assert.dom('[data-test-learn-more-links] a').exists({ count: 3 });
+ assert
+ .dom('[data-test-feedback-form]')
+ .hasText("Don't see what you're looking for on this page? Let us know via our feedback form .");
+ });
+ test('shows the learn more card on enterprise', async function (assert) {
await visit('/vault/dashboard');
assert.dom('[data-test-learn-more-title]').hasText('Learn more');
assert
From a2b8b80e471cb2bb98c0d95cff5436f6e5c3c712 Mon Sep 17 00:00:00 2001
From: Kianna Quach
Date: Thu, 24 Aug 2023 12:23:43 -0700
Subject: [PATCH 19/21] Update links to not use hds
---
ui/app/components/dashboard/learn-more-card.js | 8 ++++----
.../components/dashboard/learn-more-card.hbs | 13 ++++++-------
.../components/dashboard/secrets-engines-card.hbs | 11 ++++-------
3 files changed, 14 insertions(+), 18 deletions(-)
diff --git a/ui/app/components/dashboard/learn-more-card.js b/ui/app/components/dashboard/learn-more-card.js
index f195144f6d05..0da6a59d57ad 100644
--- a/ui/app/components/dashboard/learn-more-card.js
+++ b/ui/app/components/dashboard/learn-more-card.js
@@ -19,23 +19,23 @@ export default class DashboardLearnMoreCard extends Component {
get learnMoreLinks() {
return [
{
- link: 'https://developer.hashicorp.com/vault/tutorials/secrets-management',
+ link: '/vault/tutorials/secrets-management',
icon: 'docs-link',
title: 'Secrets Management',
},
{
- link: 'https://developer.hashicorp.com/vault/tutorials/monitoring',
+ link: '/vault/tutorials/monitoring',
icon: 'docs-link',
title: 'Monitor & Troubleshooting',
},
{
- link: 'https://developer.hashicorp.com/vault/tutorials/adp/transform',
+ link: '/vault/tutorials/adp/transform',
icon: 'learn-link',
title: 'Advanced Data Protection: Transform engine',
requiredFeature: 'Transform Secrets Engine',
},
{
- link: 'https://developer.hashicorp.com/vault/tutorials/secrets-management/pki-engine',
+ link: '/vault/tutorials/secrets-management/pki-engine',
icon: 'learn-link',
title: 'Build your own Certificate Authority (CA)',
},
diff --git a/ui/app/templates/components/dashboard/learn-more-card.hbs b/ui/app/templates/components/dashboard/learn-more-card.hbs
index 6af58d1968cc..bd23e5a6f01e 100644
--- a/ui/app/templates/components/dashboard/learn-more-card.hbs
+++ b/ui/app/templates/components/dashboard/learn-more-card.hbs
@@ -11,13 +11,12 @@
(not learnMoreLink.requiredFeature)
)
}}
-
+
+
+
+ {{learnMoreLink.title}}
+
+
{{/if}}
{{/each}}
diff --git a/ui/app/templates/components/dashboard/secrets-engines-card.hbs b/ui/app/templates/components/dashboard/secrets-engines-card.hbs
index d6d8050dd0c1..dbaab989e778 100644
--- a/ui/app/templates/components/dashboard/secrets-engines-card.hbs
+++ b/ui/app/templates/components/dashboard/secrets-engines-card.hbs
@@ -62,13 +62,10 @@
{{#if (gt this.filteredSecretsEngines.length 4)}}
-
+
+ Show all
+
+
{{/if}}
\ No newline at end of file
From 5bed8a401b6b8b3189e45f0d26ea04d234762ec9 Mon Sep 17 00:00:00 2001
From: Kianna Quach
Date: Thu, 24 Aug 2023 13:01:52 -0700
Subject: [PATCH 20/21] Fix styling after link update
---
ui/app/templates/components/dashboard/learn-more-card.hbs | 6 +++++-
.../templates/components/dashboard/secrets-engines-card.hbs | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/ui/app/templates/components/dashboard/learn-more-card.hbs b/ui/app/templates/components/dashboard/learn-more-card.hbs
index bd23e5a6f01e..ad9d0d80f9e5 100644
--- a/ui/app/templates/components/dashboard/learn-more-card.hbs
+++ b/ui/app/templates/components/dashboard/learn-more-card.hbs
@@ -12,7 +12,11 @@
)
}}
-
+
{{learnMoreLink.title}}
diff --git a/ui/app/templates/components/dashboard/secrets-engines-card.hbs b/ui/app/templates/components/dashboard/secrets-engines-card.hbs
index dbaab989e778..176216c79a86 100644
--- a/ui/app/templates/components/dashboard/secrets-engines-card.hbs
+++ b/ui/app/templates/components/dashboard/secrets-engines-card.hbs
@@ -62,7 +62,7 @@
{{#if (gt this.filteredSecretsEngines.length 4)}}
-
+
Show all
From 0f235dd562f68d26296d5b8627cd7ea1df060f6c Mon Sep 17 00:00:00 2001
From: Kianna Quach
Date: Thu, 24 Aug 2023 13:08:05 -0700
Subject: [PATCH 21/21] Add more styling to learn more links
---
.../components/dashboard/learn-more-card.hbs | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/ui/app/templates/components/dashboard/learn-more-card.hbs b/ui/app/templates/components/dashboard/learn-more-card.hbs
index ad9d0d80f9e5..69bef79a1891 100644
--- a/ui/app/templates/components/dashboard/learn-more-card.hbs
+++ b/ui/app/templates/components/dashboard/learn-more-card.hbs
@@ -11,16 +11,14 @@
(not learnMoreLink.requiredFeature)
)
}}
-
-
-
- {{learnMoreLink.title}}
-
-
+
+
+ {{learnMoreLink.title}}
+
{{/if}}
{{/each}}