diff --git a/ui/app/components/manage-dropdown.ts b/ui/app/components/manage-dropdown.ts
new file mode 100644
index 0000000000..e524fcb744
--- /dev/null
+++ b/ui/app/components/manage-dropdown.ts
@@ -0,0 +1,6 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+export { default } from 'core/components/manage-dropdown';
diff --git a/ui/app/components/mount-backend-form.ts b/ui/app/components/mount-backend-form.ts
index c75e918baf..91f72c9f28 100644
--- a/ui/app/components/mount-backend-form.ts
+++ b/ui/app/components/mount-backend-form.ts
@@ -3,21 +3,21 @@
* SPDX-License-Identifier: BUSL-1.1
*/
+import { action, set } from '@ember/object';
+import { service } from '@ember/service';
+import { waitFor } from '@ember/test-waiters';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
-import { service } from '@ember/service';
-import { action, set } from '@ember/object';
+import { filterEnginesByMountCategory } from 'core/utils/all-engines-metadata';
import { task } from 'ember-concurrency';
-import { waitFor } from '@ember/test-waiters';
-import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import { MOUNT_CATEGORIES } from 'vault/utils/plugin-catalog-helpers';
-import type FlashMessageService from 'vault/services/flash-messages';
+import type { ApiError } from '@ember-data/adapter/error';
import type Store from '@ember-data/store';
import type AuthMethodForm from 'vault/forms/auth/method';
-import type CapabilitiesService from 'vault/services/capabilities';
import type ApiService from 'vault/services/api';
-import type { ApiError } from '@ember-data/adapter/error';
+import type CapabilitiesService from 'vault/services/capabilities';
+import type FlashMessageService from 'vault/services/flash-messages';
import type { ValidationMap } from 'vault/vault/app-types';
/**
diff --git a/ui/app/components/mount-backend/type-form.js b/ui/app/components/mount-backend/type-form.js
index d1859a3e5a..fcc4a3bb32 100644
--- a/ui/app/components/mount-backend/type-form.js
+++ b/ui/app/components/mount-backend/type-form.js
@@ -3,18 +3,18 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import Component from '@glimmer/component';
-import { service } from '@ember/service';
import { action } from '@ember/object';
+import { service } from '@ember/service';
+import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
+import { filterEnginesByMountCategory } from 'core/utils/all-engines-metadata';
import keys from 'core/utils/keys';
-import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import {
- enhanceEnginesWithCatalogData,
categorizeEnginesByStatus,
+ enhanceEnginesWithCatalogData,
MOUNT_CATEGORIES,
- PLUGIN_TYPES,
PLUGIN_CATEGORIES,
+ PLUGIN_TYPES,
} from 'vault/utils/plugin-catalog-helpers';
/**
diff --git a/ui/app/components/secret-engine/list.hbs b/ui/app/components/secret-engine/list.hbs
index 8232cd1166..663b66c494 100644
--- a/ui/app/components/secret-engine/list.hbs
+++ b/ui/app/components/secret-engine/list.hbs
@@ -140,7 +140,7 @@
@text="Disable engines"
@color="critical"
@icon="trash"
- {{on "click" (fn (mut this.enginesToDisable) this.selectedItems)}}
+ {{on "click" (fn this.setEnginesToDisable this.selectedItems)}}
/>
{{/if}}
@@ -171,28 +171,7 @@
<:popupMenu as |rowData|>
{{#let (this.getEngineResourceData rowData.path) as |backendData|}}
-
-
- View configuration
- {{#if (not-eq backendData.type "cubbyhole")}}
- Delete
- {{/if}}
-
+
{{/let}}
@@ -201,16 +180,6 @@
{{/if}}
{{! End Table Section }}
- {{#if this.engineToDisable}}
-
- {{/if}}
-
{{#if this.enginesToDisable}}
{
@service declare readonly wizard: WizardService;
@tracked secretEngineOptions: Array | [] = [];
- @tracked engineToDisable: SecretsEngineResource | undefined = undefined;
@tracked enginesToDisable: Array | null = null;
@tracked engineTypeFilters: Array = [];
@@ -289,6 +288,16 @@ export default class SecretEngineList extends Component {
this.selectedItems = tableData.selectedRowsKeys;
}
+ @action
+ setEnginesToDisable(engines: Array) {
+ this.enginesToDisable = engines;
+ }
+
+ @action
+ clearEnginesToDisable() {
+ this.enginesToDisable = null;
+ }
+
async disableSingleEngine(engine: SecretsEngineResource) {
const { engineType, id, path } = engine;
try {
@@ -302,14 +311,13 @@ export default class SecretEngineList extends Component {
}
}
- @dropTask
- *disableMultipleEngines(enginePathsToDisable: Array) {
+ disableMultipleEngines = dropTask(async (enginePathsToDisable: Array) => {
const enginesToDisable = this.displayableBackends.filter((engine: SecretsEngineResource) =>
enginePathsToDisable.includes(engine.path)
);
try {
for (const engine of enginesToDisable) {
- yield this.disableSingleEngine(engine);
+ await this.disableSingleEngine(engine);
}
// Navigate once all operations are complete
@@ -317,15 +325,5 @@ export default class SecretEngineList extends Component {
} finally {
this.enginesToDisable = null;
}
- }
-
- @dropTask
- *disableEngine(engine: SecretsEngineResource) {
- try {
- yield this.disableSingleEngine(engine);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } finally {
- this.engineToDisable = undefined;
- }
- }
+ });
}
diff --git a/ui/app/components/secret-engines/catalog.ts b/ui/app/components/secret-engines/catalog.ts
index 550cf626f8..e6c06c5201 100644
--- a/ui/app/components/secret-engines/catalog.ts
+++ b/ui/app/components/secret-engines/catalog.ts
@@ -3,19 +3,19 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import Component from '@glimmer/component';
-import { service } from '@ember/service';
import { action } from '@ember/object';
+import { service } from '@ember/service';
+import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
-import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
-import {
- enhanceEnginesWithCatalogData,
- categorizeEnginesByStatus,
- MOUNT_CATEGORIES,
- PLUGIN_TYPES,
- PLUGIN_CATEGORIES,
-} from 'vault/utils/plugin-catalog-helpers';
+import { filterEnginesByMountCategory } from 'core/utils/all-engines-metadata';
import type { PluginCatalogData } from 'vault/services/plugin-catalog';
+import {
+ categorizeEnginesByStatus,
+ enhanceEnginesWithCatalogData,
+ MOUNT_CATEGORIES,
+ PLUGIN_CATEGORIES,
+ PLUGIN_TYPES,
+} from 'vault/utils/plugin-catalog-helpers';
import type VersionService from 'vault/services/version';
diff --git a/ui/app/controllers/vault/cluster/secrets/backend/list.js b/ui/app/controllers/vault/cluster/secrets/backend/list.js
index ab95c0c4aa..964ebfc285 100644
--- a/ui/app/controllers/vault/cluster/secrets/backend/list.js
+++ b/ui/app/controllers/vault/cluster/secrets/backend/list.js
@@ -3,14 +3,13 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { or } from '@ember/object/computed';
-import { computed } from '@ember/object';
-import { service } from '@ember/service';
import Controller from '@ember/controller';
-import BackendCrumbMixin from 'vault/mixins/backend-crumb';
+import { computed } from '@ember/object';
+import { or } from '@ember/object/computed';
+import { service } from '@ember/service';
import ListController from 'core/mixins/list-controller';
import { keyIsFolder } from 'core/utils/key-utils';
-import { task } from 'ember-concurrency';
+import BackendCrumbMixin from 'vault/mixins/backend-crumb';
export default Controller.extend(ListController, BackendCrumbMixin, {
flashMessages: service(),
@@ -68,20 +67,4 @@ export default Controller.extend(ListController, BackendCrumbMixin, {
});
},
},
-
- disableEngine: task(function* (engine) {
- const { engineType, id, path } = engine;
- try {
- yield this.api.sys.mountsDisableSecretsEngine(id);
- this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } catch (err) {
- const { message } = yield this.api.parseError(err);
- this.flashMessages.danger(
- `There was an error disabling the ${engineType} Secrets Engines at ${path}: ${message}.`
- );
- } finally {
- this.engineToDisable = null;
- }
- }).drop(),
});
diff --git a/ui/app/forms/secrets/engine.ts b/ui/app/forms/secrets/engine.ts
index 7e39262492..a1dcb4f48d 100644
--- a/ui/app/forms/secrets/engine.ts
+++ b/ui/app/forms/secrets/engine.ts
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: BUSL-1.1
*/
+import { ALL_ENGINES } from 'core/utils/all-engines-metadata';
import MountForm from 'vault/forms/mount';
-import { ALL_ENGINES } from 'vault/utils/all-engines-metadata';
import { isKnownExternalPlugin } from 'vault/utils/external-plugin-helpers';
import FormField from 'vault/utils/forms/field';
import FormFieldGroup from 'vault/utils/forms/field-group';
diff --git a/ui/app/helpers/engines-display-data.ts b/ui/app/helpers/engines-display-data.ts
index 4924e9a7e7..a71fe61517 100644
--- a/ui/app/helpers/engines-display-data.ts
+++ b/ui/app/helpers/engines-display-data.ts
@@ -3,60 +3,4 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { ALL_ENGINES, type EngineDisplayData } from 'vault/utils/all-engines-metadata';
-import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
-
-/**
- * Default metadata for unknown engine plugins
- */
-export const unknownEngineMetadata = (methodType?: string): EngineDisplayData => ({
- type: methodType || 'unknown',
- displayName: methodType || 'Unknown plugin',
- glyph: 'lock',
- mountCategory: ['secret', 'auth'],
-});
-
-/**
- * Helper function to retrieve engine metadata for a given `methodType`.
- * It searches the `ALL_ENGINES` array for an engine with a matching type and returns its metadata object.
- * The `ALL_ENGINES` array includes secret and auth engines, including those supported only in enterprise.
- * These details (such as mount type and enterprise licensing) are included in the returned engine object.
- *
- * For external plugins that have a builtin mapping (e.g., "vault-plugin-secrets-keymgmt" -> "keymgmt"),
- * this function returns the metadata for the corresponding builtin engine, preserving the original
- * external plugin name in the type field.
- *
- * Example usage:
- * const engineMetadata = engineDisplayData('kmip');
- * if (engineMetadata?.requiresEnterprise) {
- * console.log(`This mount: ${engineMetadata.engineType} requires an enterprise license`);
- * }
- *
- * @param {string} methodType - The engine type (sometimes called backend) to look up (e.g., "aws", "azure", "vault-plugin-secrets-keymgmt").
- * @returns {Object} - The engine metadata, which includes information about its mount type (e.g., secret or auth)
- * and whether it requires an enterprise license. For unknown engines, returns a default unknown plugin object.
- */
-export default function engineDisplayData(methodType: string): EngineDisplayData {
- // First try to find an exact match
- const builtinEngine = ALL_ENGINES?.find((t) => t.type === methodType);
- if (builtinEngine) {
- return builtinEngine;
- }
-
- // If no direct match, check if this is a known external plugin and use its builtin mapping
- const effectiveType = getEffectiveEngineType(methodType);
- if (effectiveType !== methodType) {
- // This is a known external plugin with a builtin mapping
- const mappedEngine = ALL_ENGINES?.find((t) => t.type === effectiveType);
- if (mappedEngine) {
- // Return the mapped engine metadata but preserve the original external plugin type
- return {
- ...mappedEngine,
- type: methodType, // Keep the original external plugin name for identification
- };
- }
- }
-
- // Return default unknown plugin metadata
- return unknownEngineMetadata(methodType);
-}
+export { default } from 'core/helpers/engines-display-data';
diff --git a/ui/app/models/mfa-login-enforcement.js b/ui/app/models/mfa-login-enforcement.js
index 9bc2d4bd7d..d7cd19b176 100644
--- a/ui/app/models/mfa-login-enforcement.js
+++ b/ui/app/models/mfa-login-enforcement.js
@@ -6,11 +6,11 @@
import Model, { attr, hasMany } from '@ember-data/model';
import ArrayProxy from '@ember/array/proxy';
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
-import { withModelValidations } from 'vault/decorators/model-validations';
-import { isPresent } from '@ember/utils';
import { service } from '@ember/service';
+import { isPresent } from '@ember/utils';
+import { filterEnginesByMountCategory } from 'core/utils/all-engines-metadata';
+import { withModelValidations } from 'vault/decorators/model-validations';
import { addManyToArray, addToArray } from 'vault/helpers/add-to-array';
-import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
const validations = {
name: [{ type: 'presence', message: 'Name is required' }],
diff --git a/ui/app/models/secret-engine.js b/ui/app/models/secret-engine.js
index c6e798d248..5e2183c121 100644
--- a/ui/app/models/secret-engine.js
+++ b/ui/app/models/secret-engine.js
@@ -4,15 +4,14 @@
*/
import Model, { attr, belongsTo } from '@ember-data/model';
-import { computed } from '@ember/object'; // eslint-disable-line
-import { equal } from '@ember/object/computed'; // eslint-disable-line
-import { withModelValidations } from 'vault/decorators/model-validations';
+import { ALL_ENGINES, isAddonEngine } from 'core/utils/all-engines-metadata';
import { withExpandedAttributes } from 'vault/decorators/model-expanded-attributes';
-import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
-import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
-import { ALL_ENGINES, INTERNAL_ENGINE_TYPES, isAddonEngine } from 'vault/utils/all-engines-metadata';
-import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
+import { withModelValidations } from 'vault/decorators/model-validations';
import engineDisplayData from 'vault/helpers/engines-display-data';
+import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
+import { INTERNAL_ENGINE_TYPES } from 'vault/utils/all-engines-metadata';
+import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
+import { WHITESPACE_WARNING } from 'vault/utils/forms/validators';
const LINKED_BACKENDS = supportedSecretBackends();
diff --git a/ui/app/resources/secrets/engine.ts b/ui/app/resources/secrets/engine.ts
index 0c522f0a1a..7f9dcd1181 100644
--- a/ui/app/resources/secrets/engine.ts
+++ b/ui/app/resources/secrets/engine.ts
@@ -3,14 +3,14 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { baseResourceFactory } from 'vault/resources/base-factory';
+import engineDisplayData from 'vault/helpers/engines-display-data';
import {
supportedSecretBackends,
SupportedSecretBackendsEnum,
} from 'vault/helpers/supported-secret-backends';
+import { baseResourceFactory } from 'vault/resources/base-factory';
import { INTERNAL_ENGINE_TYPES, isAddonEngine } from 'vault/utils/all-engines-metadata';
import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
-import engineDisplayData from 'vault/helpers/engines-display-data';
import type { Mount } from 'vault/mount';
diff --git a/ui/app/routes/vault/cluster/secrets/backend/list.js b/ui/app/routes/vault/cluster/secrets/backend/list.js
index 4e82e3c8cd..c02b4dbdae 100644
--- a/ui/app/routes/vault/cluster/secrets/backend/list.js
+++ b/ui/app/routes/vault/cluster/secrets/backend/list.js
@@ -3,19 +3,19 @@
* SPDX-License-Identifier: BUSL-1.1
*/
+import { assert } from '@ember/debug';
import { set } from '@ember/object';
-import { hash } from 'rsvp';
import Route from '@ember/routing/route';
+import { service } from '@ember/service';
+import { filterEnginesByMountCategory, isAddonEngine } from 'core/utils/all-engines-metadata';
+import { pathIsDirectory } from 'kv/utils/kv-breadcrumbs';
+import { hash } from 'rsvp';
+import engineDisplayData from 'vault/helpers/engines-display-data';
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
-import { isAddonEngine, filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
+import { getEnginePathParam } from 'vault/utils/backend-route-helpers';
import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
import { getModelTypeForEngine } from 'vault/utils/model-helpers/secret-engine-helpers';
-import { service } from '@ember/service';
import { normalizePath } from 'vault/utils/path-encoding-helpers';
-import { getEnginePathParam } from 'vault/utils/backend-route-helpers';
-import { assert } from '@ember/debug';
-import { pathIsDirectory } from 'kv/utils/kv-breadcrumbs';
-import engineDisplayData from 'vault/helpers/engines-display-data';
const SUPPORTED_BACKENDS = supportedSecretBackends();
diff --git a/ui/app/templates/vault/cluster/secrets/backend/list.hbs b/ui/app/templates/vault/cluster/secrets/backend/list.hbs
index 9b42cdda9a..4482cc5aa2 100644
--- a/ui/app/templates/vault/cluster/secrets/backend/list.hbs
+++ b/ui/app/templates/vault/cluster/secrets/backend/list.hbs
@@ -9,24 +9,14 @@
(options-for-backend this.backendType this.tab) (engines-display-data this.backendType)
as |options engineDisplayData|
}}
-
-
- Configure
- Delete
-
+
-{{/if}}
\ No newline at end of file
+{{/let}}
\ No newline at end of file
diff --git a/ui/app/utils/all-engines-metadata.ts b/ui/app/utils/all-engines-metadata.ts
index 0adc37aa1e..d15b54ccff 100644
--- a/ui/app/utils/all-engines-metadata.ts
+++ b/ui/app/utils/all-engines-metadata.ts
@@ -3,355 +3,4 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-/**
- * Metadata configuration for secret and auth engines, including enterprise.
- *
- * This file defines and exports engine metadata, including its
- * displayName, mountCategory, requiresEnterprise, and other relevant properties. It serves as a
- * centralized source of truth for engine-related configurations.
- *
- * Key responsibilities:
- * - Define metadata for all engines.
- * - Provide utility functions or constants for accessing engine-specific data.
- * - Facilitate dynamic engine rendering and behavior based on metadata.
- *
- * Example usage:
- * If an enterprise license is present, return all secret engines;
- * otherwise, return only the secret engines supported in OSS.
- * return filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: this.version.isEnterprise });
- */
-
-export interface EngineDisplayData {
- pluginCategory?: string; // The plugin category is used to group engines in the UI. e.g., 'cloud', 'infra', 'generic'
- displayName: string;
- engineRoute?: string; // engines that have their own Ember engine will have this route defined.
- glyph?: string;
- isWIF?: boolean; // flag for 'Workload Identity Federation' engines. - https://developer.hashicorp.com/hcp/docs/hcp/iam/service-principal/workload-identity-federation
- mountCategory: string[];
- requiredFeature?: string; // flag for engines that require the ADP (Advanced Data Protection) feature. - https://www.hashicorp.com/en/blog/advanced-data-protection-adp-now-available-in-hcp-vault
- requiresEnterprise?: boolean;
- isConfigurable?: boolean; // for secret engines that have additional configuration pages and actions.
- isOnlyMountable?: boolean; // The UI only supports configuration views for these secrets engines. The CLI must be used to manage other engine resources (i.e. roles, credentials).
- type: string;
- value?: string;
- configRoute?: string; // override for custom route if not "configuration.plugin-settings" (used for Ember engines)
-}
-
-/**
- * @param mountCategory - Given mount category to filter by, e.g., 'auth' or 'secret'.
- * @param isEnterprise - Optional boolean to indicate if enterprise engines should be included in the results.
- * @returns Filtered array of engines that match the given mount category
- */
-export function filterEnginesByMountCategory({
- mountCategory,
- isEnterprise = false,
-}: {
- mountCategory: 'auth' | 'secret';
- isEnterprise: boolean;
-}) {
- return isEnterprise
- ? ALL_ENGINES.filter((engine) => engine.mountCategory.includes(mountCategory))
- : ALL_ENGINES.filter(
- (engine) => engine.mountCategory.includes(mountCategory) && !engine.requiresEnterprise
- );
-}
-
-export function isAddonEngine(type: string, version: number) {
- if (type === 'kv' && version === 1) {
- return false;
- }
- const engineRoute = ALL_ENGINES.find((engine) => engine.type === type)?.engineRoute;
- return !!engineRoute;
-}
-
-// The "sys/mounts" and "sys/internal/ui/mounts" endpoints return a "secret/" key containing
-// all mounts enabled in Vault. Some types are internal Vault APIs, not user-mountable secrets engines,
-// and should be filtered in some scenarios, such as listing secrets engines.
-export const INTERNAL_ENGINE_TYPES = ['system', 'identity', 'agent_registry'];
-
-export const ALL_ENGINES: EngineDisplayData[] = [
- {
- pluginCategory: 'cloud',
- displayName: 'AliCloud',
- glyph: 'alibaba-color',
- mountCategory: ['auth', 'secret'],
- type: 'alicloud',
- },
- {
- pluginCategory: 'generic',
- displayName: 'AppRole',
- glyph: 'cpu',
- mountCategory: ['auth'],
- type: 'approle',
- value: 'approle',
- },
- {
- pluginCategory: 'cloud',
- displayName: 'AWS',
- glyph: 'aws-color',
- isConfigurable: true,
- isWIF: true,
- mountCategory: ['auth', 'secret'],
- type: 'aws',
- },
- {
- pluginCategory: 'cloud',
- displayName: 'Azure',
- glyph: 'azure-color',
- isOnlyMountable: true,
- isConfigurable: true,
- isWIF: true,
- mountCategory: ['auth', 'secret'],
- type: 'azure',
- },
- {
- pluginCategory: 'infra',
- displayName: 'Consul',
- glyph: 'consul-color',
- mountCategory: ['secret'],
- type: 'consul',
- },
- {
- displayName: 'Cubbyhole',
- type: 'cubbyhole',
- mountCategory: ['secret'],
- },
- {
- pluginCategory: 'infra',
- displayName: 'Databases',
- glyph: 'database',
- mountCategory: ['secret'],
- type: 'database',
- },
- {
- pluginCategory: 'cloud',
- displayName: 'GitHub',
- glyph: 'github-color',
- mountCategory: ['auth'],
- type: 'github',
- value: 'github',
- },
- {
- pluginCategory: 'cloud',
- displayName: 'Google Cloud',
- glyph: 'gcp-color',
- isOnlyMountable: true,
- isConfigurable: true,
- isWIF: true,
- mountCategory: ['auth', 'secret'],
- type: 'gcp',
- },
- {
- pluginCategory: 'cloud',
- displayName: 'Google Cloud KMS',
- glyph: 'gcp-color',
- mountCategory: ['secret'],
- type: 'gcpkms',
- },
- {
- pluginCategory: 'generic',
- displayName: 'JWT',
- glyph: 'jwt',
- mountCategory: ['auth'],
- type: 'jwt',
- value: 'jwt',
- },
- {
- pluginCategory: 'generic',
- displayName: 'KV',
- engineRoute: 'kv.list',
- configRoute: 'kv.configuration', // only utilized to display config data for kvv2, not in conjunction with isConfigurable as templates determine whether engine is kv v1 or v2
- glyph: 'key-values',
- mountCategory: ['secret'],
- type: 'kv',
- },
- {
- pluginCategory: 'generic',
- displayName: 'KMIP',
- engineRoute: 'kmip.scopes.index',
- configRoute: 'kmip.configuration',
- isConfigurable: true,
- glyph: 'lock',
- mountCategory: ['secret'],
- requiredFeature: 'KMIP',
- requiresEnterprise: true,
- type: 'kmip',
- },
- {
- pluginCategory: 'generic',
- displayName: 'Transform',
- glyph: 'transform-data',
- mountCategory: ['secret'],
- requiredFeature: 'Transform Secrets Engine',
- requiresEnterprise: true,
- type: 'transform',
- },
- {
- pluginCategory: 'cloud',
- displayName: 'Key Management',
- glyph: 'key',
- mountCategory: ['secret'],
- requiredFeature: 'Key Management Secrets Engine',
- requiresEnterprise: true,
- type: 'keymgmt',
- },
- {
- pluginCategory: 'generic',
- displayName: 'Kubernetes',
- engineRoute: 'kubernetes.overview',
- configRoute: 'kubernetes.configuration',
- glyph: 'kubernetes-color',
- isConfigurable: true,
- mountCategory: ['auth', 'secret'],
- type: 'kubernetes',
- },
- {
- pluginCategory: 'generic',
- displayName: 'LDAP',
- isConfigurable: true,
- engineRoute: 'ldap.overview',
- configRoute: 'ldap.configuration',
- glyph: 'folder-users',
- mountCategory: ['auth', 'secret'],
- type: 'ldap',
- },
- {
- pluginCategory: 'infra',
- displayName: 'Nomad',
- glyph: 'nomad-color',
- mountCategory: ['secret'],
- type: 'nomad',
- },
- {
- pluginCategory: 'generic',
- displayName: 'OIDC',
- glyph: 'openid-color',
- mountCategory: ['auth'],
- type: 'oidc',
- value: 'oidc',
- },
- {
- pluginCategory: 'infra',
- displayName: 'Okta',
- glyph: 'okta-color',
- mountCategory: ['auth'],
- type: 'okta',
- value: 'okta',
- },
- {
- pluginCategory: 'generic',
- displayName: 'PKI Certificates',
- isConfigurable: true,
- engineRoute: 'pki.overview',
- configRoute: 'pki.configuration',
- glyph: 'certificate',
- mountCategory: ['secret'],
- type: 'pki',
- },
- {
- pluginCategory: 'infra',
- displayName: 'RADIUS',
- glyph: 'mainframe',
- mountCategory: ['auth'],
- type: 'radius',
- value: 'radius',
- },
- {
- pluginCategory: 'infra',
- displayName: 'RabbitMQ',
- glyph: 'rabbitmq-color',
- mountCategory: ['secret'],
- type: 'rabbitmq',
- },
- {
- pluginCategory: 'generic',
- displayName: 'SAML',
- glyph: 'saml-color',
- mountCategory: ['auth'],
- requiresEnterprise: true,
- type: 'saml',
- value: 'saml',
- },
- {
- pluginCategory: 'generic',
- displayName: 'SSH',
- glyph: 'terminal-screen',
- isConfigurable: true,
- mountCategory: ['secret'],
- type: 'ssh',
- },
- {
- pluginCategory: 'generic',
- displayName: 'TLS Certificates',
- glyph: 'certificate',
- mountCategory: ['auth'],
- type: 'cert',
- value: 'cert',
- },
- {
- pluginCategory: 'generic',
- displayName: 'TOTP',
- glyph: 'history',
- mountCategory: ['secret'],
- type: 'totp',
- },
- {
- pluginCategory: 'generic',
- displayName: 'Transit',
- glyph: 'swap-horizontal',
- mountCategory: ['secret'],
- type: 'transit',
- },
- {
- displayName: 'Token',
- type: 'token',
- mountCategory: ['auth'],
- },
- {
- pluginCategory: 'generic',
- displayName: 'Userpass',
- glyph: 'users',
- mountCategory: ['auth'],
- type: 'userpass',
- value: 'userpass',
- },
-
- // TODO: enable builtin plugins after confirming with Product
- //
- // {
- // pluginCategory: 'generic',
- // displayName: 'Ad',
- // glyph: 'folder',
- // isOldEngine: true,
- // isOnlyMountable: true,
- // mountCategory: ['secret'],
- // type: 'ad',
- // },
- // {
- // pluginCategory: 'cloud',
- // displayName: 'MongoDB Atlas',
- // glyph: 'mongodb-color',
- // isOldEngine: true,
- // isOnlyMountable: true,
- // mountCategory: ['secret'],
- // type: 'mongodbatlas',
- // },
- // {
- // pluginCategory: 'infra',
- // displayName: 'OpenLDAP',
- // glyph: 'folder-users',
- // isOldEngine: true,
- // isOnlyMountable: true,
- // mountCategory: ['secret'],
- // type: 'openldap',
- // },
- // {
- // pluginCategory: 'infra',
- // displayName: 'Terraform',
- // glyph: 'terraform-color',
- // isOldEngine: true,
- // isOnlyMountable: true,
- // mountCategory: ['secret'],
- // type: 'terraform',
- // },
-];
+export * from 'core/utils/all-engines-metadata';
diff --git a/ui/lib/core/addon/components/manage-dropdown.hbs b/ui/lib/core/addon/components/manage-dropdown.hbs
new file mode 100644
index 0000000000..9f4a154209
--- /dev/null
+++ b/ui/lib/core/addon/components/manage-dropdown.hbs
@@ -0,0 +1,46 @@
+{{!
+ Copyright IBM Corp. 2016, 2025
+ SPDX-License-Identifier: BUSL-1.1
+}}
+
+
+ {{#if this.isIcon}}
+
+ {{else}}
+
+ {{/if}}
+
+ {{! Yield point for engine-specific custom menu items (e.g., KV's Generate policy) }}
+ {{yield D}}
+
+ Configure
+
+ {{#if this.shouldShowDelete}}
+ Delete
+ {{/if}}
+
+
+{{#if this.engineToDisable}}
+
+{{/if}}
\ No newline at end of file
diff --git a/ui/lib/core/addon/components/manage-dropdown.ts b/ui/lib/core/addon/components/manage-dropdown.ts
new file mode 100644
index 0000000000..be019d9b7c
--- /dev/null
+++ b/ui/lib/core/addon/components/manage-dropdown.ts
@@ -0,0 +1,117 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { action } from '@ember/object';
+import { service } from '@ember/service';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+
+import type RouterService from '@ember/routing/router-service';
+import type SecretsEngineResource from 'vault/resources/secrets/engine';
+import type ApiService from 'vault/services/api';
+import type FlashMessageService from 'vault/services/flash-messages';
+
+/**
+ * @module ManageDropdown
+ * Reusable component for displaying the Manage dropdown used in secret engine headers & secret engine mount list.
+ *
+ * @example
+ * // In main app page headers and list components — uses the resource getter for the full absolute route
+ *
+ *
+ * // In Ember engine templates (pki, kubernetes, ldap, kmip, kv) — pass the short relative route,
+ * // since HDS @route resolves relative to the engine's router mount
+ *
+ *
+ * // With custom menu items (like KV's Generate policy) — icon variant in a Ember engine list
+ *
+ * Generate policy
+ *
+ *
+ * @param {SecretsEngineResource} model - The secrets engine resource containing the engine details
+ * @param {string} configRoute - Route for the Configure action.
+ * @param {string} variant - Set to "icon" for "..." icon button, otherwise shows "Manage" text button (default)
+ */
+
+interface Args {
+ model: SecretsEngineResource;
+ configRoute: string;
+ variant?: 'icon';
+}
+
+export default class ManageDropdown extends Component {
+ @service declare readonly router: RouterService;
+ @service('app-router') declare readonly appRouter: RouterService;
+ @service declare readonly api: ApiService;
+ @service declare readonly flashMessages: FlashMessageService;
+
+ @tracked engineToDisable: SecretsEngineResource | undefined = undefined;
+
+ get isIcon() {
+ return this.args.variant === 'icon';
+ }
+
+ get configureRouteModel() {
+ return this.args.model.id;
+ }
+
+ get shouldShowDelete() {
+ // Don't show delete for cubbyhole engine
+ return this.args.model.type !== 'cubbyhole';
+ }
+
+ transitionToBackends() {
+ // First try using the router service, which is available in most contexts
+ if (this.router) {
+ this.router.transitionTo('vault.cluster.secrets.backends');
+ return;
+ }
+
+ // Fallback for ember-engine components which use appRouter instead of router service
+ if (this.appRouter) {
+ this.appRouter.transitionTo('vault.cluster.secrets.backends');
+ }
+ }
+
+ @action
+ handleDeleteClick(engine: SecretsEngineResource) {
+ this.engineToDisable = engine;
+ }
+
+ @action
+ handleModalClose() {
+ this.engineToDisable = undefined;
+ }
+
+ @action
+ async handleModalConfirm() {
+ if (this.engineToDisable) {
+ const { engineType, id, path } = this.engineToDisable;
+
+ try {
+ await this.api.sys.mountsDisableSecretsEngine(id);
+ this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
+ this.transitionToBackends();
+ } catch (error) {
+ const { message } = await this.api.parseError(error);
+ this.flashMessages.danger(
+ `There was an error disabling the ${engineType} Secrets Engine at ${path}: ${message}.`
+ );
+ } finally {
+ this.engineToDisable = undefined;
+ }
+ }
+ }
+}
diff --git a/ui/lib/core/addon/helpers/engines-display-data.ts b/ui/lib/core/addon/helpers/engines-display-data.ts
new file mode 100644
index 0000000000..d54920d50f
--- /dev/null
+++ b/ui/lib/core/addon/helpers/engines-display-data.ts
@@ -0,0 +1,62 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { ALL_ENGINES, type EngineDisplayData } from 'core/utils/all-engines-metadata';
+import { getEffectiveEngineType } from 'vault/utils/external-plugin-helpers';
+
+/**
+ * Default metadata for unknown engine plugins
+ */
+export const unknownEngineMetadata = (methodType?: string): EngineDisplayData => ({
+ type: methodType || 'unknown',
+ displayName: methodType || 'Unknown plugin',
+ glyph: 'lock',
+ mountCategory: ['secret', 'auth'],
+});
+
+/**
+ * Helper function to retrieve engine metadata for a given `methodType`.
+ * It searches the `ALL_ENGINES` array for an engine with a matching type and returns its metadata object.
+ * The `ALL_ENGINES` array includes secret and auth engines, including those supported only in enterprise.
+ * These details (such as mount type and enterprise licensing) are included in the returned engine object.
+ *
+ * For external plugins that have a builtin mapping (e.g., "vault-plugin-secrets-keymgmt" -> "keymgmt"),
+ * this function returns the metadata for the corresponding builtin engine, preserving the original
+ * external plugin name in the type field.
+ *
+ * Example usage:
+ * const engineMetadata = engineDisplayData('kmip');
+ * if (engineMetadata?.requiresEnterprise) {
+ * console.log(`This mount: ${engineMetadata.engineType} requires an enterprise license`);
+ * }
+ *
+ * @param {string} methodType - The engine type (sometimes called backend) to look up (e.g., "aws", "azure", "vault-plugin-secrets-keymgmt").
+ * @returns {Object} - The engine metadata, which includes information about its mount type (e.g., secret or auth)
+ * and whether it requires an enterprise license. For unknown engines, returns a default unknown plugin object.
+ */
+export default function engineDisplayData(methodType: string): EngineDisplayData {
+ // First try to find an exact match
+ const builtinEngine = ALL_ENGINES?.find((t) => t.type === methodType);
+ if (builtinEngine) {
+ return builtinEngine;
+ }
+
+ // If no direct match, check if this is a known external plugin and use its builtin mapping
+ const effectiveType = getEffectiveEngineType(methodType);
+ if (effectiveType !== methodType) {
+ // This is a known external plugin with a builtin mapping
+ const mappedEngine = ALL_ENGINES?.find((t) => t.type === effectiveType);
+ if (mappedEngine) {
+ // Return the mapped engine metadata but preserve the original external plugin type
+ return {
+ ...mappedEngine,
+ type: methodType, // Keep the original external plugin name for identification
+ };
+ }
+ }
+
+ // Return default unknown plugin metadata
+ return unknownEngineMetadata(methodType);
+}
diff --git a/ui/lib/core/addon/utils/all-engines-metadata.ts b/ui/lib/core/addon/utils/all-engines-metadata.ts
new file mode 100644
index 0000000000..86cd6e8c12
--- /dev/null
+++ b/ui/lib/core/addon/utils/all-engines-metadata.ts
@@ -0,0 +1,353 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+/**
+ * Metadata configuration for secret and auth engines, including enterprise.
+ *
+ * This file defines and exports engine metadata, including its
+ * displayName, mountCategory, requiresEnterprise, and other relevant properties. It serves as a
+ * centralized source of truth for engine-related configurations.
+ *
+ * Key responsibilities:
+ * - Define metadata for all engines.
+ * - Provide utility functions or constants for accessing engine-specific data.
+ * - Facilitate dynamic engine rendering and behavior based on metadata.
+ *
+ * Example usage:
+ * If an enterprise license is present, return all secret engines;
+ * otherwise, return only the secret engines supported in OSS.
+ * return filterEnginesByMountCategory({ mountCategory: 'secret', isEnterprise: this.version.isEnterprise });
+ */
+
+export interface EngineDisplayData {
+ pluginCategory?: string; // The plugin category is used to group engines in the UI. e.g., 'cloud', 'infra', 'generic'
+ displayName: string;
+ engineRoute?: string; // engines that have their own Ember engine will have this route defined.
+ glyph?: string;
+ isWIF?: boolean; // flag for 'Workload Identity Federation' engines. - https://developer.hashicorp.com/hcp/docs/hcp/iam/service-principal/workload-identity-federation
+ mountCategory: string[];
+ requiredFeature?: string; // flag for engines that require the ADP (Advanced Data Protection) feature. - https://www.hashicorp.com/en/blog/advanced-data-protection-adp-now-available-in-hcp-vault
+ requiresEnterprise?: boolean;
+ isConfigurable?: boolean; // for secret engines that have additional configuration pages and actions.
+ isOnlyMountable?: boolean; // The UI only supports configuration views for these secrets engines. The CLI must be used to manage other engine resources (i.e. roles, credentials).
+ type: string;
+ value?: string;
+ configRoute?: string; // override for custom route if not "configuration.plugin-settings" (used for Ember engines)
+}
+
+/**
+ * @param mountCategory - Given mount category to filter by, e.g., 'auth' or 'secret'.
+ * @param isEnterprise - Optional boolean to indicate if enterprise engines should be included in the results.
+ * @returns Filtered array of engines that match the given mount category
+ */
+export function filterEnginesByMountCategory({
+ mountCategory,
+ isEnterprise = false,
+}: {
+ mountCategory: 'auth' | 'secret';
+ isEnterprise: boolean;
+}) {
+ return isEnterprise
+ ? ALL_ENGINES.filter((engine) => engine.mountCategory.includes(mountCategory))
+ : ALL_ENGINES.filter(
+ (engine) => engine.mountCategory.includes(mountCategory) && !engine.requiresEnterprise
+ );
+}
+
+export function isAddonEngine(type: string, version: number) {
+ if (type === 'kv' && version === 1) {
+ return false;
+ }
+ const engineRoute = ALL_ENGINES.find((engine) => engine.type === type)?.engineRoute;
+ return !!engineRoute;
+}
+
+// The "sys/mounts" and "sys/internal/ui/mounts" endpoints return a "secret/" key containing
+// all mounts enabled in Vault. Some types are internal Vault APIs, not user-mountable secrets engines,
+// and should be filtered in some scenarios, such as listing secrets engines.
+export const INTERNAL_ENGINE_TYPES = ['system', 'identity', 'agent_registry'];
+
+export const ALL_ENGINES: EngineDisplayData[] = [
+ {
+ pluginCategory: 'cloud',
+ displayName: 'AliCloud',
+ glyph: 'alibaba-color',
+ mountCategory: ['auth', 'secret'],
+ type: 'alicloud',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'AppRole',
+ glyph: 'cpu',
+ mountCategory: ['auth'],
+ type: 'approle',
+ value: 'approle',
+ },
+ {
+ pluginCategory: 'cloud',
+ displayName: 'AWS',
+ glyph: 'aws-color',
+ isConfigurable: true,
+ isWIF: true,
+ mountCategory: ['auth', 'secret'],
+ type: 'aws',
+ },
+ {
+ pluginCategory: 'cloud',
+ displayName: 'Azure',
+ glyph: 'azure-color',
+ isOnlyMountable: true,
+ isConfigurable: true,
+ isWIF: true,
+ mountCategory: ['auth', 'secret'],
+ type: 'azure',
+ },
+ {
+ pluginCategory: 'infra',
+ displayName: 'Consul',
+ glyph: 'consul-color',
+ mountCategory: ['secret'],
+ type: 'consul',
+ },
+ {
+ displayName: 'Cubbyhole',
+ type: 'cubbyhole',
+ mountCategory: ['secret'],
+ },
+ {
+ pluginCategory: 'infra',
+ displayName: 'Databases',
+ glyph: 'database',
+ mountCategory: ['secret'],
+ type: 'database',
+ },
+ {
+ pluginCategory: 'cloud',
+ displayName: 'GitHub',
+ glyph: 'github-color',
+ mountCategory: ['auth'],
+ type: 'github',
+ value: 'github',
+ },
+ {
+ pluginCategory: 'cloud',
+ displayName: 'Google Cloud',
+ glyph: 'gcp-color',
+ isOnlyMountable: true,
+ isConfigurable: true,
+ isWIF: true,
+ mountCategory: ['auth', 'secret'],
+ type: 'gcp',
+ },
+ {
+ pluginCategory: 'cloud',
+ displayName: 'Google Cloud KMS',
+ glyph: 'gcp-color',
+ mountCategory: ['secret'],
+ type: 'gcpkms',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'JWT',
+ glyph: 'jwt',
+ mountCategory: ['auth'],
+ type: 'jwt',
+ value: 'jwt',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'KV',
+ engineRoute: 'kv.list',
+ configRoute: 'kv.configuration', // only utilized to display config data for kvv2, not in conjunction with isConfigurable as templates determine whether engine is kv v1 or v2
+ glyph: 'key-values',
+ mountCategory: ['secret'],
+ type: 'kv',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'KMIP',
+ engineRoute: 'kmip.scopes.index',
+ configRoute: 'kmip.configuration',
+ isConfigurable: true,
+ glyph: 'lock',
+ mountCategory: ['secret'],
+ requiredFeature: 'KMIP',
+ requiresEnterprise: true,
+ type: 'kmip',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'Transform',
+ glyph: 'transform-data',
+ mountCategory: ['secret'],
+ requiredFeature: 'Transform Secrets Engine',
+ requiresEnterprise: true,
+ type: 'transform',
+ },
+ {
+ pluginCategory: 'cloud',
+ displayName: 'Key Management',
+ glyph: 'key',
+ mountCategory: ['secret'],
+ requiredFeature: 'Key Management Secrets Engine',
+ requiresEnterprise: true,
+ type: 'keymgmt',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'Kubernetes',
+ engineRoute: 'kubernetes.overview',
+ configRoute: 'kubernetes.configuration',
+ glyph: 'kubernetes-color',
+ isConfigurable: true,
+ mountCategory: ['auth', 'secret'],
+ type: 'kubernetes',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'LDAP',
+ isConfigurable: true,
+ engineRoute: 'ldap.overview',
+ configRoute: 'ldap.configuration',
+ glyph: 'folder-users',
+ mountCategory: ['auth', 'secret'],
+ type: 'ldap',
+ },
+ {
+ pluginCategory: 'infra',
+ displayName: 'Nomad',
+ glyph: 'nomad-color',
+ mountCategory: ['secret'],
+ type: 'nomad',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'OIDC',
+ glyph: 'openid-color',
+ mountCategory: ['auth'],
+ type: 'oidc',
+ value: 'oidc',
+ },
+ {
+ pluginCategory: 'infra',
+ displayName: 'Okta',
+ glyph: 'okta-color',
+ mountCategory: ['auth'],
+ type: 'okta',
+ value: 'okta',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'PKI Certificates',
+ isConfigurable: true,
+ engineRoute: 'pki.overview',
+ configRoute: 'pki.configuration',
+ glyph: 'certificate',
+ mountCategory: ['secret'],
+ type: 'pki',
+ },
+ {
+ pluginCategory: 'infra',
+ displayName: 'RADIUS',
+ glyph: 'mainframe',
+ mountCategory: ['auth'],
+ type: 'radius',
+ value: 'radius',
+ },
+ {
+ pluginCategory: 'infra',
+ displayName: 'RabbitMQ',
+ glyph: 'rabbitmq-color',
+ mountCategory: ['secret'],
+ type: 'rabbitmq',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'SAML',
+ glyph: 'saml-color',
+ mountCategory: ['auth'],
+ requiresEnterprise: true,
+ type: 'saml',
+ value: 'saml',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'SSH',
+ glyph: 'terminal-screen',
+ isConfigurable: true,
+ mountCategory: ['secret'],
+ type: 'ssh',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'TLS Certificates',
+ glyph: 'certificate',
+ mountCategory: ['auth'],
+ type: 'cert',
+ value: 'cert',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'TOTP',
+ glyph: 'history',
+ mountCategory: ['secret'],
+ type: 'totp',
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'Transit',
+ glyph: 'swap-horizontal',
+ mountCategory: ['secret'],
+ type: 'transit',
+ },
+ {
+ displayName: 'Token',
+ type: 'token',
+ mountCategory: ['auth'],
+ },
+ {
+ pluginCategory: 'generic',
+ displayName: 'Userpass',
+ glyph: 'users',
+ mountCategory: ['auth'],
+ type: 'userpass',
+ value: 'userpass',
+ },
+
+ // TODO: enable builtin plugins after confirming with Product
+ //
+ // {
+ // pluginCategory: 'generic',
+ // displayName: 'Ad',
+ // glyph: 'folder',
+ // isOnlyMountable: true,
+ // mountCategory: ['secret'],
+ // type: 'ad',
+ // },
+ // {
+ // pluginCategory: 'cloud',
+ // displayName: 'MongoDB Atlas',
+ // glyph: 'mongodb-color',
+ // isOnlyMountable: true,
+ // mountCategory: ['secret'],
+ // type: 'mongodbatlas',
+ // },
+ // {
+ // pluginCategory: 'infra',
+ // displayName: 'OpenLDAP',
+ // glyph: 'folder-users',
+ // isOnlyMountable: true,
+ // mountCategory: ['secret'],
+ // type: 'openldap',
+ // },
+ // {
+ // pluginCategory: 'infra',
+ // displayName: 'Terraform',
+ // glyph: 'terraform-color',
+ // isOnlyMountable: true,
+ // mountCategory: ['secret'],
+ // type: 'terraform',
+ // },
+];
diff --git a/ui/lib/core/app/components/manage-dropdown.js b/ui/lib/core/app/components/manage-dropdown.js
new file mode 100644
index 0000000000..e524fcb744
--- /dev/null
+++ b/ui/lib/core/app/components/manage-dropdown.js
@@ -0,0 +1,6 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+export { default } from 'core/components/manage-dropdown';
diff --git a/ui/lib/core/app/helpers/engines-display-data.js b/ui/lib/core/app/helpers/engines-display-data.js
new file mode 100644
index 0000000000..a71fe61517
--- /dev/null
+++ b/ui/lib/core/app/helpers/engines-display-data.js
@@ -0,0 +1,6 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+export { default } from 'core/helpers/engines-display-data';
diff --git a/ui/lib/kmip/addon/components/page/scopes.hbs b/ui/lib/kmip/addon/components/page/scopes.hbs
index b16ccc4234..ea9ec1de51 100644
--- a/ui/lib/kmip/addon/components/page/scopes.hbs
+++ b/ui/lib/kmip/addon/components/page/scopes.hbs
@@ -7,21 +7,7 @@
<:actions>
-
-
- Configure
- Delete
-
+
@@ -117,14 +103,4 @@
{{/if}}
-{{/if}}
-
-{{#if this.engineToDisable}}
-
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/kmip/addon/components/page/scopes.ts b/ui/lib/kmip/addon/components/page/scopes.ts
index b4b9dac68c..89dec64c86 100644
--- a/ui/lib/kmip/addon/components/page/scopes.ts
+++ b/ui/lib/kmip/addon/components/page/scopes.ts
@@ -3,21 +3,21 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import Component from '@glimmer/component';
-import { tracked } from '@glimmer/tracking';
-import { service } from '@ember/service';
import { action } from '@ember/object';
import { getOwner } from '@ember/owner';
-import { task } from 'ember-concurrency';
+import { service } from '@ember/service';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
import type RouterService from '@ember/routing/router-service';
-import type SecretMountPath from 'vault/services/secret-mount-path';
-import type ApiService from 'vault/services/api';
import type { CapabilitiesMap, EngineOwner } from 'vault/app-types';
import type SecretsEngineResource from 'vault/resources/secrets/engine';
+import type ApiService from 'vault/services/api';
import FlashMessageService from 'vault/services/flash-messages';
+import type SecretMountPath from 'vault/services/secret-mount-path';
interface Args {
+ secretsEngine: SecretsEngineResource;
scopes: string[];
capabilities: CapabilitiesMap;
}
@@ -27,7 +27,6 @@ export default class KmipScopesPageComponent extends Component {
@service declare readonly secretMountPath: SecretMountPath;
@service declare readonly api: ApiService;
@service declare readonly flashMessages: FlashMessageService;
- @tracked engineToDisable: SecretsEngineResource | undefined = undefined;
@tracked scopeToDelete: string | null = null;
@@ -56,22 +55,4 @@ export default class KmipScopesPageComponent extends Component {
this.flashMessages.danger(`Error deleting scope ${this.scopeToDelete}: ${message}`);
}
}
-
- @task
- *disableEngine(engine: SecretsEngineResource) {
- const { engineType, id, path } = engine;
-
- try {
- yield this.api.sys.mountsDisableSecretsEngine(id);
- this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } catch (err) {
- const { message } = yield this.api.parseError(err);
- this.flashMessages.danger(
- `There was an error disabling the ${engineType} Secrets Engine at ${path}: ${message}.`
- );
- } finally {
- this.engineToDisable = undefined;
- }
- }
}
diff --git a/ui/lib/kubernetes/addon/components/kubernetes-header.hbs b/ui/lib/kubernetes/addon/components/kubernetes-header.hbs
index 8dca4cfee7..69f296d30f 100644
--- a/ui/lib/kubernetes/addon/components/kubernetes-header.hbs
+++ b/ui/lib/kubernetes/addon/components/kubernetes-header.hbs
@@ -15,21 +15,7 @@
{{#if @configRoute}}
{{else}}
-
-
- Configure
- Delete
-
+
{{/if}}
diff --git a/ui/lib/kubernetes/addon/components/kubernetes-header.ts b/ui/lib/kubernetes/addon/components/kubernetes-header.ts
deleted file mode 100644
index a6769c4fce..0000000000
--- a/ui/lib/kubernetes/addon/components/kubernetes-header.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { service } from '@ember/service';
-import Component from '@glimmer/component';
-import { tracked } from '@glimmer/tracking';
-import { task } from 'ember-concurrency';
-
-import type SecretsEngineResource from 'vault/resources/secrets/engine';
-import type RouterService from '@ember/routing/router-service';
-import type FlashMessageService from 'vault/services/flash-messages';
-import type ApiService from 'vault/services/api';
-
-/**
- * @module KubernetesHeader handles the ldap page header.
- *
- * @example
- *
- *
- * @param {object} secretsEngine - A model contains a ldap secret engine resource.
- * @param {object} config - A model contains the configuration of the ldap secret engine.
- */
-
-interface Args {
- secretsEngine: SecretsEngineResource;
- config: Record;
-}
-
-export default class KubernetesHeader extends Component {
- @service('app-router') declare readonly router: RouterService;
- @service declare readonly api: ApiService;
- @service declare readonly flashMessages: FlashMessageService;
-
- @tracked engineToDisable: SecretsEngineResource | undefined = undefined;
-
- @task
- *disableEngine(engine: SecretsEngineResource) {
- const { engineType, id, path } = engine;
-
- try {
- yield this.api.sys.mountsDisableSecretsEngine(id);
- this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } catch (err) {
- const { message } = yield this.api.parseError(err);
- this.flashMessages.danger(
- `There was an error disabling the ${engineType} Secrets Engine at ${path}: ${message}.`
- );
- } finally {
- this.engineToDisable = undefined;
- }
- }
-}
diff --git a/ui/lib/kv/addon/components/page/list.hbs b/ui/lib/kv/addon/components/page/list.hbs
index a3ba752d8e..d0ccd0be6e 100644
--- a/ui/lib/kv/addon/components/page/list.hbs
+++ b/ui/lib/kv/addon/components/page/list.hbs
@@ -11,8 +11,7 @@
<:actions>
-
-
+
<:customTrigger as |openFlyout|>
@@ -20,19 +19,7 @@
- Configure
- Delete
-
+
{{/if}}
{{/if}}
-{{/if}}
-
-{{#if this.engineToDisable}}
-
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/kv/addon/components/page/list.js b/ui/lib/kv/addon/components/page/list.js
index 2f77aecdb3..f5c8fe9aa7 100644
--- a/ui/lib/kv/addon/components/page/list.js
+++ b/ui/lib/kv/addon/components/page/list.js
@@ -3,14 +3,13 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import Component from '@glimmer/component';
-import { service } from '@ember/service';
import { action } from '@ember/object';
-import { tracked } from '@glimmer/tracking';
import { getOwner } from '@ember/owner';
+import { service } from '@ember/service';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
import { ancestorKeysForKey } from 'core/utils/key-utils';
import { pathIsDirectory } from 'kv/utils/kv-breadcrumbs';
-import { task } from 'ember-concurrency';
/**
* @module List
@@ -32,7 +31,6 @@ export default class KvListPageComponent extends Component {
@tracked secretPath;
@tracked metadataToDelete = null; // set to the metadata intended to delete
- @tracked engineToDisable = undefined;
// used for KV list and list-directory view
// ex: beep/
@@ -59,24 +57,6 @@ export default class KvListPageComponent extends Component {
};
}
- @task
- *disableEngine(engine) {
- const { engineType, id, path } = engine;
-
- try {
- yield this.api.sys.mountsDisableSecretsEngine(id);
- this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } catch (err) {
- const { message } = yield this.api.parseError(err);
- this.flashMessages.danger(
- `There was an error disabling the ${engineType} Secrets Engine at ${path}: ${message}.`
- );
- } finally {
- this.engineToDisable = undefined;
- }
- }
-
@action
async onDelete(secretPath) {
try {
diff --git a/ui/lib/ldap/addon/components/ldap-header.hbs b/ui/lib/ldap/addon/components/ldap-header.hbs
index 422c21b0ba..f52fd7f9c4 100644
--- a/ui/lib/ldap/addon/components/ldap-header.hbs
+++ b/ui/lib/ldap/addon/components/ldap-header.hbs
@@ -15,21 +15,7 @@
{{#if @configRoute}}
{{else}}
-
-
- Configure
- Delete
-
+
{{/if}}
@@ -51,16 +37,6 @@
{{/if}}
-{{#if this.engineToDisable}}
-
-{{/if}}
-
{{yield to="toolbarFilters"}}
diff --git a/ui/lib/ldap/addon/components/ldap-header.ts b/ui/lib/ldap/addon/components/ldap-header.ts
deleted file mode 100644
index af9ce1d792..0000000000
--- a/ui/lib/ldap/addon/components/ldap-header.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * Copyright (c) HashiCorp, Inc.
- * SPDX-License-Identifier: BUSL-1.1
- */
-
-import { service } from '@ember/service';
-import Component from '@glimmer/component';
-import { tracked } from '@glimmer/tracking';
-import { task } from 'ember-concurrency';
-
-import type SecretsEngineResource from 'vault/resources/secrets/engine';
-import type RouterService from '@ember/routing/router-service';
-import type FlashMessageService from 'vault/services/flash-messages';
-import type ApiService from 'vault/services/api';
-
-/**
- * @module LdapHeader handles the ldap page header.
- *
- * @example
- *
- *
- * @param {object} secretsEngine - A model contains a ldap secret engine resource.
- * @param {object} config - A model contains the configuration of the ldap secret engine.
- */
-
-interface Args {
- secretsEngine: SecretsEngineResource;
- config: Record;
-}
-
-export default class LdapHeader extends Component {
- @service('app-router') declare readonly router: RouterService;
- @service declare readonly api: ApiService;
- @service declare readonly flashMessages: FlashMessageService;
-
- @tracked engineToDisable: SecretsEngineResource | undefined = undefined;
-
- @task
- *disableEngine(engine: SecretsEngineResource) {
- const { engineType, id, path } = engine;
-
- try {
- yield this.api.sys.mountsDisableSecretsEngine(id);
- this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } catch (err) {
- const { message } = yield this.api.parseError(err);
- this.flashMessages.danger(
- `There was an error disabling the ${engineType} Secrets Engine at ${path}: ${message}.`
- );
- } finally {
- this.engineToDisable = undefined;
- }
- }
-}
diff --git a/ui/lib/pki/addon/components/pki-page-header.hbs b/ui/lib/pki/addon/components/pki-page-header.hbs
index c584b79447..4ea78c2fae 100644
--- a/ui/lib/pki/addon/components/pki-page-header.hbs
+++ b/ui/lib/pki/addon/components/pki-page-header.hbs
@@ -17,21 +17,7 @@
{{#if @configRoute}}
{{else}}
-
-
- Configure
- Delete
-
+
{{/if}}
@@ -58,14 +44,4 @@
Tidy
-{{/if}}
-
-{{#if this.engineToDisable}}
-
{{/if}}
\ No newline at end of file
diff --git a/ui/lib/pki/addon/components/pki-page-header.ts b/ui/lib/pki/addon/components/pki-page-header.ts
index 4e6436c7fa..e6f398cf47 100644
--- a/ui/lib/pki/addon/components/pki-page-header.ts
+++ b/ui/lib/pki/addon/components/pki-page-header.ts
@@ -4,16 +4,12 @@
*/
import { service } from '@ember/service';
-import { tracked } from '@glimmer/tracking';
import Component from '@glimmer/component';
-import { task } from 'ember-concurrency';
-import type { PATH_MAP } from 'vault/utils/constants/capabilities';
-import type ApiService from 'vault/services/api';
-import type CapabilitiesService from 'vault/services/capabilities';
-import type FlashMessageService from 'vault/services/flash-messages';
import type RouterService from '@ember/routing/router-service';
import type SecretsEngineResource from 'vault/resources/secrets/engine';
+import type CapabilitiesService from 'vault/services/capabilities';
+import type { PATH_MAP } from 'vault/utils/constants/capabilities';
/**
* @module PkiPageHeader
@@ -25,7 +21,7 @@ import type SecretsEngineResource from 'vault/resources/secrets/engine';
*/
interface Args {
- backend: { id: string };
+ backend: SecretsEngineResource;
}
const ROUTE_PATH_MAP = {
@@ -36,12 +32,8 @@ const ROUTE_PATH_MAP = {
export default class PkiPageHeader extends Component {
@service('app-router') declare readonly router: RouterService;
- @service declare readonly api: ApiService;
- @service declare readonly flashMessages: FlashMessageService;
@service declare readonly capabilities: CapabilitiesService;
- @tracked engineToDisable = undefined;
-
get breadcrumbs() {
return [
{ label: 'Vault', route: 'vault', icon: 'vault', linkExternal: true },
@@ -61,22 +53,4 @@ export default class PkiPageHeader extends Component {
}
return null;
}
-
- @task
- *disableEngine(engine: SecretsEngineResource) {
- const { engineType, id, path } = engine;
-
- try {
- yield this.api.sys.mountsDisableSecretsEngine(id);
- this.flashMessages.success(`The ${engineType} Secrets Engine at ${path} has been disabled.`);
- this.router.transitionTo('vault.cluster.secrets.backends');
- } catch (err) {
- const { message } = yield this.api.parseError(err);
- this.flashMessages.danger(
- `There was an error disabling the ${engineType} Secrets Engine at ${path}: ${message}.`
- );
- } finally {
- this.engineToDisable = undefined;
- }
- }
}
diff --git a/ui/tests/acceptance/secrets/manage-dropdown-routing-test.js b/ui/tests/acceptance/secrets/manage-dropdown-routing-test.js
new file mode 100644
index 0000000000..5ce554b326
--- /dev/null
+++ b/ui/tests/acceptance/secrets/manage-dropdown-routing-test.js
@@ -0,0 +1,518 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { click, currentRouteName, fillIn, findAll, settled, visit } from '@ember/test-helpers';
+import { setupApplicationTest } from 'ember-qunit';
+import { module, test } from 'qunit';
+import { v4 as uuidv4 } from 'uuid';
+import engineDisplayData from 'vault/helpers/engines-display-data';
+import { login } from 'vault/tests/helpers/auth/auth-helpers';
+import { runCmd } from 'vault/tests/helpers/commands';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
+
+const SECRET_ENGINE_MANAGE_DROPDOWN_ROUTING_CASES = [
+ {
+ key: 'alicloud',
+ type: 'alicloud',
+ isEnginePathClickable: false,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'azure',
+ type: 'azure',
+ isEnginePathClickable: true,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'gcp',
+ type: 'gcp',
+ isEnginePathClickable: true,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'gcpkms',
+ type: 'gcpkms',
+ isEnginePathClickable: false,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'keymgmt',
+ type: 'keymgmt',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'kubernetes',
+ type: 'kubernetes',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ expectedActionConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.kubernetes.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.kubernetes.configure',
+ ],
+ expectedLandingConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.kubernetes.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.kubernetes.configure',
+ ],
+ },
+ {
+ key: 'kvv1',
+ type: 'kv',
+ version: 1,
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'kvv2',
+ type: 'kv',
+ version: 2,
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: true,
+ showConfigure: true,
+ showDelete: true,
+ expectedLandingConfigureRoutesOverride: ['vault.cluster.secrets.backend.kv.configuration'],
+ },
+ {
+ key: 'transform',
+ type: 'transform',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'transit',
+ type: 'transit',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'kmip',
+ type: 'kmip',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ expectedActionConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.kmip.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.kmip.configure',
+ ],
+ expectedLandingConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.kmip.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.kmip.configure',
+ ],
+ },
+ {
+ key: 'ldap',
+ type: 'ldap',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ expectedActionConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.ldap.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.ldap.configure',
+ ],
+ expectedLandingConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.ldap.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.ldap.configure',
+ ],
+ },
+ {
+ key: 'pki',
+ type: 'pki',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ expectedActionConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.pki.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.pki.configuration.create',
+ ],
+ expectedLandingConfigureRoutesOverride: [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.pki.configuration',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.pki.configuration.create',
+ ],
+ },
+ {
+ key: 'ssh',
+ type: 'ssh',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'totp',
+ type: 'totp',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'aws',
+ type: 'aws',
+ isEnginePathClickable: true,
+ showManageDropdown: true,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'consul',
+ type: 'consul',
+ isEnginePathClickable: false,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'nomad',
+ type: 'nomad',
+ isEnginePathClickable: false,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'rabbitmq',
+ type: 'rabbitmq',
+ isEnginePathClickable: false,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ },
+ {
+ key: 'database',
+ type: 'database',
+ isEnginePathClickable: true,
+ showManageDropdown: false,
+ showGeneratePolicy: false,
+ showConfigure: true,
+ showDelete: true,
+ expectedLandingRouteOverride: 'vault.cluster.secrets.backend.overview',
+ expectedLandingConfigureRoutesOverride: [],
+ },
+];
+
+const secretsEngineListRoute = '/vault/secrets-engines';
+
+const mountEngine = async ({ type, version }, path) => {
+ await mountSecrets.visit();
+ await click(GENERAL.cardContainer(type));
+ await fillIn(GENERAL.inputByAttr('path'), path);
+ if (type === 'kv' && version === 1) {
+ await click(GENERAL.button('Method Options'));
+ await mountSecrets.version(1);
+ }
+ await click(GENERAL.submitButton);
+};
+
+const filterEngineRowByPath = async (path) => {
+ await visit(secretsEngineListRoute);
+ const searchInputSelector = GENERAL.inputSearch('secret-engine-path');
+ if (findAll(searchInputSelector).length) {
+ await fillIn(searchInputSelector, path);
+ }
+};
+
+const clickVisibleMenuItem = async (name) => {
+ const visibleItem = findAll(GENERAL.menuItem(name)).find((el) => el.offsetParent !== null);
+ if (!visibleItem) {
+ throw new Error(`No visible menu item found for: ${name}`);
+ }
+ await click(visibleItem);
+};
+
+const assertMenuOptionVisibility = (assert, visibilityByOption, contextLabel, engineKey) => {
+ for (const [option, isVisible] of Object.entries(visibilityByOption)) {
+ if (isVisible) {
+ assert.dom(GENERAL.menuItem(option)).exists(`${contextLabel} shows ${option} for ${engineKey}`);
+ } else {
+ assert
+ .dom(GENERAL.menuItem(option))
+ .doesNotExist(`${contextLabel} does not show ${option} for ${engineKey}`);
+ }
+ }
+};
+
+const clickVisibleConfirmButton = async () => {
+ const visibleConfirmButton = findAll(GENERAL.confirmButton).find((el) => el.offsetParent !== null);
+ if (!visibleConfirmButton) {
+ return false;
+ }
+ await click(visibleConfirmButton);
+ return true;
+};
+
+const expectedActionConfigureRoutes = (engineType) => {
+ const { isConfigurable, configRoute } = engineDisplayData(engineType);
+ if (!isConfigurable) {
+ return ['vault.cluster.secrets.backend.configuration.general-settings'];
+ }
+
+ if (configRoute) {
+ return [`vault.cluster.secrets.backend.${configRoute}`];
+ }
+
+ return [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.configuration.edit',
+ ];
+};
+
+const expectedLandingRoute = ({ type, version = 1 }) => {
+ const engineData = engineDisplayData(type);
+ const isKvV1 = type === 'kv' && version === 1;
+
+ if (engineData.isOnlyMountable) {
+ return 'vault.cluster.secrets.backend.configuration.general-settings';
+ }
+ if (engineData.engineRoute && !isKvV1) {
+ return `vault.cluster.secrets.backend.${engineData.engineRoute}`;
+ }
+ return 'vault.cluster.secrets.backend.list-root';
+};
+
+const expectedLandingConfigureRoutes = ({ type, version = 1 }) => {
+ const engineData = engineDisplayData(type);
+ const isKvV1 = type === 'kv' && version === 1;
+
+ if (engineData.engineRoute && !isKvV1) {
+ if (engineData.configRoute) {
+ return [`vault.cluster.secrets.backend.${engineData.configRoute}`];
+ }
+ }
+
+ if (engineData.isConfigurable) {
+ return [
+ // if the engine is configured
+ 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ // if the engine is not configured
+ 'vault.cluster.secrets.backend.configuration.edit',
+ ];
+ }
+
+ return ['vault.cluster.secrets.backend.configuration.general-settings'];
+};
+
+const runEngineCase = async (assert, engine, uid, isEnterprise = false) => {
+ const mountPath = `manage-${engine.key}-${uid}`;
+ const actionConfigureRoutes =
+ engine.expectedActionConfigureRoutesOverride || expectedActionConfigureRoutes(engine.type);
+ const expectedManage = {
+ showManageDropdown: engine.showManageDropdown ?? false,
+ showGeneratePolicy: (engine.showGeneratePolicy ?? false) && isEnterprise,
+ showConfigure: engine.showConfigure ?? true,
+ showDelete: engine.showDelete ?? true,
+ };
+
+ // if engine path already exists, delete it before starting the test
+ await runCmd(`delete sys/mounts/${mountPath}`);
+
+ // mount the engine
+ await mountEngine(engine, mountPath);
+
+ // verify the engine shows in the list
+ await filterEngineRowByPath(mountPath);
+ assert.dom(GENERAL.tableRow()).exists(`row renders for ${engine.key}`);
+
+ assert.dom(GENERAL.menuTrigger).exists(`Action menu is shown for ${engine.key}`);
+ await click(GENERAL.menuTrigger);
+
+ assertMenuOptionVisibility(
+ assert,
+ {
+ Configure: expectedManage.showConfigure,
+ Delete: expectedManage.showDelete,
+ },
+ 'Action menu',
+ engine.key
+ );
+
+ if (expectedManage.showConfigure) {
+ // click configure and verify route
+ await clickVisibleMenuItem('Configure');
+ await settled();
+ assert.true(
+ actionConfigureRoutes.includes(currentRouteName()),
+ `Action: Configure routes correctly for ${engine.key}`
+ );
+ await filterEngineRowByPath(mountPath);
+ }
+
+ if (expectedManage.showDelete) {
+ // click delete and verify the engine is removed from the list
+ await filterEngineRowByPath(mountPath);
+ await click(GENERAL.menuTrigger);
+ await clickVisibleMenuItem('Delete');
+ const didConfirmActionDelete = await clickVisibleConfirmButton();
+ assert.true(didConfirmActionDelete, `Action: Delete shows confirm button for ${engine.key}`);
+ await settled();
+
+ await filterEngineRowByPath(mountPath);
+ assert.dom(GENERAL.tableRow()).doesNotExist(`Action: Delete removes ${engine.key} mount`);
+
+ // remount the engine for manage dropdown testing
+ await mountEngine(engine, mountPath);
+ await filterEngineRowByPath(mountPath);
+ }
+
+ const isEnginePathClickable = engine.isEnginePathClickable ?? false;
+ const backendLinkSelector = `a[href*="/vault/secrets-engines/${mountPath}"]`;
+
+ if (!isEnginePathClickable) {
+ // if the engine path is not expected to be clickable, verify it's not a link and skip the rest of the test
+ assert.dom(backendLinkSelector).doesNotExist(`EnginePath is not a clickable link for ${engine.key}`);
+ await runCmd(`delete sys/mounts/${mountPath}`);
+ return;
+ }
+
+ assert.dom(backendLinkSelector).exists(`EnginePath is a clickable link for ${engine.key}`);
+ await click(backendLinkSelector);
+
+ const routeAfterPathClick = engine.expectedLandingRouteOverride || expectedLandingRoute(engine);
+ assert.strictEqual(
+ currentRouteName(),
+ routeAfterPathClick,
+ `Engine path click redirects to ${routeAfterPathClick} for ${engine.key}`
+ );
+
+ const shouldShowManageDropdown = expectedManage.showManageDropdown;
+
+ if (!shouldShowManageDropdown) {
+ // if manage dropdown is not expected to show on the landing page, verify it's not shown and skip the rest of the test
+ assert
+ .dom(GENERAL.dropdownToggle('Manage'))
+ .doesNotExist(`Manage dropdown is not shown on landing page for ${engine.key}`);
+ await runCmd(`delete sys/mounts/${mountPath}`);
+ return;
+ }
+
+ assert
+ .dom(GENERAL.dropdownToggle('Manage'))
+ .exists(`Manage dropdown shows on landing page for ${engine.key}`);
+ await click(GENERAL.dropdownToggle('Manage'));
+ assertMenuOptionVisibility(
+ assert,
+ {
+ 'Generate policy': expectedManage.showGeneratePolicy,
+ Configure: expectedManage.showConfigure,
+ Delete: expectedManage.showDelete,
+ },
+ 'Manage dropdown',
+ engine.key
+ );
+
+ if (expectedManage.showConfigure) {
+ // click configure and verify route
+ await clickVisibleMenuItem('Configure');
+ await settled();
+ const allowedConfigureRoutes =
+ engine.expectedLandingConfigureRoutesOverride || expectedLandingConfigureRoutes(engine);
+ assert.true(
+ allowedConfigureRoutes.includes(currentRouteName()),
+ `Manage Configure routes correctly for ${engine.key}`
+ );
+
+ await filterEngineRowByPath(mountPath);
+ await click(backendLinkSelector);
+ await click(GENERAL.dropdownToggle('Manage'));
+ }
+
+ if (expectedManage.showDelete) {
+ // click delete and verify the engine is removed from the list
+ await clickVisibleMenuItem('Delete');
+ const didConfirmManageDelete = await clickVisibleConfirmButton();
+ if (!didConfirmManageDelete) {
+ assert.true(
+ engine.type === 'kubernetes',
+ `Manage Delete missing confirm is only expected for kubernetes; got ${engine.key}`
+ );
+ await runCmd(`delete sys/mounts/${mountPath}`);
+ await filterEngineRowByPath(mountPath);
+ assert.dom(GENERAL.tableRow()).doesNotExist(`Manage Delete removes ${engine.key} mount`);
+ return;
+ }
+ await settled();
+
+ await filterEngineRowByPath(mountPath);
+ assert.dom(GENERAL.tableRow()).doesNotExist(`Manage Delete removes ${engine.key} mount`);
+ return; // if the delete action is confirmed, the engine should be removed and we can end the test here without needing to clean up again
+ }
+};
+
+module('Acceptance | secrets-engines/manage-dropdown routing', function (hooks) {
+ setupApplicationTest(hooks);
+
+ hooks.beforeEach(function () {
+ this.uid = uuidv4();
+ return login();
+ });
+
+ for (const engine of SECRET_ENGINE_MANAGE_DROPDOWN_ROUTING_CASES) {
+ const isEnterpriseOnly = !!engineDisplayData(engine.type).requiresEnterprise;
+ const engineLabel = isEnterpriseOnly ? `${engine.key} (enterprise only)` : engine.key;
+
+ test(`manage dropdown coverage | ${engineLabel}`, async function (assert) {
+ const isEnterprise = this.owner.lookup('service:version').isEnterprise;
+ await runEngineCase(assert, engine, this.uid, isEnterprise);
+ });
+ }
+});
diff --git a/ui/tests/acceptance/secrets/mounts-test.js b/ui/tests/acceptance/secrets/mounts-test.js
index 46d25eb1ee..dbb87fbeb4 100644
--- a/ui/tests/acceptance/secrets/mounts-test.js
+++ b/ui/tests/acceptance/secrets/mounts-test.js
@@ -4,35 +4,34 @@
*/
import {
+ click,
currentRouteName,
currentURL,
- settled,
- click,
- findAll,
fillIn,
- visit,
+ findAll,
+ settled,
typeIn,
+ visit,
waitFor,
} from '@ember/test-helpers';
import { clickTrigger } from 'ember-power-select/test-support/helpers';
-import { module, test, skip } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
+import { module, skip, test } from 'qunit';
import { v4 as uuidv4 } from 'uuid';
import { runCmd, tokenWithPolicyCmd } from 'vault/tests/helpers/commands';
import { create } from 'ember-cli-page-object';
-import page from 'vault/tests/pages/settings/mount-secret-backend';
-import { login } from 'vault/tests/helpers/auth/auth-helpers';
-import consoleClass from 'vault/tests/pages/components/console/ui-panel';
-import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
-import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
-import { GENERAL } from 'vault/tests/helpers/general-selectors';
-import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
-import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
-import { SELECTORS as OIDC } from 'vault/tests/helpers/oidc-config';
-import { adminOidcCreateRead, adminOidcCreate } from 'vault/tests/helpers/secret-engine/policy-generator';
-import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
import engineDisplayData from 'vault/helpers/engines-display-data';
+import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
+import { login } from 'vault/tests/helpers/auth/auth-helpers';
+import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import { SELECTORS as OIDC } from 'vault/tests/helpers/oidc-config';
+import { adminOidcCreate, adminOidcCreateRead } from 'vault/tests/helpers/secret-engine/policy-generator';
+import { SECRET_ENGINE_SELECTORS as SES } from 'vault/tests/helpers/secret-engine/secret-engine-selectors';
+import consoleClass from 'vault/tests/pages/components/console/ui-panel';
+import { default as mountSecrets, default as page } from 'vault/tests/pages/settings/mount-secret-backend';
+import { filterEnginesByMountCategory } from 'vault/utils/all-engines-metadata';
const consoleComponent = create(consoleClass);
@@ -152,6 +151,7 @@ module('Acceptance | secrets-engines/enable', function (hooks) {
await page.secretList();
await settled();
+ await fillIn(GENERAL.inputSearch('secret-engine-path'), path);
assert
.dom(GENERAL.tableData(`${path}/`, 'path'))
.exists({ count: 1 }, 'renders only one instance of the engine');
diff --git a/ui/tests/acceptance/secrets/secrets-nav-test-helper.js b/ui/tests/acceptance/secrets/secrets-nav-test-helper.js
index 8667ca4f2b..1186b987ef 100644
--- a/ui/tests/acceptance/secrets/secrets-nav-test-helper.js
+++ b/ui/tests/acceptance/secrets/secrets-nav-test-helper.js
@@ -32,7 +32,7 @@ export default (test, type) => {
await fillIn(GENERAL.inputSearch('secret-engine-path'), backend);
await click(GENERAL.menuTrigger);
- await click(GENERAL.menuItem('View configuration'));
+ await click(GENERAL.menuItem('Configure'));
assert.strictEqual(
currentRouteName(),
`${BASE_ROUTE}.${this.expectedConfigEditRoute}`,
@@ -117,7 +117,7 @@ export default (test, type) => {
await visit(`/vault/secrets-engines`);
await fillIn(GENERAL.inputSearch('secret-engine-path'), backend);
await click(GENERAL.menuTrigger);
- await click(GENERAL.menuItem('View configuration'));
+ await click(GENERAL.menuItem('Configure'));
// For configurable engines, clicking "View configuration" will direct to its plugin settings route
await waitUntil(() => currentRouteName() === `${BASE_ROUTE}.${configRoute}`);
diff --git a/ui/tests/acceptance/settings-test.js b/ui/tests/acceptance/settings-test.js
index e34a6a6ac0..e754176b4b 100644
--- a/ui/tests/acceptance/settings-test.js
+++ b/ui/tests/acceptance/settings-test.js
@@ -3,16 +3,16 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { currentURL, visit, click, fillIn, currentRouteName, waitUntil } from '@ember/test-helpers';
-import { module, test } from 'qunit';
+import { click, currentRouteName, currentURL, fillIn, visit, waitUntil } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
+import { module, test } from 'qunit';
import { v4 as uuidv4 } from 'uuid';
-import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
import { login } from 'vault/tests/helpers/auth/auth-helpers';
import { deleteEngineCmd, mountEngineCmd, runCmd } from 'vault/tests/helpers/commands';
-import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { mountBackend } from 'vault/tests/helpers/components/mount-backend-form-helpers';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend';
module('Acceptance | secret engine mount settings', function (hooks) {
setupApplicationTest(hooks);
@@ -63,7 +63,7 @@ module('Acceptance | secret engine mount settings', function (hooks) {
await visit('/vault/secrets-engines');
await fillIn(GENERAL.inputSearch('secret-engine-path'), path);
await click(GENERAL.menuTrigger);
- await click(GENERAL.menuItem('View configuration'));
+ await click(GENERAL.menuItem('Configure'));
// since ldap hasn't been configured yet, it should redirect to configure page
assert.strictEqual(
currentURL(),
@@ -93,7 +93,7 @@ module('Acceptance | secret engine mount settings', function (hooks) {
await visit('/vault/secrets-engines');
await fillIn(GENERAL.inputSearch('secret-engine-path'), path);
await click(GENERAL.menuTrigger);
- await click(GENERAL.menuItem('View configuration'));
+ await click(GENERAL.menuItem('Configure'));
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.configuration.general-settings');
assert.strictEqual(
currentURL(),
@@ -125,7 +125,7 @@ module('Acceptance | secret engine mount settings', function (hooks) {
await visit('/vault/secrets-engines');
await fillIn(GENERAL.inputSearch('secret-engine-path'), path);
await click(GENERAL.menuTrigger);
- await click(GENERAL.menuItem('View configuration'));
+ await click(GENERAL.menuItem('Configure'));
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.configuration.edit');
assert.strictEqual(
currentURL(),
diff --git a/ui/tests/integration/components/kmip/page/scopes-test.js b/ui/tests/integration/components/kmip/page/scopes-test.js
index 1dc3fe05de..e16de02ea6 100644
--- a/ui/tests/integration/components/kmip/page/scopes-test.js
+++ b/ui/tests/integration/components/kmip/page/scopes-test.js
@@ -3,15 +3,16 @@
* SPDX-License-Identifier: BUSL-1.1
*/
-import { module, test } from 'qunit';
-import { setupRenderingTest } from 'ember-qunit';
-import { setupEngine } from 'ember-engines/test-support';
-import { setupMirage } from 'ember-cli-mirage/test-support';
import { click, fillIn, render } from '@ember/test-helpers';
+import { setupMirage } from 'ember-cli-mirage/test-support';
+import { setupEngine } from 'ember-engines/test-support';
+import { setupRenderingTest } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
+import { module, test } from 'qunit';
import sinon from 'sinon';
-import { GENERAL } from 'vault/tests/helpers/general-selectors';
+import SecretsEngineResource from 'vault/resources/secrets/engine';
import { getErrorResponse } from 'vault/tests/helpers/api/error-response';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
module('Integration | Component | kmip | Page::Scopes', function (hooks) {
setupRenderingTest(hooks);
@@ -20,6 +21,7 @@ module('Integration | Component | kmip | Page::Scopes', function (hooks) {
hooks.beforeEach(function () {
this.backend = 'kmip-test';
+ this.secretsEngine = new SecretsEngineResource({ path: this.backend, type: 'kmip' });
this.owner.lookup('service:secret-mount-path').update(this.backend);
const { secrets } = this.owner.lookup('service:api');
@@ -51,7 +53,7 @@ module('Integration | Component | kmip | Page::Scopes', function (hooks) {
this.renderComponent = () =>
render(
- hbs``,
+ hbs``,
{ owner: this.engine }
);
});
diff --git a/ui/tests/integration/components/manage-dropdown-test.js b/ui/tests/integration/components/manage-dropdown-test.js
new file mode 100644
index 0000000000..48cfffe831
--- /dev/null
+++ b/ui/tests/integration/components/manage-dropdown-test.js
@@ -0,0 +1,161 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { click, render } from '@ember/test-helpers';
+import { setupRenderingTest } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+import { module, test } from 'qunit';
+import sinon from 'sinon';
+import SecretsEngineResource from 'vault/resources/secrets/engine';
+import { GENERAL } from 'vault/tests/helpers/general-selectors';
+
+const DEFAULT_MOUNT_DATA = {
+ accessor: 'test_accessor',
+ config: {},
+ description: '',
+ external_entropy_access: false,
+ local: false,
+ plugin_version: '',
+ running_plugin_version: '',
+ running_sha256: '',
+ seal_wrap: false,
+ uuid: 'test-uuid',
+};
+
+const TEST_CASES = [
+ {
+ label: 'alicloud',
+ type: 'alicloud',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'azure',
+ type: 'azure',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ },
+ {
+ label: 'gcp',
+ type: 'gcp',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ },
+ {
+ label: 'gcpkms',
+ type: 'gcpkms',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'keymgmt',
+ type: 'keymgmt',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'kubernetes',
+ type: 'kubernetes',
+ expectedRoute: 'vault.cluster.secrets.backend.kubernetes.configuration',
+ },
+ {
+ label: 'kvv1',
+ type: 'kv',
+ version: 1,
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'kvv2',
+ type: 'kv',
+ version: 2,
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'transform',
+ type: 'transform',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'transit',
+ type: 'transit',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ { label: 'kmip', type: 'kmip', expectedRoute: 'vault.cluster.secrets.backend.kmip.configuration' },
+ { label: 'ldap', type: 'ldap', expectedRoute: 'vault.cluster.secrets.backend.ldap.configuration' },
+ { label: 'pki', type: 'pki', expectedRoute: 'vault.cluster.secrets.backend.pki.configuration' },
+ {
+ label: 'ssh',
+ type: 'ssh',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ },
+ {
+ label: 'totp',
+ type: 'totp',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'aws',
+ type: 'aws',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ },
+ {
+ label: 'consul',
+ type: 'consul',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'nomad',
+ type: 'nomad',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'rabbitmq',
+ type: 'rabbitmq',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+ {
+ label: 'database',
+ type: 'database',
+ expectedRoute: 'vault.cluster.secrets.backend.configuration.general-settings',
+ },
+];
+
+module('Integration | Component | manage-dropdown | Configure link', function (hooks) {
+ setupRenderingTest(hooks);
+
+ const makeModel = ({ type, version, id }) => {
+ const options = version ? { version } : undefined;
+ return new SecretsEngineResource({
+ ...DEFAULT_MOUNT_DATA,
+ path: `${id}/`,
+ type,
+ options,
+ });
+ };
+
+ TEST_CASES.forEach(({ label, type, version, expectedRoute }) => {
+ test(`Configure link routes correctly for ${label}`, async function (assert) {
+ const routing = this.owner.lookup('service:-routing');
+ const transitionSpy = sinon.stub(routing, 'transitionTo');
+ const id = `${label}-integration-test`;
+ this.model = makeModel({ type, version, id });
+
+ await render(
+ hbs``
+ );
+
+ await click(GENERAL.menuTrigger);
+ await click(GENERAL.menuItem('Configure'));
+
+ assert.true(transitionSpy.called, `Configure action for ${label} triggers a route transition`);
+ assert.strictEqual(
+ transitionSpy.firstCall.args[0],
+ expectedRoute,
+ `Configure action for ${label} transitions to ${expectedRoute}`
+ );
+ assert.true(
+ JSON.stringify(transitionSpy.firstCall.args).includes(id),
+ `Configure action for ${label} includes model id ${id}`
+ );
+
+ transitionSpy.restore();
+ });
+ });
+});
diff --git a/ui/tests/unit/components/manage-dropdown-test.js b/ui/tests/unit/components/manage-dropdown-test.js
new file mode 100644
index 0000000000..50d6e0c64f
--- /dev/null
+++ b/ui/tests/unit/components/manage-dropdown-test.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright IBM Corp. 2016, 2025
+ * SPDX-License-Identifier: BUSL-1.1
+ */
+
+import { setupTest } from 'ember-qunit';
+import { module, test } from 'qunit';
+import SecretsEngineResource from 'vault/resources/secrets/engine';
+
+const makeResource = ({ type, version }) => {
+ const options = version ? { version } : undefined;
+ return new SecretsEngineResource({
+ accessor: 'test_accessor',
+ config: {},
+ description: '',
+ external_entropy_access: false,
+ local: false,
+ options,
+ path: `${type}-test/`,
+ plugin_version: '',
+ running_plugin_version: '',
+ running_sha256: '',
+ seal_wrap: false,
+ type,
+ uuid: 'test-uuid',
+ });
+};
+
+module('Unit | Component | manage-dropdown', function (hooks) {
+ setupTest(hooks);
+
+ test('backendConfigurationLink: addon engines with configRoute always use their config route', function (assert) {
+ const kubernetes = makeResource({ type: 'kubernetes' });
+ assert.strictEqual(
+ kubernetes.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.kubernetes.configuration',
+ 'kubernetes always routes to its configuration page'
+ );
+
+ const ldap = makeResource({ type: 'ldap' });
+ assert.strictEqual(
+ ldap.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.ldap.configuration',
+ 'ldap always routes to its configuration page'
+ );
+
+ const pki = makeResource({ type: 'pki' });
+ assert.strictEqual(
+ pki.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.pki.configuration',
+ 'pki always routes to its configuration page'
+ );
+ });
+
+ test('backendConfigurationLink: configurable engines without configRoute route to plugin-settings', function (assert) {
+ const ssh = makeResource({ type: 'ssh' });
+ assert.strictEqual(
+ ssh.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.configuration.plugin-settings',
+ 'configurable engine routes to the plugin-settings view'
+ );
+ });
+
+ test('backendConfigurationLink: non-configurable engines always route to general-settings', function (assert) {
+ const alicloud = makeResource({ type: 'alicloud' });
+ assert.strictEqual(
+ alicloud.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.configuration.general-settings'
+ );
+ });
+
+ test('backendConfigurationLink: KV v1 routes to general-settings', function (assert) {
+ const kvV1 = makeResource({ type: 'kv', version: 1 });
+ assert.strictEqual(
+ kvV1.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.configuration.general-settings'
+ );
+ });
+
+ test('backendConfigurationLink: KV v2 routes to general-settings (configRoute is display-only)', function (assert) {
+ const kvV2 = makeResource({ type: 'kv', version: 2 });
+ assert.strictEqual(
+ kvV2.backendConfigurationLink,
+ 'vault.cluster.secrets.backend.configuration.general-settings',
+ "kv's configRoute is skipped because it's for display only"
+ );
+ });
+});
diff --git a/ui/tests/unit/helpers/engines-display-data-test.js b/ui/tests/unit/helpers/engines-display-data-test.js
index cefcd797c5..2fcd68681f 100644
--- a/ui/tests/unit/helpers/engines-display-data-test.js
+++ b/ui/tests/unit/helpers/engines-display-data-test.js
@@ -3,8 +3,8 @@
* SPDX-License-Identifier: BUSL-1.1
*/
+import engineDisplayData, { unknownEngineMetadata } from 'core/helpers/engines-display-data';
import { module, test } from 'qunit';
-import engineDisplayData, { unknownEngineMetadata } from 'vault/helpers/engines-display-data';
import { ALL_ENGINES } from 'vault/utils/all-engines-metadata';
module('Unit | Helper | engines-display-data', function () {