[AC-2509] add member access component (#9411)
* Added new report card and FeatureFlag for MemberAccessReport * Add new "isEnterpriseOrgGuard" * Add member access icon * Show upgrade organization dialog for enterprise on member access report click * verify member access featureflag on enterprise org guard * add comment with TODO information for follow up task * Initial member access report component * Improved readability, removed path to wrong component and refactored buildReports to use the productType * finished MemberAccessReport layout and added temporary service to provide mock data * Moved member-access-report files to bitwarden_license/ Removed unnecessary files * Added new tools path on bitwarden_license to the CODEOWNERS file * added member access description to the messages.json * layout changes to member access report * Created new reports-routing under bitwarden_license Moved member-access-report files to corresponding subfolder * Added search logic * Removed routing from member-access-report BL component on OSS. Added member-access-report navigation to organizations-routing on BL * removed unnecessary ng-container * Added OrganizationPermissionsGuard and canAccessReports validation to member-access-report navigation * replaced deprecated search code with searchControl * Address PR feedback * removed unnecessary canAccessReports method
This commit is contained in:
parent
dd40faf72e
commit
0e2c486a38
|
@ -29,6 +29,7 @@ libs/common/src/models/export @bitwarden/team-tools-dev
|
|||
libs/common/src/tools @bitwarden/team-tools-dev
|
||||
libs/importer @bitwarden/team-tools-dev
|
||||
libs/tools @bitwarden/team-tools-dev
|
||||
bitwarden_license/bit-web/src/app/tools @bitwarden/team-tools-dev
|
||||
|
||||
## Localization/Crowdin (Tools team)
|
||||
apps/browser/src/_locales @bitwarden/team-tools-dev
|
||||
|
|
|
@ -8445,6 +8445,9 @@
|
|||
"memberAccessReportDesc": {
|
||||
"message": "Ensure members have access to the right credentials and their accounts are secure. Use this report to obtain a CSV of member access and account configurations."
|
||||
},
|
||||
"memberAccessReportPageDesc": {
|
||||
"message": "Audit organization member access across groups, collections, and collection items. The CSV export provides a detailed breakdown per member, including information on collection permissions and account configurations."
|
||||
},
|
||||
"higherKDFIterations": {
|
||||
"message": "Higher KDF iterations can help protect your master password from being brute forced by an attacker."
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ import { RouterModule, Routes } from "@angular/router";
|
|||
|
||||
import { AuthGuard } from "@bitwarden/angular/auth/guards";
|
||||
import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { IsEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard";
|
||||
import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard";
|
||||
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component";
|
||||
|
||||
|
@ -58,6 +59,23 @@ const routes: Routes = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "reporting/reports",
|
||||
canActivate: [AuthGuard, organizationPermissionsGuard((org) => org.canAccessReports)],
|
||||
children: [
|
||||
{
|
||||
path: "member-access-report",
|
||||
loadComponent: () =>
|
||||
import(
|
||||
"../../tools/reports/member-access-report/member-access-report.component"
|
||||
).then((mod) => mod.MemberAccessReportComponent),
|
||||
data: {
|
||||
titleId: "memberAccessReport",
|
||||
},
|
||||
canActivate: [IsEnterpriseOrgGuard],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<app-header>
|
||||
<bit-search
|
||||
[formControl]="searchControl"
|
||||
[placeholder]="'searchMembers' | i18n"
|
||||
class="tw-grow"
|
||||
></bit-search>
|
||||
|
||||
<button type="button" bitButton buttonType="primary">
|
||||
<span>{{ "export" | i18n }}</span>
|
||||
<i class="bwi bwi-fw bwi-sign-in" aria-hidden="true"></i>
|
||||
</button>
|
||||
</app-header>
|
||||
|
||||
<div class="tw-max-w-4xl">
|
||||
<p bitTypography="body1">
|
||||
{{ "memberAccessReportPageDesc" | i18n }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<bit-table [dataSource]="dataSource" class="tw-mt-2">
|
||||
<ng-container header>
|
||||
<tr>
|
||||
<th bitCell bitSortable="name" default>Members</th>
|
||||
<th bitCell bitSortable="groups">Groups</th>
|
||||
<th bitCell bitSortable="collections">Collections</th>
|
||||
<th bitCell bitSortable="items">Items</th>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template body let-rows$>
|
||||
<tr bitRow *ngFor="let r of rows$ | async">
|
||||
<td bitCell>
|
||||
<div class="tw-flex tw-items-center">
|
||||
<bit-avatar size="small" [text]="r.name" class="tw-mr-3"></bit-avatar>
|
||||
<div class="tw-flex tw-flex-col">
|
||||
<button type="button" bitLink>
|
||||
{{ r.name }}
|
||||
</button>
|
||||
|
||||
<div class="tw-text-sm tw-mt-1 tw-text-muted">
|
||||
{{ r.email }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td bitCell class="tw-text-muted tw-w-[278px] tw-p-4">{{ r.groups }}</td>
|
||||
<td bitCell class="tw-text-muted tw-w-[278px] tw-p-4">{{ r.collections }}</td>
|
||||
<td bitCell class="tw-text-muted tw-w-[278px] tw-p-4">{{ r.items }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
|
@ -0,0 +1,33 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { FormControl } from "@angular/forms";
|
||||
import { debounceTime } from "rxjs";
|
||||
|
||||
import { SearchModule, TableDataSource } from "@bitwarden/components";
|
||||
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
|
||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||
|
||||
import { MemberAccessReportService } from "./member-access-report.service";
|
||||
import { MemberAccessReportView } from "./view/member-access-report.view";
|
||||
|
||||
@Component({
|
||||
selector: "member-access-report",
|
||||
templateUrl: "member-access-report.component.html",
|
||||
imports: [SharedModule, SearchModule, HeaderModule],
|
||||
standalone: true,
|
||||
})
|
||||
export class MemberAccessReportComponent implements OnInit {
|
||||
protected dataSource = new TableDataSource<MemberAccessReportView>();
|
||||
protected searchControl = new FormControl("", { nonNullable: true });
|
||||
|
||||
constructor(protected reportService: MemberAccessReportService) {
|
||||
// Connect the search input to the table dataSource filter input
|
||||
this.searchControl.valueChanges
|
||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||
.subscribe((v) => (this.dataSource.filter = v));
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.dataSource.data = this.reportService.getMemberAccessMockData();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { MemberAccessReportView } from "./view/member-access-report.view";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class MemberAccessReportService {
|
||||
//Temporary method to provide mock data for test purposes only
|
||||
getMemberAccessMockData(): MemberAccessReportView[] {
|
||||
const memberAccess = new MemberAccessReportView();
|
||||
memberAccess.email = "sjohnson@email.com";
|
||||
memberAccess.name = "Sarah Johnson";
|
||||
memberAccess.groups = 3;
|
||||
memberAccess.collections = 12;
|
||||
memberAccess.items = 3;
|
||||
|
||||
const memberAccess2 = new MemberAccessReportView();
|
||||
memberAccess2.email = "jlull@email.com";
|
||||
memberAccess2.name = "James Lull";
|
||||
memberAccess2.groups = 2;
|
||||
memberAccess2.collections = 24;
|
||||
memberAccess2.items = 2;
|
||||
|
||||
const memberAccess3 = new MemberAccessReportView();
|
||||
memberAccess3.email = "bwilliams@email.com";
|
||||
memberAccess3.name = "Beth Williams";
|
||||
memberAccess3.groups = 6;
|
||||
memberAccess3.collections = 12;
|
||||
memberAccess3.items = 1;
|
||||
|
||||
const memberAccess4 = new MemberAccessReportView();
|
||||
memberAccess4.email = "rwilliams@email.com";
|
||||
memberAccess4.name = "Ray Williams";
|
||||
memberAccess4.groups = 5;
|
||||
memberAccess4.collections = 21;
|
||||
memberAccess4.items = 2;
|
||||
|
||||
return [memberAccess, memberAccess2, memberAccess3, memberAccess4];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
export class MemberAccessReportView {
|
||||
name: string;
|
||||
email: string;
|
||||
collections: number;
|
||||
groups: number;
|
||||
items: number;
|
||||
}
|
Loading…
Reference in New Issue