1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +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> <h2 slot="summary" class="tw-mb-0" bitTypography="h2" noMargin>{{ "secrets" | i18n }}</h2>
<sm-secrets-list <sm-secrets-list
baseRoute="secrets" baseRoute="secrets"
[disableVirtualScroll]="true"
(deleteSecretsEvent)="openDeleteSecret($event)" (deleteSecretsEvent)="openDeleteSecret($event)"
(newSecretEvent)="openNewSecretDialog()" (newSecretEvent)="openNewSecretDialog()"
(editSecretEvent)="openEditSecret($event)" (editSecretEvent)="openEditSecret($event)"

View File

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