vault/ui/app/utils/plugin-catalog-helpers.ts
Vault Automation 91eabbd0db
[Feature][UI]: General Settings Follow Up Items (#8965) (#9262)
* UI: VAULT-39172 VAULT-38567 general settings followup (#8910)

* Add unsaved changes fields

* Set up default values for TTL and update general-settings

* Add form error state

* Ass TODO cmment

* Move actions back!

* Update unsaved changes state

* Address comments and add TODOs

* UI: VAULT-39264 Lease Duration TTL picker (#9080)

* Update default and max ttl to show correct default

* Query sys/internal endpoint for ttl values

* WIP ttl-picker-v2

* Intialize values and check for if ttl value is unset

* Use ttlKey instead of name

* Set name to be ttlKey

* Show validation for ttl picker

* Fix validation bugs

* Remove lease duration files

* Add copyright headers

* Initalize only when its a custom value

* Update ttl-picker to not have a dropdown

* Validate field before converting to secs

* [UI] Fix styling and update version card component (#9214)

* Fix styling and update version card component

* Update unsaved changes

* Code cleanup

* More code cleanup!

* Add helper function

* Remove query for lease duration

* Fix outstanding issues

* Captialize unsaved changes

* Update util name

* Remove action helper

* [UI]: General Settings design feedback updates (#9257)

* Small refactor based on design feedback

* More refactoring!

* Rename variables so it makes more sense!

* Remove unused modal fields

Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
2025-09-10 15:27:45 -07:00

215 lines
6.5 KiB
TypeScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { isEmpty } from '@ember/utils';
import type { EngineDisplayData } from './all-engines-metadata';
import type { PluginCatalogPlugin } from 'vault/services/plugin-catalog';
/**
* Constants for plugin catalog functionality
*/
const DEFAULT_EXTERNAL_PLUGIN_GLYPH = '';
/**
* Plugin categories used throughout the application
*/
export const PLUGIN_CATEGORIES = {
GENERIC: 'generic',
CLOUD: 'cloud',
INFRA: 'infra',
EXTERNAL: 'external',
} as const;
/**
* Mount categories for different engine types
*/
export const MOUNT_CATEGORIES = {
SECRET: 'secret',
AUTH: 'auth',
DATABASE: 'database',
} as const;
/**
* Plugin types used in catalog responses
*/
export const PLUGIN_TYPES = {
SECRET: 'secret',
AUTH: 'auth',
DATABASE: 'database',
} as const;
export interface PluginCatalogResponse {
data: {
detailed: PluginCatalogPlugin[];
secret?: string[];
auth?: string[];
database?: string[];
};
}
/**
* Enhanced engine display data with plugin catalog information
*/
export interface EnhancedEngineDisplayData extends EngineDisplayData {
version?: string;
builtin?: boolean;
deprecationStatus?: string;
isAvailable?: boolean;
pluginData?: PluginCatalogPlugin;
}
/**
* Enhances static engine data with plugin catalog information including availability status,
* deprecation status, and discovery of external plugins not in static metadata.
*
* @param allEngines - Array of static engine metadata
* @param secretEnginesDetailed - Array of detailed secret engine info from catalog
* @param databasePluginsDetailed - Array of detailed database plugin info from catalog
* @returns Enhanced engines with catalog data and dynamically discovered external plugins
*/
export function enhanceEnginesWithCatalogData(
allEngines: EngineDisplayData[],
secretEnginesDetailed: PluginCatalogPlugin[] = [],
databasePluginsDetailed: PluginCatalogPlugin[] = []
): EnhancedEngineDisplayData[] {
if (isEmpty(secretEnginesDetailed) && isEmpty(databasePluginsDetailed)) {
return allEngines;
}
// First, enhance existing static engines with catalog data
const enhancedEngines = allEngines.map((engine) => {
if (engine.type === MOUNT_CATEGORIES.DATABASE) {
const isDatabaseAvailable = databasePluginsDetailed.length > 0;
if (isDatabaseAvailable) {
const representativePlugin = databasePluginsDetailed[0];
return {
...engine,
builtin: representativePlugin?.builtin,
deprecationStatus: representativePlugin?.deprecation_status,
version: representativePlugin?.version,
isAvailable: true,
pluginData: representativePlugin,
};
} else {
return {
...engine,
isAvailable: false,
};
}
}
const pluginData = secretEnginesDetailed.find((plugin) => plugin.name === engine.type);
if (pluginData) {
return {
...engine,
builtin: pluginData.builtin,
deprecationStatus: pluginData.deprecation_status,
version: pluginData.version,
isAvailable: true,
pluginData,
};
}
return {
...engine,
isAvailable: false,
};
});
// Find plugins in catalog that don't exist in static metadata
const staticEngineTypes = new Set(allEngines.map((engine) => engine.type));
const externalPlugins: EnhancedEngineDisplayData[] = [];
// Process secret engines from the detailed array
secretEnginesDetailed.forEach((plugin) => {
// Skip if this plugin already exists in static metadata
if (staticEngineTypes.has(plugin.name)) {
return;
}
// Look for a static engine type that this plugin name contains or matches
// This handles cases like "my-custom-aws-plugin" matching the "aws" static engine
const matchingStaticEngine = allEngines.find((engine) => {
// Direct type match (e.g., plugin.name = "aws" matches engine.type = "aws")
if (plugin.name === engine.type) {
return true;
}
// Pattern match (e.g., plugin.name = "my-custom-aws-plugin" contains "aws")
return plugin.name.includes(engine.type) || plugin.name.includes(engine.type.replace('-', ''));
});
// Create external engine metadata with defaults
const externalEngine: EnhancedEngineDisplayData = {
type: plugin.name,
displayName: plugin.name
.split('-')
.map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' '), // Convert kebab-case to Title Case
mountCategory: [MOUNT_CATEGORIES.SECRET],
pluginCategory: PLUGIN_CATEGORIES.EXTERNAL, // Mark as external since it's not in static metadata
glyph: matchingStaticEngine?.glyph || DEFAULT_EXTERNAL_PLUGIN_GLYPH, // Use glyph from matching type or default
isAvailable: true,
builtin: plugin.builtin,
deprecationStatus: plugin.deprecation_status,
version: plugin.version,
pluginData: plugin,
};
externalPlugins.push(externalEngine);
});
return [...enhancedEngines, ...externalPlugins];
}
/**
* Categorizes engines by their availability status for display purposes.
* Separate enabled and disabled plugins in the UI.
* Engines with isAvailable === false are considered disabled.
*
* @param engines - Array of enhanced engine data with availability information
* @returns Object containing separate arrays for enabled and disabled engines
*
* @example
* ```typescript
* const engines = enhanceEnginesWithCatalogData(staticEngines, catalogData);
* const { enabled, disabled } = categorizeEnginesByStatus(engines);
*
* // Render enabled plugins first, then disabled plugins with different styling
* enabled.forEach(engine => renderEnabledPlugin(engine));
* disabled.forEach(engine => renderDisabledPlugin(engine));
* ```
*/
export interface CategorizedEngines {
enabled: EnhancedEngineDisplayData[];
disabled: EnhancedEngineDisplayData[];
}
export function categorizeEnginesByStatus(engines: EnhancedEngineDisplayData[]): CategorizedEngines {
const enabled: EnhancedEngineDisplayData[] = [];
const disabled: EnhancedEngineDisplayData[] = [];
engines.forEach((engine) => {
if (engine.isAvailable !== false) {
enabled.push(engine);
} else {
disabled.push(engine);
}
});
return { enabled, disabled };
}
export function getPluginVersionsFromEngineType(list: PluginCatalogPlugin[] | undefined, name: string) {
if (!list) return [];
return list.reduce((acc: string[], item: PluginCatalogPlugin) => {
if (item.name === name) acc.push(item.version);
return acc;
}, []);
}