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

[SM-1548] Updating the secrets list to use <bit-table-scroll> (#16201)

* Updating the secrets list to use <bit-table-scroll>

* fixing virtaul scroll issue
This commit is contained in:
cd-bitwarden
2025-09-05 20:19:47 -04:00
committed by GitHub
parent 7eb89eb45a
commit 92b92488d9
3 changed files with 272 additions and 141 deletions

View File

@@ -83,6 +83,7 @@
<h2 slot="summary" class="tw-mb-0" bitTypography="h2" noMargin>{{ "secrets" | i18n }}</h2>
<sm-secrets-list
baseRoute="secrets"
[disableVirtualScroll]="true"
(deleteSecretsEvent)="openDeleteSecret($event)"
(newSecretEvent)="openNewSecretDialog()"
(editSecretEvent)="openEditSecret($event)"

View File

@@ -23,155 +23,282 @@
</bit-no-items>
</ng-container>
<bit-table *ngIf="secrets?.length >= 1" [dataSource]="dataSource">
<bit-table-scroll
*ngIf="secrets?.length >= 1 && !disableVirtualScroll"
[dataSource]="dataSource"
[rowSize]="53"
>
<ng-container header>
<tr>
<th bitCell class="tw-w-0">
<label class="!tw-mb-0 tw-flex tw-w-fit tw-gap-2 !tw-font-bold !tw-text-muted">
<input
type="checkbox"
(change)="$event ? toggleAll() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
/>
{{ "all" | i18n }}
</label>
</th>
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
<th bitCell bitSortable="projects" [fn]="sortProjects">{{ "project" | i18n }}</th>
<th bitCell bitSortable="revisionDate">{{ "lastEdited" | i18n }}</th>
<th bitCell class="tw-w-0">
<button
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[label]="'options' | i18n"
[bitMenuTriggerFor]="tableMenu"
></button>
</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let secret of rows$ | async">
<td bitCell>
<th bitCell class="tw-w-0">
<label class="!tw-mb-0 tw-flex tw-w-fit tw-gap-2 !tw-font-bold !tw-text-muted">
<input
type="checkbox"
(change)="$event ? selection.toggle(secret.id) : null"
[checked]="selection.isSelected(secret.id)"
(change)="$event ? toggleAll() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
/>
</td>
<td bitCell>
<div class="tw-flex tw-items-center tw-gap-4 tw-break-all">
<i class="bwi bwi-key tw-text-muted" aria-hidden="true"></i>
<div>
<div *ngIf="!trash">
<button type="button" bitLink (click)="editSecret(secret)">
{{ secret.name }}
</button>
</div>
<div *ngIf="trash">{{ secret.name }}</div>
<div class="tw-text-sm tw-text-muted">
{{ secret.id }}
<button
type="button"
bitIconButton="bwi-clone"
buttonType="main"
size="small"
[label]="'copyUuid' | i18n"
(click)="copySecretUuidEvent.emit(secret.id)"
></button>
</div>
{{ "all" | i18n }}
</label>
</th>
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
<th bitCell bitSortable="projects" [fn]="sortProjects">{{ "project" | i18n }}</th>
<th bitCell bitSortable="revisionDate">{{ "lastEdited" | i18n }}</th>
<th bitCell class="tw-w-0">
<button
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[label]="'options' | i18n"
[bitMenuTriggerFor]="tableMenu"
></button>
</th>
</ng-container>
<ng-template bitRowDef let-row>
<td bitCell>
<input
type="checkbox"
(change)="$event ? selection.toggle(row.id) : null"
[checked]="selection.isSelected(row.id)"
/>
</td>
<td bitCell>
<div class="tw-flex tw-items-center tw-gap-4 tw-break-all">
<i class="bwi bwi-key tw-text-muted" aria-hidden="true"></i>
<div>
<div *ngIf="!trash">
<button type="button" bitLink (click)="editSecret(row)">
{{ row.name }}
</button>
</div>
<div *ngIf="trash">{{ row.name }}</div>
<div class="tw-text-sm tw-text-muted">
{{ row.id }}
<button
type="button"
bitIconButton="bwi-clone"
buttonType="main"
size="small"
[label]="'copyUuid' | i18n"
(click)="copySecretUuidEvent.emit(row.id)"
></button>
</div>
</div>
</td>
<td bitCell>
<span
*ngFor="let project of secret.projects"
bitBadge
variant="secondary"
class="tw-ml-1"
[title]="project.name"
maxWidthClass="tw-max-w-60"
>
{{ project.name }}
</span>
<span *ngIf="secret.projects.length === 0" bitBadge variant="warning" class="tw-ml-1"
><i class="bwi bwi-fw bwi-exclamation-triangle tw-mr-1" aria-hidden="true"></i
>{{ "unassigned" | i18n }}</span
>
</td>
<td bitCell class="tw-whitespace-nowrap">{{ secret.revisionDate | date: "medium" }}</td>
<td bitCell>
<button
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[label]="'options' | i18n"
[bitMenuTriggerFor]="secretMenu"
></button>
</td>
</div>
</td>
<td bitCell>
<span
*ngFor="let project of row.projects"
bitBadge
variant="secondary"
class="tw-ml-1"
[title]="project.name"
maxWidthClass="tw-max-w-60"
>
{{ project.name }}
</span>
<span *ngIf="row.projects.length === 0" bitBadge variant="warning" class="tw-ml-1"
><i class="bwi bwi-fw bwi-exclamation-triangle tw-mr-1" aria-hidden="true"></i
>{{ "unassigned" | i18n }}</span
>
</td>
<td bitCell class="tw-whitespace-nowrap">{{ row.revisionDate | date: "medium" }}</td>
<td bitCell>
<button
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[label]="'options' | i18n"
[bitMenuTriggerFor]="secretMenu"
></button>
</td>
<bit-menu #secretMenu>
<button
type="button"
bitMenuItem
(click)="editSecret(secret)"
*ngIf="secret.write && !trash"
>
<i class="bwi bwi-fw bwi-pencil" aria-hidden="true"></i>
{{ "editSecret" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="copySecretNameEvent.emit(secret.name)"
*ngIf="!trash"
>
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySecretName" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="copySecretValueEvent.emit(secret.id)"
*ngIf="!trash"
>
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySecretValue" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="restoreSecretsEvent.emit([secret.id])"
*ngIf="trash"
>
<i class="bwi bwi-fw bwi-refresh" aria-hidden="true"></i>
{{ "restoreSecret" | i18n }}
</button>
<button
type="button"
bitMenuItem
*ngIf="viewEventsAllowed$ | async as allowed"
(click)="openEventsDialog(secret)"
>
<i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i>
<span> {{ "viewEvents" | i18n }} </span>
</button>
<button
type="button"
bitMenuItem
(click)="deleteSecretsEvent.emit([secret])"
*ngIf="secret.write"
>
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
<span class="tw-text-danger">{{
(trash ? "permanentlyDelete" : "deleteSecret") | i18n
}}</span>
</button>
</bit-menu>
</tr>
<bit-menu #secretMenu>
<button type="button" bitMenuItem (click)="editSecret(row)" *ngIf="row.write && !trash">
<i class="bwi bwi-fw bwi-pencil" aria-hidden="true"></i>
{{ "editSecret" | i18n }}
</button>
<button type="button" bitMenuItem (click)="copySecretNameEvent.emit(row.name)" *ngIf="!trash">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySecretName" | i18n }}
</button>
<button type="button" bitMenuItem (click)="copySecretValueEvent.emit(row.id)" *ngIf="!trash">
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySecretValue" | i18n }}
</button>
<button type="button" bitMenuItem (click)="restoreSecretsEvent.emit([row.id])" *ngIf="trash">
<i class="bwi bwi-fw bwi-refresh" aria-hidden="true"></i>
{{ "restoreSecret" | i18n }}
</button>
<button
type="button"
bitMenuItem
*ngIf="viewEventsAllowed$ | async as allowed"
(click)="openEventsDialog(row)"
>
<i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i>
<span> {{ "viewEvents" | i18n }} </span>
</button>
<button type="button" bitMenuItem (click)="deleteSecretsEvent.emit([row])" *ngIf="row.write">
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
<span class="tw-text-danger">{{
(trash ? "permanentlyDelete" : "deleteSecret") | i18n
}}</span>
</button>
</bit-menu>
</ng-template>
</bit-table>
</bit-table-scroll>
<ng-container *ngIf="disableVirtualScroll">
<bit-table *ngIf="secrets?.length >= 1" [dataSource]="dataSource">
<ng-container header>
<tr>
<th bitCell class="tw-w-0">
<label class="!tw-mb-0 tw-flex tw-w-fit tw-gap-2 !tw-font-bold !tw-text-muted">
<input
type="checkbox"
(change)="$event ? toggleAll() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()"
/>
{{ "all" | i18n }}
</label>
</th>
<th bitCell bitSortable="name" default>{{ "name" | i18n }}</th>
<th bitCell bitSortable="projects" [fn]="sortProjects">{{ "project" | i18n }}</th>
<th bitCell bitSortable="revisionDate">{{ "lastEdited" | i18n }}</th>
<th bitCell class="tw-w-0">
<button
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[label]="'options' | i18n"
[bitMenuTriggerFor]="tableMenu"
></button>
</th>
</tr>
</ng-container>
<ng-template body let-rows$>
<tr bitRow *ngFor="let secret of rows$ | async">
<td bitCell>
<input
type="checkbox"
(change)="$event ? selection.toggle(secret.id) : null"
[checked]="selection.isSelected(secret.id)"
/>
</td>
<td bitCell>
<div class="tw-flex tw-items-center tw-gap-4 tw-break-all">
<i class="bwi bwi-key tw-text-muted" aria-hidden="true"></i>
<div>
<div *ngIf="!trash">
<button type="button" bitLink (click)="editSecret(secret)">
{{ secret.name }}
</button>
</div>
<div *ngIf="trash">{{ secret.name }}</div>
<div class="tw-text-sm tw-text-muted">
{{ secret.id }}
<button
type="button"
bitIconButton="bwi-clone"
buttonType="main"
size="small"
[label]="'copyUuid' | i18n"
(click)="copySecretUuidEvent.emit(secret.id)"
></button>
</div>
</div>
</div>
</td>
<td bitCell>
<span
*ngFor="let project of secret.projects"
bitBadge
variant="secondary"
class="tw-ml-1"
[title]="project.name"
maxWidthClass="tw-max-w-60"
>
{{ project.name }}
</span>
<span *ngIf="secret.projects.length === 0" bitBadge variant="warning" class="tw-ml-1"
><i class="bwi bwi-fw bwi-exclamation-triangle tw-mr-1" aria-hidden="true"></i
>{{ "unassigned" | i18n }}</span
>
</td>
<td bitCell class="tw-whitespace-nowrap">{{ secret.revisionDate | date: "medium" }}</td>
<td bitCell>
<button
type="button"
bitIconButton="bwi-ellipsis-v"
buttonType="main"
[label]="'options' | i18n"
[bitMenuTriggerFor]="secretMenu"
></button>
</td>
<bit-menu #secretMenu>
<button
type="button"
bitMenuItem
(click)="editSecret(secret)"
*ngIf="secret.write && !trash"
>
<i class="bwi bwi-fw bwi-pencil" aria-hidden="true"></i>
{{ "editSecret" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="copySecretNameEvent.emit(secret.name)"
*ngIf="!trash"
>
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySecretName" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="copySecretValueEvent.emit(secret.id)"
*ngIf="!trash"
>
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
{{ "copySecretValue" | i18n }}
</button>
<button
type="button"
bitMenuItem
(click)="restoreSecretsEvent.emit([secret.id])"
*ngIf="trash"
>
<i class="bwi bwi-fw bwi-refresh" aria-hidden="true"></i>
{{ "restoreSecret" | i18n }}
</button>
<button
type="button"
bitMenuItem
*ngIf="viewEventsAllowed$ | async as allowed"
(click)="openEventsDialog(secret)"
>
<i class="bwi bwi-fw bwi-billing" aria-hidden="true"></i>
<span> {{ "viewEvents" | i18n }} </span>
</button>
<button
type="button"
bitMenuItem
(click)="deleteSecretsEvent.emit([secret])"
*ngIf="secret.write"
>
<i class="bwi bwi-fw bwi-trash tw-text-danger" aria-hidden="true"></i>
<span class="tw-text-danger">{{
(trash ? "permanentlyDelete" : "deleteSecret") | i18n
}}</span>
</button>
</bit-menu>
</tr>
</ng-template>
</bit-table>
</ng-container>
<bit-menu #tableMenu>
<button type="button" bitMenuItem (click)="bulkRestoreSecrets()" *ngIf="trash">

View File

@@ -29,6 +29,9 @@ import { SecretService } from "../secrets/secret.service";
export class SecretsListComponent implements OnDestroy, OnInit {
protected dataSource = new TableDataSource<SecretListView>();
@Input()
disableVirtualScroll: boolean;
@Input()
get secrets(): SecretListView[] {
return this._secrets;