mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-30 22:41:33 +01:00
PM-17392 Slide out drawer (#13096)
This commit is contained in:
parent
26a0594056
commit
d0018548ed
@ -117,6 +117,11 @@ export type AtRiskApplicationDetail = {
|
|||||||
atRiskPasswordCount: number;
|
atRiskPasswordCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AppAtRiskMembersDialogParams = {
|
||||||
|
members: MemberDetailsFlat[];
|
||||||
|
applicationName: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request to drop a password health report application
|
* Request to drop a password health report application
|
||||||
* Model is expected by the API endpoint
|
* Model is expected by the API endpoint
|
||||||
@ -143,4 +148,11 @@ export interface PasswordHealthReportApplicationsRequest {
|
|||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DrawerType {
|
||||||
|
None = 0,
|
||||||
|
AppAtRiskMembers = 1,
|
||||||
|
OrgAtRiskMembers = 2,
|
||||||
|
OrgAtRiskApps = 3,
|
||||||
|
}
|
||||||
|
|
||||||
export type PasswordHealthReportApplicationId = Opaque<string, "PasswordHealthReportApplicationId">;
|
export type PasswordHealthReportApplicationId = Opaque<string, "PasswordHealthReportApplicationId">;
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import { BehaviorSubject } from "rxjs";
|
import { BehaviorSubject } from "rxjs";
|
||||||
import { finalize } from "rxjs/operators";
|
import { finalize } from "rxjs/operators";
|
||||||
|
|
||||||
import { ApplicationHealthReportDetail } from "../models/password-health";
|
import {
|
||||||
|
AppAtRiskMembersDialogParams,
|
||||||
|
ApplicationHealthReportDetail,
|
||||||
|
AtRiskApplicationDetail,
|
||||||
|
AtRiskMemberDetail,
|
||||||
|
DrawerType,
|
||||||
|
} from "../models/password-health";
|
||||||
|
|
||||||
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
||||||
|
|
||||||
export class RiskInsightsDataService {
|
export class RiskInsightsDataService {
|
||||||
private applicationsSubject = new BehaviorSubject<ApplicationHealthReportDetail[] | null>(null);
|
private applicationsSubject = new BehaviorSubject<ApplicationHealthReportDetail[] | null>(null);
|
||||||
|
|
||||||
@ -22,6 +27,12 @@ export class RiskInsightsDataService {
|
|||||||
private dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null);
|
private dataLastUpdatedSubject = new BehaviorSubject<Date | null>(null);
|
||||||
dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable();
|
dataLastUpdated$ = this.dataLastUpdatedSubject.asObservable();
|
||||||
|
|
||||||
|
openDrawer = false;
|
||||||
|
activeDrawerType: DrawerType = DrawerType.None;
|
||||||
|
atRiskMemberDetails: AtRiskMemberDetail[] = [];
|
||||||
|
appAtRiskMembers: AppAtRiskMembersDialogParams | null = null;
|
||||||
|
atRiskAppDetails: AtRiskApplicationDetail[] | null = null;
|
||||||
|
|
||||||
constructor(private reportService: RiskInsightsReportService) {}
|
constructor(private reportService: RiskInsightsReportService) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,4 +68,46 @@ export class RiskInsightsDataService {
|
|||||||
refreshApplicationsReport(organizationId: string): void {
|
refreshApplicationsReport(organizationId: string): void {
|
||||||
this.fetchApplicationsReport(organizationId, true);
|
this.fetchApplicationsReport(organizationId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isActiveDrawerType = (drawerType: DrawerType): boolean => {
|
||||||
|
return this.activeDrawerType === drawerType;
|
||||||
|
};
|
||||||
|
|
||||||
|
setDrawerForOrgAtRiskMembers = (atRiskMemberDetails: AtRiskMemberDetail[]): void => {
|
||||||
|
this.resetDrawer(DrawerType.OrgAtRiskMembers);
|
||||||
|
this.activeDrawerType = DrawerType.OrgAtRiskMembers;
|
||||||
|
this.atRiskMemberDetails = atRiskMemberDetails;
|
||||||
|
this.openDrawer = !this.openDrawer;
|
||||||
|
};
|
||||||
|
|
||||||
|
setDrawerForAppAtRiskMembers = (
|
||||||
|
atRiskMembersDialogParams: AppAtRiskMembersDialogParams,
|
||||||
|
): void => {
|
||||||
|
this.resetDrawer(DrawerType.None);
|
||||||
|
this.activeDrawerType = DrawerType.AppAtRiskMembers;
|
||||||
|
this.appAtRiskMembers = atRiskMembersDialogParams;
|
||||||
|
this.openDrawer = !this.openDrawer;
|
||||||
|
};
|
||||||
|
|
||||||
|
setDrawerForOrgAtRiskApps = (atRiskApps: AtRiskApplicationDetail[]): void => {
|
||||||
|
this.resetDrawer(DrawerType.OrgAtRiskApps);
|
||||||
|
this.activeDrawerType = DrawerType.OrgAtRiskApps;
|
||||||
|
this.atRiskAppDetails = atRiskApps;
|
||||||
|
this.openDrawer = !this.openDrawer;
|
||||||
|
};
|
||||||
|
|
||||||
|
closeDrawer = (): void => {
|
||||||
|
this.resetDrawer(DrawerType.None);
|
||||||
|
};
|
||||||
|
|
||||||
|
private resetDrawer = (drawerType: DrawerType): void => {
|
||||||
|
if (this.activeDrawerType !== drawerType) {
|
||||||
|
this.openDrawer = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeDrawerType = DrawerType.None;
|
||||||
|
this.atRiskMemberDetails = [];
|
||||||
|
this.appAtRiskMembers = null;
|
||||||
|
this.atRiskAppDetails = null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||||
import {
|
import {
|
||||||
DialogService,
|
|
||||||
Icons,
|
Icons,
|
||||||
NoItemsModule,
|
NoItemsModule,
|
||||||
SearchModule,
|
SearchModule,
|
||||||
@ -38,9 +37,6 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
|
|||||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component";
|
|
||||||
import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component";
|
|
||||||
import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component";
|
|
||||||
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
|
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -131,7 +127,6 @@ export class AllApplicationsComponent implements OnInit {
|
|||||||
protected reportService: RiskInsightsReportService,
|
protected reportService: RiskInsightsReportService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
protected criticalAppsService: CriticalAppsService,
|
protected criticalAppsService: CriticalAppsService,
|
||||||
protected dialogService: DialogService,
|
|
||||||
) {
|
) {
|
||||||
this.searchControl.valueChanges
|
this.searchControl.valueChanges
|
||||||
.pipe(debounceTime(200), takeUntilDestroyed())
|
.pipe(debounceTime(200), takeUntilDestroyed())
|
||||||
@ -176,24 +171,23 @@ export class AllApplicationsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showAppAtRiskMembers = async (applicationName: string) => {
|
showAppAtRiskMembers = async (applicationName: string) => {
|
||||||
openAppAtRiskMembersDialog(this.dialogService, {
|
const info = {
|
||||||
members:
|
members:
|
||||||
this.dataSource.data.find((app) => app.applicationName === applicationName)
|
this.dataSource.data.find((app) => app.applicationName === applicationName)
|
||||||
?.atRiskMemberDetails ?? [],
|
?.atRiskMemberDetails ?? [],
|
||||||
applicationName,
|
applicationName,
|
||||||
});
|
};
|
||||||
|
this.dataService.setDrawerForAppAtRiskMembers(info);
|
||||||
};
|
};
|
||||||
|
|
||||||
showOrgAtRiskMembers = async () => {
|
showOrgAtRiskMembers = async () => {
|
||||||
this.dialogService.open(OrgAtRiskMembersDialogComponent, {
|
const dialogData = this.reportService.generateAtRiskMemberList(this.dataSource.data);
|
||||||
data: this.reportService.generateAtRiskMemberList(this.dataSource.data),
|
this.dataService.setDrawerForOrgAtRiskMembers(dialogData);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
showOrgAtRiskApps = async () => {
|
showOrgAtRiskApps = async () => {
|
||||||
this.dialogService.open(OrgAtRiskAppsDialogComponent, {
|
const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data);
|
||||||
data: this.reportService.generateAtRiskApplicationList(this.dataSource.data),
|
this.dataService.setDrawerForOrgAtRiskApps(data);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onCheckboxChange(applicationName: string, event: Event) {
|
onCheckboxChange(applicationName: string, event: Event) {
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
<bit-dialog>
|
|
||||||
<span bitDialogTitle>{{ applicationName }}</span>
|
|
||||||
<ng-container bitDialogContent>
|
|
||||||
<div class="tw-flex tw-flex-col tw-gap-2">
|
|
||||||
<span bitDialogTitle>{{ "atRiskMembersWithCount" | i18n: members.length }} </span>
|
|
||||||
<span>{{ "atRiskMembersDescriptionWithApp" | i18n: applicationName }}</span>
|
|
||||||
<div class="tw-mt-1">
|
|
||||||
<ng-container *ngFor="let member of members">
|
|
||||||
<div>{{ member.email }}</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container bitDialogFooter>
|
|
||||||
<button bitButton bitDialogClose buttonType="secondary" type="button">
|
|
||||||
{{ "close" | i18n }}
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</bit-dialog>
|
|
@ -1,35 +0,0 @@
|
|||||||
import { DIALOG_DATA } from "@angular/cdk/dialog";
|
|
||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { Component, Inject } from "@angular/core";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|
||||||
import { MemberDetailsFlat } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
|
|
||||||
import { ButtonModule, DialogModule, DialogService } from "@bitwarden/components";
|
|
||||||
|
|
||||||
type AppAtRiskMembersDialogParams = {
|
|
||||||
members: MemberDetailsFlat[];
|
|
||||||
applicationName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const openAppAtRiskMembersDialog = (
|
|
||||||
dialogService: DialogService,
|
|
||||||
dialogConfig: AppAtRiskMembersDialogParams,
|
|
||||||
) =>
|
|
||||||
dialogService.open<boolean, AppAtRiskMembersDialogParams>(AppAtRiskMembersDialogComponent, {
|
|
||||||
data: dialogConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: "./app-at-risk-members-dialog.component.html",
|
|
||||||
imports: [ButtonModule, CommonModule, JslibModule, DialogModule],
|
|
||||||
})
|
|
||||||
export class AppAtRiskMembersDialogComponent {
|
|
||||||
protected members: MemberDetailsFlat[];
|
|
||||||
protected applicationName: string;
|
|
||||||
|
|
||||||
constructor(@Inject(DIALOG_DATA) private params: AppAtRiskMembersDialogParams) {
|
|
||||||
this.members = params.members;
|
|
||||||
this.applicationName = params.applicationName;
|
|
||||||
}
|
|
||||||
}
|
|
@ -43,7 +43,7 @@
|
|||||||
>
|
>
|
||||||
</tools-card>
|
</tools-card>
|
||||||
<tools-card
|
<tools-card
|
||||||
class="tw-flex-1"
|
class="tw-flex-1 tw-cursor-pointer"
|
||||||
[title]="'atRiskApplications' | i18n"
|
[title]="'atRiskApplications' | i18n"
|
||||||
[value]="applicationSummary.totalAtRiskApplicationCount"
|
[value]="applicationSummary.totalAtRiskApplicationCount"
|
||||||
[maxValue]="applicationSummary.totalApplicationCount"
|
[maxValue]="applicationSummary.totalApplicationCount"
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
import {
|
import {
|
||||||
DialogService,
|
|
||||||
Icons,
|
Icons,
|
||||||
NoItemsModule,
|
NoItemsModule,
|
||||||
SearchModule,
|
SearchModule,
|
||||||
@ -30,9 +29,6 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
|
|||||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
|
||||||
|
|
||||||
import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component";
|
|
||||||
import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component";
|
|
||||||
import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component";
|
|
||||||
import { RiskInsightsTabType } from "./risk-insights.component";
|
import { RiskInsightsTabType } from "./risk-insights.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -114,7 +110,6 @@ export class CriticalApplicationsComponent implements OnInit {
|
|||||||
protected dataService: RiskInsightsDataService,
|
protected dataService: RiskInsightsDataService,
|
||||||
protected criticalAppsService: CriticalAppsService,
|
protected criticalAppsService: CriticalAppsService,
|
||||||
protected reportService: RiskInsightsReportService,
|
protected reportService: RiskInsightsReportService,
|
||||||
protected dialogService: DialogService,
|
|
||||||
protected i18nService: I18nService,
|
protected i18nService: I18nService,
|
||||||
) {
|
) {
|
||||||
this.searchControl.valueChanges
|
this.searchControl.valueChanges
|
||||||
@ -123,24 +118,23 @@ export class CriticalApplicationsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showAppAtRiskMembers = async (applicationName: string) => {
|
showAppAtRiskMembers = async (applicationName: string) => {
|
||||||
openAppAtRiskMembersDialog(this.dialogService, {
|
const data = {
|
||||||
members:
|
members:
|
||||||
this.dataSource.data.find((app) => app.applicationName === applicationName)
|
this.dataSource.data.find((app) => app.applicationName === applicationName)
|
||||||
?.atRiskMemberDetails ?? [],
|
?.atRiskMemberDetails ?? [],
|
||||||
applicationName,
|
applicationName,
|
||||||
});
|
};
|
||||||
|
this.dataService.setDrawerForAppAtRiskMembers(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
showOrgAtRiskMembers = async () => {
|
showOrgAtRiskMembers = async () => {
|
||||||
this.dialogService.open(OrgAtRiskMembersDialogComponent, {
|
const data = this.reportService.generateAtRiskMemberList(this.dataSource.data);
|
||||||
data: this.reportService.generateAtRiskMemberList(this.dataSource.data),
|
this.dataService.setDrawerForOrgAtRiskMembers(data);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
showOrgAtRiskApps = async () => {
|
showOrgAtRiskApps = async () => {
|
||||||
this.dialogService.open(OrgAtRiskAppsDialogComponent, {
|
const data = this.reportService.generateAtRiskApplicationList(this.dataSource.data);
|
||||||
data: this.reportService.generateAtRiskApplicationList(this.dataSource.data),
|
this.dataService.setDrawerForOrgAtRiskApps(data);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) {
|
trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) {
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
<bit-dialog>
|
|
||||||
<ng-container bitDialogTitle>
|
|
||||||
<span bitDialogTitle>{{ "atRiskApplicationsWithCount" | i18n: atRiskApps.length }} </span>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container bitDialogContent>
|
|
||||||
<div class="tw-flex tw-flex-col tw-gap-2">
|
|
||||||
<span bitTypography="body1">{{ "atRiskApplicationsDescription" | i18n }}</span>
|
|
||||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
|
||||||
<div bitTypography="body2" class="tw-font-bold">{{ "application" | i18n }}</div>
|
|
||||||
<div bitTypography="body2" class="tw-font-bold">{{ "atRiskPasswords" | i18n }}</div>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngFor="let app of atRiskApps">
|
|
||||||
<div class="tw-flex tw-justify-between tw-mt-2">
|
|
||||||
<div>{{ app.applicationName }}</div>
|
|
||||||
<div>{{ app.atRiskPasswordCount }}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container bitDialogFooter>
|
|
||||||
<button bitButton bitDialogClose buttonType="secondary" type="button">
|
|
||||||
{{ "close" | i18n }}
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</bit-dialog>
|
|
@ -1,24 +0,0 @@
|
|||||||
import { DIALOG_DATA } from "@angular/cdk/dialog";
|
|
||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { Component, Inject } from "@angular/core";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|
||||||
import { AtRiskApplicationDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
|
|
||||||
import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components";
|
|
||||||
|
|
||||||
export const openOrgAtRiskMembersDialog = (
|
|
||||||
dialogService: DialogService,
|
|
||||||
dialogConfig: AtRiskApplicationDetail[],
|
|
||||||
) =>
|
|
||||||
dialogService.open<boolean, AtRiskApplicationDetail[]>(OrgAtRiskAppsDialogComponent, {
|
|
||||||
data: dialogConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: "./org-at-risk-apps-dialog.component.html",
|
|
||||||
imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule],
|
|
||||||
})
|
|
||||||
export class OrgAtRiskAppsDialogComponent {
|
|
||||||
constructor(@Inject(DIALOG_DATA) protected atRiskApps: AtRiskApplicationDetail[]) {}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<bit-dialog>
|
|
||||||
<ng-container bitDialogTitle>
|
|
||||||
<span bitDialogTitle>{{ "atRiskMembersWithCount" | i18n: atRiskMembers.length }} </span>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container bitDialogContent>
|
|
||||||
<div class="tw-flex tw-flex-col tw-gap-2">
|
|
||||||
<span bitTypography="body1">{{ "atRiskMembersDescription" | i18n }}</span>
|
|
||||||
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
|
||||||
<div bitTypography="body2" class="tw-font-bold">{{ "email" | i18n }}</div>
|
|
||||||
<div bitTypography="body2" class="tw-font-bold">{{ "atRiskPasswords" | i18n }}</div>
|
|
||||||
</div>
|
|
||||||
<ng-container *ngFor="let member of atRiskMembers">
|
|
||||||
<div class="tw-flex tw-justify-between tw-mt-2">
|
|
||||||
<div>{{ member.email }}</div>
|
|
||||||
<div>{{ member.atRiskPasswordCount }}</div>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container bitDialogFooter>
|
|
||||||
<button bitButton bitDialogClose buttonType="secondary" type="button">
|
|
||||||
{{ "close" | i18n }}
|
|
||||||
</button>
|
|
||||||
</ng-container>
|
|
||||||
</bit-dialog>
|
|
@ -1,24 +0,0 @@
|
|||||||
import { DIALOG_DATA } from "@angular/cdk/dialog";
|
|
||||||
import { CommonModule } from "@angular/common";
|
|
||||||
import { Component, Inject } from "@angular/core";
|
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
|
||||||
import { AtRiskMemberDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
|
|
||||||
import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components";
|
|
||||||
|
|
||||||
export const openOrgAtRiskMembersDialog = (
|
|
||||||
dialogService: DialogService,
|
|
||||||
dialogConfig: AtRiskMemberDetail[],
|
|
||||||
) =>
|
|
||||||
dialogService.open<boolean, AtRiskMemberDetail[]>(OrgAtRiskMembersDialogComponent, {
|
|
||||||
data: dialogConfig,
|
|
||||||
});
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
standalone: true,
|
|
||||||
templateUrl: "./org-at-risk-members-dialog.component.html",
|
|
||||||
imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule],
|
|
||||||
})
|
|
||||||
export class OrgAtRiskMembersDialogComponent {
|
|
||||||
constructor(@Inject(DIALOG_DATA) protected atRiskMembers: AtRiskMemberDetail[]) {}
|
|
||||||
}
|
|
@ -1,57 +1,126 @@
|
|||||||
<ng-container>
|
<ng-container>
|
||||||
<div class="tw-mb-1 text-primary" bitTypography="body1">{{ "accessIntelligence" | i18n }}</div>
|
<bit-layout>
|
||||||
<h1 bitTypography="h1">{{ "riskInsights" | i18n }}</h1>
|
<div class="tw-mb-1 text-primary" bitTypography="body1">{{ "accessIntelligence" | i18n }}</div>
|
||||||
<div class="tw-text-muted tw-max-w-4xl tw-mb-2">
|
<h1 bitTypography="h1">{{ "riskInsights" | i18n }}</h1>
|
||||||
{{ "reviewAtRiskPasswords" | i18n }}
|
<div class="tw-text-muted tw-max-w-4xl tw-mb-2">
|
||||||
</div>
|
{{ "reviewAtRiskPasswords" | i18n }}
|
||||||
<div
|
</div>
|
||||||
class="tw-bg-primary-100 tw-rounded-lg tw-w-full tw-px-8 tw-py-4 tw-my-4 tw-flex tw-items-center"
|
<div
|
||||||
>
|
class="tw-bg-primary-100 tw-rounded-lg tw-w-full tw-px-8 tw-py-4 tw-my-4 tw-flex tw-items-center"
|
||||||
<i
|
>
|
||||||
class="bwi bwi-exclamation-triangle bwi-lg tw-text-[1.2rem] text-muted"
|
<i
|
||||||
aria-hidden="true"
|
class="bwi bwi-exclamation-triangle bwi-lg tw-text-[1.2rem] text-muted"
|
||||||
></i>
|
aria-hidden="true"
|
||||||
<span class="tw-mx-4">{{
|
></i>
|
||||||
"dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a")
|
<span class="tw-mx-4">{{
|
||||||
}}</span>
|
"dataLastUpdated" | i18n: (dataLastUpdated$ | async | date: "MMMM d, y 'at' h:mm a")
|
||||||
<span class="tw-flex tw-justify-center tw-w-16">
|
}}</span>
|
||||||
<a
|
<span class="tw-flex tw-justify-center tw-w-16">
|
||||||
*ngIf="!(isRefreshing$ | async)"
|
<a
|
||||||
bitButton
|
*ngIf="!(isRefreshing$ | async)"
|
||||||
buttonType="unstyled"
|
bitButton
|
||||||
class="tw-border-none !tw-font-normal tw-cursor-pointer !tw-py-0"
|
buttonType="unstyled"
|
||||||
[bitAction]="refreshData.bind(this)"
|
class="tw-border-none !tw-font-normal tw-cursor-pointer !tw-py-0"
|
||||||
>
|
[bitAction]="refreshData.bind(this)"
|
||||||
{{ "refresh" | i18n }}
|
>
|
||||||
</a>
|
{{ "refresh" | i18n }}
|
||||||
<span>
|
</a>
|
||||||
<i
|
<span>
|
||||||
*ngIf="isRefreshing$ | async"
|
<i
|
||||||
class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]"
|
*ngIf="isRefreshing$ | async"
|
||||||
aria-hidden="true"
|
class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]"
|
||||||
></i>
|
aria-hidden="true"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</div>
|
||||||
</div>
|
<bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
|
||||||
<bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
|
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: appsCount }}">
|
||||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: appsCount }}">
|
<tools-all-applications></tools-all-applications>
|
||||||
<tools-all-applications></tools-all-applications>
|
</bit-tab>
|
||||||
</bit-tab>
|
<bit-tab *ngIf="isCriticalAppsFeatureEnabled">
|
||||||
<bit-tab *ngIf="isCriticalAppsFeatureEnabled">
|
<ng-template bitTabLabel>
|
||||||
<ng-template bitTabLabel>
|
<i class="bwi bwi-star"></i>
|
||||||
<i class="bwi bwi-star"></i>
|
{{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }}
|
||||||
{{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }}
|
</ng-template>
|
||||||
</ng-template>
|
<tools-critical-applications></tools-critical-applications>
|
||||||
<tools-critical-applications></tools-critical-applications>
|
</bit-tab>
|
||||||
</bit-tab>
|
<bit-tab *ngIf="showDebugTabs" label="Raw Data">
|
||||||
<bit-tab *ngIf="showDebugTabs" label="Raw Data">
|
<tools-password-health></tools-password-health>
|
||||||
<tools-password-health></tools-password-health>
|
</bit-tab>
|
||||||
</bit-tab>
|
<bit-tab *ngIf="showDebugTabs" label="Raw Data + members">
|
||||||
<bit-tab *ngIf="showDebugTabs" label="Raw Data + members">
|
<tools-password-health-members></tools-password-health-members>
|
||||||
<tools-password-health-members></tools-password-health-members>
|
</bit-tab>
|
||||||
</bit-tab>
|
<bit-tab *ngIf="showDebugTabs" label="Raw Data + uri">
|
||||||
<bit-tab *ngIf="showDebugTabs" label="Raw Data + uri">
|
<tools-password-health-members-uri></tools-password-health-members-uri>
|
||||||
<tools-password-health-members-uri></tools-password-health-members-uri>
|
</bit-tab>
|
||||||
</bit-tab>
|
</bit-tab-group>
|
||||||
</bit-tab-group>
|
|
||||||
|
<bit-drawer style="width: 30%" [(open)]="dataService.openDrawer">
|
||||||
|
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskMembers)">
|
||||||
|
<bit-drawer-header
|
||||||
|
title="{{ 'atRiskMembersWithCount' | i18n: dataService.atRiskMemberDetails.length }}"
|
||||||
|
>
|
||||||
|
</bit-drawer-header>
|
||||||
|
<bit-drawer-body>
|
||||||
|
<span bitTypography="body1" class="tw-text-muted tw-text-sm">{{
|
||||||
|
"atRiskMembersDescription" | i18n
|
||||||
|
}}</span>
|
||||||
|
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||||
|
<div bitTypography="body2" class="tw-font-bold">{{ "email" | i18n }}</div>
|
||||||
|
<div bitTypography="body2" class="tw-font-bold">{{ "atRiskPasswords" | i18n }}</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngFor="let member of dataService.atRiskMemberDetails">
|
||||||
|
<div class="tw-flex tw-justify-between tw-mt-2">
|
||||||
|
<div>{{ member.email }}</div>
|
||||||
|
<div>{{ member.atRiskPasswordCount }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</bit-drawer-body>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.AppAtRiskMembers)">
|
||||||
|
<bit-drawer-header title="{{ dataService.appAtRiskMembers.applicationName }}">
|
||||||
|
</bit-drawer-header>
|
||||||
|
<bit-drawer-body>
|
||||||
|
<div bitTypography="body1" class="tw-mb-2">
|
||||||
|
{{ "atRiskMembersWithCount" | i18n: dataService.appAtRiskMembers.members.length }}
|
||||||
|
</div>
|
||||||
|
<div bitTypography="body1" class="tw-text-muted tw-text-sm tw-mb-2">
|
||||||
|
{{
|
||||||
|
"atRiskMembersDescriptionWithApp" | i18n: dataService.appAtRiskMembers.applicationName
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<div class="tw-mt-1">
|
||||||
|
<ng-container *ngFor="let member of dataService.appAtRiskMembers.members">
|
||||||
|
<div>{{ member.email }}</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</bit-drawer-body>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="dataService.isActiveDrawerType(drawerTypes.OrgAtRiskApps)">
|
||||||
|
<bit-drawer-header
|
||||||
|
title="{{ 'atRiskApplicationsWithCount' | i18n: dataService.atRiskAppDetails.length }}"
|
||||||
|
>
|
||||||
|
</bit-drawer-header>
|
||||||
|
|
||||||
|
<bit-drawer-body>
|
||||||
|
<span bitTypography="body2" class="tw-text-muted tw-text-sm">{{
|
||||||
|
"atRiskApplicationsDescription" | i18n
|
||||||
|
}}</span>
|
||||||
|
<div class="tw-flex tw-justify-between tw-mt-2 tw-text-muted">
|
||||||
|
<div bitTypography="body2" class="tw-font-bold">{{ "application" | i18n }}</div>
|
||||||
|
<div bitTypography="body2" class="tw-font-bold">{{ "atRiskPasswords" | i18n }}</div>
|
||||||
|
</div>
|
||||||
|
<ng-container *ngFor="let app of dataService.atRiskAppDetails">
|
||||||
|
<div class="tw-flex tw-justify-between tw-mt-2">
|
||||||
|
<div>{{ app.applicationName }}</div>
|
||||||
|
<div>{{ app.atRiskPasswordCount }}</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</bit-drawer-body>
|
||||||
|
</ng-container>
|
||||||
|
</bit-drawer>
|
||||||
|
</bit-layout>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||||
import {
|
import {
|
||||||
ApplicationHealthReportDetail,
|
ApplicationHealthReportDetail,
|
||||||
|
DrawerType,
|
||||||
PasswordHealthReportApplicationsResponse,
|
PasswordHealthReportApplicationsResponse,
|
||||||
} from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
|
} from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
|
||||||
// eslint-disable-next-line no-restricted-imports -- used for dependency injection
|
// eslint-disable-next-line no-restricted-imports -- used for dependency injection
|
||||||
@ -19,7 +20,15 @@ import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
|||||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||||
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags";
|
||||||
import { OrganizationId } from "@bitwarden/common/types/guid";
|
import { OrganizationId } from "@bitwarden/common/types/guid";
|
||||||
import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components";
|
import {
|
||||||
|
AsyncActionsModule,
|
||||||
|
ButtonModule,
|
||||||
|
DrawerBodyComponent,
|
||||||
|
DrawerComponent,
|
||||||
|
DrawerHeaderComponent,
|
||||||
|
LayoutComponent,
|
||||||
|
TabsModule,
|
||||||
|
} from "@bitwarden/components";
|
||||||
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
|
import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module";
|
||||||
|
|
||||||
import { AllApplicationsComponent } from "./all-applications.component";
|
import { AllApplicationsComponent } from "./all-applications.component";
|
||||||
@ -51,6 +60,10 @@ export enum RiskInsightsTabType {
|
|||||||
PasswordHealthMembersURIComponent,
|
PasswordHealthMembersURIComponent,
|
||||||
NotifiedMembersTableComponent,
|
NotifiedMembersTableComponent,
|
||||||
TabsModule,
|
TabsModule,
|
||||||
|
DrawerComponent,
|
||||||
|
DrawerBodyComponent,
|
||||||
|
DrawerHeaderComponent,
|
||||||
|
LayoutComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class RiskInsightsComponent implements OnInit {
|
export class RiskInsightsComponent implements OnInit {
|
||||||
@ -77,7 +90,7 @@ export class RiskInsightsComponent implements OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private dataService: RiskInsightsDataService,
|
protected dataService: RiskInsightsDataService,
|
||||||
private criticalAppsService: CriticalAppsService,
|
private criticalAppsService: CriticalAppsService,
|
||||||
) {
|
) {
|
||||||
this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
||||||
@ -137,5 +150,13 @@ export class RiskInsightsComponent implements OnInit {
|
|||||||
queryParams: { tabIndex: newIndex },
|
queryParams: { tabIndex: newIndex },
|
||||||
queryParamsHandling: "merge",
|
queryParamsHandling: "merge",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// close drawer when tabs are changed
|
||||||
|
this.dataService.closeDrawer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of drawer types
|
||||||
|
get drawerTypes(): typeof DrawerType {
|
||||||
|
return DrawerType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user