1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-26074] All Activities tab - Updated UI (#16587)

* PM-26074 simplified and update the all-activities tab

* PM-26074 removed learn more

* PM-26074 fixing missing line
This commit is contained in:
Vijay Oommen
2025-09-29 15:19:14 -05:00
committed by GitHub
parent e784622f67
commit 0bfc5daa7c
10 changed files with 168 additions and 42 deletions

View File

@@ -59,9 +59,28 @@
"createNewLoginItem": {
"message": "Create new login item"
},
"criticalApplicationsActivityDescription": {
"onceYouMarkCriticalApplicationsActivityDescription": {
"message": "Once you mark applications critical, they will display here."
},
"viewAtRiskMembers": {
"message": "View at-risk members"
},
"viewAtRiskApplications": {
"message": "View at-risk applications"
},
"criticalApplicationsAreAtRisk": {
"message": "$COUNT$ out of $TOTAL$ critical applications are at-risk due to at-risk passwords",
"placeholders": {
"count": {
"content": "$1",
"example": "3"
},
"total": {
"content": "$2",
"example": "5"
}
}
},
"criticalApplicationsWithCount": {
"message": "Critical applications ($COUNT$)",
"placeholders": {
@@ -80,6 +99,15 @@
}
}
},
"countOfApplicationsAtRisk": {
"message": "$COUNT$ applications at-risk",
"placeholders": {
"count": {
"content": "$1",
"example": "3"
}
}
},
"notifiedMembersWithCount": {
"message": "Notified members ($COUNT$)",
"placeholders": {

View File

@@ -0,0 +1,44 @@
import { BehaviorSubject } from "rxjs";
import { OrganizationReportSummary } from "../models/report-models";
export class AllActivitiesService {
/// This class is used to manage the summary of all applications
/// and critical applications.
/// Going forward, this class can be simplified by using the RiskInsightsDataService
/// as it contains the application summary data.
private reportSummarySubject$ = new BehaviorSubject<OrganizationReportSummary>({
totalMemberCount: 0,
totalCriticalMemberCount: 0,
totalAtRiskMemberCount: 0,
totalCriticalAtRiskMemberCount: 0,
totalApplicationCount: 0,
totalCriticalApplicationCount: 0,
totalAtRiskApplicationCount: 0,
totalCriticalAtRiskApplicationCount: 0,
newApplications: [],
});
reportSummary$ = this.reportSummarySubject$.asObservable();
setCriticalAppsReportSummary(summary: OrganizationReportSummary) {
this.reportSummarySubject$.next({
...this.reportSummarySubject$.getValue(),
totalCriticalApplicationCount: summary.totalApplicationCount,
totalCriticalAtRiskApplicationCount: summary.totalAtRiskApplicationCount,
totalCriticalMemberCount: summary.totalMemberCount,
totalCriticalAtRiskMemberCount: summary.totalAtRiskMemberCount,
});
}
setAllAppsReportSummary(summary: OrganizationReportSummary) {
this.reportSummarySubject$.next({
...this.reportSummarySubject$.getValue(),
totalMemberCount: summary.totalMemberCount,
totalAtRiskMemberCount: summary.totalAtRiskMemberCount,
totalApplicationCount: summary.totalApplicationCount,
totalAtRiskApplicationCount: summary.totalAtRiskApplicationCount,
});
}
}

View File

@@ -5,3 +5,4 @@ export * from "./critical-apps-api.service";
export * from "./risk-insights-api.service";
export * from "./risk-insights-report.service";
export * from "./risk-insights-data.service";
export * from "./all-activities.service";

View File

@@ -3,6 +3,7 @@ import { NgModule } from "@angular/core";
import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
import { CriticalAppsService } from "@bitwarden/bit-common/dirt/reports/risk-insights";
import {
AllActivitiesService,
CriticalAppsApiService,
MemberCipherDetailsApiService,
PasswordHealthService,
@@ -73,6 +74,11 @@ import { RiskInsightsComponent } from "./risk-insights.component";
useClass: CriticalAppsApiService,
deps: [ApiService],
}),
safeProvider({
provide: AllActivitiesService,
useClass: AllActivitiesService,
deps: [],
}),
],
})
export class AccessIntelligenceModule {}

View File

