mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-28 04:10:44 -04:00
* WIP secrets sidebar * Remove unwanted text and put some things back.. * Add secrets templates for sidebar * Fix tests * Update more Secrets navlinks * Add copywrite headers * Creates secrets.hbs so its the parent route * Update secrets comment * Update component name * Update sidebar to use helper * Secrets sync breadcrumbs * Address feedback~ * Use enum and add helper test * Fix links! Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
This commit is contained in:
parent
3d9a5c5d7d
commit
3842e8df73
30 changed files with 354 additions and 110 deletions
|
|
@ -169,10 +169,19 @@ export default class App extends Application {
|
|||
'api',
|
||||
'capabilities',
|
||||
'version',
|
||||
// services needed for Secrets sidebar component
|
||||
'current-cluster',
|
||||
'permissions',
|
||||
'-portal',
|
||||
'namespace',
|
||||
],
|
||||
externalRoutes: {
|
||||
kvSecretOverview: 'vault.cluster.secrets.backend.kv.secret.index',
|
||||
clientCountOverview: 'vault.cluster.clients',
|
||||
// routes needed for Secrets sidebar component
|
||||
secrets: 'vault.cluster.secrets',
|
||||
sync: 'vault.cluster.sync',
|
||||
vault: 'vault.cluster',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
8
ui/app/templates/vault/cluster/secrets.hbs
Normal file
8
ui/app/templates/vault/cluster/secrets.hbs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{{!
|
||||
Copyright IBM Corp. 2016, 2025
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Sidebar::Nav::Secrets />
|
||||
|
||||
{{outlet}}
|
||||
|
|
@ -7,15 +7,7 @@
|
|||
<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.secrets" @text="Secrets Engines" data-test-sidebar-nav-link="Secrets Engines" />
|
||||
{{#if this.showSecretsSync}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.sync"
|
||||
@text="Secrets Sync"
|
||||
@badge={{if this.flags.isHvdManaged "Plus" ""}}
|
||||
data-test-sidebar-nav-link="Secrets Sync"
|
||||
/>
|
||||
{{/if}}
|
||||
<Nav.Link @route="vault.cluster.secrets" @text="Secrets" @hasSubItems={{true}} data-test-sidebar-nav-link="Secrets" />
|
||||
|
||||
{{#if (display-nav-item this.navSection.resilienceAndRecovery)}}
|
||||
<Nav.Link
|
||||
|
|
|
|||
31
ui/lib/core/addon/components/sidebar/nav/secrets.hbs
Normal file
31
ui/lib/core/addon/components/sidebar/nav/secrets.hbs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{{!
|
||||
Copyright IBM Corp. 2016, 2025
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Hds::AppSideNav::Portal @ariaLabel="Secrets Navigation Links" data-test-sidebar-nav-panel="Secrets" as |Nav|>
|
||||
<Nav.BackLink
|
||||
@route={{if @isEngine "vault" "vault.cluster"}}
|
||||
@isRouteExternal={{@isEngine}}
|
||||
@current-when={{false}}
|
||||
@icon="arrow-left"
|
||||
@text="Back to main navigation"
|
||||
data-test-sidebar-nav-link="Back to main navigation"
|
||||
/>
|
||||
<Nav.Title data-test-sidebar-nav-heading="Secrets">Secrets</Nav.Title>
|
||||
<Nav.Link
|
||||
@route={{if @isEngine "secrets" "vault.cluster.secrets"}}
|
||||
@isRouteExternal={{@isEngine}}
|
||||
@text="Secrets engines"
|
||||
data-test-sidebar-nav-link="Secrets engines"
|
||||
/>
|
||||
{{#if (display-nav-item this.routeName.secretsSync)}}
|
||||
<Nav.Link
|
||||
@route={{if @isEngine "sync" "vault.cluster.sync"}}
|
||||
@isRouteExternal={{@isEngine}}
|
||||
@text="Secrets sync"
|
||||
@badge={{if this.flags.isHvdManaged "Plus" ""}}
|
||||
data-test-sidebar-nav-link="Secrets sync"
|
||||
/>
|
||||
{{/if}}
|
||||
</Hds::AppSideNav::Portal>
|
||||
22
ui/lib/core/addon/components/sidebar/nav/secrets.ts
Normal file
22
ui/lib/core/addon/components/sidebar/nav/secrets.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import { RouteName } from 'core/helpers/display-nav-item';
|
||||
|
||||
interface Args {
|
||||
isEngine?: boolean;
|
||||
}
|
||||
|
||||
export default class SidebarNavSecretsComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
|
||||
routeName = {
|
||||
secretsSync: RouteName.SECRETS_SYNC,
|
||||
};
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import type PermissionsService from 'vault/services/permissions';
|
|||
import type FlagsService from 'vault/services/flags';
|
||||
|
||||
export enum RouteName {
|
||||
SECRETS_SYNC = 'secrets-sync',
|
||||
SECRETS_RECOVERY = 'secrets-recovery',
|
||||
SEAL = 'seal',
|
||||
REPLICATION = 'replication',
|
||||
|
|
@ -36,10 +37,13 @@ export default class NavBar extends Helper {
|
|||
@service declare readonly flags: FlagsService;
|
||||
|
||||
compute([navItem]: string[]) {
|
||||
const { SECRETS_RECOVERY, SEAL, REPLICATION, VAULT_USAGE, LICENSE } = RouteName;
|
||||
const { SECRETS_RECOVERY, SEAL, REPLICATION, VAULT_USAGE, LICENSE, SECRETS_SYNC } = RouteName;
|
||||
const { RESILIENCE_AND_RECOVERY, REPORTING, CLIENT_COUNT } = NavSection;
|
||||
|
||||
switch (navItem) {
|
||||
// secrets sync nav items
|
||||
case SECRETS_SYNC:
|
||||
return this.supportsSecretsSync;
|
||||
// client count nav items
|
||||
case CLIENT_COUNT:
|
||||
return this.supportsClientCount;
|
||||
|
|
@ -131,6 +135,21 @@ export default class NavBar extends Helper {
|
|||
!this.version.hasPKIOnly
|
||||
);
|
||||
}
|
||||
|
||||
get supportsSecretsSync() {
|
||||
// always show for HVD managed clusters
|
||||
if (this.flags.isHvdManaged) return true;
|
||||
|
||||
if (this.flags.secretsSyncIsActivated) {
|
||||
// activating the feature requires different permissions than using the feature.
|
||||
// we want to show the link to allow activation regardless of permissions to sys/sync
|
||||
// and only check permissions if the feature has been activated
|
||||
return this.permissions.hasNavPermission('sync');
|
||||
}
|
||||
|
||||
// otherwise we show the link depending on whether or not the feature exists
|
||||
return this.version.hasSecretsSync;
|
||||
}
|
||||
}
|
||||
|
||||
export function computeNavBar(context: object, navItem: string): boolean {
|
||||
|
|
|
|||
6
ui/lib/core/app/components/sidebar/nav/secrets.js
Normal file
6
ui/lib/core/app/components/sidebar/nav/secrets.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
export { default } from 'core/components/sidebar/nav/secrets';
|
||||
|
|
@ -7,7 +7,8 @@
|
|||
@icon={{get (find-by "type" @destination.type (sync-destinations)) "icon"}}
|
||||
@title={{@destination.name}}
|
||||
@breadcrumbs={{array
|
||||
(hash label="Secrets Sync" route="secrets.overview")
|
||||
(hash label="Vault" route="vault" icon="vault" linkExternal=true)
|
||||
(hash label="Secrets sync" route="secrets.overview")
|
||||
(hash label="Destinations" route="secrets.destinations")
|
||||
(hash label="Destination")
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Page::Header @title="Secrets Sync">
|
||||
<Page::Header @title="Secrets sync">
|
||||
<:breadcrumbs>
|
||||
<Page::Breadcrumbs @breadcrumbs={{this.breadcrumbs}} />
|
||||
</:breadcrumbs>
|
||||
<:badges>
|
||||
{{#if this.flags.isHvdManaged}}
|
||||
<Hds::Badge @text="Plus feature" @color="highlight" @size="large" data-test-badge="Plus feature" />
|
||||
|
|
|
|||
|
|
@ -14,4 +14,16 @@ interface Args {
|
|||
|
||||
export default class LandingCtaComponent extends Component<Args> {
|
||||
@service declare readonly flags: FlagsService;
|
||||
|
||||
breadcrumbs = [
|
||||
{
|
||||
label: 'Vault',
|
||||
route: 'vault',
|
||||
icon: 'vault',
|
||||
linkExternal: true,
|
||||
},
|
||||
{
|
||||
label: 'Secrets sync',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,22 +55,24 @@ export default class DestinationsCreateForm extends Component<Args> {
|
|||
? {
|
||||
title: `Create Destination for ${typeDisplayName}`,
|
||||
breadcrumbs: [
|
||||
{ label: 'Secrets Sync', route: 'secrets.overview' },
|
||||
{ label: 'Select Destination', route: 'secrets.destinations.create' },
|
||||
{ label: 'Create Destination' },
|
||||
{ label: 'Vault', route: 'vault', icon: 'vault', linkExternal: true },
|
||||
{ label: 'Secrets sync', route: 'secrets.overview' },
|
||||
{ label: 'Select destination', route: 'secrets.destinations.create' },
|
||||
{ label: 'Create destination' },
|
||||
],
|
||||
}
|
||||
: {
|
||||
title: `Edit ${name}`,
|
||||
breadcrumbs: [
|
||||
{ label: 'Secrets Sync', route: 'secrets.overview' },
|
||||
{ label: 'Vault', route: 'vault', icon: 'vault', linkExternal: true },
|
||||
{ label: 'Secrets sync', route: 'secrets.overview' },
|
||||
{ label: 'Destinations', route: 'secrets.destinations' },
|
||||
{
|
||||
label: 'Destination',
|
||||
route: 'secrets.destinations.destination.secrets',
|
||||
model: { name, type },
|
||||
},
|
||||
{ label: 'Edit Destination' },
|
||||
{ label: 'Edit destination' },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@
|
|||
|
||||
<SyncHeader
|
||||
@title="Select a Destination"
|
||||
@breadcrumbs={{array (hash label="Secrets Sync" route="secrets.overview") (hash label="Select Destination")}}
|
||||
@breadcrumbs={{array
|
||||
(hash label="Vault" route="vault" icon="vault" linkExternal=true)
|
||||
(hash label="Secrets sync" route="secrets.overview")
|
||||
(hash label="Select Destination")
|
||||
}}
|
||||
/>
|
||||
|
||||
{{#each (array "cloud" "dev-tools") as |category|}}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if @destinations}}
|
||||
<SyncHeader @title="Secrets Sync" />
|
||||
<SyncHeader @title="Secrets sync" @breadcrumbs={{this.breadcrumbs}} />
|
||||
|
||||
<div class="tabs-container box is-bottomless is-marginless is-paddingless">
|
||||
<nav class="tabs" aria-label="destination tabs">
|
||||
|
|
|
|||
|
|
@ -48,6 +48,18 @@ export default class SyncSecretsDestinationsPageComponent extends Component<Args
|
|||
}
|
||||
}
|
||||
|
||||
breadcrumbs = [
|
||||
{
|
||||
label: 'Vault',
|
||||
route: 'vault',
|
||||
icon: 'vault',
|
||||
linkExternal: true,
|
||||
},
|
||||
{
|
||||
label: 'Secrets sync',
|
||||
},
|
||||
];
|
||||
|
||||
fetchAssociationsForDestinations = task(this, {}, async (page = 1) => {
|
||||
try {
|
||||
const total = page * this.pageSize;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,20 @@ export default class SyncEngine extends Engine {
|
|||
modulePrefix = modulePrefix;
|
||||
Resolver = Resolver;
|
||||
dependencies = {
|
||||
services: ['flash-messages', 'flags', 'app-router', 'store', 'api', 'capabilities', 'version'],
|
||||
externalRoutes: ['kvSecretOverview', 'clientCountOverview'],
|
||||
services: [
|
||||
'flash-messages',
|
||||
'flags',
|
||||
'app-router',
|
||||
'store',
|
||||
'api',
|
||||
'capabilities',
|
||||
'version',
|
||||
'-portal',
|
||||
'permissions',
|
||||
'current-cluster',
|
||||
'namespace',
|
||||
],
|
||||
externalRoutes: ['kvSecretOverview', 'clientCountOverview', 'vault', 'secrets', 'sync'],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
8
ui/lib/sync/addon/templates/application.hbs
Normal file
8
ui/lib/sync/addon/templates/application.hbs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{{!
|
||||
Copyright IBM Corp. 2016, 2025
|
||||
SPDX-License-Identifier: BUSL-1.1
|
||||
}}
|
||||
|
||||
<Sidebar::Nav::Secrets @isEngine={{true}} />
|
||||
|
||||
{{outlet}}
|
||||
|
|
@ -32,7 +32,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
|
|||
test('root-only nav items are unavailable', async function (assert) {
|
||||
await login();
|
||||
|
||||
['Dashboard', 'Secrets Engines', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
['Dashboard', 'Secrets', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item in chroot listener`);
|
||||
});
|
||||
// Client count is not root-only, but it is hidden for chroot
|
||||
|
|
@ -54,7 +54,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
|
|||
const userDefault = await runCmd(createTokenCmd());
|
||||
|
||||
await loginNs(namespace, userDefault);
|
||||
[('Dashboard', 'Secrets Engines', 'Access', 'Operational tools')].forEach((nav) => {
|
||||
[('Dashboard', 'Secrets', 'Access', 'Operational tools')].forEach((nav) => {
|
||||
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item for user with default policy`);
|
||||
});
|
||||
['Client count', 'Replication', 'Raft Storage', 'License', 'Seal Vault'].forEach((nav) => {
|
||||
|
|
@ -84,7 +84,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
|
|||
);
|
||||
|
||||
await loginNs(namespace, reader);
|
||||
['Dashboard', 'Secrets Engines', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
['Dashboard', 'Secrets', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item for user with read access policy`);
|
||||
});
|
||||
['Replication', 'Raft Storage', 'License', 'Seal Vault', 'Client count'].forEach((nav) => {
|
||||
|
|
@ -116,7 +116,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
|
|||
await runCmd(`write sys/namespaces/child -f`, false);
|
||||
|
||||
await loginNs(namespace, childReader);
|
||||
['Dashboard', 'Secrets Engines', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
['Dashboard', 'Secrets', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item`);
|
||||
});
|
||||
['Client count', 'Replication', 'Raft Storage', 'License', 'Seal Vault'].forEach((nav) => {
|
||||
|
|
@ -125,7 +125,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
|
|||
|
||||
await loginNs(`${namespace}/child`, childReader);
|
||||
|
||||
['Dashboard', 'Secrets Engines', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
['Dashboard', 'Secrets', 'Access', 'Operational tools'].forEach((nav) => {
|
||||
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item within child namespace`);
|
||||
});
|
||||
['Replication', 'Raft Storage', 'License', 'Seal Vault', 'Client count'].forEach((nav) => {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,10 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) {
|
|||
test(`it should render enterprise only navigation links`, async function (assert) {
|
||||
assert.dom(panel('Cluster')).exists('Cluster nav panel renders');
|
||||
|
||||
await click(GENERAL.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
assert.strictEqual(currentURL(), '/vault/sync/secrets/overview', 'Sync route renders');
|
||||
await click(GENERAL.navLink('Back to main navigation'));
|
||||
|
||||
await click(GENERAL.navLink('Client count'));
|
||||
assert.dom(panel('Client count')).exists('Client count nav panel renders');
|
||||
|
|
|
|||
|
|
@ -535,7 +535,7 @@ module('Acceptance | secrets/database/*', function (hooks) {
|
|||
assert.dom('[data-test-edit-link]').hasText('Edit configuration', 'Edit button exists with correct text');
|
||||
// Check with restricted permissions
|
||||
await login(token);
|
||||
await click('[data-test-sidebar-nav-link="Secrets Engines"]');
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
assert.dom(GENERAL.tableData(`${backend}/`, 'path')).exists('Shows backend on secret list page');
|
||||
await navToConnection(backend, connection);
|
||||
assert.strictEqual(
|
||||
|
|
|
|||
|
|
@ -573,7 +573,7 @@ module('Acceptance | Enterprise | kv-v2 workflow | edge cases', function (hooks)
|
|||
setupApplicationTest(hooks);
|
||||
|
||||
const navToEngine = async (backend) => {
|
||||
await click(GENERAL.navLink('Secrets Engines'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
return await click(`${GENERAL.tableData(`${backend}/`, 'path')} a`);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ module('Acceptance | sidebar navigation', function (hooks) {
|
|||
|
||||
const links = [
|
||||
{ label: 'Raft Storage', route: '/vault/storage/raft' },
|
||||
{ label: 'Secrets Engines', route: '/vault/secrets-engines' },
|
||||
{ label: 'Secrets', route: '/vault/secrets-engines' },
|
||||
{ label: 'Dashboard', route: '/vault/dashboard' },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ module('Acceptance | sync | destination (singular)', function (hooks) {
|
|||
|
||||
test('it should transition to overview route via breadcrumb', async function (assert) {
|
||||
await visit('vault/sync/secrets/destinations/aws-sm/destination-aws/secrets');
|
||||
await click(ts.breadcrumbAtIdx(0));
|
||||
await click(ts.breadcrumbAtIdx(1));
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/sync/secrets/overview',
|
||||
|
|
@ -39,7 +39,8 @@ module('Acceptance | sync | destination (singular)', function (hooks) {
|
|||
});
|
||||
|
||||
test('it should transition to correct routes when performing actions', async function (assert) {
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
await click(GENERAL.tab('Destinations'));
|
||||
await click(GENERAL.listItemLink);
|
||||
assert.dom(GENERAL.tab('Secrets')).hasClass('active', 'Secrets hdsTab is active');
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ module('Acceptance | sync | destinations (plural)', function (hooks) {
|
|||
},
|
||||
};
|
||||
});
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
await click(ts.cta.button);
|
||||
await click(ts.selectType('aws-sm'));
|
||||
await fillIn(ts.inputByAttr('name'), 'foo');
|
||||
|
|
@ -71,7 +72,8 @@ module('Acceptance | sync | destinations (plural)', function (hooks) {
|
|||
};
|
||||
});
|
||||
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
await click(ts.cta.button);
|
||||
await click(ts.selectType(type));
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,8 @@ module('Acceptance | sync | overview', function (hooks) {
|
|||
|
||||
test('it should transition to correct routes when performing actions', async function (assert) {
|
||||
syncScenario(this.server);
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
await click(ts.destinations.list.create);
|
||||
await click(ts.createCancel);
|
||||
await click(ts.overviewCard.actionText('Create new'));
|
||||
|
|
@ -77,7 +78,7 @@ module('Acceptance | sync | overview', function (hooks) {
|
|||
await click(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.action('sync'));
|
||||
await click(GENERAL.cancelButton);
|
||||
await click(ts.breadcrumbLink('Secrets Sync'));
|
||||
await click(ts.breadcrumbLink('Secrets sync'));
|
||||
await waitFor(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.action('details'));
|
||||
|
|
@ -196,7 +197,8 @@ module('Acceptance | sync | overview', function (hooks) {
|
|||
|
||||
// confirm we're in admin/foo
|
||||
assert.dom('[data-test-badge-namespace]').hasText('foo');
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
|
|
@ -247,7 +249,8 @@ module('Acceptance | sync | overview', function (hooks) {
|
|||
// confirm we're in admin/foo
|
||||
assert.dom('[data-test-badge-namespace]').hasText('foo');
|
||||
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(GENERAL.navLink('Secrets'));
|
||||
await click(GENERAL.navLink('Secrets sync'));
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
|
|||
test('it should render nav links', async function (assert) {
|
||||
const links = [
|
||||
'Dashboard',
|
||||
'Secrets Engines',
|
||||
'Secrets Sync',
|
||||
'Secrets',
|
||||
'Access',
|
||||
'Operational tools',
|
||||
'Resilience and recovery',
|
||||
|
|
@ -130,70 +129,6 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
|
|||
assert.dom(GENERAL.navHeading('Client Counts')).doesNotExist('Client count link is hidden.');
|
||||
});
|
||||
|
||||
test('it should render badge for promotional links on managed clusters', async function (assert) {
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
const promotionalLinks = ['Secrets Sync'];
|
||||
stubFeaturesAndPermissions(this.owner, true, true);
|
||||
await renderComponent();
|
||||
|
||||
promotionalLinks.forEach((link) => {
|
||||
assert.dom(GENERAL.navLink(link)).hasText(`${link} Plus`, `${link} link renders Plus badge`);
|
||||
});
|
||||
});
|
||||
|
||||
// Secrets Sync side nav link has multiple combinations of three variables to test:
|
||||
// 1. cluster type: enterprise (on and off license), HVD managed or community
|
||||
// 2. activation status: activated or not
|
||||
// 3. permissions: policy access to sys/sync routes or not
|
||||
|
||||
test('community: it hides Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, false, false);
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).doesNotExist();
|
||||
});
|
||||
|
||||
test('ent but feature is not on license: it hides Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, []);
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).doesNotExist();
|
||||
});
|
||||
|
||||
test('ent (on license), activated and permissions: it shows Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync']);
|
||||
this.flags.activatedFlags = ['secrets-sync'];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).exists();
|
||||
});
|
||||
|
||||
test('ent (on license), activated and no permissions: it hides Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync'], false);
|
||||
this.flags.activatedFlags = ['secrets-sync'];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).doesNotExist();
|
||||
});
|
||||
|
||||
test('ent (on license), not activated and permissions: it shows Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync']);
|
||||
this.flags.activatedFlags = [];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).exists();
|
||||
});
|
||||
|
||||
test('ent (on license), not activated and no permissions: it shows Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync'], false);
|
||||
this.flags.activatedFlags = [];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).exists();
|
||||
});
|
||||
|
||||
test('hvd managed: it shows Secrets Sync nav link regardless of activation status or permissions', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, [], false);
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
this.flags.activatedFlags = [];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets Sync')).exists();
|
||||
});
|
||||
|
||||
test('it does NOT show Secrets Recovery when user is in HVD admin namespace', async function (assert) {
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
|
||||
|
|
|
|||
120
ui/tests/integration/components/sidebar/nav/secrets-test.js
Normal file
120
ui/tests/integration/components/sidebar/nav/secrets-test.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* Copyright IBM Corp. 2016, 2025
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import { stubFeaturesAndPermissions } from 'vault/tests/helpers/components/sidebar-nav';
|
||||
import { setRunOptions } from 'ember-a11y-testing/test-support';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import { allFeatures } from 'core/utils/all-features';
|
||||
|
||||
const renderComponent = () => {
|
||||
return render(hbs`
|
||||
<Sidebar::Frame @isVisible={{true}}>
|
||||
<Sidebar::Nav::Secrets />
|
||||
</Sidebar::Frame>
|
||||
`);
|
||||
};
|
||||
|
||||
module('Integration | Component | sidebar-nav-secrets', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.flags = this.owner.lookup('service:flags');
|
||||
setRunOptions({
|
||||
rules: {
|
||||
// This is an issue with Hds::AppHeader::HomeLink
|
||||
'aria-prohibited-attr': { enabled: false },
|
||||
// TODO: fix use Dropdown on user-menu
|
||||
'nested-interactive': { enabled: false },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it should hide links and headings user does not have access to', async function (assert) {
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink()).exists({ count: 2 }, 'Nav links are hidden other than secrets engines');
|
||||
assert.dom(GENERAL.navHeading()).exists({ count: 1 }, 'Headings are hidden other than Secrets engines');
|
||||
});
|
||||
|
||||
test('it should render nav links', async function (assert) {
|
||||
const links = ['Secrets engines', 'Secrets sync'];
|
||||
// do not add PKI-only Secrets feature as it hides Client count nav link
|
||||
const features = allFeatures().filter((feat) => feat !== 'PKI-only Secrets');
|
||||
stubFeaturesAndPermissions(this.owner, true, true, features);
|
||||
await renderComponent();
|
||||
|
||||
assert.dom(GENERAL.navLink()).exists({ count: links.length + 1 }, 'Correct number of links render');
|
||||
links.forEach((link) => {
|
||||
assert.dom(GENERAL.navLink(link)).hasText(link, `${link} link renders`);
|
||||
});
|
||||
});
|
||||
|
||||
test('it should render badge for promotional links on managed clusters', async function (assert) {
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
const promotionalLinks = ['Secrets sync'];
|
||||
stubFeaturesAndPermissions(this.owner, true, true);
|
||||
await renderComponent();
|
||||
|
||||
promotionalLinks.forEach((link) => {
|
||||
assert.dom(GENERAL.navLink(link)).hasText(`${link} Plus`, `${link} link renders Plus badge`);
|
||||
});
|
||||
});
|
||||
|
||||
// Secrets Sync side nav link has multiple combinations of three variables to test:
|
||||
// 1. cluster type: enterprise (on and off license), HVD managed or community
|
||||
// 2. activation status: activated or not
|
||||
// 3. permissions: policy access to sys/sync routes or not
|
||||
|
||||
test('community: it hides Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, false, false);
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).doesNotExist();
|
||||
});
|
||||
|
||||
test('ent but feature is not on license: it hides Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, []);
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).doesNotExist();
|
||||
});
|
||||
|
||||
test('ent (on license), activated and permissions: it shows Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync']);
|
||||
this.flags.activatedFlags = ['secrets-sync'];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).exists();
|
||||
});
|
||||
|
||||
test('ent (on license), activated and no permissions: it hides Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync'], false);
|
||||
this.flags.activatedFlags = ['secrets-sync'];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).doesNotExist();
|
||||
});
|
||||
|
||||
test('ent (on license), not activated and permissions: it shows Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync']);
|
||||
this.flags.activatedFlags = [];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).exists();
|
||||
});
|
||||
|
||||
test('ent (on license), not activated and no permissions: it shows Secrets Sync nav link', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, ['Secrets Sync'], false);
|
||||
this.flags.activatedFlags = [];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).exists();
|
||||
});
|
||||
|
||||
test('hvd managed: it shows Secrets Sync nav link regardless of activation status or permissions', async function (assert) {
|
||||
stubFeaturesAndPermissions(this.owner, true, false, [], false);
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
this.flags.activatedFlags = [];
|
||||
await renderComponent();
|
||||
assert.dom(GENERAL.navLink('Secrets sync')).exists();
|
||||
});
|
||||
});
|
||||
|
|
@ -62,7 +62,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
|||
assert.expect(2);
|
||||
|
||||
await this.renderComponent();
|
||||
assert.dom(PAGE.breadcrumbs).hasText('Secrets Sync Select Destination Create Destination');
|
||||
assert.dom(GENERAL.breadcrumbs).hasText('Vault Secrets sync Select destination Create destination');
|
||||
await click(PAGE.cancelButton);
|
||||
const transition = this.transitionStub.calledWith('vault.cluster.sync.secrets.destinations.create');
|
||||
assert.true(transition, 'transitions to vault.cluster.sync.secrets.destinations.create on cancel');
|
||||
|
|
@ -92,7 +92,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
|||
assert.expect(2);
|
||||
|
||||
await this.renderComponent();
|
||||
assert.dom(PAGE.breadcrumbs).hasText('Secrets Sync Destinations Destination Edit Destination');
|
||||
assert.dom(GENERAL.breadcrumbs).hasText('Vault Secrets sync Destinations Destination Edit destination');
|
||||
|
||||
await click(PAGE.cancelButton);
|
||||
const transition = this.transitionStub.calledWith('vault.cluster.sync.secrets.destinations.destination');
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
|||
});
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Secrets Sync', 'Page title renders');
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Secrets sync', 'Page title renders');
|
||||
assert.dom(cta.summary).doesNotExist('CTA does not render');
|
||||
assert.dom(tab('Overview')).hasText('Overview', 'Overview tab renders');
|
||||
assert.dom(tab('Destinations')).hasText('Destinations', 'Destinations tab renders');
|
||||
|
|
@ -115,7 +115,7 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
|||
await this.renderComponent();
|
||||
|
||||
assert.dom(overview.optInBanner.container).doesNotExist('Opt-in banner is not shown');
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Secrets Sync');
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Secrets sync');
|
||||
assert.dom(GENERAL.badge('Plus feature')).hasText('Plus feature', 'Plus feature badge renders');
|
||||
assert.dom(cta.button).hasText('Create first destination', 'CTA action renders');
|
||||
assert.dom(cta.summary).exists();
|
||||
|
|
@ -158,7 +158,7 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
|||
this.isActivated = true;
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Secrets Sync');
|
||||
assert.dom(GENERAL.hdsPageHeaderTitle).hasText('Secrets sync');
|
||||
assert.dom(cta.button).hasText('Create first destination', 'CTA action renders');
|
||||
assert.dom(cta.summary).exists();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,46 @@ module('Unit | Helper | displayNavItem', function (hooks) {
|
|||
this.permissionsStub.restore();
|
||||
});
|
||||
|
||||
module('secrets sync', function () {
|
||||
test('it returns true when it is hvd managed', function (assert) {
|
||||
this.flags.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
|
||||
const supportsClientCount = computeNavBar(this, RouteName.SECRETS_SYNC);
|
||||
|
||||
assert.true(supportsClientCount);
|
||||
});
|
||||
|
||||
test('it returns true when it is secrets sync activated but not hvd managed', function (assert) {
|
||||
this.flags.featureFlags = [];
|
||||
this.flags.activatedFlags = [RouteName.SECRETS_SYNC];
|
||||
this.permissionsStub.returns(true);
|
||||
|
||||
const supportsClientCount = computeNavBar(this, RouteName.SECRETS_SYNC);
|
||||
|
||||
assert.true(supportsClientCount);
|
||||
});
|
||||
|
||||
test('it returns false when it is secrets sync activated but not hvd managed and permissions is false', function (assert) {
|
||||
this.flags.featureFlags = [];
|
||||
this.flags.activatedFlags = [RouteName.SECRETS_SYNC];
|
||||
this.permissionsStub.returns(false);
|
||||
|
||||
const supportsClientCount = computeNavBar(this, RouteName.SECRETS_SYNC);
|
||||
|
||||
assert.false(supportsClientCount);
|
||||
});
|
||||
|
||||
test('it returns false when it is enterprise', function (assert) {
|
||||
this.flags.featureFlags = [];
|
||||
this.version.type = 'community';
|
||||
this.features = [];
|
||||
|
||||
const supportsClientCount = computeNavBar(this, RouteName.SECRETS_SYNC);
|
||||
|
||||
assert.false(supportsClientCount);
|
||||
});
|
||||
});
|
||||
|
||||
module('client count', function () {
|
||||
test('it returns true when there are permissions and cluster is not secondary', function (assert) {
|
||||
this.permissionsStub.returns(true);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default create({
|
|||
maxTTLVal: fillable('[data-test-ttl-value="Max Lease TTL"]'),
|
||||
maxTTLUnit: fillable('[data-test-ttl-unit="Max Lease TTL"] [data-test-select="ttl-unit"]'),
|
||||
enableEngine: clickable('[data-test-enable-engine]'),
|
||||
secretList: clickable('[data-test-sidebar-nav-link="Secrets Engines"]'),
|
||||
secretList: clickable('[data-test-sidebar-nav-link="Secrets"]'),
|
||||
defaultTTLVal: fillable('input[data-test-ttl-value="Default Lease TTL"]'),
|
||||
defaultTTLUnit: fillable('[data-test-ttl-unit="Default Lease TTL"] [data-test-select="ttl-unit"]'),
|
||||
enable: async function (type, path) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue