UI: Add tune support to Kubernetes Secrets Engine (#11855) (#11935)

* updating kub to use new config pages

* fix tests

* remove cta

* fix test

* updating configure route data, adding tests for dropdown/exit button

Co-authored-by: Dan Rivera <dan.rivera@hashicorp.com>
This commit is contained in:
Vault Automation 2026-01-23 09:27:22 -08:00 committed by GitHub
parent 433f90417a
commit e21dfb9707
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 80 additions and 48 deletions

View file

@ -197,8 +197,9 @@ export const ALL_ENGINES: EngineDisplayData[] = [
pluginCategory: 'generic',
displayName: 'Kubernetes',
engineRoute: 'kubernetes.overview',
configRoute: 'kubernetes.configuration',
glyph: 'kubernetes-color',
isOldEngine: true,
isConfigurable: true,
mountCategory: ['auth', 'secret'],
type: 'kubernetes',
},

View file

@ -3,26 +3,34 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Page::Header @title={{@model.id}} @icon={{@model.icon}}>
<Page::Header
@title={{if @configRoute (concat @model.id " configuration") @model.id}}
@icon={{@model.icon}}
@subtitle="Kubernetes"
>
<:breadcrumbs>
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
</:breadcrumbs>
<:actions>
<Hds::Dropdown as |D|>
<D.ToggleButton @text="Manage" @color="secondary" data-test-dropdown="Manage" />
<D.Interactive
@icon="settings"
@route={{if @promptConfig "configure" "configuration"}}
@model={{@model.id}}
data-test-popup-menu="Configure"
>Configure</D.Interactive>
<D.Interactive
{{on "click" (fn (mut this.engineToDisable) @model)}}
@color="critical"
@icon="trash"
data-test-popup-menu="Delete"
>Delete</D.Interactive>
</Hds::Dropdown>
{{#if @configRoute}}
<Hds::Button @color="secondary" @route="overview" @text="Exit configuration" data-test-button="Exit configuration" />
{{else}}
<Hds::Dropdown as |D|>
<D.ToggleButton @text="Manage" @color="secondary" data-test-dropdown="Manage" />
<D.Interactive
@icon="settings"
@route={{if @promptConfig "configure" "configuration"}}
@model={{@model.id}}
data-test-popup-menu="Configure"
>Configure</D.Interactive>
<D.Interactive
{{on "click" (fn (mut this.engineToDisable) @model)}}
@color="critical"
@icon="trash"
data-test-popup-menu="Delete"
>Delete</D.Interactive>
</Hds::Dropdown>
{{/if}}
</:actions>
</Page::Header>
@ -38,7 +46,6 @@
<ul>
<li><LinkTo @route="overview" data-test-tab="overview">Overview</LinkTo></li>
<li><LinkTo @route="roles" data-test-tab="roles">Roles</LinkTo></li>
<li><LinkTo @route="configuration" data-test-tab="config">Configuration</LinkTo></li>
</ul>
</nav>
{{/if}}

View file

@ -3,8 +3,12 @@
SPDX-License-Identifier: BUSL-1.1
}}
{{! TODO: VAULT-40884 Add @configRoute argument with value "configuration" so mount config tabs show }}
<KubernetesHeader @model={{@secretsEngine}} @promptConfig={{@promptConfig}} @breadcrumbs={{@breadcrumbs}}>
<KubernetesHeader
@model={{@secretsEngine}}
@promptConfig={{@promptConfig}}
@breadcrumbs={{@breadcrumbs}}
@configRoute="configuration"
>
{{#if @config}}
<ToolbarLink @route="configure" data-test-secret-backend-configure>
Edit configuration
@ -29,12 +33,4 @@
</span>
</div>
{{/if}}
{{else}}
<ConfigCta />
{{/if}}
<SecretsEngineMountConfig
@secretsEngine={{@secretsEngine}}
class="has-top-margin-xl has-bottom-margin-xl"
data-test-mount-config
/>
{{/if}}

View file

@ -3,12 +3,12 @@
SPDX-License-Identifier: BUSL-1.1
}}
{{! TODO: VAULT-40884 Update Page::Header to use Kubernetes::Header and pass @configRoute argument with value "configure" for mount config tabs to show}}
<Page::Header @title="Configure Kubernetes">
<:breadcrumbs>
<Page::Breadcrumbs @breadcrumbs={{@breadcrumbs}} />
</:breadcrumbs>
</Page::Header>
<KubernetesHeader
@model={{@engine}}
@promptConfig={{@promptConfig}}
@breadcrumbs={{@breadcrumbs}}
@configRoute="configure"
/>
<hr class="is-marginless has-background-gray-200" />

View file

@ -11,6 +11,7 @@ import type { KubernetesApplicationModel } from './application';
import type SecretMountPath from 'vault/services/secret-mount-path';
import type Controller from '@ember/controller';
import type { Breadcrumb } from 'vault/app-types';
import type RouterService from '@ember/routing/router-service';
interface RouteController extends Controller {
breadcrumbs: Array<Breadcrumb>;
@ -21,6 +22,7 @@ export type KubernetesConfigureModel = ModelFrom<KubernetesConfigureRoute>;
export default class KubernetesConfigureRoute extends Route {
@service declare readonly secretMountPath: SecretMountPath;
@service('app-router') declare readonly router: RouterService;
model() {
const { config, configError, secretsEngine, promptConfig } = this.modelFor(
@ -33,6 +35,12 @@ export default class KubernetesConfigureRoute extends Route {
return { secretsEngine, config, promptConfig };
}
afterModel(resolvedModel: KubernetesConfigureModel) {
if (!resolvedModel.config) {
this.router.transitionTo('vault.cluster.secrets.backend.kubernetes.configure');
}
}
setupController(controller: RouteController, resolvedModel: KubernetesConfigureModel) {
super.setupController(controller, resolvedModel);
const { currentPath } = this.secretMountPath;

View file

@ -23,9 +23,9 @@ export default class KubernetesConfigureRoute extends Route {
@service declare readonly secretMountPath: SecretMountPath;
async model() {
const { config } = this.modelFor('application') as KubernetesApplicationModel;
const { config, secretsEngine } = this.modelFor('application') as KubernetesApplicationModel;
const data = config || { disable_local_ca_jwt: false };
return new KubernetesConfigForm(data, { isNew: !config });
return { form: new KubernetesConfigForm(data, { isNew: !config }), secretsEngine };
}
setupController(controller: RouteController, resolvedModel: KubernetesConfigureModel) {

View file

@ -3,4 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Page::Configure @form={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
<Page::Configure @form={{this.model.form}} @breadcrumbs={{this.breadcrumbs}} @engine={{this.model.secretsEngine}} />

View file

@ -68,9 +68,8 @@ module('Acceptance | kubernetes | configuration', function (hooks) {
await this.visitConfiguration();
assert.strictEqual(
currentRouteName(),
'vault.cluster.secrets.backend.kubernetes.configuration',
'Transitions to configuration route on fetch 404'
'vault.cluster.secrets.backend.kubernetes.configure',
'Transitions to configure route on fetch 404'
);
assert.dom('[data-test-empty-state-title]').hasText('Kubernetes not configured', 'Config cta renders');
});
});

View file

@ -7,7 +7,7 @@ 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 { render } from '@ember/test-helpers';
import { click, render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import sinon from 'sinon';
@ -71,7 +71,6 @@ module('Integration | Component | kubernetes | KubernetesHeader', function (hook
);
assert.dom('[data-test-tab="overview"]').hasText('Overview', 'Overview tab renders');
assert.dom('[data-test-tab="roles"]').hasText('Roles', 'Roles tab renders');
assert.dom('[data-test-tab="config"]').hasText('Configuration', 'Configuration tab renders');
});
test('it should render filter for roles', async function (assert) {
@ -96,4 +95,27 @@ module('Integration | Component | kubernetes | KubernetesHeader', function (hook
.dom('.toolbar-actions [data-test-yield]')
.hasText('It yields!', 'Block is yielded for toolbar actions');
});
test('it should render a dropdown when configRoute is omitted', async function (assert) {
await render(
hbs`<KubernetesHeader @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}} />`,
{
owner: this.engine,
}
);
assert.dom(GENERAL.dropdownToggle('Manage')).hasText('Manage', 'Manage dropdown renders');
await click(GENERAL.dropdownToggle('Manage'));
assert.dom(GENERAL.menuItem('Configure')).exists('Configure dropdown item renders');
assert.dom(GENERAL.menuItem('Delete')).exists('Configure dropdown item renders');
});
test('it should render exit configuration button when configRoute is provided', async function (assert) {
await render(
hbs`<KubernetesHeader @configRoute="configuration" @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} @handleSearch={{this.handleSearch}} @handleInput={{this.handleInput}} @handleKeyDown={{this.handleKeyDown}} />`,
{
owner: this.engine,
}
);
assert.dom(GENERAL.button('Exit configuration')).exists('Exit configuration button renders');
});
});

View file

@ -49,16 +49,15 @@ module('Integration | Component | kubernetes | Page::Configuration', function (h
};
});
test('it should render tab page header, config cta and mount config', async function (assert) {
test('it should render tab page header', async function (assert) {
await this.renderComponent();
assert
.dom(GENERAL.icon('kubernetes-color'))
.hasClass('hds-icon-kubernetes-color', 'Kubernetes icon renders in title');
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('kubernetes-test', 'Mount path renders in title');
assert
.dom(GENERAL.hdsPageHeaderTitle)
.hasText('kubernetes-test configuration', 'Mount path renders in title');
assert.dom(SES.configure).doesNotExist('Toolbar action does not render when engine is not configured');
assert.dom(GENERAL.emptyStateTitle).hasText('Kubernetes not configured');
assert.dom(GENERAL.emptyStateActions).hasText('Configure Kubernetes');
assert.dom('[data-test-mount-config]').exists('Mount config renders');
});
test('it should render message for inferred configuration', async function (assert) {