mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-30 22:41:33 +01:00
[EC-678] [EC-673] Fix active tab not showing selected while in child route (#3964)
* [PS-1114] hide reporting sidebar if only events * [PS-1114] add orgRedirectGuard * [PS-1114] highlight tabs based on route subset * [PS-1114] redirect to correct child route on tab - Use new OrgRedirectGuard * [PS-1114] add settings redirect using guard - refactored guard to accept array of strings * [EC-678] [EC-673] remove remaining methods * [EC-678][EC-673] address PR feedback - change switch to if statements - remove ternary
This commit is contained in:
parent
4afe0f5d89
commit
6f4771da6c
32
apps/web/src/app/organizations/guards/org-redirect.guard.ts
Normal file
32
apps/web/src/app/organizations/guards/org-redirect.guard.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from "@angular/router";
|
||||
|
||||
import {
|
||||
canAccessOrgAdmin,
|
||||
OrganizationService,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class OrganizationRedirectGuard implements CanActivate {
|
||||
constructor(private router: Router, private organizationService: OrganizationService) {}
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const org = this.organizationService.get(route.params.organizationId);
|
||||
|
||||
const customRedirect = route.data?.autoRedirectCallback;
|
||||
if (customRedirect) {
|
||||
let redirectPath = customRedirect(org);
|
||||
if (typeof redirectPath === "string") {
|
||||
redirectPath = [redirectPath];
|
||||
}
|
||||
return this.router.createUrlTree([state.url, ...redirectPath]);
|
||||
}
|
||||
|
||||
if (canAccessOrgAdmin(org)) {
|
||||
return this.router.createUrlTree(["/organizations", org.id]);
|
||||
}
|
||||
return this.router.createUrlTree(["/"]);
|
||||
}
|
||||
}
|
@ -8,13 +8,10 @@
|
||||
></app-organization-switcher>
|
||||
<bit-tab-nav-bar class="-tw-mb-px">
|
||||
<bit-tab-link route="vault">{{ "vault" | i18n }}</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowManageTab(organization)" [route]="getManageRoute(organization)">
|
||||
<bit-tab-link *ngIf="canShowManageTab(organization)" route="manage">
|
||||
{{ "manage" | i18n }}
|
||||
</bit-tab-link>
|
||||
<bit-tab-link
|
||||
*ngIf="canShowReportsTab(organization)"
|
||||
[route]="getReportRoute(organization)"
|
||||
>
|
||||
<bit-tab-link *ngIf="canShowReportsTab(organization)" route="reporting">
|
||||
{{ getReportTabLabel(organization) | i18n }}
|
||||
</bit-tab-link>
|
||||
<bit-tab-link *ngIf="canShowBillingTab(organization)" route="billing">{{
|
||||
|
@ -72,24 +72,4 @@ export class OrganizationLayoutComponent implements OnInit, OnDestroy {
|
||||
getReportTabLabel(organization: Organization): string {
|
||||
return organization.useEvents ? "reporting" : "reports";
|
||||
}
|
||||
|
||||
getReportRoute(organization: Organization): string {
|
||||
return organization.useEvents ? "reporting/events" : "reporting/reports";
|
||||
}
|
||||
|
||||
getManageRoute(organization: Organization): string {
|
||||
let route: string;
|
||||
switch (true) {
|
||||
case organization.canManageUsers:
|
||||
route = "manage/members";
|
||||
break;
|
||||
case organization.canViewAssignedCollections || organization.canViewAllCollections:
|
||||
route = "manage/collections";
|
||||
break;
|
||||
case organization.canManageGroups:
|
||||
route = "manage/groups";
|
||||
break;
|
||||
}
|
||||
return route;
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,10 @@ import {
|
||||
canAccessOrgAdmin,
|
||||
canManageCollections,
|
||||
} from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "./guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "./guards/org-redirect.guard";
|
||||
import { OrganizationLayoutComponent } from "./layouts/organization-layout.component";
|
||||
import { CollectionsComponent } from "./manage/collections.component";
|
||||
import { GroupsComponent } from "./manage/groups.component";
|
||||
@ -47,7 +49,11 @@ const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
redirectTo: "members",
|
||||
canActivate: [OrganizationRedirectGuard],
|
||||
data: {
|
||||
autoRedirectCallback: getManageRoute,
|
||||
},
|
||||
children: [], // This is required to make the auto redirect work
|
||||
},
|
||||
{
|
||||
path: "collections",
|
||||
@ -94,6 +100,19 @@ const routes: Routes = [
|
||||
},
|
||||
];
|
||||
|
||||
function getManageRoute(organization: Organization): string {
|
||||
if (organization.canManageUsers) {
|
||||
return "members";
|
||||
}
|
||||
if (organization.canViewAssignedCollections || organization.canViewAllCollections) {
|
||||
return "collections";
|
||||
}
|
||||
if (organization.canManageGroups) {
|
||||
return "groups";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
|
@ -5,6 +5,7 @@ import { canAccessReportingTab } from "@bitwarden/common/abstractions/organizati
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "../guards/org-redirect.guard";
|
||||
import { EventsComponent } from "../manage/events.component";
|
||||
import { ExposedPasswordsReportComponent } from "../tools/exposed-passwords-report.component";
|
||||
import { InactiveTwoFactorReportComponent } from "../tools/inactive-two-factor-report.component";
|
||||
@ -22,7 +23,15 @@ const routes: Routes = [
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessReportingTab },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "reports" },
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
canActivate: [OrganizationRedirectGuard],
|
||||
data: {
|
||||
autoRedirectCallback: getReportRoute,
|
||||
},
|
||||
children: [], // This is required to make the auto redirect work,
|
||||
},
|
||||
{
|
||||
path: "reports",
|
||||
component: ReportsHomeComponent,
|
||||
@ -80,6 +89,17 @@ const routes: Routes = [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function getReportRoute(organization: Organization): string {
|
||||
if (organization.canAccessEventLogs) {
|
||||
return "events";
|
||||
}
|
||||
if (organization.canAccessReports) {
|
||||
return "reports";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
|
@ -22,7 +22,8 @@ export class ReportingComponent implements OnInit, OnDestroy {
|
||||
.pipe(
|
||||
concatMap(async (params) => {
|
||||
this.organization = await this.organizationService.get(params.organizationId);
|
||||
this.showLeftNav = this.organization.canAccessEventLogs;
|
||||
this.showLeftNav =
|
||||
this.organization.canAccessEventLogs && this.organization.canAccessReports;
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
|
@ -5,6 +5,7 @@ import { canAccessSettingsTab } from "@bitwarden/common/abstractions/organizatio
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
|
||||
import { OrganizationPermissionsGuard } from "../guards/org-permissions.guard";
|
||||
import { OrganizationRedirectGuard } from "../guards/org-redirect.guard";
|
||||
import { PoliciesComponent } from "../policies";
|
||||
|
||||
import { AccountComponent } from "./account.component";
|
||||
@ -18,7 +19,15 @@ const routes: Routes = [
|
||||
canActivate: [OrganizationPermissionsGuard],
|
||||
data: { organizationPermissions: canAccessSettingsTab },
|
||||
children: [
|
||||
{ path: "", pathMatch: "full", redirectTo: "account" },
|
||||
{
|
||||
path: "",
|
||||
pathMatch: "full",
|
||||
canActivate: [OrganizationRedirectGuard],
|
||||
data: {
|
||||
autoRedirectCallback: getSettingsRoute,
|
||||
},
|
||||
children: [], // This is required to make the auto redirect work,
|
||||
},
|
||||
{ path: "account", component: AccountComponent, data: { titleId: "organizationInfo" } },
|
||||
{
|
||||
path: "two-factor",
|
||||
@ -45,6 +54,25 @@ const routes: Routes = [
|
||||
},
|
||||
];
|
||||
|
||||
function getSettingsRoute(organization: Organization) {
|
||||
if (organization.isOwner) {
|
||||
return "account";
|
||||
}
|
||||
if (organization.canManagePolicies) {
|
||||
return "policies";
|
||||
}
|
||||
if (organization.canAccessImportExport) {
|
||||
return ["tools", "import"];
|
||||
}
|
||||
if (organization.canManageSso) {
|
||||
return "sso";
|
||||
}
|
||||
if (organization.canManageScim) {
|
||||
return "scim";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
|
@ -4,7 +4,12 @@
|
||||
<div class="card">
|
||||
<div class="card-header">{{ "settings" | i18n }}</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a routerLink="account" class="list-group-item" routerLinkActive="active">
|
||||
<a
|
||||
routerLink="account"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.isOwner"
|
||||
>
|
||||
{{ "organizationInfo" | i18n }}
|
||||
</a>
|
||||
<a
|
||||
@ -19,7 +24,7 @@
|
||||
routerLink="two-factor"
|
||||
class="list-group-item"
|
||||
routerLinkActive="active"
|
||||
*ngIf="organization?.use2fa"
|
||||
*ngIf="organization?.use2fa && organization?.isOwner"
|
||||
>
|
||||
{{ "twoStepLogin" | i18n }}
|
||||
</a>
|
||||
|
@ -9,7 +9,13 @@ export function canAccessVaultTab(org: Organization): boolean {
|
||||
}
|
||||
|
||||
export function canAccessSettingsTab(org: Organization): boolean {
|
||||
return org.isOwner;
|
||||
return (
|
||||
org.isOwner ||
|
||||
org.canManagePolicies ||
|
||||
org.canManageSso ||
|
||||
org.canManageScim ||
|
||||
org.canAccessImportExport
|
||||
);
|
||||
}
|
||||
|
||||
export function canAccessMembersTab(org: Organization): boolean {
|
||||
|
@ -2,6 +2,7 @@
|
||||
bitTabListItem
|
||||
[routerLink]="disabled ? null : route"
|
||||
routerLinkActive
|
||||
[routerLinkActiveOptions]="routerLinkMatchOptions"
|
||||
#rla="routerLinkActive"
|
||||
[active]="rla.isActive"
|
||||
[disabled]="disabled"
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FocusableOption } from "@angular/cdk/a11y";
|
||||
import { AfterViewInit, Component, HostListener, Input, OnDestroy, ViewChild } from "@angular/core";
|
||||
import { RouterLinkActive } from "@angular/router";
|
||||
import { IsActiveMatchOptions, RouterLinkActive } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { TabListItemDirective } from "../shared/tab-list-item.directive";
|
||||
@ -17,6 +17,13 @@ export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestr
|
||||
@ViewChild(TabListItemDirective) tabItem: TabListItemDirective;
|
||||
@ViewChild("rla") routerLinkActive: RouterLinkActive;
|
||||
|
||||
readonly routerLinkMatchOptions: IsActiveMatchOptions = {
|
||||
queryParams: "ignored",
|
||||
matrixParams: "ignored",
|
||||
paths: "subset",
|
||||
fragment: "ignored",
|
||||
};
|
||||
|
||||
@Input() route: string;
|
||||
@Input() disabled = false;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user