UI: Add plugin settings route and tab to configuration page (#9031) (#9039)

* adding plugin settings tab and route

* updating plugin settings

* removing current when for secret engine nav link

* fix tab name

* adding empty state

Co-authored-by: Dan Rivera <dan.rivera@hashicorp.com>
This commit is contained in:
Vault Automation 2025-09-02 12:57:27 -06:00 committed by GitHub
parent f9ebc677fc
commit 1fcf55471d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 190 additions and 7 deletions

View File

@ -31,6 +31,18 @@
General settings General settings
</LinkTo> </LinkTo>
</li> </li>
{{! If engine is not configurable, hide plugin settings link }}
{{#if (get (engines-display-data @model.secretsEngine.type) "isConfigurable")}}
<li>
<LinkTo
@route="vault.cluster.secrets.backend.configuration.plugin-settings"
@model={{@model.secretsEngine.id}}
>
{{get (engines-display-data @model.secretsEngine.type) "displayName"}}
settings
</LinkTo>
</li>
{{/if}}
</ul> </ul>
</nav> </nav>
</div> </div>

View File

@ -0,0 +1,62 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<SecretEngine::PageHeader @model={{@model}} />
{{#if @model.config}}
{{#each this.displayFields as |field|}}
{{! public key while not sensitive when editing/creating, should be hidden by default on viewing }}
{{#if (eq field "public_key")}}
<InfoTableRow @label="Public key" @value={{@model.config.public_key}}>
<MaskedInput @value={{@model.config.public_key}} @name={{field}} @displayOnly={{true}} @allowCopy={{true}} />
</InfoTableRow>
{{else}}
<InfoTableRow
@alwaysRender={{not (is-empty-value (get @model.config field))}}
@label={{this.label field}}
@value={{get @model.config field}}
@formatTtl={{this.isDuration field}}
/>
{{/if}}
{{/each}}
{{else}}
{{#if (get (engines-display-data @model.secretsEngine.type) "isConfigurable")}}
{{! Prompt user to configure the secret engine }}
<EmptyState
data-test-config-cta
@title="{{get (engines-display-data @model.secretsEngine.type) 'displayName'}} not configured"
@message="Get started by configuring your {{get
(engines-display-data @model.secretsEngine.type)
'displayName'
}} secrets engine."
>
<Hds::Link::Standalone
@icon="chevron-right"
@iconPosition="trailing"
@text="Configure {{get (engines-display-data @model.secretsEngine.type) 'displayName'}}"
@route="vault.cluster.secrets.backend.configuration.edit"
@model={{@id}}
/>
</EmptyState>
{{else}}
<EmptyState
data-test-no-config
@title="No configuration details available"
@message="{{get
(engines-display-data @model.secretsEngine.type)
'displayName'
}} does not have any plugin specific configuration. All configurable parameters for this engine are under 'General Settings'."
>
<Hds::Link::Standalone
@icon="chevron-right"
@iconPosition="trailing"
@text="Back to general settings"
@route="vault.cluster.secrets.backend.configuration.general-settings"
@model={{@id}}
/>
</EmptyState>
{{/if}}
{{/if}}

View File

@ -0,0 +1,107 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import Component from '@glimmer/component';
import { toLabel } from 'core/helpers/to-label';
import engineDisplayData from 'vault/helpers/engines-display-data';
import SecretsEngineResource from 'vault/resources/secrets/engine';
/**
* @module PluginSettingsComponent is used to configure extended plugin level settings for secrets engines.
*
* @example
* ```js
* <Secrets:Page:PluginSettings
* @model={{this.model}}
* />
* ```
*
* @param {string} secretsEngine - secrets engine resource
*/
interface Args {
model: {
secretsEngine: SecretsEngineResource;
};
}
export default class PluginSettingsComponent extends Component<Args> {
awsFields = [
'role_arn',
'identity_token_audience',
'identity_token_ttl',
'access_key',
'region',
'iam_endpoint',
'sts_endpoint',
'max_retries',
'lease',
'lease_max',
'issuer',
];
azureFields = [
'subscription_id',
'tenant_id',
'client_id',
'identity_token_audience',
'identity_token_ttl',
'root_password_ttl',
'environment',
'issuer',
];
gcpFields = [
'service_account_email',
'ttl',
'max_ttl',
'identity_token_audience',
'identity_token_ttl',
'issuer',
];
sshFields = ['public_key', 'generate_signing_key'];
get displayFields() {
switch (engineDisplayData(this.args.model.secretsEngine.type)?.displayName) {
case 'AWS':
return this.awsFields;
case 'Azure':
return this.azureFields;
case 'Google Cloud':
return this.gcpFields;
case 'SSH':
return this.sshFields;
default:
return [];
}
}
label = (field: string) => {
const label = toLabel([field]);
// convert words like id and ttl to uppercase
const formattedLabel = label
.split(' ')
.map((word: string) => {
const acronyms = ['id', 'ttl', 'arn', 'iam', 'sts'];
return acronyms.includes(word.toLowerCase()) ? word.toUpperCase() : word;
})
.join(' ');
// map specific fields to custom labels
return (
{
lease: 'Default Lease TTL',
lease_max: 'Max Lease TTL',
ttl: 'Config TTL',
}[field] || formattedLabel
);
};
isDuration = (field: string) => {
return ['identity_token_ttl', 'root_password_ttl', 'lease', 'lease_max', 'ttl', 'max_ttl'].includes(
field
);
};
}

View File

@ -7,12 +7,7 @@
<Nav.Title data-test-sidebar-nav-heading="Vault">Vault</Nav.Title> <Nav.Title data-test-sidebar-nav-heading="Vault">Vault</Nav.Title>
<Nav.Link @route="vault.cluster.dashboard" @text="Dashboard" data-test-sidebar-nav-link="Dashboard" /> <Nav.Link @route="vault.cluster.dashboard" @text="Dashboard" data-test-sidebar-nav-link="Dashboard" />
<Nav.Link <Nav.Link @route="vault.cluster.secrets" @text="Secrets Engines" data-test-sidebar-nav-link="Secrets Engines" />
@route="vault.cluster.secrets"
@current-when="vault.cluster.secrets vault.cluster.secrets.mounts vault.cluster.secrets.backend.configuration.edit"
@text="Secrets Engines"
data-test-sidebar-nav-link="Secrets Engines"
/>
{{#if this.showSecretsSync}} {{#if this.showSecretsSync}}
<Nav.Link <Nav.Link
@route="vault.cluster.sync" @route="vault.cluster.sync"

View File

@ -187,8 +187,9 @@ Router.map(function () {
this.mount('pki'); this.mount('pki');
this.route('index', { path: '/' }); this.route('index', { path: '/' });
this.route('configuration', function () { this.route('configuration', function () {
this.route('index', { path: '/' }); this.route('index', { path: '/' }); // this is still used by old engines
this.route('general-settings'); this.route('general-settings');
this.route('plugin-settings');
// only CONFIGURABLE_SECRET_ENGINES can be configured and access the edit route // only CONFIGURABLE_SECRET_ENGINES can be configured and access the edit route
this.route('edit'); this.route('edit');
}); });

View File

@ -0,0 +1,6 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
}}
<SecretEngine::Page::PluginSettings @model={{this.model}} />