From 5d265dd2844807a1e2a53fd667daf4bf30b0f560 Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Wed, 11 Feb 2026 12:49:53 -0500 Subject: [PATCH] [UI][VAULT-42484][VAULT-42483]: sidebar bugs (#12263) (#12293) * WIP * WIP... * Update page headers and move logic back * remove unused stuff * Fix failing tests * Use hasNavPermission * Update raft storage locatin and add namespace link on top * Update access sidebar link to Access control * Update order of API_PATHS * Remove namespace link Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com> --- ui/app/components/raft-storage-overview.hbs | 2 +- ui/app/components/raft-storage-restore.hbs | 2 +- ui/app/services/permissions.js | 48 ++++++++++--------- .../vault/cluster/access/methods.hbs | 2 +- .../vault/cluster/access/mfa/index.hbs | 2 +- .../templates/vault/cluster/access/oidc.hbs | 2 +- .../vault/cluster/policies/create.hbs | 2 +- .../vault/cluster/policies/index.hbs | 4 +- .../addon/components/sidebar/nav/access.hbs | 2 +- .../addon/components/sidebar/nav/clients.hbs | 4 +- .../addon/components/sidebar/nav/cluster.hbs | 21 ++++---- .../addon/components/sidebar/nav/cluster.js | 15 +++--- ui/tests/acceptance/access/methods-test.js | 2 +- ui/tests/acceptance/chroot-namespace-test.js | 10 ++-- .../acceptance/enterprise-sidebar-nav-test.js | 10 ++-- .../acceptance/oidc-config/clients-test.js | 2 +- ui/tests/acceptance/policy/policies-test.js | 2 +- ui/tests/acceptance/sidebar-nav-test.js | 20 ++++---- .../components/policy-example-test.js | 2 +- .../components/sidebar/nav/cluster-test.js | 12 ++--- 20 files changed, 84 insertions(+), 82 deletions(-) diff --git a/ui/app/components/raft-storage-overview.hbs b/ui/app/components/raft-storage-overview.hbs index fed5f6ca62..918e5ebbdf 100644 --- a/ui/app/components/raft-storage-overview.hbs +++ b/ui/app/components/raft-storage-overview.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: BUSL-1.1 }} - + <:actions> diff --git a/ui/app/components/raft-storage-restore.hbs b/ui/app/components/raft-storage-restore.hbs index 45fbd6e51e..1ce0137d7d 100644 --- a/ui/app/components/raft-storage-restore.hbs +++ b/ui/app/components/raft-storage-restore.hbs @@ -6,7 +6,7 @@ <:breadcrumbs> diff --git a/ui/app/services/permissions.js b/ui/app/services/permissions.js index 703a3cdadd..07874b685b 100644 --- a/ui/app/services/permissions.js +++ b/ui/app/services/permissions.js @@ -68,22 +68,29 @@ export const PERMISSIONS_BANNER_STATES = { export const RESULTANT_ACL_PATH = 'sys/internal/ui/resultant-acl'; // export for tests +// API_PATHS is used to find the first accessible path for a given nav item, so it must be kept in order of the sidebar nav structure. const API_PATHS = { - access: { - methods: 'sys/auth', - mfa: 'identity/mfa/method', - oidc: 'identity/oidc/client', - entities: 'identity/entity/id', - groups: 'identity/group/id', - leases: 'sys/leases/lookup', - namespaces: 'sys/namespaces', - 'control-groups': 'sys/control-group/', + sync: { + destinations: 'sys/sync/destinations', + associations: 'sys/sync/associations', + config: 'sys/sync/config', + github: 'sys/sync/github-apps', }, policies: { acl: 'sys/policies/acl', rgp: 'sys/policies/rgp', egp: 'sys/policies/egp', }, + access: { + 'control-groups': 'sys/control-group/', + leases: 'sys/leases/lookup', + methods: 'sys/auth', + mfa: 'identity/mfa/method', + oidc: 'identity/oidc/client', + namespaces: 'sys/namespaces', + groups: 'identity/group/id', + entities: 'identity/entity/id', + }, tools: { wrap: 'sys/wrapping/wrap', lookup: 'sys/wrapping/lookup', @@ -92,39 +99,34 @@ const API_PATHS = { random: 'sys/tools/random', hash: 'sys/tools/hash', }, + settings: { + customMessages: 'sys/config/ui/custom-messages', + }, status: { + seal: 'sys/seal', replication: 'sys/replication', license: 'sys/license', - seal: 'sys/seal', raft: 'sys/storage/raft/configuration', }, clients: { activity: 'sys/internal/counters/activity', config: 'sys/internal/counters/config', }, - settings: { - customMessages: 'sys/config/ui/custom-messages', - }, - sync: { - destinations: 'sys/sync/destinations', - associations: 'sys/sync/associations', - config: 'sys/sync/config', - github: 'sys/sync/github-apps', - }, monitoring: { 'utilization-report': 'sys/utilization-report', }, }; +// API_PATHS_TO_ROUTE_PARAMS is used to resolve route params for that path when checking permissions for the nav item. const API_PATHS_TO_ROUTE_PARAMS = { - 'sys/auth': { route: 'vault.cluster.access.methods', models: [] }, 'identity/entity/id': { route: 'vault.cluster.access.identity', models: ['entities'] }, 'identity/group/id': { route: 'vault.cluster.access.identity', models: ['groups'] }, - 'sys/leases/lookup': { route: 'vault.cluster.access.leases', models: [] }, - 'sys/namespaces': { route: 'vault.cluster.access.namespaces', models: [] }, - 'sys/control-group/': { route: 'vault.cluster.access.control-groups', models: [] }, 'identity/mfa/method': { route: 'vault.cluster.access.mfa', models: [] }, 'identity/oidc/client': { route: 'vault.cluster.access.oidc', models: [] }, + 'sys/auth': { route: 'vault.cluster.access.methods', models: [] }, + 'sys/control-group/': { route: 'vault.cluster.access.control-groups', models: [] }, + 'sys/leases/lookup': { route: 'vault.cluster.access.leases', models: [] }, + 'sys/namespaces': { route: 'vault.cluster.access.namespaces', models: [] }, }; // Canary endpoints: quick check for “meaningful UI access” in the *current* namespace. diff --git a/ui/app/templates/vault/cluster/access/methods.hbs b/ui/app/templates/vault/cluster/access/methods.hbs index 2e16cb3b5d..ac8e53cf89 100644 --- a/ui/app/templates/vault/cluster/access/methods.hbs +++ b/ui/app/templates/vault/cluster/access/methods.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: BUSL-1.1 }} - + <:breadcrumbs> diff --git a/ui/app/templates/vault/cluster/access/mfa/index.hbs b/ui/app/templates/vault/cluster/access/mfa/index.hbs index 6994eab133..ebbc91ef75 100644 --- a/ui/app/templates/vault/cluster/access/mfa/index.hbs +++ b/ui/app/templates/vault/cluster/access/mfa/index.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: BUSL-1.1 }} - + <:description> Configure and enforce multi-factor authentication (MFA) for users logging into Vault, for any diff --git a/ui/app/templates/vault/cluster/access/oidc.hbs b/ui/app/templates/vault/cluster/access/oidc.hbs index 83ecd50bdf..4d7bef18ac 100644 --- a/ui/app/templates/vault/cluster/access/oidc.hbs +++ b/ui/app/templates/vault/cluster/access/oidc.hbs @@ -4,7 +4,7 @@ }} {{#if this.header}} - + <:breadcrumbs> diff --git a/ui/app/templates/vault/cluster/policies/create.hbs b/ui/app/templates/vault/cluster/policies/create.hbs index 83ee389445..71d6196b14 100644 --- a/ui/app/templates/vault/cluster/policies/create.hbs +++ b/ui/app/templates/vault/cluster/policies/create.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: BUSL-1.1 }} - + <:breadcrumbs> + <:badges> {{#if (not-eq this.policyType "acl")}} @@ -13,7 +13,7 @@ diff --git a/ui/lib/core/addon/components/sidebar/nav/access.hbs b/ui/lib/core/addon/components/sidebar/nav/access.hbs index 7c601d6db4..2b95d53738 100644 --- a/ui/lib/core/addon/components/sidebar/nav/access.hbs +++ b/ui/lib/core/addon/components/sidebar/nav/access.hbs @@ -7,7 +7,7 @@ @ariaLabel="Access Navigation Links" tabindex="0" role="region" - data-test-sidebar-nav-panel="Access" + data-test-sidebar-nav-panel="Access control" as |Nav| > {{#if @canReadConfig}} {{/if}} - {{#if (or (has-permission "access") (has-permission "policies"))}} {{/if}} {{#if (has-permission "tools")}} @@ -44,14 +43,6 @@ }} Monitoring {{/if}} - {{#if (and this.cluster.usingRaft this.isRootNamespace (has-permission "status" routeParams="raft"))}} - - {{/if}} {{#if (display-nav-item this.navSection.reporting)}} {{/if}} + {{#if (and this.cluster.usingRaft this.isRootNamespace (has-permission "status" routeParams="raft"))}} + + {{/if}} \ No newline at end of file diff --git a/ui/lib/core/addon/components/sidebar/nav/cluster.js b/ui/lib/core/addon/components/sidebar/nav/cluster.js index aa2d831449..d619cf8aac 100644 --- a/ui/lib/core/addon/components/sidebar/nav/cluster.js +++ b/ui/lib/core/addon/components/sidebar/nav/cluster.js @@ -53,23 +53,24 @@ export default class SidebarNavClusterComponent extends Component { } get accessRoute() { - if (this.permissions.hasPermission('policies')) { + if (this.permissions.hasNavPermission('policies')) { return 'vault.cluster.policies'; } - if (this.permissions.hasPermission('access')) { - return 'vault.cluster.access'; + if (this.permissions.hasNavPermission('access')) { + return this.permissions.navPathParams('access').route; } return null; } get accessRouteModels() { - if (this.permissions.hasPermission('policies')) { - return this.routeParamsFor('policies').models; + if (this.permissions.hasNavPermission('policies')) { + return this.routeParamsFor('policies')?.models; } - if (this.permissions.hasPermission('access')) { - return this.routeParamsFor('access').models; + + if (this.permissions.hasNavPermission('access')) { + return this.routeParamsFor('access')?.models; } return null; diff --git a/ui/tests/acceptance/access/methods-test.js b/ui/tests/acceptance/access/methods-test.js index 18f4f9cb24..4c57c3b39e 100644 --- a/ui/tests/acceptance/access/methods-test.js +++ b/ui/tests/acceptance/access/methods-test.js @@ -88,7 +88,7 @@ module('Acceptance | auth-methods list view', function (hooks) { for (const [key] of Object.entries(authPayload)) { assert .dom(GENERAL.linkedBlock(sanitizePath(key))) - .exists({ count: 1 }, `auth method ${key} appears in list view after navigating from OIDC Provider`); + .exists({ count: 1 }, `auth method ${key} appears in list view after navigating from OIDC provider`); } }); diff --git a/ui/tests/acceptance/chroot-namespace-test.js b/ui/tests/acceptance/chroot-namespace-test.js index 2074a1b5bb..c93c4bf397 100644 --- a/ui/tests/acceptance/chroot-namespace-test.js +++ b/ui/tests/acceptance/chroot-namespace-test.js @@ -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', 'Access', 'Operational tools'].forEach((nav) => { + ['Dashboard', 'Secrets', 'Access control', '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', 'Access', 'Operational tools')].forEach((nav) => { + [('Dashboard', 'Secrets', 'Access control', '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', 'Access', 'Operational tools'].forEach((nav) => { + ['Dashboard', 'Secrets', 'Access control', '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', 'Access', 'Operational tools'].forEach((nav) => { + ['Dashboard', 'Secrets', 'Access control', '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', 'Access', 'Operational tools'].forEach((nav) => { + ['Dashboard', 'Secrets', 'Access control', '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) => { diff --git a/ui/tests/acceptance/enterprise-sidebar-nav-test.js b/ui/tests/acceptance/enterprise-sidebar-nav-test.js index 9a2c2f8d55..c140521b95 100644 --- a/ui/tests/acceptance/enterprise-sidebar-nav-test.js +++ b/ui/tests/acceptance/enterprise-sidebar-nav-test.js @@ -31,11 +31,11 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) { await click(GENERAL.navLink('Client count')); assert.dom(panel('Client count')).exists('Client count nav panel renders'); - assert.dom(GENERAL.navLink('Client Usage')).hasClass('active', 'Client Usage link is active'); + assert.dom(GENERAL.navLink('Client usage')).hasClass('active', 'Client usage link is active'); assert.strictEqual(currentURL(), '/vault/clients/counts/overview', 'Client counts route renders'); await click(GENERAL.navLink('Back to main navigation')); - await click(GENERAL.navLink('Access')); + await click(GENERAL.navLink('Access control')); await click(GENERAL.navLink('Approval workflow')); assert.strictEqual(currentURL(), '/vault/access/control-groups', 'Approval workflow route renders'); @@ -43,7 +43,7 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) { assert.strictEqual(currentURL(), '/vault/access/namespaces?page=1', 'Replication route renders'); await click(GENERAL.navLink('Back to main navigation')); - await click(GENERAL.navLink('Access')); + await click(GENERAL.navLink('Access control')); await click(GENERAL.navLink('Role governing policies')); assert.strictEqual(currentURL(), '/vault/policies/rgp', 'Role governing policies route renders'); @@ -54,8 +54,8 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) { test('it should link to correct routes at the access level', async function (assert) { assert.expect(12); - await click(GENERAL.navLink('Access')); - assert.dom(panel('Access')).exists('Access nav panel renders'); + await click(GENERAL.navLink('Access control')); + assert.dom(panel('Access control')).exists('Access nav panel renders'); const links = [ { label: 'ACL policies', route: '/vault/policies/acl' }, diff --git a/ui/tests/acceptance/oidc-config/clients-test.js b/ui/tests/acceptance/oidc-config/clients-test.js index 63bc96aab2..28a24e980b 100644 --- a/ui/tests/acceptance/oidc-config/clients-test.js +++ b/ui/tests/acceptance/oidc-config/clients-test.js @@ -355,7 +355,7 @@ module('Acceptance | oidc-config clients', function (hooks) { await visit(OIDC_BASE_URL); assert.strictEqual(currentURL(), '/vault/access/oidc'); - assert.dom(GENERAL.hdsPageHeaderTitle).hasText('OIDC Provider'); + assert.dom(GENERAL.hdsPageHeaderTitle).hasText('OIDC provider'); assert.dom(SELECTORS.oidcHeader).hasText( `Configure Vault to act as an OIDC identity provider, and offer Vault’s various authentication methods and source of identity to any client applications. Learn more Create your first app`, diff --git a/ui/tests/acceptance/policy/policies-test.js b/ui/tests/acceptance/policy/policies-test.js index a32f622fec..68d16081bd 100644 --- a/ui/tests/acceptance/policy/policies-test.js +++ b/ui/tests/acceptance/policy/policies-test.js @@ -40,7 +40,7 @@ module('Acceptance | policies', function (hooks) { test('it navigates to and from policy show page from sidebar', async function (assert) { await visit('/vault/dashboard'); - await click(GENERAL.navLink('Access')); + await click(GENERAL.navLink('Access control')); assert.strictEqual(currentURL(), '/vault/policies/acl', 'currentURL is /vault/policies/acl'); await fillIn('[data-test-component="navigate-input"]', 'default'); // filter for the policy in case there are many on this view and the default policy is on the second page await click('[data-test-policy-link="default"]'); diff --git a/ui/tests/acceptance/sidebar-nav-test.js b/ui/tests/acceptance/sidebar-nav-test.js index 318ae0a457..cb325e5616 100644 --- a/ui/tests/acceptance/sidebar-nav-test.js +++ b/ui/tests/acceptance/sidebar-nav-test.js @@ -45,7 +45,7 @@ module('Acceptance | sidebar navigation', function (hooks) { assert.dom(panel('Cluster')).exists('Cluster nav panel renders'); const subNavs = [ - { label: 'Access', route: 'policies/acl' }, + { label: 'Access control', route: 'policies/acl' }, { label: 'Operational tools', route: 'tools/wrap' }, ]; @@ -58,7 +58,7 @@ module('Acceptance | sidebar navigation', function (hooks) { } const links = [ - { label: 'Raft Storage', route: '/vault/storage/raft' }, + { label: 'Raft storage', route: '/vault/storage/raft' }, { label: 'Secrets', route: '/vault/secrets-engines' }, { label: 'Dashboard', route: '/vault/dashboard' }, ]; @@ -72,8 +72,8 @@ module('Acceptance | sidebar navigation', function (hooks) { test('it should link to correct routes at the access level', async function (assert) { assert.expect(8); - await click(link('Access')); - assert.dom(panel('Access')).exists('Access nav panel renders'); + await click(link('Access control')); + assert.dom(panel('Access control')).exists('Access nav panel renders'); const links = [ { label: 'ACL policies', route: '/vault/policies/acl' }, @@ -117,23 +117,23 @@ module('Acceptance | sidebar navigation', function (hooks) { await click(link('Client count')); assert.dom(panel('Client count')).exists('Client counts nav panel renders'); assert.strictEqual(currentURL(), '/vault/clients/counts/overview', 'Top level nav link renders overview'); - assert.dom(link('Client Usage')).hasClass('active'); + assert.dom(link('Client usage')).hasClass('active'); await click(link('Configuration')); assert.strictEqual(currentURL(), '/vault/clients/config', 'Clients configuration renders'); assert.dom(link('Configuration')).hasClass('active'); - await click(link('Client Usage')); + await click(link('Client usage')); assert.strictEqual(currentURL(), '/vault/clients/counts/overview', 'Sub nav link navigates to overview'); - assert.dom(link('Client Usage')).hasClass('active'); + assert.dom(link('Client usage')).hasClass('active'); }); test('it should display access nav when mounting and configuring auth methods', async function (assert) { - await click(link('Access')); + await click(link('Access control')); await click('[data-test-sidebar-nav-link="Authentication methods"]'); await click('[data-test-auth-enable]'); - assert.dom('[data-test-sidebar-nav-panel="Access"]').exists('Access nav panel renders'); + assert.dom('[data-test-sidebar-nav-panel="Access control"]').exists('Access nav panel renders'); await click(link('Authentication methods')); await click(GENERAL.linkedBlock('token')); await click('[data-test-configure-link]'); - assert.dom('[data-test-sidebar-nav-panel="Access"]').exists('Access nav panel renders'); + assert.dom('[data-test-sidebar-nav-panel="Access control"]').exists('Access nav panel renders'); }); }); diff --git a/ui/tests/integration/components/policy-example-test.js b/ui/tests/integration/components/policy-example-test.js index 64d7c8078e..3eb3015c02 100644 --- a/ui/tests/integration/components/policy-example-test.js +++ b/ui/tests/integration/components/policy-example-test.js @@ -18,7 +18,7 @@ const SELECTORS = { module('Integration | Component | policy-example', function (hooks) { setupRenderingTest(hooks); - test('it renders the correct paragraph for ACL policy', async function (assert) { + test('it renders the correct paragraph for ACL Policy', async function (assert) { await render(hbs` route !== 'clients'); @@ -131,7 +131,7 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) { usingRaft: true, hasChrootNamespace: true, }); - const links = ['Client Counts', 'Replication', 'Raft Storage', 'License', 'Seal Vault']; + const links = ['Client Counts', 'Replication', 'Raft storage', 'License', 'Seal Vault']; await renderComponent(); assert