[UI][VAULT-41961] Update Access nav and breadcrumbs (#11869) (#11915)

* Update access nav and breadcrumbs

* Fix tsts and update permissions

* Remove pausetest

* Check if root is an array

* Fix edit breadcrumb

* Fix more tests!

* Fix logic

Co-authored-by: Kianna <30884335+kiannaquach@users.noreply.github.com>
This commit is contained in:
Vault Automation 2026-01-22 17:55:18 -08:00 committed by GitHub
parent e745f92bc5
commit bb75e0846a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 352 additions and 268 deletions

View file

@ -3,7 +3,16 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Page::Header @title={{titleize (pluralize this.identityType)}} />
<Page::Header @title={{titleize (pluralize this.identityType)}}>
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label=(titleize (pluralize this.identityType)))
}}
/>
</:breadcrumbs>
</Page::Header>
<nav class="tabs" aria-label="navigation for entities">
<ul>

View file

@ -40,6 +40,11 @@ export default class MfaLoginEnforcementHeaderComponent extends Component {
get breadcrumbs() {
return [
{
label: 'Vault',
route: 'vault.cluster.dashboard',
icon: 'vault',
},
{
label: 'Enforcements',
route: 'vault.cluster.access.mfa.enforcements.index',

View file

@ -33,14 +33,18 @@ export default class OidcClientForm extends Component {
: 'limited';
get breadcrumbs() {
const firstBreadcrumb = this.args.model.isNew
const applicationOrDetailsBreadcrumb = this.args.model.isNew
? { label: 'Applications', route: 'vault.cluster.access.oidc.clients' }
: {
label: 'Details',
route: 'vault.cluster.access.oidc.clients.client.details',
model: this.args.model.name,
};
return [firstBreadcrumb, { label: this.args.model.isNew ? 'Create Application' : 'Edit Application' }];
return [
{ label: 'Vault', route: 'vault.cluster.dashboard', icon: 'vault' },
applicationOrDetailsBreadcrumb,
{ label: this.args.model.isNew ? 'Create Application' : 'Edit Application' },
];
}
get modelAssignments() {

View file

@ -37,14 +37,18 @@ export default class OidcProviderForm extends Component {
: 'limited';
get breadcrumbs() {
const firstBreadcrumb = this.args.model.isNew
const providersOrDetailsBreadcrumb = this.args.model.isNew
? { label: 'Providers', route: 'vault.cluster.access.oidc.providers' }
: {
label: 'Details',
route: 'vault.cluster.access.oidc.providers.provider.details',
model: this.args.model.name,
};
return [firstBreadcrumb, { label: this.args.model.isNew ? 'Create Provider' : 'Edit Provider' }];
return [
{ label: 'Vault', route: 'vault.cluster.dashboard', icon: 'vault' },
providersOrDetailsBreadcrumb,
{ label: this.args.model.isNew ? 'Create Provider' : 'Edit Provider' },
];
}
// function passed to search select

View file

@ -40,14 +40,18 @@ export default class OidcScopeFormComponent extends Component {
}`;
get breadcrumbs() {
const firstBreadcrumb = this.args.model.isNew
const scopesOrDetailsBreadcrumb = this.args.model.isNew
? { label: 'Scopes', route: 'vault.cluster.access.oidc.scopes' }
: {
label: 'Details',
route: 'vault.cluster.access.oidc.scopes.scope.details',
model: this.args.model.name,
};
return [firstBreadcrumb, { label: this.args.model.isNew ? 'Create Scope' : 'Edit Scope' }];
return [
{ label: 'Vault', route: 'vault.cluster.dashboard', icon: 'vault' },
scopesOrDetailsBreadcrumb,
{ label: this.args.model.isNew ? 'Create Scope' : 'Edit Scope' },
];
}
@task

View file

@ -16,6 +16,7 @@ export default class PagePolicyShow extends Component<Args> {
// Provide defaults so crumbs don't error as the component is torn down
const { policyType = 'acl', id = 'policy' } = this.args.model || {};
return [
{ label: 'Vault', route: 'vault.cluster.dashboard', icon: 'vault' },
{
label: `${policyType.toUpperCase()} policies`,
route: 'vault.cluster.policies',

View file

@ -25,12 +25,20 @@ export default Controller.extend(ListController, {
},
backendCrumb: computed('clusterController.model.name', function () {
return {
label: 'Leases',
text: 'Leases',
path: 'vault.cluster.access.leases.list-root',
model: this.clusterController.model.name,
};
return [
{
label: 'Vault',
text: 'Vault',
path: 'vault.cluster.dashboard',
icon: 'vault',
},
{
label: 'Leases',
text: 'Leases',
path: 'vault.cluster.access.leases.list-root',
model: this.clusterController.model.name,
},
];
}),
isLoading: false,

View file

@ -12,12 +12,20 @@ export default Controller.extend({
clusterController: controller('vault.cluster'),
backendCrumb: computed('clusterController.model.name', function () {
return {
label: 'Leases',
text: 'Leases',
path: 'vault.cluster.access.leases.list-root',
model: this.clusterController.model.name,
};
return [
{
label: 'Vault',
text: 'Vault',
path: 'vault.cluster.dashboard',
icon: 'vault',
},
{
label: 'Leases',
text: 'Leases',
path: 'vault.cluster.access.leases.list-root',
model: this.clusterController.model.name,
},
];
}),
flashMessages: service(),

View file

@ -4,7 +4,17 @@
}}
{{#if (has-feature "Control Groups")}}
<Page::Header @title="Control Groups" />
<Page::Header @title="Approval workflow">
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Approval workflow" route="vault.cluster.access.control-groups")
(hash label="Accessor")
}}
/>
</:breadcrumbs>
</Page::Header>
{{#if this.model.canConfigure}}
<Toolbar>

View file

@ -4,9 +4,19 @@
}}
{{#if (has-feature "Control Groups")}}
<Page::Header @title="Control Groups" />
<Page::Header @title="Approval workflow">
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Approval workflow" route="vault.cluster.access.control-groups")
(hash label="Configure")
}}
/>
</:breadcrumbs>
</Page::Header>
<EditForm @model={{this.model}} @onSave={{action "onSave"}} />
{{else}}
<UpgradePage @title="Control Groups" @minimumEdition="Vault Enterprise Premium" />
<UpgradePage @title="Approval workflow" @minimumEdition="Vault Enterprise Premium" />
{{/if}}

View file

@ -4,7 +4,16 @@
}}
{{#if (has-feature "Control Groups")}}
<Page::Header @title="Control Groups" />
<Page::Header @title="Approval workflow">
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Approval workflow" route="vault.cluster.access.control-groups")
}}
/>
</:breadcrumbs>
</Page::Header>
{{#if this.model.canConfigure}}
<Toolbar>
<ToolbarActions>
@ -17,8 +26,8 @@
<form {{action (nav-to-route "vault.cluster.access.control-group-accessor" this.model.id) on="submit"}}>
<div class="box is-sideless is-fullwidth is-marginless">
<p class="has-text-grey is-size-8">
Control Groups add additional authorization factors to be required before satisfying a request. If you have a Control
Group accessor, provide it here to view the lookup the authorization progress.
Approval workflows add additional authorization factors to be required before satisfying a request. If you have a
Control Group accessor, provide it here to view the lookup the authorization progress.
</p>
<label for="accessor" class="is-label">
Accessor
@ -32,5 +41,5 @@
</div>
</form>
{{else}}
<UpgradePage @title="Control Groups" @minimumEdition="Vault Enterprise Premium" />
<UpgradePage @title="Approval workflow" @minimumEdition="Vault Enterprise Premium" />
{{/if}}

View file

@ -3,6 +3,18 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Page::Header @title="Create {{titleize this.model.identityType}}" />
<Page::Header @title="Create {{titleize this.model.identityType}}">
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash
label=(titleize this.model.identityType) route="vault.cluster.access.identity" model=(pluralize this.identityType)
)
(hash label="Create")
}}
/>
</:breadcrumbs>
</Page::Header>
<Identity::EditForm @model={{this.model}} @onSave={{action "navAfterSave"}} />

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Enforcements" route="vault.cluster.access.mfa.enforcements.index")
(hash label=this.model.name)
}}

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Methods" route="vault.cluster.access.mfa.methods.index")
(hash label="Configure MFA Method")
}}

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Methods" route="vault.cluster.access.mfa.methods.index")
(hash label=this.model.method.name)
}}

View file

@ -6,7 +6,9 @@
{{#if (has-feature "Namespaces")}}
<Page::Header @title="Namespaces">
<:breadcrumbs>
<Page::Breadcrumbs @breadcrumbs={{array (hash label="Namespaces")}} />
<Page::Breadcrumbs
@breadcrumbs={{array (hash label="Vault" route="vault.cluster.dashboard" icon="vault") (hash label="Namespaces")}}
/>
</:breadcrumbs>
</Page::Header>

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Assignments" route="vault.cluster.access.oidc.assignments")
(hash label=@model.name)
}}

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Details" route="vault.cluster.access.oidc.assignments.assignment.details" model=this.model.name)
(hash label="Edit Assignment")
}}

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Assignments" route="vault.cluster.access.oidc.assignments")
(hash label="Create Assignment")
}}

View file

@ -8,6 +8,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Applications" route="vault.cluster.access.oidc.clients")
(hash label=this.model.name)
}}

View file

@ -6,7 +6,11 @@
<Page::Header @title="Create Key">
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array (hash label="Keys" route="vault.cluster.access.oidc.keys") (hash label="Create Key")}}
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Keys" route="vault.cluster.access.oidc.keys")
(hash label="Create Key")
}}
/>
</:breadcrumbs>
</Page::Header>

View file

@ -7,7 +7,11 @@
<Page::Header @title={{this.model.name}}>
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array (hash label="Keys" route="vault.cluster.access.oidc.keys") (hash label=this.model.name)}}
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Keys" route="vault.cluster.access.oidc.keys")
(hash label=this.model.name)
}}
/>
</:breadcrumbs>
</Page::Header>

View file

@ -8,6 +8,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Providers" route="vault.cluster.access.oidc.providers")
(hash label=this.model.name)
}}

View file

@ -6,7 +6,11 @@
<Page::Header @title={{this.model.name}}>
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array (hash label="Scopes" route="vault.cluster.access.oidc.scopes") (hash label=this.model.name)}}
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label="Scopes" route="vault.cluster.access.oidc.scopes")
(hash label=this.model.name)
}}
/>
</:breadcrumbs>
</Page::Header>

View file

@ -3,5 +3,5 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Sidebar::Nav::Policies />
<Sidebar::Nav::Access />
{{outlet}}

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label=(concat (uppercase this.policyType) " policies") route="vault.cluster.policies" model=this.policyType)
(hash label="Create")
}}

View file

@ -9,6 +9,14 @@
<Hds::Badge @text="Sentinel" />
{{/if}}
</:badges>
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label=(concat (uppercase this.policyType) " Policies"))
}}
/>
</:breadcrumbs>
</Page::Header>
<Toolbar>
{{#if this.model.meta.total}}

View file

@ -3,5 +3,5 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Sidebar::Nav::Policies />
<Sidebar::Nav::Access />
{{outlet}}

View file

@ -7,6 +7,7 @@
<:breadcrumbs>
<Page::Breadcrumbs
@breadcrumbs={{array
(hash label="Vault" route="vault.cluster.dashboard" icon="vault")
(hash label=(concat (uppercase this.policyType) " policies") route="vault.cluster.policies" model=this.policyType)
(hash label=this.model.id route="vault.cluster.policy.show" model=this.model.id)
(hash label="Edit")

View file

@ -10,6 +10,7 @@
@text={{path.text}}
@route={{path.path}}
@model={{path.model}}
@icon={{path.icon}}
data-test-secret-breadcrumb={{path.text}}
/>
{{else}}

View file

@ -49,7 +49,11 @@ export default class KeyValueHeader extends Component {
const baseKeyModel = encodePath(this.args.baseKey?.id);
if (root) {
crumbs.push(root);
if (Array.isArray(root)) {
crumbs.push(...root);
} else {
crumbs.push(root);
}
}
if (!baseKey) {

View file

@ -18,59 +18,93 @@
data-test-sidebar-nav-link="Back to main navigation"
/>
{{#if (has-permission "access" routeParams=(array "methods" "mfa" "oidc"))}}
<Nav.Title data-test-sidebar-nav-heading="Authentication">Authentication</Nav.Title>
{{! Policies }}
{{#if (or (has-permission "policies") (has-permission "access"))}}
<Nav.Title data-test-sidebar-nav-heading="Access control">Access control</Nav.Title>
{{/if}}
{{#if (has-permission "access" routeParams="methods")}}
<Nav.Link
@route="vault.cluster.access.methods"
@current-when="vault.cluster.access.methods vault.cluster.access.method vault.cluster.settings.auth"
@text="Authentication Methods"
data-test-sidebar-nav-link="Authentication Methods"
/>
{{/if}}
{{#if (has-permission "access" routeParams="mfa")}}
<Nav.Link
@route="vault.cluster.access.mfa.methods"
@current-when="vault.cluster.access.mfa.methods vault.cluster.access.mfa.enforcements vault.cluster.access.mfa.index"
@text="Multi-Factor Authentication"
data-test-sidebar-nav-link="Multi-Factor Authentication"
/>
{{/if}}
{{#if (has-permission "access" routeParams="oidc")}}
<Nav.Link @route="vault.cluster.access.oidc" @text="OIDC Provider" data-test-sidebar-nav-link="OIDC Provider" />
{{#if (has-permission "policies")}}
{{#if (has-permission "policies" routeParams="acl")}}
<Nav.Link
@route="vault.cluster.policies"
@model="acl"
@current-when="vault.cluster.policies vault.cluster.policy"
@text="ACL policies"
data-test-sidebar-nav-link="ACL policies"
/>
{{/if}}
{{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="rgp"))}}
<Nav.Link
@route="vault.cluster.policies"
@model="rgp"
@current-when="vault.cluster.policies vault.cluster.policy"
@text="Role governing policies"
data-test-sidebar-nav-link="Role governing policies"
/>
{{/if}}
{{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="egp"))}}
<Nav.Link
@route="vault.cluster.policies"
@model="egp"
@current-when="vault.cluster.policies vault.cluster.policy"
@text="Endpoint governing policies"
data-test-sidebar-nav-link="Endpoint governing policies"
/>
{{/if}}
{{/if}}
{{#if (and (has-feature "Control Groups") (has-permission "access" routeParams="control-groups"))}}
<Nav.Title data-test-sidebar-nav-heading="Access Control">Access Control</Nav.Title>
<Nav.Link
@route="vault.cluster.access.control-groups"
@current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure"
@text="Control Groups"
data-test-sidebar-nav-link="Control Groups"
/>
{{/if}}
{{#if (has-permission "access")}}
{{#if (and (has-feature "Control Groups") (has-permission "access" routeParams="control-groups"))}}
<Nav.Link
@route="vault.cluster.access.control-groups"
@current-when="vault.cluster.access.control-groups vault.cluster.access.control-group-accessor vault.cluster.access.control-groups-configure"
@text="Approval workflow"
data-test-sidebar-nav-link="Approval workflow"
/>
{{/if}}
{{#if (has-permission "access" routeParams="leases")}}
<Nav.Link @route="vault.cluster.access.leases" @text="Leases" data-test-sidebar-nav-link="Leases" />
{{/if}}
{{#if (has-permission "access" routeParams=(array "namespaces" "groups" "entities"))}}
<Nav.Title data-test-sidebar-nav-heading="Organization">Organization</Nav.Title>
{{/if}}
{{#if (and (has-feature "Namespaces") (has-permission "access" routeParams="namespaces"))}}
<Nav.Link @route="vault.cluster.access.namespaces" @text="Namespaces" data-test-sidebar-nav-link="Namespaces" />
{{/if}}
{{#if (has-permission "access" routeParams="groups")}}
<Nav.Link @route="vault.cluster.access.identity" @model="groups" @text="Groups" data-test-sidebar-nav-link="Groups" />
{{/if}}
{{#if (has-permission "access" routeParams="entities")}}
<Nav.Link
@route="vault.cluster.access.identity"
@model="entities"
@text="Entities"
data-test-sidebar-nav-link="Entities"
/>
{{/if}}
{{! Authentication }}
{{#if (has-permission "access" routeParams=(array "methods" "mfa" "oidc"))}}
<Nav.Title data-test-sidebar-nav-heading="Authentication">Authentication</Nav.Title>
{{/if}}
{{#if (has-permission "access" routeParams="methods")}}
<Nav.Link
@route="vault.cluster.access.methods"
@current-when="vault.cluster.access.methods vault.cluster.access.method vault.cluster.settings.auth"
@text="Authentication methods"
data-test-sidebar-nav-link="Authentication methods"
/>
{{/if}}
{{#if (has-permission "access" routeParams="mfa")}}
<Nav.Link
@route="vault.cluster.access.mfa.methods"
@current-when="vault.cluster.access.mfa.methods vault.cluster.access.mfa.enforcements vault.cluster.access.mfa.index"
@text="Multi-factor authentication"
data-test-sidebar-nav-link="Multi-factor authentication"
/>
{{/if}}
{{#if (has-permission "access" routeParams="oidc")}}
<Nav.Link @route="vault.cluster.access.oidc" @text="OIDC provider" data-test-sidebar-nav-link="OIDC provider" />
{{/if}}
{{#if (has-permission "access" routeParams="leases")}}
<Nav.Title data-test-sidebar-nav-heading="Administration">Administration</Nav.Title>
<Nav.Link @route="vault.cluster.access.leases" @text="Leases" data-test-sidebar-nav-link="Leases" />
{{#if (has-permission "access" routeParams=(array "namespaces" "groups" "entities"))}}
<Nav.Title data-test-sidebar-nav-heading="Organization">Organization</Nav.Title>
{{/if}}
{{#if (and (has-feature "Namespaces") (has-permission "access" routeParams="namespaces"))}}
<Nav.Link @route="vault.cluster.access.namespaces" @text="Namespaces" data-test-sidebar-nav-link="Namespaces" />
{{/if}}
{{#if (has-permission "access" routeParams="groups")}}
<Nav.Link @route="vault.cluster.access.identity" @model="groups" @text="Groups" data-test-sidebar-nav-link="Groups" />
{{/if}}
{{#if (has-permission "access" routeParams="entities")}}
<Nav.Link
@route="vault.cluster.access.identity"
@model="entities"
@text="Entities"
data-test-sidebar-nav-link="Entities"
/>
{{/if}}
{{/if}}
</Hds::AppSideNav::Portal>

View file

@ -24,25 +24,15 @@
data-test-sidebar-nav-link="Secrets Recovery"
/>
{{/unless}}
{{#if (has-permission "access")}}
{{#if (or (has-permission "access") (has-permission "policies"))}}
<Nav.Link
@route={{get (route-params-for "access") "route"}}
@models={{get (route-params-for "access") "models"}}
@current-when="vault.cluster.access vault.cluster.settings.auth"
@route={{this.accessRoute}}
@models={{this.accessRouteModels}}
@text="Access"
@hasSubItems={{true}}
data-test-sidebar-nav-link="Access"
/>
{{/if}}
{{#if (has-permission "policies")}}
<Nav.Link
@route="vault.cluster.policies"
@models={{get (route-params-for "policies") "models"}}
@text="Policies"
@hasSubItems={{true}}
data-test-sidebar-nav-link="Policies"
/>
{{/if}}
{{#if (has-permission "tools")}}
<Nav.Link
@route="vault.cluster.tools.tool"

View file

@ -57,4 +57,31 @@ export default class SidebarNavClusterComponent extends Component {
// otherwise we show the link depending on whether or not the feature exists
return this.version.hasSecretsSync;
}
get accessRoute() {
if (this.permissions.hasPermission('policies')) {
return 'vault.cluster.policies';
}
if (this.permissions.hasPermission('access')) {
return 'vault.cluster.access';
}
return null;
}
get accessRouteModels() {
if (this.permissions.hasPermission('policies')) {
return this.routeParamsFor('policies').models;
}
if (this.permissions.hasPermission('access')) {
return this.routeParamsFor('access').models;
}
return null;
}
routeParamsFor(routeName) {
return this.permissions.navPathParams(routeName);
}
}

View file

@ -1,44 +0,0 @@
{{!
Copyright IBM Corp. 2016, 2025
SPDX-License-Identifier: BUSL-1.1
}}
<Hds::AppSideNav::Portal @ariaLabel="Policies Navigation Links" data-test-sidebar-nav-panel="Policies" as |Nav|>
<Nav.BackLink
@route="vault.cluster"
@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="Policies">Policies</Nav.Title>
{{#if (has-permission "policies" routeParams="acl")}}
<Nav.Link
@route="vault.cluster.policies"
@model="acl"
@current-when="vault.cluster.policies vault.cluster.policy"
@text="ACL Policies"
data-test-sidebar-nav-link="ACL Policies"
/>
{{/if}}
{{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="rgp"))}}
<Nav.Link
@route="vault.cluster.policies"
@model="rgp"
@current-when="vault.cluster.policies vault.cluster.policy"
@text="Role-Governing Policies"
data-test-sidebar-nav-link="Role-Governing Policies"
/>
{{/if}}
{{#if (and (has-feature "Sentinel") (has-permission "policies" routeParams="egp"))}}
<Nav.Link
@route="vault.cluster.policies"
@model="egp"
@current-when="vault.cluster.policies vault.cluster.policy"
@text="Endpoint Governing Policies"
data-test-sidebar-nav-link="Endpoint Governing Policies"
/>
{{/if}}
</Hds::AppSideNav::Portal>

View file

@ -1,6 +0,0 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
export { default } from 'core/components/sidebar/nav/policies';

View file

@ -28,7 +28,7 @@ module('Acceptance | auth-methods list view', function (hooks) {
test('it navigates to auth method', async function (assert) {
await visit('/vault/access/');
assert.strictEqual(currentRouteName(), 'vault.cluster.access.methods', 'navigates to the correct route');
assert.dom('[data-test-sidebar-nav-link="Authentication Methods"]').hasClass('active');
assert.dom('[data-test-sidebar-nav-link="Authentication methods"]').hasClass('active');
});
test('it filters by name and auth type', async function (assert) {
@ -83,7 +83,7 @@ module('Acceptance | auth-methods list view', function (hooks) {
.exists({ count: 1 }, `auth method ${key} appears in list view`);
}
await visit('/vault/settings/auth/enable');
await click(GENERAL.navLink('OIDC Provider'));
await click(GENERAL.navLink('OIDC provider'));
await visit('/vault/access/');
for (const [key] of Object.entries(authPayload)) {
assert

View file

@ -31,8 +31,11 @@ module('Acceptance | Enterprise | /access/namespaces', function (hooks) {
});
test('the route displays the breadcrumb trail', async function (assert) {
assert.dom(GENERAL.breadcrumb).exists({ count: 1 }, 'Only one breadcrumb is displayed');
assert.dom(GENERAL.breadcrumb).hasText('Namespaces', 'Breadcrumb trail is displayed correctly');
assert.dom(GENERAL.breadcrumb).exists({ count: 2 }, 'Only two breadcrumb is displayed');
assert.dom(GENERAL.breadcrumbAtIdx(0)).hasText('Vault', 'Breadcrumb trail is displayed correctly');
assert
.dom(GENERAL.currentBreadcrumb('Namespaces'))
.hasText('Namespaces', 'Namespace breadcrumb trail is displayed correctly');
});
test('the route should update namespace list after create/delete WITH manual refresh in the CLI', async function (assert) {

View file

@ -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', 'Tools', 'Policies'].forEach((nav) => {
['Dashboard', 'Secrets Engines', 'Access', '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
@ -57,7 +57,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
[('Dashboard', 'Secrets Engines', 'Access', 'Tools')].forEach((nav) => {
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item for user with default policy`);
});
['Policies', 'Client Count', 'Replication', 'Raft Storage', 'License', 'Seal Vault'].forEach((nav) => {
['Client Count', 'Replication', 'Raft Storage', 'License', 'Seal Vault'].forEach((nav) => {
assert.dom(navLink(nav)).doesNotExist(`Does not show ${nav} nav item for user with default policy`);
});
@ -84,7 +84,7 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
);
await loginNs(namespace, reader);
['Dashboard', 'Secrets Engines', 'Access', 'Policies', 'Tools'].forEach((nav) => {
['Dashboard', 'Secrets Engines', 'Access', '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) => {
@ -119,12 +119,12 @@ module('Acceptance | chroot-namespace enterprise ui', function (hooks) {
['Dashboard', 'Secrets Engines', 'Access', 'Tools'].forEach((nav) => {
assert.dom(navLink(nav)).exists(`Shows ${nav} nav item`);
});
['Policies', 'Client Count', 'Replication', 'Raft Storage', 'License', 'Seal Vault'].forEach((nav) => {
['Client Count', 'Replication', 'Raft Storage', 'License', 'Seal Vault'].forEach((nav) => {
assert.dom(navLink(nav)).doesNotExist(`Does not show ${nav} nav item`);
});
await loginNs(`${namespace}/child`, childReader);
['Dashboard', 'Secrets Engines', 'Access', 'Policies', 'Tools'].forEach((nav) => {
['Dashboard', 'Secrets Engines', 'Access', '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) => {

View file

@ -38,7 +38,9 @@ module('Acceptance | cluster', function (hooks) {
await login(userToken);
await visit('/vault/access');
assert.dom('[data-test-sidebar-nav-link="Policies"]').doesNotExist();
assert.dom('[data-test-sidebar-nav-link="ACL policies"]').doesNotExist();
assert.dom('[data-test-sidebar-nav-link="Role governing policies"]').doesNotExist();
assert.dom('[data-test-sidebar-nav-link="Endpoint governing policies"]').doesNotExist();
});
test('it hides mfa setup if user does not have entityId (ex: is a root user)', async function (assert) {
@ -72,7 +74,9 @@ module('Acceptance | cluster', function (hooks) {
await login(userToken);
await visit('/vault/access');
assert.dom('[data-test-sidebar-nav-link="Policies"]').hasAttribute('href', '/ui/vault/policies/rgp');
assert.dom('[data-test-sidebar-nav-link="ACL policies"]').doesNotExist();
assert.dom('[data-test-sidebar-nav-link="Role governing policies"]').exists();
assert.dom('[data-test-sidebar-nav-link="Endpoint governing policies"]').doesNotExist();
});
test('shows error banner if resultant-acl check fails', async function (assert) {

View file

@ -57,18 +57,44 @@ module('Acceptance | Enterprise | sidebar navigation', function (hooks) {
await click(link('License'));
assert.strictEqual(currentURL(), '/vault/license', 'License route renders');
await click(link('Access'));
await click(link('Control Groups'));
assert.strictEqual(currentURL(), '/vault/access/control-groups', 'Control groups route renders');
await click(link('Approval workflow'));
assert.strictEqual(currentURL(), '/vault/access/control-groups', 'Approval workflow route renders');
await click(link('Namespaces'));
assert.strictEqual(currentURL(), '/vault/access/namespaces?page=1', 'Replication route renders');
await click(link('Back to main navigation'));
await click(link('Policies'));
await click(link('Role-Governing Policies'));
assert.strictEqual(currentURL(), '/vault/policies/rgp', 'Role-Governing Policies route renders');
await click(link('Access'));
await click(link('Role governing policies'));
assert.strictEqual(currentURL(), '/vault/policies/rgp', 'Role governing policies route renders');
await click(link('Endpoint Governing Policies'));
assert.strictEqual(currentURL(), '/vault/policies/egp', 'Endpoint Governing Policies route renders');
await click(link('Endpoint governing policies'));
assert.strictEqual(currentURL(), '/vault/policies/egp', 'Endpoint governing policies route renders');
});
test('it should link to correct routes at the access level', async function (assert) {
assert.expect(12);
await click(link('Access'));
assert.dom(panel('Access')).exists('Access nav panel renders');
const links = [
{ label: 'ACL policies', route: '/vault/policies/acl' },
{ label: 'Role governing policies', route: '/vault/policies/rgp' },
{ label: 'Endpoint governing policies', route: '/vault/policies/egp' },
{ label: 'Approval workflow', route: '/vault/access/control-groups' },
{ label: 'Leases', route: '/vault/access/leases/list' },
{ label: 'Authentication methods', route: '/vault/access' },
{ label: 'Multi-factor authentication', route: '/vault/access/mfa' },
{ label: 'OIDC provider', route: '/vault/access/oidc' },
{ label: 'Namespaces', route: '/vault/access/namespaces' },
{ label: 'Groups', route: '/vault/access/identity/groups' },
{ label: 'Entities', route: '/vault/access/identity/entities' },
];
for (const l of links) {
await click(link(l.label));
assert.ok(currentURL().includes(l.route), `${l.label} route renders`);
}
});
});

View file

@ -34,7 +34,7 @@ module('Acceptance | mfa-login-enforcement', function (hooks) {
});
await visit('/ui/vault/access');
await click('[data-test-sidebar-nav-link="Multi-Factor Authentication"]');
await click('[data-test-sidebar-nav-link="Multi-factor authentication"]');
await click(GENERAL.tab('enforcements'));
await click('[data-test-enforcement-create]');
// Fill out form
@ -48,7 +48,7 @@ module('Acceptance | mfa-login-enforcement', function (hooks) {
test('it should create login enforcement', async function (assert) {
await visit('/ui/vault/access');
await click('[data-test-sidebar-nav-link="Multi-Factor Authentication"]');
await click('[data-test-sidebar-nav-link="Multi-factor authentication"]');
await click(GENERAL.tab('enforcements'));
await click('[data-test-enforcement-create]');
@ -65,7 +65,7 @@ module('Acceptance | mfa-login-enforcement', function (hooks) {
'Cancel transitions to enforcements list'
);
await click('[data-test-enforcement-create]');
await click('.hds-breadcrumb a');
await click(GENERAL.breadcrumbAtIdx(1));
assert.strictEqual(
currentRouteName(),
'vault.cluster.access.mfa.enforcements.index',
@ -117,7 +117,7 @@ module('Acceptance | mfa-login-enforcement', function (hooks) {
'vault.cluster.access.mfa.enforcements.enforcement.index',
'Details more menu action transitions to enforcement route'
);
await click('.hds-breadcrumb a');
await click(GENERAL.breadcrumbAtIdx(1));
await click(GENERAL.menuTrigger);
await click('[data-test-list-item-link="edit"]');
assert.strictEqual(

View file

@ -58,7 +58,7 @@ module('Acceptance | mfa-method', function (hooks) {
'vault.cluster.access.mfa.methods.create',
'New method link transitions to create route'
);
await click('.hds-breadcrumb a');
await click(GENERAL.breadcrumbAtIdx(1));
const methods = this.getMethods();
const model = this.store.peekRecord('mfa-method', methods[0].id);
@ -78,7 +78,7 @@ module('Acceptance | mfa-method', function (hooks) {
'vault.cluster.access.mfa.methods.method.index',
'Details more menu action transitions to method route'
);
await click('.hds-breadcrumb a');
await click(GENERAL.breadcrumbAtIdx(1));
await click(GENERAL.menuTrigger);
await click('[data-test-mfa-method-menu-link="edit"]');
assert.strictEqual(
@ -154,7 +154,7 @@ module('Acceptance | mfa-method', function (hooks) {
}
assert.dom(GENERAL.infoRowValue(label)).hasText(value, `${label} value renders`);
});
await click('.hds-breadcrumb a');
await click(GENERAL.breadcrumbAtIdx(1));
}
await click('[data-test-mfa-method-list-item]');
@ -210,7 +210,7 @@ module('Acceptance | mfa-method', function (hooks) {
'vault.cluster.access.mfa.methods.method.index',
`${type} method is displayed on save`
);
await click('.hds-breadcrumb a');
await click(GENERAL.breadcrumbAtIdx(1));
assert
.dom('[data-test-mfa-method-list-item]')
.exists({ count: methodCount + index + 1 }, `List updates with new ${type} method`);
@ -233,7 +233,7 @@ module('Acceptance | mfa-method', function (hooks) {
);
await click('[data-test-tab="enforcements"]');
assert.dom('[data-test-list-item]').hasTextContaining('bar', 'Enforcement is listed in method view');
await click('[data-test-sidebar-nav-link="Multi-Factor Authentication"]');
await click('[data-test-sidebar-nav-link="Multi-factor authentication"]');
await click('[data-test-tab="enforcements"]');
assert
.dom('[data-test-list-item="bar"]')

View file

@ -108,7 +108,7 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
);
// navigate to details from index page
await click(GENERAL.breadcrumbAtIdx(0));
await click(GENERAL.breadcrumbAtIdx(1));
await click(GENERAL.menuTrigger);
await click('[data-test-oidc-scope-menu-link="details"]');
assert.strictEqual(
@ -232,7 +232,7 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
.hasText('this is an edit test', 'has correct edited description');
// create a provider using test-scope
await click(GENERAL.breadcrumbAtIdx(0));
await click(GENERAL.breadcrumbAtIdx(1));
await click(GENERAL.tab('providers'));
assert.dom(GENERAL.tab('providers')).hasClass('active', 'providers tab is active');
await click('[data-test-oidc-provider-create]');
@ -376,7 +376,7 @@ module('Acceptance | oidc-config providers and scopes', function (hooks) {
);
// navigate to details from index page
await click(GENERAL.breadcrumbAtIdx(0));
await click(GENERAL.breadcrumbAtIdx(1));
assert.strictEqual(
currentRouteName(),
'vault.cluster.access.oidc.providers.index',

View file

@ -126,7 +126,7 @@ module('Acceptance | policies/acl', function (hooks) {
);
assert.dom(GENERAL.hdsPageHeaderTitle).hasText(policyLower, 'displays the policy name on the show page');
assert.dom(GENERAL.latestFlashContent).hasText(`ACL policy "${policyLower}" was successfully created.`);
await click(GENERAL.breadcrumbAtIdx(0));
await click(GENERAL.breadcrumbAtIdx(1));
assert.strictEqual(currentURL(), `/vault/policies/acl`, 'navigates to policy list from breadcrumb');
// List of policies can get long quickly -- filter for the policy to make the test more robust

View file

@ -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('Policies'));
await click(GENERAL.navLink('Access'));
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"]');

View file

@ -40,13 +40,12 @@ module('Acceptance | sidebar navigation', function (hooks) {
});
test('it should link to correct routes at the cluster level', async function (assert) {
assert.expect(11);
assert.expect(9);
assert.dom(panel('Cluster')).exists('Cluster nav panel renders');
const subNavs = [
{ label: 'Access', route: 'access' },
{ label: 'Policies', route: 'policies/acl' },
{ label: 'Access', route: 'policies/acl' },
{ label: 'Tools', route: 'tools/wrap' },
];
@ -72,18 +71,19 @@ module('Acceptance | sidebar navigation', function (hooks) {
});
test('it should link to correct routes at the access level', async function (assert) {
assert.expect(7);
assert.expect(8);
await click(link('Access'));
assert.dom(panel('Access')).exists('Access nav panel renders');
const links = [
{ label: 'Multi-Factor Authentication', route: '/vault/access/mfa' },
{ label: 'OIDC Provider', route: '/vault/access/oidc' },
{ label: 'ACL policies', route: '/vault/policies/acl' },
{ label: 'Leases', route: '/vault/access/leases/list' },
{ label: 'Authentication methods', route: '/vault/access' },
{ label: 'Multi-factor authentication', route: '/vault/access/mfa' },
{ label: 'OIDC provider', route: '/vault/access/oidc' },
{ label: 'Groups', route: '/vault/access/identity/groups' },
{ label: 'Entities', route: '/vault/access/identity/entities' },
{ label: 'Leases', route: '/vault/access/leases/list' },
{ label: 'Authentication Methods', route: '/vault/access' },
];
for (const l of links) {
@ -92,16 +92,6 @@ module('Acceptance | sidebar navigation', function (hooks) {
}
});
test('it should link to correct routes at the policies level', async function (assert) {
assert.expect(2);
await click(link('Policies'));
assert.dom(panel('Policies')).exists('Access nav panel renders');
await click(link('ACL Policies'));
assert.strictEqual(currentURL(), '/vault/policies/acl', 'ACL Policies route renders');
});
test('it should link to correct routes at the tools level', async function (assert) {
assert.expect(7);
@ -139,9 +129,10 @@ module('Acceptance | sidebar navigation', function (hooks) {
test('it should display access nav when mounting and configuring auth methods', async function (assert) {
await click(link('Access'));
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');
await click(link('Authentication Methods'));
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');

View file

@ -33,11 +33,10 @@ module('Integration | Component | sidebar-nav-access', function (hooks) {
});
test('it should render nav headings', async function (assert) {
const headings = ['Authentication', 'Access Control', 'Organization', 'Administration'];
const headings = ['Access control', 'Authentication', 'Organization'];
stubFeaturesAndPermissions(this.owner);
await renderComponent();
assert
.dom('[data-test-sidebar-nav-heading]')
.exists({ count: headings.length }, 'Correct number of headings render');
@ -61,18 +60,20 @@ module('Integration | Component | sidebar-nav-access', function (hooks) {
test('it should render nav links', async function (assert) {
const links = [
'Back to main navigation',
'Authentication Methods',
'Multi-Factor Authentication',
'OIDC Provider',
'Control Groups',
'ACL policies',
'Role governing policies',
'Endpoint governing policies',
'Approval workflow',
'Leases',
'Authentication methods',
'Multi-factor authentication',
'OIDC provider',
'Namespaces',
'Groups',
'Entities',
'Leases',
];
stubFeaturesAndPermissions(this.owner);
await renderComponent();
assert
.dom('[data-test-sidebar-nav-link]')
.exists({ count: links.length }, 'Correct number of links render');

View file

@ -62,7 +62,6 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
'Secrets Sync',
'Secrets Recovery',
'Access',
'Policies',
'Tools',
'Replication',
'Raft Storage',

View file

@ -1,62 +0,0 @@
/**
* 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';
const renderComponent = () => {
return render(hbs`
<Sidebar::Frame @isVisible={{true}}>
<Sidebar::Nav::Policies />
</Sidebar::Frame>
`);
};
module('Integration | Component | sidebar-nav-policies', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
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 user does not have access to', async function (assert) {
await renderComponent();
assert
.dom('[data-test-sidebar-nav-link]')
.exists({ count: 1 }, 'Nav links are hidden other than back link');
});
test('it should render nav headings and links', async function (assert) {
const links = [
'Back to main navigation',
'ACL Policies',
'Role-Governing Policies',
'Endpoint Governing Policies',
];
stubFeaturesAndPermissions(this.owner);
await renderComponent();
assert.dom('[data-test-sidebar-nav-heading]').exists({ count: 1 }, 'Correct number of headings render');
assert.dom('[data-test-sidebar-nav-heading="Policies"]').hasText('Policies', 'Policies heading renders');
assert
.dom('[data-test-sidebar-nav-link]')
.exists({ count: links.length }, 'Correct number of links render');
links.forEach((link) => {
assert.dom(`[data-test-sidebar-nav-link="${link}"]`).hasText(link, `${link} link renders`);
});
});
});