@@ -1,9 +1,18 @@
<div class="tw-flex-col">
<span bitTypography="h6" class="tw-flex tw-text-main">{{ title | i18n }}</span>
<span bitTypography="h6" class="tw-flex tw-text-main">{{ title }}</span>
<div class="tw-flex tw-items-baseline tw-gap-2">
<span bitTypography="h3">{{ cardMetrics | i18n: value }}</span>
<span bitTypography="h3">{{ cardMetrics }}</span>
</div>
<div class="tw-flex tw-items-baseline tw-gap-2">
<span bitTypography="body2">{{ metricDescription | i18n }}</span>
<div class="tw-flex tw-items-baseline tw-mt-4 tw-gap-2">
<span bitTypography="body2">{{ metricDescription }}</span>
</div>
@if (showNavigationLink) {
<div class="tw-flex tw-items-baseline tw-mt-4 tw-gap-2">
<p bitTypography="body1">
<a bitLink (click)="navigateToLink(navigationLink)" rel="noreferrer">
{{ navigationText }}
</a>
</p>
</div>
}
</div>

View File

@@ -1,13 +1,14 @@
import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { Router } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { TypographyModule } from "@bitwarden/components";
import { ButtonModule, LinkModule, TypographyModule } from "@bitwarden/components";
@Component({
selector: "dirt-activity-card",
templateUrl: "./activity-card.component.html",
imports: [CommonModule, TypographyModule, JslibModule],
imports: [CommonModule, TypographyModule, JslibModule, LinkModule, ButtonModule],
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",
@@ -18,10 +19,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
*/
@@ -30,4 +27,25 @@ export class ActivityCardComponent {
* The description text to display below the value and metrics
*/
@Input() metricDescription: string = "";
/**
* The link to navigate to for more information
*/
@Input() navigationLink: string = "";
/**
* The text to display for the navigation link
*/
@Input() navigationText: string = "";
/**
* Show Navigation link
*/
@Input() showNavigationLink: boolean = false;
constructor(private router: Router) {}
navigateToLink = async (navigationLink: string) => {
await this.router.navigateByUrl(navigationLink);
};
}

View File

@@ -21,19 +21,33 @@
<div class="tw-flex tw-gap-4 tw-col-span-6">
<dirt-activity-card
class="tw-col-span-2 tw-cursor-pointer"
[title]="'atRiskMembers'"
[value]="atRiskMemberCount"
[cardMetrics]="'membersAtRisk'"
[metricDescription]="'membersAtRiskActivityDescription'"
[title]="'atRiskMembers' | i18n"
[cardMetrics]="'membersAtRisk' | i18n: totalCriticalAppsAtRiskMemberCount"
[metricDescription]="'membersAtRiskActivityDescription' | i18n"
navigationText="{{ 'viewAtRiskMembers' | i18n }}"
navigationLink="{{ getLinkForRiskInsightsTab(RiskInsightsTabType.AllApps) }}"
[showNavigationLink]="totalCriticalAppsAtRiskMemberCount > 0"
>
</dirt-activity-card>
<dirt-activity-card
#allAppsOrgAtRiskApplications
class="tw-col-span-2 tw-cursor-pointer"
[title]="'criticalApplications'"
[value]="criticalApplicationsCount"
[cardMetrics]="'countOfCriticalApplications'"
[metricDescription]="'criticalApplicationsActivityDescription'"
[title]="'criticalApplications' | i18n"
[cardMetrics]="
totalCriticalAppsCount === 0
? ('countOfCriticalApplications' | i18n: totalCriticalAppsCount)
: ('countOfApplicationsAtRisk' | i18n: totalCriticalAppsCount)
"
[metricDescription]="
totalCriticalAppsCount === 0
? ('onceYouMarkCriticalApplicationsActivityDescription' | i18n)
: ('criticalApplicationsAreAtRisk'
| i18n: totalCriticalAppsAtRiskCount : totalCriticalAppsCount)
"
navigationText="{{ 'viewAtRiskApplications' | i18n }}"
navigationLink="{{ getLinkForRiskInsightsTab(RiskInsightsTabType.CriticalApps) }}"
[showNavigationLink]="totalCriticalAppsAtRiskCount > 0"
>
</dirt-activity-card>
</div>

View File

@@ -1,23 +1,22 @@
import { Component, DestroyRef, inject, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute } from "@angular/router";
import { BehaviorSubject, combineLatest, firstValueFrom, of, switchMap } from "rxjs";
import { BehaviorSubject, firstValueFrom } from "rxjs";
import {
CriticalAppsService,
AllActivitiesService,
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";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { getById } from "@bitwarden/common/platform/misc";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { ActivityCardComponent } from "./activity-card.component";
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
import { RiskInsightsTabType } from "./risk-insights.component";
@Component({
selector: "tools-all-activity",
@@ -28,8 +27,9 @@ export class AllActivityComponent implements OnInit {
protected isLoading$ = this.dataService.isLoading$;
protected noData$ = new BehaviorSubject(true);
organization: Organization | null = null;
atRiskMemberCount = 0;
criticalApplicationsCount = 0;
totalCriticalAppsAtRiskMemberCount = 0;
totalCriticalAppsCount = 0;
totalCriticalAppsAtRiskCount = 0;
destroyRef = inject(DestroyRef);
@@ -43,21 +43,13 @@ export class AllActivityComponent implements OnInit {
this.organizationService.organizations$(userId).pipe(getById(organizationId)),
)) ?? null;
combineLatest([
this.dataService.applications$,
this.criticalAppsService.getAppsListForOrg(organizationId as OrganizationId),
])
.pipe(
takeUntilDestroyed(this.destroyRef),
switchMap(([apps, criticalApps]) => {
const atRiskMembers = this.reportService.generateAtRiskMemberList(apps ?? []);
return of({ apps, atRiskMembers, criticalApps });
}),
)
.subscribe(({ apps, atRiskMembers, criticalApps }) => {
this.noData$.next((apps?.length ?? 0) === 0);
this.atRiskMemberCount = atRiskMembers?.length ?? 0;
this.criticalApplicationsCount = criticalApps?.length ?? 0;
this.allActivitiesService.reportSummary$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((summary) => {
this.noData$.next(summary.totalApplicationCount === 0);
this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount;
this.totalCriticalAppsCount = summary.totalCriticalApplicationCount;
this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount;
});
}
}
@@ -67,7 +59,15 @@ export class AllActivityComponent implements OnInit {
private accountService: AccountService,
protected organizationService: OrganizationService,
protected dataService: RiskInsightsDataService,
protected reportService: RiskInsightsReportService,
protected criticalAppsService: CriticalAppsService,
protected allActivitiesService: AllActivitiesService,
) {}
get RiskInsightsTabType() {
return RiskInsightsTabType;
}
getLinkForRiskInsightsTab(tabIndex: RiskInsightsTabType): string {
const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId");
return `/organizations/${organizationId}/access-intelligence/risk-insights?tabIndex=${tabIndex}`;
}
}

View File

@@ -6,6 +6,7 @@ import { combineLatest, debounceTime, firstValueFrom, map, Observable, of, switc
import { Security } from "@bitwarden/assets/svg";
import {
AllActivitiesService,
CriticalAppsService,
RiskInsightsDataService,
RiskInsightsReportService,
@@ -120,6 +121,7 @@ export class AllApplicationsComponent implements OnInit {
if (data) {
this.dataSource.data = data;
this.applicationSummary = this.reportService.generateApplicationsSummary(data);
this.allActivitiesService.setAllAppsReportSummary(this.applicationSummary);
}
if (organization) {
this.organization = organization;
@@ -142,6 +144,7 @@ export class AllApplicationsComponent implements OnInit {
private accountService: AccountService,
protected criticalAppsService: CriticalAppsService,
protected riskInsightsEncryptionService: RiskInsightsEncryptionService,
protected allActivitiesService: AllActivitiesService,
) {
this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed())

View File

@@ -8,6 +8,7 @@ import { combineLatest, debounceTime, firstValueFrom, map, switchMap } from "rxj
import { Security } from "@bitwarden/assets/svg";
import {
AllActivitiesService,
CriticalAppsService,
RiskInsightsDataService,
RiskInsightsReportService,
@@ -99,6 +100,7 @@ export class CriticalApplicationsComponent implements OnInit {
this.dataSource.data = applications;
this.applicationSummary = this.reportService.generateApplicationsSummary(applications);
this.enableRequestPasswordChange = this.applicationSummary.totalAtRiskMemberCount > 0;
this.allActivitiesService.setCriticalAppsReportSummary(this.applicationSummary);
}
});
}
@@ -176,6 +178,7 @@ export class CriticalApplicationsComponent implements OnInit {
private configService: ConfigService,
private adminTaskService: DefaultAdminTaskService,
private accountService: AccountService,
private allActivitiesService: AllActivitiesService,
) {
this.searchControl.valueChanges
.pipe(debounceTime(200), takeUntilDestroyed())