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

[PM-26203] new apps dialog (#16696)

This commit is contained in:
Alex
2025-10-09 11:22:12 -04:00
committed by GitHub
parent d17fa04b7a
commit 85113f2f0a
4 changed files with 189 additions and 8 deletions

View File

@@ -187,6 +187,18 @@
"markAppAsCritical": { "markAppAsCritical": {
"message": "Mark app as critical" "message": "Mark app as critical"
}, },
"markAsCritical": {
"message": "Mark as critical"
},
"applicationsSelected": {
"message": "applications selected"
},
"selectApplication": {
"message": "Select application"
},
"unselectApplication": {
"message": "Unselect application"
},
"applicationsMarkedAsCriticalSuccess": { "applicationsMarkedAsCriticalSuccess": {
"message": "Applications marked as critical" "message": "Applications marked as critical"
}, },
@@ -298,6 +310,15 @@
"reviewNow": { "reviewNow": {
"message": "Review now" "message": "Review now"
}, },
"prioritizeCriticalApplications": {
"message": "Prioritize critical applications"
},
"atRiskItems": {
"message": "At-risk items"
},
"markAsCriticalPlaceholder": {
"message": "Mark as critical functionality will be implemented in a future update"
},
"unmarkAsCritical": { "unmarkAsCritical": {
"message": "Unmark as critical" "message": "Unmark as critical"
}, },

View File

@@ -13,11 +13,12 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { getById } from "@bitwarden/common/platform/misc"; import { getById } from "@bitwarden/common/platform/misc";
import { ToastService } from "@bitwarden/components"; import { ToastService, DialogService } from "@bitwarden/components";
import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { ActivityCardComponent } from "./activity-card.component"; import { ActivityCardComponent } from "./activity-card.component";
import { PasswordChangeMetricComponent } from "./activity-cards/password-change-metric.component"; import { PasswordChangeMetricComponent } from "./activity-cards/password-change-metric.component";
import { NewApplicationsDialogComponent } from "./new-applications-dialog.component";
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
import { RiskInsightsTabType } from "./risk-insights.component"; import { RiskInsightsTabType } from "./risk-insights.component";
@@ -37,6 +38,7 @@ export class AllActivityComponent implements OnInit {
totalCriticalAppsCount = 0; totalCriticalAppsCount = 0;
totalCriticalAppsAtRiskCount = 0; totalCriticalAppsAtRiskCount = 0;
newApplicationsCount = 0; newApplicationsCount = 0;
newApplications: string[] = [];
passwordChangeMetricHasProgressBar = false; passwordChangeMetricHasProgressBar = false;
destroyRef = inject(DestroyRef); destroyRef = inject(DestroyRef);
@@ -57,6 +59,7 @@ export class AllActivityComponent implements OnInit {
this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount; this.totalCriticalAppsAtRiskMemberCount = summary.totalCriticalAtRiskMemberCount;
this.totalCriticalAppsCount = summary.totalCriticalApplicationCount; this.totalCriticalAppsCount = summary.totalCriticalApplicationCount;
this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount; this.totalCriticalAppsAtRiskCount = summary.totalCriticalAtRiskApplicationCount;
this.newApplications = summary.newApplications;
this.newApplicationsCount = summary.newApplications.length; this.newApplicationsCount = summary.newApplications.length;
}); });
@@ -76,6 +79,7 @@ export class AllActivityComponent implements OnInit {
protected allActivitiesService: AllActivitiesService, protected allActivitiesService: AllActivitiesService,
private toastService: ToastService, private toastService: ToastService,
private i18nService: I18nService, private i18nService: I18nService,
private dialogService: DialogService,
) {} ) {}
get RiskInsightsTabType() { get RiskInsightsTabType() {
@@ -89,14 +93,13 @@ export class AllActivityComponent implements OnInit {
/** /**
* Handles the review new applications button click. * Handles the review new applications button click.
* Shows a toast notification as a placeholder until the dialog is implemented. * Opens a dialog showing the list of new applications that can be marked as critical.
* TODO: Implement dialog for reviewing new applications (follow-up task)
*/ */
onReviewNewApplications = () => { onReviewNewApplications = async () => {
this.toastService.showToast({ const dialogRef = NewApplicationsDialogComponent.open(this.dialogService, {
variant: "info", newApplications: this.newApplications,
title: this.i18nService.t("applicationsNeedingReview"),
message: this.i18nService.t("newApplicationsWithCount", this.newApplicationsCount.toString()),
}); });
await firstValueFrom(dialogRef.closed);
}; };
} }

View File

@@ -0,0 +1,71 @@
<bit-dialog [dialogSize]="'default'">
<span bitDialogTitle>{{ "prioritizeCriticalApplications" | i18n }}</span>
<div bitDialogContent>
<div class="tw-overflow-x-auto">
<table class="tw-w-full tw-border-collapse">
<thead>
<tr class="tw-border-b tw-border-secondary-300">
<th bitTypography="body2" class="tw-text-left tw-py-3 tw-px-2 tw-w-12"></th>
<th bitTypography="body2" class="tw-text-left tw-py-3 tw-px-2 tw-font-semibold">
{{ "application" | i18n }}
</th>
<th bitTypography="body2" class="tw-text-right tw-py-3 tw-px-2 tw-font-semibold">
{{ "atRiskItems" | i18n }}
</th>
</tr>
</thead>
<tbody>
@for (app of newApplications; track app) {
<tr class="tw-border-b tw-border-secondary-300 hover:tw-bg-background-alt">
<td class="tw-py-3 tw-px-2">
<button
type="button"
class="tw-bg-transparent tw-border-0 tw-p-0 tw-cursor-pointer"
(click)="toggleSelection(app)"
[attr.aria-label]="
isSelected(app) ? ('unselectApplication' | i18n) : ('selectApplication' | i18n)
"
>
<i
class="bwi tw-text-muted"
[ngClass]="isSelected(app) ? 'bwi-star-f' : 'bwi-star'"
aria-hidden="true"
></i>
</button>
</td>
<td bitTypography="body1" class="tw-py-3 tw-px-2">
<div class="tw-flex tw-items-center tw-gap-2">
<i class="bwi bwi-globe tw-text-muted" aria-hidden="true"></i>
<span>{{ app }}</span>
</div>
</td>
<td bitTypography="body1" class="tw-py-3 tw-px-2 tw-text-right tw-text-muted"></td>
</tr>
}
</tbody>
</table>
</div>
</div>
<ng-container bitDialogFooter>
<button
type="button"
bitButton
size="small"
buttonType="primary"
(click)="onMarkAsCritical()"
[attr.aria-label]="'markAsCritical' | i18n"
>
{{ "markAsCritical" | i18n }}
</button>
<button
type="button"
bitButton
size="small"
buttonType="secondary"
[bitDialogClose]
[attr.aria-label]="'cancel' | i18n"
>
{{ "cancel" | i18n }}
</button>
</ng-container>
</bit-dialog>

View File

@@ -0,0 +1,86 @@
import { CommonModule } from "@angular/common";
import { Component, inject } from "@angular/core";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import {
ButtonModule,
DialogModule,
DialogService,
ToastService,
TypographyModule,
} from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
export interface NewApplicationsDialogData {
newApplications: string[];
}
@Component({
templateUrl: "./new-applications-dialog.component.html",
imports: [CommonModule, ButtonModule, DialogModule, TypographyModule, I18nPipe],
})
export class NewApplicationsDialogComponent {
protected newApplications: string[] = [];
protected selectedApplications: Set<string> = new Set<string>();
private toastService = inject(ToastService);
private i18nService = inject(I18nService);
/**
* Opens the new applications dialog
* @param dialogService The dialog service instance
* @param data Dialog data containing the list of new applications
* @returns Dialog reference
*/
static open(dialogService: DialogService, data: NewApplicationsDialogData) {
const ref = dialogService.open<boolean, NewApplicationsDialogData>(
NewApplicationsDialogComponent,
{
data,
},
);
// Set the component's data after opening
const instance = ref.componentInstance as NewApplicationsDialogComponent;
if (instance) {
instance.newApplications = data.newApplications;
}
return ref;
}
/**
* Toggles the selection state of an application.
* @param applicationName The application to toggle
*/
toggleSelection = (applicationName: string) => {
if (this.selectedApplications.has(applicationName)) {
this.selectedApplications.delete(applicationName);
} else {
this.selectedApplications.add(applicationName);
}
};
/**
* Checks if an application is currently selected.
* @param applicationName The application to check
* @returns True if selected, false otherwise
*/
isSelected = (applicationName: string): boolean => {
return this.selectedApplications.has(applicationName);
};
/**
* Placeholder handler for mark as critical functionality.
* Shows a toast notification with count of selected applications.
* TODO: Implement actual mark as critical functionality (PM-26203 follow-up)
*/
onMarkAsCritical = () => {
const selectedCount = this.selectedApplications.size;
this.toastService.showToast({
variant: "info",
title: this.i18nService.t("markAsCritical"),
message: `${selectedCount} ${this.i18nService.t("applicationsSelected")}`,
});
};
}