diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index dafd7f00a04..cda4e70f915 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -280,6 +280,24 @@ "totalApplications": { "message": "Total applications" }, + "applicationsNeedingReview": { + "message": "Applications needing review" + }, + "newApplicationsWithCount": { + "message": "$COUNT$ new applications", + "placeholders": { + "count": { + "content": "$1", + "example": "13" + } + } + }, + "newApplicationsDescription": { + "message": "Review new applications to mark as critical and keep your organization secure" + }, + "reviewNow": { + "message": "Review now" + }, "unmarkAsCritical": { "message": "Unmark as critical" }, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts index b8992b1a05f..42e4b8975c4 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/all-activities.service.ts @@ -68,6 +68,7 @@ export class AllActivitiesService { totalAtRiskMemberCount: summary.totalAtRiskMemberCount, totalApplicationCount: summary.totalApplicationCount, totalAtRiskApplicationCount: summary.totalAtRiskApplicationCount, + newApplications: summary.newApplications, }); } diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index c9a51e804dc..dc078d810c2 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -226,7 +226,23 @@ export class RiskInsightsReportService { const atRiskMembers = reports.flatMap((x) => x.atRiskMemberDetails); const uniqueAtRiskMembers = getUniqueMembers(atRiskMembers); - // TODO: totalCriticalMemberCount, totalCriticalAtRiskMemberCount, totalCriticalApplicationCount, totalCriticalAtRiskApplicationCount, and newApplications will be handled with future logic implementation + // TODO: Replace with actual new applications detection logic (PM-26185) + const dummyNewApplications = [ + "github.com", + "google.com", + "stackoverflow.com", + "gitlab.com", + "bitbucket.org", + "npmjs.com", + "docker.com", + "aws.amazon.com", + "azure.microsoft.com", + "jenkins.io", + "terraform.io", + "kubernetes.io", + "atlassian.net", + ]; + return { totalMemberCount: uniqueMembers.length, totalAtRiskMemberCount: uniqueAtRiskMembers.length, @@ -236,7 +252,7 @@ export class RiskInsightsReportService { totalCriticalAtRiskMemberCount: 0, totalCriticalApplicationCount: 0, totalCriticalAtRiskApplicationCount: 0, - newApplications: [], + newApplications: dummyNewApplications, }; } 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 index 17ae964dbed..0eb9b30367c 100644 --- 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 @@ -1,12 +1,29 @@
{{ title }}
+ @if (iconClass) { + + } {{ cardMetrics }}
{{ metricDescription }}
- @if (showNavigationLink) { + @if (buttonClick.observed && buttonText) { +
+ +
+ } + @if (showNavigationLink && !buttonText) {

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 index 2dc7c6a9c79..c8c73cd0e5a 100644 --- 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 @@ -1,9 +1,9 @@ import { CommonModule } from "@angular/common"; -import { Component, Input } from "@angular/core"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Router } from "@angular/router"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { ButtonModule, LinkModule, TypographyModule } from "@bitwarden/components"; +import { ButtonModule, ButtonType, LinkModule, TypographyModule } from "@bitwarden/components"; @Component({ selector: "dirt-activity-card", @@ -43,9 +43,34 @@ export class ActivityCardComponent { */ @Input() showNavigationLink: boolean = false; + /** + * Icon class to display next to metrics (e.g., "bwi-exclamation-triangle"). + * If null, no icon is displayed. + */ + @Input() iconClass: string | null = null; + + /** + * Button text. If provided, a button will be displayed instead of a navigation link. + */ + @Input() buttonText: string = ""; + + /** + * Button type (e.g., "primary", "secondary") + */ + @Input() buttonType: ButtonType = "primary"; + + /** + * Event emitted when button is clicked + */ + @Output() buttonClick = new EventEmitter(); + constructor(private router: Router) {} navigateToLink = async (navigationLink: string) => { await this.router.navigateByUrl(navigationLink); }; + + onButtonClick = () => { + this.buttonClick.emit(); + }; } 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 390102aced9..844b2f92bb3 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 @@ -41,5 +41,18 @@ > + +

  • + + +
  • } 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 83ce743d6d0..31ab0351162 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 @@ -11,7 +11,9 @@ import { OrganizationService } from "@bitwarden/common/admin-console/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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { getById } from "@bitwarden/common/platform/misc"; +import { ToastService } from "@bitwarden/components"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { ActivityCardComponent } from "./activity-card.component"; @@ -34,6 +36,7 @@ export class AllActivityComponent implements OnInit { totalCriticalAppsAtRiskMemberCount = 0; totalCriticalAppsCount = 0; totalCriticalAppsAtRiskCount = 0; + newApplicationsCount = 0; passwordChangeMetricHasProgressBar = false; destroyRef = inject(DestroyRef); @@ -54,6 +57,7 @@ export class AllActivityComponent implements OnInit { this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; + this.newApplicationsCount = summary.newApplications.length; }); this.allActivitiesService.passwordChangeProgressMetricHasProgressBar$ @@ -70,6 +74,8 @@ export class AllActivityComponent implements OnInit { protected organizationService: OrganizationService, protected dataService: RiskInsightsDataService, protected allActivitiesService: AllActivitiesService, + private toastService: ToastService, + private i18nService: I18nService, ) {} get RiskInsightsTabType() { @@ -80,4 +86,17 @@ export class AllActivityComponent implements OnInit { const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); return `/organizations/${organizationId}/access-intelligence/risk-insights?tabIndex=${tabIndex}`; } + + /** + * Handles the review new applications button click. + * Shows a toast notification as a placeholder until the dialog is implemented. + * TODO: Implement dialog for reviewing new applications (follow-up task) + */ + onReviewNewApplications = () => { + this.toastService.showToast({ + variant: "info", + title: this.i18nService.t("applicationsNeedingReview"), + message: this.i18nService.t("newApplicationsWithCount", this.newApplicationsCount.toString()), + }); + }; }