1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 02:33:46 +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 }} {{ "markAppAsCritical" | i18n }}
</button> </button>
</div> </div>
<bit-table [dataSource]="dataSource">
<ng-container header> <app-table-row-scrollable
<tr> [dataSource]="dataSource"
<th *ngIf="isCriticalAppsFeatureEnabled"></th> [showRowCheckBox]="true"
<th bitSortable="applicationName" bitCell>{{ "application" | i18n }}</th> [showRowMenuForCriticalApps]="false"
<th bitSortable="atRiskPasswordCount" bitCell>{{ "atRiskPasswords" | i18n }}</th> [selectedUrls]="selectedUrls"
<th bitSortable="passwordCount" bitCell>{{ "totalPasswords" | i18n }}</th> [isCriticalAppsFeatureEnabled]="isCriticalAppsFeatureEnabled"
<th bitSortable="atRiskMemberCount" bitCell>{{ "atRiskMembers" | i18n }}</th> [isDrawerIsOpenForThisRecord]="isDrawerOpenForTableRow"
<th bitSortable="memberCount" bitCell>{{ "totalMembers" | i18n }}</th> [checkboxChange]="onCheckboxChange"
</tr> [showAppAtRiskMembers]="showAppAtRiskMembers"
</ng-container> ></app-table-row-scrollable>
<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>
</div> </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 { SharedModule } from "@bitwarden/web-vault/app/shared";
import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; 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"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component";
@Component({ @Component({
@@ -51,6 +52,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"
PipesModule, PipesModule,
NoItemsModule, NoItemsModule,
SharedModule, SharedModule,
AppTableRowScrollableComponent,
], ],
}) })
export class AllApplicationsComponent implements OnInit { export class AllApplicationsComponent implements OnInit {
@@ -190,14 +192,18 @@ export class AllApplicationsComponent implements OnInit {
this.dataService.setDrawerForOrgAtRiskApps(data, invokerId); this.dataService.setDrawerForOrgAtRiskApps(data, invokerId);
}; };
onCheckboxChange(applicationName: string, event: Event) { onCheckboxChange = (applicationName: string, event: Event) => {
const isChecked = (event.target as HTMLInputElement).checked; const isChecked = (event.target as HTMLInputElement).checked;
if (isChecked) { if (isChecked) {
this.selectedUrls.add(applicationName); this.selectedUrls.add(applicationName);
} else { } else {
this.selectedUrls.delete(applicationName); this.selectedUrls.delete(applicationName);
} }
} };
getSelectedUrls = () => Array.from(this.selectedUrls); 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" [formControl]="searchControl"
></bit-search> ></bit-search>
</div> </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> <app-table-row-scrollable
<button type="button" bitMenuItem (click)="unmarkAsCriticalApp(r.applicationName)"> [dataSource]="dataSource"
<i aria-hidden="true" class="bwi bwi-star-f"></i> {{ "unmarkAsCriticalApp" | i18n }} [showRowCheckBox]="false"
</button> [showRowMenuForCriticalApps]="true"
</bit-menu> [isCriticalAppsFeatureEnabled]="true"
</td> [isDrawerIsOpenForThisRecord]="isDrawerOpenForTableRow"
</tr> [showAppAtRiskMembers]="showAppAtRiskMembers"
</ng-template> [unmarkAsCriticalApp]="unmarkAsCriticalApp"
</bit-table> ></app-table-row-scrollable>
</div> </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 { CreateTasksRequest } from "../../vault/services/abstractions/admin-task.abstraction";
import { DefaultAdminTaskService } from "../../vault/services/default-admin-task.service"; import { DefaultAdminTaskService } from "../../vault/services/default-admin-task.service";
import { AppTableRowScrollableComponent } from "./app-table-row-scrollable.component";
import { RiskInsightsTabType } from "./risk-insights.component"; import { RiskInsightsTabType } from "./risk-insights.component";
@Component({ @Component({
standalone: true, standalone: true,
selector: "tools-critical-applications", selector: "tools-critical-applications",
templateUrl: "./critical-applications.component.html", templateUrl: "./critical-applications.component.html",
imports: [CardComponent, HeaderModule, SearchModule, NoItemsModule, PipesModule, SharedModule], imports: [
CardComponent,
HeaderModule,
SearchModule,
NoItemsModule,
PipesModule,
SharedModule,
AppTableRowScrollableComponent,
],
providers: [DefaultAdminTaskService], providers: [DefaultAdminTaskService],
}) })
export class CriticalApplicationsComponent implements OnInit { export class CriticalApplicationsComponent implements OnInit {
@@ -181,4 +190,7 @@ export class CriticalApplicationsComponent implements OnInit {
trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) { trackByFunction(_: number, item: ApplicationHealthReportDetailWithCriticalFlag) {
return item.applicationName; 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> <span class="tw-sr-only">{{ "loading" | i18n }}</span>
</div> </div>
<div class="tw-mt-4" *ngIf="!loading"> <div class="tw-mt-4" *ngIf="!loading">
<bit-table [dataSource]="dataSource"> <bit-table-scroll [dataSource]="dataSource" [rowSize]="53">
<ng-container header> <ng-container header>
<tr bitRow>
<th bitCell bitSortable="hostURI">{{ "application" | i18n }}</th> <th bitCell bitSortable="hostURI">{{ "application" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "weakness" | 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">{{ "timesReused" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th> <th bitCell class="tw-text-right">{{ "timesExposed" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th> <th bitCell class="tw-text-right">{{ "totalMembers" | i18n }}</th>
</tr>
</ng-container> </ng-container>
<ng-template body let-rows$> <ng-template bitRowDef let-row>
<tr bitRow *ngFor="let r of rows$ | async">
<td bitCell> <td bitCell>
<ng-container> <ng-container>
<span>{{ r.hostURI }}</span> <span>{{ row.hostURI }}</span>
</ng-container> </ng-container>
</td> </td>
<td bitCell class="tw-text-right"> <td bitCell class="tw-text-right">
<span <span
bitBadge bitBadge
*ngIf="passwordStrengthMap.has(r.id)" *ngIf="passwordStrengthMap.has(row.id)"
[variant]="passwordStrengthMap.get(r.id)[1]" [variant]="passwordStrengthMap.get(row.id)[1]"
> >
{{ passwordStrengthMap.get(r.id)[0] | i18n }} {{ passwordStrengthMap.get(row.id)[0] | i18n }}
</span> </span>
</td> </td>
<td bitCell class="tw-text-right"> <td bitCell class="tw-text-right">
<span bitBadge *ngIf="passwordUseMap.has(r.login.password)" variant="warning"> <span bitBadge *ngIf="passwordUseMap.has(row.login.password)" variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(r.login.password) }} {{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }}
</span> </span>
</td> </td>
<td bitCell class="tw-text-right"> <td bitCell class="tw-text-right">
<span bitBadge *ngIf="exposedPasswordMap.has(r.id)" variant="warning"> <span bitBadge *ngIf="exposedPasswordMap.has(row.id)" variant="warning">
{{ "exposedXTimes" | i18n: exposedPasswordMap.get(r.id) }} {{ "exposedXTimes" | i18n: exposedPasswordMap.get(row.id) }}
</span> </span>
</td> </td>
<td bitCell class="tw-text-right" data-testid="total-membership"> <td bitCell class="tw-text-right" data-testid="total-membership">
{{ totalMembersMap.get(r.id) || 0 }} {{ totalMembersMap.get(row.id) || 0 }}
</td> </td>
</tr>
</ng-template> </ng-template>
</bit-table> </bit-table-scroll>
</div> </div>
</bit-container> </bit-container>

View File

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

View File

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