1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 18:23:31 +00:00

PM-18401 apply scroll tables across all tables (#13474)

This commit is contained in:
Vijay Oommen
2025-02-20 16:23:26 -06:00
committed by GitHub
parent ef34b30cc1
commit b591c053ca
9 changed files with 283 additions and 242 deletions

View File

@@ -69,55 +69,15 @@
{{ "markAppAsCritical" | i18n }}
</button>
</div>
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th *ngIf="isCriticalAppsFeatureEnabled"></th>
<th bitSortable="applicationName" bitCell>{{ "application" | i18n }}</th>
<th bitSortable="atRiskPasswordCount" bitCell>{{ "atRiskPasswords" | i18n }}</th>
<th bitSortable="passwordCount" bitCell>{{ "totalPasswords" | i18n }}</th>
<th bitSortable="atRiskMemberCount" bitCell>{{ "atRiskMembers" | i18n }}</th>
<th bitSortable="memberCount" bitCell>{{ "totalMembers" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr
bitRow
*ngFor="let r of rows$ | async; trackBy: trackByFunction"
[ngClass]="{ 'tw-bg-primary-100': dataService.drawerInvokerId === r.applicationName }"
>
<td *ngIf="isCriticalAppsFeatureEnabled">
<input
bitCheckbox
type="checkbox"
*ngIf="!r.isMarkedAsCritical"
[checked]="selectedUrls.has(r.applicationName)"
(change)="onCheckboxChange(r.applicationName, $event)"
/>
<i class="bwi bwi-star-f" *ngIf="r.isMarkedAsCritical"></i>
</td>
<td class="tw-cursor-pointer" (click)="showAppAtRiskMembers(r.applicationName)" bitCell>
<span>{{ r.applicationName }}</span>
</td>
<td bitCell>
<span>
{{ r.atRiskPasswordCount }}
</span>
</td>
<td bitCell>
<span>
{{ r.passwordCount }}
</span>
</td>
<td bitCell>
<span>
{{ r.atRiskMemberCount }}
</span>
</td>
<td bitCell data-testid="total-membership">
{{ r.memberCount }}
</td>
</tr>
</ng-template>
</bit-table>
<app-table-row-scrollable
[dataSource]="dataSource"
[showRowCheckBox]="true"
[showRowMenuForCriticalApps]="false"
[selectedUrls]="selectedUrls"
[isCriticalAppsFeatureEnabled]="isCriticalAppsFeatureEnabled"
[isDrawerIsOpenForThisRecord]="isDrawerOpenForTableRow"
[checkboxChange]="onCheckboxChange"
[showAppAtRiskMembers]="showAppAtRiskMembers"
></app-table-row-scrollable>
</div>

View File

@@ -37,6 +37,7 @@ import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.mod
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.component";
import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
@Component({
@@ -51,6 +52,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"
PipesModule,
NoItemsModule,
SharedModule,
AppTableRowScrollableComponent,
],
})
export class AllApplicationsComponent implements OnInit {
@@ -190,14 +192,18 @@ export class AllApplicationsComponent implements OnInit {
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
};
onCheckboxChange(applicationName: string, event: Event) {
onCheckboxChange = (applicationName: string, event: Event) => {
const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) {
this.selectedUrls.add(applicationName);
} else {
this.selectedUrls.delete(applicationName);
}
}
};
getSelectedUrls = () => Array.from(this.selectedUrls);
isDrawerOpenForTableRow = (applicationName: string): boolean => {
return this.dataService.drawerInvokerId === applicationName;
};
}

View File

@@ -0,0 +1,98 @@
<ng-container>
<bit-table-scroll
[dataSource]="dataSource"
[rowSize]="53"
class="tw-table tw-w-full table-hover table-list"
>
<ng-container header>
<th *ngIf="isCriticalAppsFeatureEnabled"></th>
<th bitSortable="applicationName" bitCell>{{ "application" | i18n }}</th>
<th bitSortable="atRiskPasswordCount" bitCell>{{ "atRiskPasswords" | i18n }}</th>
<th bitSortable="passwordCount" bitCell>{{ "totalPasswords" | i18n }}</th>
<th bitSortable="atRiskMemberCount" bitCell>{{ "atRiskMembers" | i18n }}</th>
<th bitSortable="memberCount" bitCell>{{ "totalMembers" | i18n }}</th>
</ng-container>
<ng-template bitRowDef let-row>
<td
bitCell
*ngIf="isCriticalAppsFeatureEnabled && showRowCheckBox"
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
<input
bitCheckbox
type="checkbox"
*ngIf="!row.isMarkedAsCritical"
[checked]="selectedUrls.has(row.applicationName)"
(change)="checkboxChange(row.applicationName, $event)"
/>
<i class="bwi bwi-star-f" *ngIf="row.isMarkedAsCritical"></i>
</td>
<td
bitCell
*ngIf="!showRowCheckBox"
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
<i class="bwi bwi-star-f" *ngIf="row.isMarkedAsCritical"></i>
</td>
<td
class="tw-cursor-pointer"
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
(click)="showAppAtRiskMembers(row.applicationName)"
(keypress)="showAppAtRiskMembers(row.applicationName)"
bitCell
>
<span>{{ row.applicationName }}</span>
</td>
<td
bitCell
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
<span>
{{ row.atRiskPasswordCount }}
</span>
</td>
<td
bitCell
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
<span>
{{ row.passwordCount }}
</span>
</td>
<td
bitCell
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
<span>
{{ row.atRiskMemberCount }}
</span>
</td>
<td
bitCell
data-testid="total-membership"
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
{{ row.memberCount }}
</td>
<td
bitCell
*ngIf="showRowMenuForCriticalApps"
[ngClass]="{ 'tw-bg-primary-100': isDrawerIsOpenForThisRecord(row.applicationName) }"
>
<button
[bitMenuTriggerFor]="rowMenu"
type="button"
bitIconButton="bwi-ellipsis-v"
size="small"
appA11yTitle="{{ 'options' | i18n }}"
></button>
<bit-menu #rowMenu>
<button type="button" bitMenuItem (click)="unmarkAsCriticalApp(row.applicationName)">
<i aria-hidden="true" class="bwi bwi-star-f"></i> {{ "unmarkAsCriticalApp" | i18n }}
</button>
</bit-menu>
</td>
</ng-template>
</bit-table-scroll>
</ng-container>

View File

@@ -0,0 +1,26 @@
import { CommonModule } from "@angular/common";
import { Component, Input } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ApplicationHealthReportDetailWithCriticalFlag } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health";
import { MenuModule, TableDataSource, TableModule } from "@bitwarden/components";
import { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module";
@Component({
selector: "app-table-row-scrollable",
standalone: true,
imports: [CommonModule, JslibModule, TableModule, SharedModule, PipesModule, MenuModule],
templateUrl: "./app-table-row-scrollable.component.html",
})
export class AppTableRowScrollableComponent {
@Input() dataSource!: TableDataSource<ApplicationHealthReportDetailWithCriticalFlag>;
@Input() showRowMenuForCriticalApps: boolean = false;
@Input() showRowCheckBox: boolean = false;
@Input() selectedUrls: Set<string> = new Set<string>();
@Input() isCriticalAppsFeatureEnabled: boolean = false;
@Input() isDrawerIsOpenForThisRecord!: (applicationName: string) => boolean;
@Input() showAppAtRiskMembers!: (applicationName: string) => void;
@Input() unmarkAsCriticalApp!: (applicationName: string) => void;
@Input() checkboxChange!: (applicationName: string, $event: Event) => void;
}

View File

@@ -73,63 +73,14 @@
[formControl]="searchControl"
></bit-search>
</div>
<bit-table [dataSource]="dataSource">
<ng-container header>
<tr>
<th></th>
<th bitSortable="applicationName" bitCell>{{ "application" | i18n }}</th>
<th bitSortable="atRiskPasswordCount" bitCell>{{ "atRiskPasswords" | i18n }}</th>
<th bitSortable="passwordCount" bitCell>{{ "totalPasswords" | i18n }}</th>
<th bitSortable="atRiskMemberCount" bitCell>{{ "atRiskMembers" | i18n }}</th>
<th bitSortable="memberCount" bitCell>{{ "totalMembers" | i18n }}</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr
bitRow
*ngFor="let r of rows$ | async; trackBy: trackByFunction"
[ngClass]="{ 'tw-bg-primary-100': dataService.drawerInvokerId === r.applicationName }"
>
<td>
<i class="bwi bwi-star-f" *ngIf="r.isMarkedAsCritical"></i>
</td>
<td class="tw-cursor-pointer" (click)="showAppAtRiskMembers(r.applicationName)" bitCell>
<span>{{ r.applicationName }}</span>
</td>
<td bitCell>
<span>
{{ r.atRiskPasswordCount }}
</span>
</td>
<td bitCell>
<span>
{{ r.passwordCount }}
</span>
</td>
<td bitCell>
<span>
{{ r.atRiskMemberCount }}
</span>
</td>
<td bitCell data-testid="total-membership">
{{ r.memberCount }}
</td>
<td bitCell>
<button
[bitMenuTriggerFor]="rowMenu"
type="button"
bitIconButton="bwi-ellipsis-v"
size="small"
appA11yTitle="{{ 'options' | i18n }}"
></button>
<bit-menu #rowMenu>
<button type="button" bitMenuItem (click)="unmarkAsCriticalApp(r.applicationName)">
<i aria-hidden="true" class="bwi bwi-star-f"></i> {{ "unmarkAsCriticalApp" | i18n }}
</button>
</bit-menu>
</td>
</tr>
</ng-template>
</bit-table>
<app-table-row-scrollable
[dataSource]="dataSource"
[showRowCheckBox]="false"
[showRowMenuForCriticalApps]="true"
[isCriticalAppsFeatureEnabled]="true"
[isDrawerIsOpenForThisRecord]="isDrawerOpenForTableRow"
[showAppAtRiskMembers]="showAppAtRiskMembers"
[unmarkAsCriticalApp]="unmarkAsCriticalApp"
></app-table-row-scrollable>
</div>

View File

@@ -35,13 +35,22 @@ import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pip
import { CreateTasksRequest } from "../../vault/services/abstractions/admin-task.abstraction";
import { DefaultAdminTaskService } from "../../vault/services/default-admin-task.service";
import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.component";
import { RiskInsightsTabType } from "./risk-insights.component";
@Component({
standalone: true,
selector: "tools-critical-applications",
templateUrl: "./critical-applications.component.html",
imports: [CardComponent, HeaderModule, SearchModule, NoItemsModule, PipesModule, SharedModule],
imports: [
CardComponent,
HeaderModule,
SearchModule,
NoItemsModule,
PipesModule,
SharedModule,
AppTableRowScrollableComponent,
],
providers: [DefaultAdminTaskService],
})
export class CriticalApplicationsComponent implements OnInit {
@@ -181,4 +190,7 @@ export class CriticalApplicationsComponent implements OnInit {
trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) {
return item.applicationName;
}
isDrawerOpenForTableRow = (applicationName: string) => {
return this.dataService.drawerInvokerId === applicationName;
};
}

View File

@@ -9,47 +9,43 @@
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-mt-4" *ngIf="!loading">
<bit-table [dataSource]="dataSource">
<bit-table-scroll [dataSource]="dataSource" [rowSize]="53">
<ng-container header>
<tr bitRow>
<th bitCell bitSortable="hostURI">{{ "application" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</tr>
<th bitCell bitSortable="hostURI">{{ "application" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>
<ng-container>
<span>{{ r.hostURI }}</span>
</ng-container>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="passwordStrengthMap.has(r.id)"
[variant]="passwordStrengthMap.get(r.id)[1]"
>
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
</span>
</td>
<td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(r.id) || 0 }}
</td>
</tr>
<ng-template bitRowDef let-row>
<td bitCell>
<ng-container>
<span>{{ row.hostURI }}</span>
</ng-container>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="passwordStrengthMap.has(row.id)"
[variant]="passwordStrengthMap.get(row.id)[1]"
>
{{ passwordStrengthMap.get(row.id)[0] | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(row.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(row.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(row.id) }}
</span>
</td>
<td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(row.id) || 0 }}
</td>
</ng-template>
</bit-table>
</bit-table-scroll>
</div>
</bit-container>

View File

@@ -8,57 +8,53 @@
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-flex tw-flex-col" *ngIf="!loading && dataSource.data.length">
<bit-table [dataSource]="dataSource">
<bit-table-scroll [dataSource]="dataSource" [rowSize]="74">
<ng-container header>
<tr bitRow>
<th bitCell></th>
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</tr>
<th bitCell></th>
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async; trackBy: trackByFunction">
<td bitCell>
<input
bitCheckbox
type="checkbox"
[checked]="selectedIds.has(r.id)"
(change)="onCheckboxChange(r.id, $event)"
/>
</td>
<td bitCell>
<ng-container>
<span>{{ r.name }}</span>
</ng-container>
<br />
<small>{{ r.subTitle }}</small>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="passwordStrengthMap.has(r.id)"
[variant]="passwordStrengthMap.get(r.id)[1]"
>
{{ passwordStrengthMap.get(r.id)[0] | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }}
</span>
</td>
<td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(r.id) || 0 }}
</td>
</tr>
<ng-template bitRowDef let-row>
<td bitCell>
<input
bitCheckbox
type="checkbox"
[checked]="selectedIds.has(row.id)"
(change)="onCheckboxChange(row.id, $event)"
/>
</td>
<td bitCell>
<ng-container>
<span>{{ row.name }}</span>
</ng-container>
<br />
<small>{{ row.subTitle }}</small>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="passwordStrengthMap.has(row.id)"
[variant]="passwordStrengthMap.get(row.id)[1]"
>
{{ passwordStrengthMap.get(row.id)[0] | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(row.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(row.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(row.id) }}
</span>
</td>
<td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(row.id) || 0 }}
</td>
</ng-template>
</bit-table>
</bit-table-scroll>
</div>

View File

@@ -9,49 +9,45 @@
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div>
<div class="tw-mt-4" *ngIf="!loading && dataSource.data.length">
<bit-table [dataSource]="dataSource">
<bit-table-scroll [dataSource]="dataSource" [rowSize]="53">
<ng-container header>
<tr bitRow>
<th bitCell></th>
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
</tr>
<th bitCell></th>
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell>
<app-vault-icon [cipher]="r"></app-vault-icon>
</td>
<td bitCell>
<ng-container>
<span>{{ r.name }}</span>
</ng-container>
<br />
<small>{{ r.subTitle }}</small>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="r.weakPasswordDetail"
[variant]="r.weakPasswordDetail?.detailValue.badgeVariant"
>
{{ r.weakPasswordDetail?.detailValue.label | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge variant="warning" *ngIf="r.exposedPasswordDetail">
{{ "exposedXTimes" | i18n: r.exposedPasswordDetail?.exposedXTimes }}
</span>
</td>
</tr>
<ng-template bitRowDef let-row>
<td bitCell>
<app-vault-icon [cipher]="row"></app-vault-icon>
</td>
<td bitCell>
<ng-container>
<span>{{ row.name }}</span>
</ng-container>
<br />
<small>{{ row.subTitle }}</small>
</td>
<td bitCell class="tw-text-right">
<span
bitBadge
*ngIf="row.weakPasswordDetail"
[variant]="row.weakPasswordDetail?.detailValue.badgeVariant"
>
{{ row.weakPasswordDetail?.detailValue.label | i18n }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(row.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }}
</span>
</td>
<td bitCell class="tw-text-right">
<span bitBadge variant="warning" *ngIf="row.exposedPasswordDetail">
{{ "exposedXTimes" | i18n: row.exposedPasswordDetail?.exposedXTimes }}
</span>
</td>
</ng-template>
</bit-table>
</bit-table-scroll>
</div>
</bit-container>