diff --git a/ui/app/components/dashboard/quick-actions-card.js b/ui/app/components/dashboard/quick-actions-card.js
new file mode 100644
index 000000000000..6652c98d532b
--- /dev/null
+++ b/ui/app/components/dashboard/quick-actions-card.js
@@ -0,0 +1,146 @@
+/**
+ * Copyright (c) HashiCorp, Inc.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+
+import Component from '@glimmer/component';
+import { action } from '@ember/object';
+import { tracked } from '@glimmer/tracking';
+import { inject as service } from '@ember/service';
+
+/**
+ * @module DashboardQuickActionsCard
+ * DashboardQuickActionsCard component allows users to see a list of secrets engines filtered by
+ * kv, pki and database and perform certain actions based on the type of secret engine selected
+ *
+ * @example
+ * ```js
+ *
+ * ```
+ */
+
+const QUICK_ACTION_ENGINES = ['pki', 'kv', 'database'];
+
+export default class DashboardQuickActionsCard extends Component {
+ @service router;
+
+ @tracked selectedEngine;
+ @tracked selectedAction;
+ @tracked mountPath;
+ @tracked paramValue;
+
+ get actionOptions() {
+ switch (this.selectedEngine) {
+ case 'kv':
+ return ['Find KV secrets'];
+ case 'database':
+ return ['Generate credentials for database'];
+ case 'pki':
+ return ['Issue certificate', 'View certificate', 'View issuer'];
+ default:
+ return [];
+ }
+ }
+
+ get searchSelectParams() {
+ switch (this.selectedAction) {
+ case 'Find KV secrets':
+ return {
+ title: 'Secret Path',
+ subText: 'Path of the secret you want to read, including the mount. E.g., secret/data/foo.',
+ buttonText: 'Read secrets',
+ model: 'secret-v2',
+ route: 'vault.cluster.secrets.backends.show',
+ };
+ case 'Generate credentials for database':
+ return {
+ title: 'Role to use',
+ buttonText: 'Generate credentials',
+ model: 'database/role',
+ route: 'vault.cluster.secrets.backend.credentials',
+ };
+ case 'Issue certificate':
+ return {
+ title: 'Role to use',
+ placeholder: 'Type to find a role',
+ buttonText: 'Issue leaf certificate',
+ model: 'pki/role',
+ route: 'vault.cluster.secrets.backend.pki.roles.role.generate',
+ };
+ case 'View certificate':
+ return {
+ title: 'Certificate serial number',
+ placeholder: '33:a3:...',
+ buttonText: 'View certificate',
+ model: 'pki/certificate/base',
+ route: 'vault.cluster.secrets.backend.pki.certificates.certificate.details',
+ };
+ case 'View issuer':
+ return {
+ title: 'Issuer',
+ placeholder: 'Type issuer name or ID',
+ buttonText: 'View issuer',
+ model: 'pki/issuer',
+ nameKey: 'issuerName',
+ route: 'vault.cluster.secrets.backend.pki.issuers.issuer.details',
+ };
+ default:
+ return {
+ placeholder: 'Please select an action above',
+ buttonText: 'Select an action',
+ model: '',
+ };
+ }
+ }
+
+ get filteredSecretEngines() {
+ return this.args.secretsEngines.filter((engine) => QUICK_ACTION_ENGINES.includes(engine.type));
+ }
+
+ get mountOptions() {
+ return this.filteredSecretEngines.map((engine) => {
+ const { id, type } = engine;
+ return { name: id, type, id };
+ });
+ }
+
+ @action
+ handleSearchEngineSelect([selection]) {
+ this.selectedEngine = selection?.type;
+ this.mountPath = selection?.id;
+ // reset tracked properties
+ this.selectedAction = null;
+ this.paramValue = null;
+ }
+
+ @action
+ setSelectedAction(selectedAction) {
+ this.selectedAction = selectedAction;
+ this.paramValue = null;
+ }
+
+ @action
+ handleActionSelect(val) {
+ if (Array.isArray(val)) {
+ this.paramValue = val[0];
+ } else {
+ this.paramValue = val;
+ }
+ }
+
+ @action
+ navigateToPage() {
+ let searchSelectParamRoute = this.searchSelectParams.route;
+
+ // kv has a special use case where if the paramValue ends in a '/' you should
+ // link to different route
+ if (this.selectedEngine === 'kv') {
+ searchSelectParamRoute =
+ this.paramValue && this.paramValue[this.paramValue?.length - 1] === '/'
+ ? 'vault.cluster.secrets.backend.list'
+ : 'vault.cluster.secrets.backend.show';
+ }
+
+ this.router.transitionTo(searchSelectParamRoute, this.mountPath, this.paramValue);
+ }
+}
diff --git a/ui/app/templates/components/dashboard/quick-actions-card.hbs b/ui/app/templates/components/dashboard/quick-actions-card.hbs
new file mode 100644
index 000000000000..d019a71c79a3
--- /dev/null
+++ b/ui/app/templates/components/dashboard/quick-actions-card.hbs
@@ -0,0 +1,63 @@
+
Quick actions
+
+
+
Secrets engines
+
+
+
+{{#if this.selectedEngine}}
+ Action
+
+
+ {{#if this.searchSelectParams.model}}
+ {{this.searchSelectParams.title}}
+
+
+
+
+
+
+ {{/if}}
+{{else}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/app/templates/vault/cluster/dashboard.hbs b/ui/app/templates/vault/cluster/dashboard.hbs
index dee8b8e29441..b5dcbf9ea2f1 100644
--- a/ui/app/templates/vault/cluster/dashboard.hbs
+++ b/ui/app/templates/vault/cluster/dashboard.hbs
@@ -6,7 +6,7 @@
- Quick Actions
+
diff --git a/ui/tests/acceptance/secrets/backend/database/secret-test.js b/ui/tests/acceptance/secrets/backend/database/secret-test.js
index 3aed0237c6fa..61d5af65761e 100644
--- a/ui/tests/acceptance/secrets/backend/database/secret-test.js
+++ b/ui/tests/acceptance/secrets/backend/database/secret-test.js
@@ -467,6 +467,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
await authPage.logout();
// Check with restricted permissions
await authPage.login(token);
+ await click('[data-test-sidebar-nav-link="Secrets engines"]');
assert.dom(`[data-test-auth-backend-link="${backend}"]`).exists('Shows backend on secret list page');
await navToConnection(backend, connection);
assert.strictEqual(
diff --git a/ui/tests/integration/components/dashboard/quick-actions-card-test.js b/ui/tests/integration/components/dashboard/quick-actions-card-test.js
new file mode 100644
index 000000000000..b8764d760285
--- /dev/null
+++ b/ui/tests/integration/components/dashboard/quick-actions-card-test.js
@@ -0,0 +1,132 @@
+/**
+ * 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 { fillIn } from '@ember/test-helpers';
+
+import { selectChoose } from 'ember-power-select/test-support/helpers';
+
+// // TODO LANDING PAGE: create SELECTORS for the data-test attributes
+
+module('Integration | Component | dashboard/quick-actions-card', function (hooks) {
+ setupRenderingTest(hooks);
+
+ hooks.beforeEach(function () {
+ this.store = this.owner.lookup('service:store');
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'kubernetes_f3400dee',
+ path: 'kubernetes-test/',
+ type: 'kubernetes',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'database_f3400dee',
+ path: 'database-test/',
+ type: 'database',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'pki_i1234dd',
+ path: 'apki-test/',
+ type: 'pki',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'secrets_j2350ii',
+ path: 'secrets-test/',
+ type: 'kv',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'nomad_123hh',
+ path: 'nomad/',
+ type: 'nomad',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'pki_f3400dee',
+ path: 'pki-0-test/',
+ type: 'pki',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'pki_i1234dd',
+ path: 'pki-1-test/',
+ description: 'pki-1-path-description',
+ type: 'pki',
+ },
+ });
+ this.store.pushPayload('secret-engine', {
+ modelName: 'secret-engine',
+ data: {
+ accessor: 'secrets_j2350ii',
+ path: 'secrets-1-test/',
+ type: 'kv',
+ },
+ });
+
+ this.secretsEngines = this.store.peekAll('secret-engine', {});
+
+ this.renderComponent = () => {
+ return render(hbs``);
+ };
+ });
+
+ 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 });
+ });
+
+ 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 });
+ });
+ 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 });
+ });
+ 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 });
+ });
+});