diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 2e5633af5c7..5348ec2e426 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -59,6 +59,9 @@ "createNewLoginItem": { "message": "Create new login item" }, + "criticalApplicationsActivityDescription": { + "message": "Once you mark applications critical, they will display here." + }, "criticalApplicationsWithCount": { "message": "Critical applications ($COUNT$)", "placeholders": { @@ -68,6 +71,15 @@ } } }, + "countOfCriticalApplications": { + "message": "$COUNT$ critical applications", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "notifiedMembersWithCount": { "message": "Notified members ($COUNT$)", "placeholders": { @@ -122,6 +134,18 @@ "atRiskMembers": { "message": "At-risk members" }, + "membersAtRiskActivityDescription":{ + "message": "Members with edit access to at-risk items for critical applications" + }, + "membersAtRisk": { + "message": "$COUNT$ members at risk", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "atRiskMembersWithCount": { "message": "At-risk members ($COUNT$)", "placeholders": { diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html new file mode 100644 index 00000000000..227ca555786 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.html @@ -0,0 +1,9 @@ +
+ {{ title | i18n }} +
+ {{ cardMetrics | i18n: value }} +
+
+ {{ metricDescription | i18n }} +
+
diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts new file mode 100644 index 00000000000..5b9433634e6 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity-card.component.ts @@ -0,0 +1,33 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { TypographyModule } from "@bitwarden/components"; + +@Component({ + selector: "dirt-activity-card", + templateUrl: "./activity-card.component.html", + imports: [CommonModule, TypographyModule, JslibModule], + host: { + class: + "tw-box-border tw-bg-background tw-block tw-text-main tw-border-solid tw-border tw-border-secondary-300 tw-border [&:not(bit-layout_*)]:tw-rounded-lg tw-rounded-lg tw-p-6", + }, +}) +export class ActivityCardComponent { + /** + * The title of the card goes here + */ + @Input() title: string = ""; + /** + * The current value of the card as emphasized text + */ + @Input() value: number | null = null; + /** + * The card metrics text to display next to the value + */ + @Input() cardMetrics: string = ""; + /** + * The description text to display below the value and metrics + */ + @Input() metricDescription: string = ""; +} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html index 330e0920942..963b1948993 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.html @@ -1,17 +1,46 @@ -
- -
-
- - -

- {{ organization.name }} -

-
- - - -
-
+@if (isLoading$ | async) { +
+ +
+} + +@if (!(isLoading$ | async) && (noData$ | async)) { +
+ + +

+ {{ organization?.name }} +

+
+ + + +
+
+} + +@if (!(isLoading$ | async) && !(noData$ | async)) { +
+
+ + + + +
+
+} diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts index 552a67e7997..b73bdf69eab 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-activity.component.ts @@ -1,9 +1,13 @@ -import { Component, OnInit } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, Observable } from "rxjs"; +import { BehaviorSubject, firstValueFrom, of, switchMap } from "rxjs"; -import { RiskInsightsDataService } from "@bitwarden/bit-common/dirt/reports/risk-insights"; -import { AtRiskApplicationDetail } from "@bitwarden/bit-common/dirt/reports/risk-insights/models/password-health"; +import { + CriticalAppsService, + RiskInsightsDataService, + RiskInsightsReportService, +} from "@bitwarden/bit-common/dirt/reports/risk-insights"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -11,17 +15,22 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getById } from "@bitwarden/common/platform/misc"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; +import { ActivityCardComponent } from "./activity-card.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @Component({ selector: "tools-all-activity", - imports: [ApplicationsLoadingComponent, SharedModule], + imports: [ApplicationsLoadingComponent, SharedModule, ActivityCardComponent], templateUrl: "./all-activity.component.html", }) export class AllActivityComponent implements OnInit { - isLoading$: Observable = this.dataService.isLoading$; - atRiskAppDetails: AtRiskApplicationDetail[] = []; + protected isLoading$ = this.dataService.isLoading$; + protected noData$ = new BehaviorSubject(true); organization: Organization | null = null; + atRiskMemberCount = 0; + criticalApplicationsCount = 0; + + destroyRef = inject(DestroyRef); async ngOnInit(): Promise { const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); @@ -32,9 +41,20 @@ export class AllActivityComponent implements OnInit { (await firstValueFrom( this.organizationService.organizations$(userId).pipe(getById(organizationId)), )) ?? null; - - this.atRiskAppDetails = this.dataService.atRiskAppDetails ?? []; } + + this.dataService.applications$ + .pipe( + takeUntilDestroyed(this.destroyRef), + switchMap((apps) => { + const atRiskMembers = this.reportService.generateAtRiskMemberList(apps ?? []); + return of({ apps, atRiskMembers }); + }), + ) + .subscribe(({ apps, atRiskMembers }) => { + this.noData$.next((apps?.length ?? 0) === 0); + this.atRiskMemberCount = atRiskMembers?.length; + }); } constructor( @@ -42,5 +62,7 @@ export class AllActivityComponent implements OnInit { private accountService: AccountService, protected organizationService: OrganizationService, protected dataService: RiskInsightsDataService, + protected reportService: RiskInsightsReportService, + protected criticalAppsService: CriticalAppsService, ) {} }