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,
) {}
}