mirror of
https://github.com/bitwarden/browser
synced 2026-02-11 22:13:32 +00:00
add risk insight data service and wire up components to it
This commit is contained in:
@@ -59,7 +59,6 @@ export class OrganizationLayoutComponent implements OnInit {
|
||||
showPaymentAndHistory$: Observable<boolean>;
|
||||
hideNewOrgButton$: Observable<boolean>;
|
||||
organizationIsUnmanaged$: Observable<boolean>;
|
||||
isAccessIntelligenceFeatureEnabled = false;
|
||||
enterpriseOrganization$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./member-cipher-details-api.service";
|
||||
export * from "./password-health.service";
|
||||
export * from "./risk-insights-report.service";
|
||||
export * from "./risk-insights-data.service";
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Observable } from "rxjs";
|
||||
import { shareReplay } from "rxjs/operators";
|
||||
|
||||
import { ApplicationHealthReportDetail } from "../models/password-health";
|
||||
|
||||
import { RiskInsightsReportService } from "./risk-insights-report.service";
|
||||
|
||||
/**
|
||||
* Singleton service to manage the report details for the Risk Insights reports.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class RiskInsightsDataService {
|
||||
// Map to store observables per organizationId
|
||||
private applicationsReportMap = new Map<string, Observable<ApplicationHealthReportDetail[]>>();
|
||||
|
||||
constructor(private reportService: RiskInsightsReportService) {}
|
||||
|
||||
/**
|
||||
* Returns an observable for the applications report of a given organizationId.
|
||||
* Utilizes shareReplay to ensure that the data is fetched only once
|
||||
* and shared among multiple subscribers.
|
||||
* @param organizationId The ID of the organization.
|
||||
* @returns Observable of ApplicationHealthReportDetail[].
|
||||
*/
|
||||
getApplicationsReport$(organizationId: string): Observable<ApplicationHealthReportDetail[]> {
|
||||
// If the observable for this organizationId already exists, return it
|
||||
if (this.applicationsReportMap.has(organizationId)) {
|
||||
return this.applicationsReportMap.get(organizationId)!;
|
||||
}
|
||||
|
||||
const applicationsReport$ = this.reportService
|
||||
.generateApplicationsReport$(organizationId)
|
||||
.pipe(shareReplay({ bufferSize: 1, refCount: true }));
|
||||
|
||||
// Store the observable in the map for future subscribers
|
||||
this.applicationsReportMap.set(organizationId, applicationsReport$);
|
||||
|
||||
return applicationsReport$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cached observable for a specific organizationId.
|
||||
* @param organizationId The ID of the organization.
|
||||
*/
|
||||
clearApplicationsReportCache(organizationId: string): void {
|
||||
if (this.applicationsReportMap.has(organizationId)) {
|
||||
this.applicationsReportMap.delete(organizationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,66 @@
|
||||
<div class="tw-mb-1 text-primary" bitTypography="body1">{{ "accessIntelligence" | i18n }}</div>
|
||||
<h1 bitTypography="h1">{{ "riskInsights" | i18n }}</h1>
|
||||
<div class="tw-text-muted tw-max-w-4xl tw-mb-2">
|
||||
{{ "reviewAtRiskPasswords" | i18n }}
|
||||
<a class="text-primary" routerLink="/login">{{ "learnMore" | i18n }}</a>
|
||||
<div *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</div>
|
||||
<div class="tw-bg-primary-100 tw-rounded-lg tw-w-full tw-px-8 tw-py-2 tw-my-4">
|
||||
<i class="bwi bwi-exclamation-triangle bwi-lg tw-text-[1.2rem] text-muted" aria-hidden="true"></i>
|
||||
<span class="tw-mx-4">{{
|
||||
"dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a")
|
||||
}}</span>
|
||||
<a
|
||||
bitButton
|
||||
buttonType="unstyled"
|
||||
class="tw-border-none !tw-font-normal tw-cursor-pointer"
|
||||
[bitAction]="refreshData.bind(this)"
|
||||
<ng-container *ngIf="!loading">
|
||||
<div class="tw-mb-1 text-primary" bitTypography="body1">{{ "accessIntelligence" | i18n }}</div>
|
||||
<h1 bitTypography="h1">{{ "riskInsights" | i18n }}</h1>
|
||||
<div class="tw-text-muted tw-max-w-4xl tw-mb-2">
|
||||
{{ "reviewAtRiskPasswords" | i18n }}
|
||||
<a class="text-primary" routerLink="/login">{{ "learnMore" | i18n }}</a>
|
||||
</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"
|
||||
>
|
||||
{{ "refresh" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
|
||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: apps.length }}">
|
||||
<tools-all-applications></tools-all-applications>
|
||||
</bit-tab>
|
||||
<bit-tab *ngIf="isCritialAppsFeatureEnabled">
|
||||
<ng-template bitTabLabel>
|
||||
<i class="bwi bwi-star"></i>
|
||||
{{ "criticalApplicationsWithCount" | i18n: criticalApps.length }}
|
||||
</ng-template>
|
||||
<tools-critical-applications></tools-critical-applications>
|
||||
</bit-tab>
|
||||
<bit-tab label="Raw Data">
|
||||
<tools-password-health></tools-password-health>
|
||||
</bit-tab>
|
||||
<bit-tab label="Raw Data + members">
|
||||
<tools-password-health-members></tools-password-health-members>
|
||||
</bit-tab>
|
||||
<bit-tab label="Raw Data + uri">
|
||||
<tools-password-health-members-uri></tools-password-health-members-uri>
|
||||
</bit-tab>
|
||||
<!-- <bit-tab>
|
||||
<ng-template bitTabLabel>
|
||||
<i class="bwi bwi-envelope"></i>
|
||||
{{ "notifiedMembersWithCount" | i18n: priorityApps.length }}
|
||||
</ng-template>
|
||||
<h2 bitTypography="h2">{{ "notifiedMembers" | i18n }}</h2>
|
||||
<tools-notified-members-table></tools-notified-members-table>
|
||||
</bit-tab> -->
|
||||
</bit-tab-group>
|
||||
<i
|
||||
class="bwi bwi-exclamation-triangle bwi-lg tw-text-[1.2rem] text-muted"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-mx-4">{{
|
||||
"dataLastUpdated" | i18n: (dataLastUpdated | date: "MMMM d, y 'at' h:mm a")
|
||||
}}</span>
|
||||
<span class="tw-flex tw-justify-center tw-w-16">
|
||||
<a
|
||||
*ngIf="!refetching"
|
||||
bitButton
|
||||
buttonType="unstyled"
|
||||
class="tw-border-none !tw-font-normal tw-cursor-pointer !tw-py-0"
|
||||
[bitAction]="refreshData.bind(this)"
|
||||
>
|
||||
{{ "refresh" | i18n }}
|
||||
</a>
|
||||
<span>
|
||||
<i
|
||||
*ngIf="refetching"
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted tw-text-[1.2rem]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<bit-tab-group [(selectedIndex)]="tabIndex" (selectedIndexChange)="onTabChange($event)">
|
||||
<bit-tab label="{{ 'allApplicationsWithCount' | i18n: appsCount }}">
|
||||
<tools-all-applications></tools-all-applications>
|
||||
</bit-tab>
|
||||
<bit-tab *ngIf="isCriticalAppsFeatureEnabled">
|
||||
<ng-template bitTabLabel>
|
||||
<i class="bwi bwi-star"></i>
|
||||
{{ "criticalApplicationsWithCount" | i18n: criticalAppsCount }}
|
||||
</ng-template>
|
||||
<tools-critical-applications></tools-critical-applications>
|
||||
</bit-tab>
|
||||
<bit-tab label="Raw Data">
|
||||
<tools-password-health></tools-password-health>
|
||||
</bit-tab>
|
||||
<bit-tab label="Raw Data + members">
|
||||
<tools-password-health-members></tools-password-health-members>
|
||||
</bit-tab>
|
||||
<bit-tab label="Raw Data + uri">
|
||||
<tools-password-health-members-uri></tools-password-health-members-uri>
|
||||
</bit-tab>
|
||||
</bit-tab-group>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, DestroyRef, OnInit, inject } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { map, switchMap } from "rxjs/operators";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import {
|
||||
MemberCipherDetailsApiService,
|
||||
RiskInsightsDataService,
|
||||
RiskInsightsReportService,
|
||||
} from "@bitwarden/bit-common/tools/reports/risk-insights";
|
||||
import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components";
|
||||
@@ -41,47 +46,76 @@ export enum RiskInsightsTabType {
|
||||
NotifiedMembersTableComponent,
|
||||
TabsModule,
|
||||
],
|
||||
providers: [RiskInsightsReportService, RiskInsightsDataService, MemberCipherDetailsApiService],
|
||||
})
|
||||
export class RiskInsightsComponent implements OnInit {
|
||||
tabIndex: RiskInsightsTabType;
|
||||
tabIndex: RiskInsightsTabType = RiskInsightsTabType.AllApps;
|
||||
|
||||
dataLastUpdated = new Date();
|
||||
isCritialAppsFeatureEnabled = false;
|
||||
|
||||
apps: any[] = [];
|
||||
criticalApps: any[] = [];
|
||||
notifiedMembers: any[] = [];
|
||||
isCriticalAppsFeatureEnabled = false;
|
||||
|
||||
async refreshData() {
|
||||
// TODO: Implement
|
||||
return new Promise((resolve) =>
|
||||
setTimeout(() => {
|
||||
this.dataLastUpdated = new Date();
|
||||
resolve(true);
|
||||
}, 1000),
|
||||
);
|
||||
appsCount = 0;
|
||||
criticalAppsCount = 0;
|
||||
notifiedMembersCount = 0;
|
||||
|
||||
private organizationId: string;
|
||||
private destroyRef = inject(DestroyRef);
|
||||
loading = true;
|
||||
refetching = false;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
private dataService: RiskInsightsDataService,
|
||||
) {
|
||||
this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
||||
this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps;
|
||||
});
|
||||
}
|
||||
|
||||
onTabChange = async (newIndex: number) => {
|
||||
async ngOnInit() {
|
||||
this.isCriticalAppsFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.CriticalApps,
|
||||
);
|
||||
|
||||
this.route.paramMap
|
||||
.pipe(
|
||||
takeUntilDestroyed(this.destroyRef),
|
||||
map((params) => params.get("organizationId")),
|
||||
switchMap((orgId) => {
|
||||
this.organizationId = orgId;
|
||||
return this.dataService.getApplicationsReport$(orgId);
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next: (applications: ApplicationHealthReportDetail[]) => {
|
||||
if (applications) {
|
||||
this.appsCount = applications.length;
|
||||
this.loading = false;
|
||||
this.refetching = false;
|
||||
this.dataLastUpdated = new Date();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async refreshData() {
|
||||
if (this.organizationId) {
|
||||
this.refetching = true;
|
||||
// Clear the cache to ensure fresh data is fetched
|
||||
this.dataService.clearApplicationsReportCache(this.organizationId);
|
||||
// Re-initialize to fetch data again
|
||||
await this.ngOnInit();
|
||||
}
|
||||
}
|
||||
|
||||
async onTabChange(newIndex: number): Promise<void> {
|
||||
await this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { tabIndex: newIndex },
|
||||
queryParamsHandling: "merge",
|
||||
});
|
||||
};
|
||||
|
||||
async ngOnInit() {
|
||||
this.isCritialAppsFeatureEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.CriticalApps,
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
|
||||
this.tabIndex = !isNaN(tabIndex) ? tabIndex : RiskInsightsTabType.AllApps;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user