Skip to content

Commit 831cf53

Browse files
committed
Add columns, description, search, and bulk select to new applications dialog
1 parent b9c9737 commit 831cf53

File tree

5 files changed

+153
-48
lines changed

5 files changed

+153
-48
lines changed

apps/web/src/locales/en/messages.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,11 @@
364364
"prioritizeCriticalApplications": {
365365
"message": "Prioritize critical applications"
366366
},
367-
"atRiskItems": {
368-
"message": "At-risk items"
367+
"selectCriticalApplicationsDescription": {
368+
"message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks."
369+
},
370+
"clickIconToMarkAppAsCritical": {
371+
"message": "Click the icon to mark an app as critical"
369372
},
370373
"markAsCriticalPlaceholder": {
371374
"message": "Mark as critical functionality will be implemented in a future update"

bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.html

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,25 @@
99

1010
<div bitDialogContent>
1111
@if (currentView() === DialogView.SelectApplications) {
12-
<dirt-review-applications-view
13-
[applications]="getApplicationNames()"
14-
[selectedApplications]="selectedApplications()"
15-
(onToggleSelection)="toggleSelection($event)"
16-
></dirt-review-applications-view>
12+
<div>
13+
<p bitTypography="body1" class="tw-mb-5">
14+
{{ "selectCriticalApplicationsDescription" | i18n }}
15+
</p>
16+
17+
<div class="tw-flex tw-items-center tw-gap-2.5 tw-mb-5">
18+
<i class="bwi bwi-star-f tw-text-xl" aria-hidden="true"></i>
19+
<p bitTypography="helper" class="tw-text-muted tw-mb-0">
20+
{{ "clickIconToMarkAppAsCritical" | i18n }}
21+
</p>
22+
</div>
23+
24+
<dirt-review-applications-view
25+
[applications]="getApplications()"
26+
[selectedApplications]="selectedApplications()"
27+
(onToggleSelection)="toggleSelection($event)"
28+
(onToggleAll)="toggleAll()"
29+
></dirt-review-applications-view>
30+
</div>
1731
}
1832

1933
@if (currentView() === DialogView.AssignTasks) {

bitwarden_license/bit-web/src/app/dirt/access-intelligence/activity/application-review-dialog/new-applications-dialog.component.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ export class NewApplicationsDialogComponent {
126126
);
127127
}
128128

129-
getApplicationNames() {
130-
return this.dialogParams.newApplications.map((application) => application.applicationName);
129+
getApplications() {
130+
return this.dialogParams.newApplications;
131131
}
132132

133133
/**
@@ -147,6 +147,19 @@ export class NewApplicationsDialogComponent {
147147
});
148148
}
149149

150+
/**
151+
* Toggles the selection state of all applications.
152+
* If all are selected, unselect all. Otherwise, select all.
153+
*/
154+
toggleAll() {
155+
const allApplicationNames = this.dialogParams.newApplications.map((app) => app.applicationName);
156+
const allSelected = this.selectedApplications().size === allApplicationNames.length;
157+
158+
this.selectedApplications.update(() => {
159+
return allSelected ? new Set() : new Set(allApplicationNames);
160+
});
161+
}
162+
150163
handleMarkAsCritical() {
151164
if (this.markingAsCritical() || this.saving()) {
152165
return; // Prevent action if already processing
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,83 @@
1-
<div class="tw-overflow-x-auto">
2-
<table class="tw-w-full tw-border-collapse">
3-
<thead>
4-
<tr class="tw-border-b tw-border-secondary-300">
5-
<th bitTypography="body2" class="tw-text-left tw-py-3 tw-px-2 tw-w-12"></th>
6-
<th bitTypography="body2" class="tw-text-left tw-py-3 tw-px-2 tw-font-semibold">
7-
{{ "application" | i18n }}
8-
</th>
9-
<th bitTypography="body2" class="tw-text-right tw-py-3 tw-px-2 tw-font-semibold">
10-
{{ "atRiskItems" | i18n }}
11-
</th>
12-
</tr>
13-
</thead>
14-
<tbody>
15-
@for (app of applications(); track app) {
16-
<tr class="tw-border-b tw-border-secondary-300 hover:tw-bg-background-alt">
17-
<td class="tw-py-3 tw-px-2">
1+
<div class="tw-space-y-3">
2+
<bit-search
3+
[placeholder]="'searchApps' | i18n"
4+
[(ngModel)]="searchText"
5+
(ngModelChange)="onSearchTextChanged($event)"
6+
></bit-search>
7+
8+
<div class="tw-overflow-x-auto">
9+
<table class="tw-w-full tw-border-collapse">
10+
<thead>
11+
<tr class="tw-border-b tw-border-secondary-300">
12+
<th bitTypography="body2" class="tw-text-left tw-py-3 tw-px-2 tw-w-12">
1813
<button
1914
type="button"
2015
class="tw-bg-transparent tw-border-0 tw-p-0 tw-cursor-pointer"
21-
(click)="toggleSelection(app)"
22-
[attr.aria-label]="
23-
selectedApplications().has(app)
24-
? ('unselectApplication' | i18n)
25-
: ('selectApplication' | i18n)
26-
"
16+
(click)="toggleAll()"
17+
[attr.aria-label]="isAllSelected() ? ('unselectAll' | i18n) : ('selectAll' | i18n)"
2718
>
2819
<i
2920
class="bwi tw-text-muted"
30-
[ngClass]="selectedApplications().has(app) ? 'bwi-star-f' : 'bwi-star'"
21+
[ngClass]="isAllSelected() ? 'bwi-star-f' : 'bwi-star'"
3122
aria-hidden="true"
3223
></i>
3324
</button>
34-
</td>
35-
<td bitTypography="body1" class="tw-py-3 tw-px-2">
36-
<div class="tw-flex tw-items-center tw-gap-2">
37-
<i class="bwi bwi-globe tw-text-muted" aria-hidden="true"></i>
38-
<span>{{ app }}</span>
39-
</div>
40-
</td>
41-
<td bitTypography="body1" class="tw-py-3 tw-px-2 tw-text-right tw-text-muted"></td>
25+
</th>
26+
<th bitTypography="body2" class="tw-text-left tw-py-3 tw-px-2 tw-font-semibold">
27+
{{ "application" | i18n }}
28+
</th>
29+
<th bitTypography="body2" class="tw-text-right tw-py-3 tw-px-2 tw-font-semibold">
30+
{{ "atRiskPasswords" | i18n }}
31+
</th>
32+
<th bitTypography="body2" class="tw-text-right tw-py-3 tw-px-2 tw-font-semibold">
33+
{{ "totalPasswords" | i18n }}
34+
</th>
35+
<th bitTypography="body2" class="tw-text-right tw-py-3 tw-px-2 tw-font-semibold">
36+
{{ "atRiskMembers" | i18n }}
37+
</th>
4238
</tr>
43-
}
44-
</tbody>
45-
</table>
39+
</thead>
40+
<tbody>
41+
@for (app of filteredApplications(); track app.applicationName) {
42+
<tr class="tw-border-b tw-border-secondary-300 hover:tw-bg-background-alt">
43+
<td class="tw-py-3 tw-px-2">
44+
<button
45+
type="button"
46+
class="tw-bg-transparent tw-border-0 tw-p-0 tw-cursor-pointer"
47+
(click)="toggleSelection(app.applicationName)"
48+
[attr.aria-label]="
49+
selectedApplications().has(app.applicationName)
50+
? ('unselectApplication' | i18n)
51+
: ('selectApplication' | i18n)
52+
"
53+
>
54+
<i
55+
class="bwi tw-text-muted"
56+
[ngClass]="
57+
selectedApplications().has(app.applicationName) ? 'bwi-star-f' : 'bwi-star'
58+
"
59+
aria-hidden="true"
60+
></i>
61+
</button>
62+
</td>
63+
<td bitTypography="body1" class="tw-py-3 tw-px-2">
64+
<div class="tw-flex tw-items-center tw-gap-2">
65+
<i class="bwi bwi-globe tw-text-muted" aria-hidden="true"></i>
66+
<span>{{ app.applicationName }}</span>
67+
</div>
68+
</td>
69+
<td bitTypography="body1" class="tw-py-3 tw-px-2 tw-text-right">
70+
{{ app.atRiskPasswordCount }}
71+
</td>
72+
<td bitTypography="body1" class="tw-py-3 tw-px-2 tw-text-right">
73+
{{ app.passwordCount }}
74+
</td>
75+
<td bitTypography="body1" class="tw-py-3 tw-px-2 tw-text-right">
76+
{{ app.atRiskMemberCount }}
77+
</td>
78+
</tr>
79+
}
80+
</tbody>
81+
</table>
82+
</div>
4683
</div>
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,61 @@
11
import { CommonModule } from "@angular/common";
2-
import { Component, input, output, ChangeDetectionStrategy } from "@angular/core";
2+
import { Component, input, output, ChangeDetectionStrategy, signal, computed } from "@angular/core";
3+
import { FormsModule } from "@angular/forms";
34

4-
import { ButtonModule, DialogModule, TypographyModule } from "@bitwarden/components";
5+
import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/dirt/reports/risk-insights";
6+
import { ButtonModule, DialogModule, SearchModule, TypographyModule } from "@bitwarden/components";
57
import { I18nPipe } from "@bitwarden/ui-common";
68

79
@Component({
810
changeDetection: ChangeDetectionStrategy.OnPush,
911
selector: "dirt-review-applications-view",
1012
templateUrl: "./review-applications-view.component.html",
11-
imports: [CommonModule, ButtonModule, DialogModule, TypographyModule, I18nPipe],
13+
imports: [
14+
CommonModule,
15+
ButtonModule,
16+
DialogModule,
17+
FormsModule,
18+
SearchModule,
19+
TypographyModule,
20+
I18nPipe,
21+
],
1222
})
1323
export class ReviewApplicationsViewComponent {
14-
readonly applications = input.required<string[]>();
24+
readonly applications = input.required<ApplicationHealthReportDetail[]>();
1525
readonly selectedApplications = input.required<Set<string>>();
1626

27+
protected readonly searchText = signal<string>("");
28+
29+
// Filter applications based on search text
30+
protected readonly filteredApplications = computed(() => {
31+
const search = this.searchText().toLowerCase();
32+
if (!search) {
33+
return this.applications();
34+
}
35+
return this.applications().filter((app) => app.applicationName.toLowerCase().includes(search));
36+
});
37+
1738
// Return the selected applications from the view
1839
onToggleSelection = output<string>();
40+
onToggleAll = output<void>();
1941

2042
toggleSelection(applicationName: string): void {
2143
this.onToggleSelection.emit(applicationName);
2244
}
45+
46+
toggleAll(): void {
47+
this.onToggleAll.emit();
48+
}
49+
50+
isAllSelected(): boolean {
51+
const filtered = this.filteredApplications();
52+
return (
53+
filtered.length > 0 &&
54+
filtered.every((app) => this.selectedApplications().has(app.applicationName))
55+
);
56+
}
57+
58+
onSearchTextChanged(searchText: string): void {
59+
this.searchText.set(searchText);
60+
}
2361
}

0 commit comments

Comments
 (0)