1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-20 11:24:07 +00:00

[PM-31163] stabilize table column widths with fixed layout (#18708)

* stabilize table column widths with fixed layout (PM-31163)

Add layout="fixed" and explicit width classes to report tables to prevent
column widths from shifting during virtual scroll.

Files changed:
- weak-passwords-report.component.html
- reused-passwords-report.component.html
- exposed-passwords-report.component.html
- inactive-two-factor-report.component.html
- unsecured-websites-report.component.html

* use auto width for name column to fix width calculation (PM-31163)

  Remove tw-w-1/2 from name column headers. With layout="fixed", the
  explicit percentages didn't sum to 100%, causing inconsistent column widths.

  Before: | 48px | 50% | 25% | 25% | = 48px + 100% (overflow)
  After:  | 48px | auto | 25% | 25% | = columns sum correctly

  Name column now uses auto to fill remaining space.

* render headers in Admin Console to fix column widths (PM-31163)

  Admin Console reports had a very wide icon column because no headers were
  rendered. Without headers, table-layout: fixed uses data row content to
  determine column widths, causing inconsistent sizing.

  Root cause:
  Three reports had their entire <ng-container header> block inside
  @if (!isAdminConsoleActive), so when isAdminConsoleActive=true (Admin
  Console), no headers were rendered at all.

  Before (broken):
    @if (!isAdminConsoleActive) {
      <ng-container header>        <!-- Entire header skipped in Admin Console -->
        <th>Icon</th>
        <th>Name</th>
        <th>Owner</th>
      </ng-container>
    }

  After (fixed):
    <ng-container header>          <!-- Always render headers -->
      <th>Icon</th>
      <th>Name</th>
      @if (!isAdminConsoleActive) {
        <th>Owner</th>             <!-- Only Owner is conditional -->
      }
    </ng-container>

  This matches the pattern already used by weak-passwords-report and
  exposed-passwords-report, which were working correctly.

  Files changed:
  - unsecured-websites-report.component.html
  - reused-passwords-report.component.html
  - inactive-two-factor-report.component.html

  Result:
  - Admin Console now renders headers with correct column widths
  - Icon column is 48px (tw-w-12) as expected
  - Owner column properly hidden in Admin Console view

* truncate long item names to prevent column overflow

- you can hover cursor for tooltip to see full name
This commit is contained in:
Alex
2026-02-17 21:31:08 -07:00
committed by GitHub
parent ec33ea4f3c
commit 03340aee71
5 changed files with 87 additions and 75 deletions

View File

@@ -43,16 +43,16 @@
></bit-chip-select>
}
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="54">
<bit-table-scroll [dataSource]="dataSource" [rowSize]="54" layout="fixed">
<ng-container header>
<th bitCell></th>
<th bitCell class="tw-w-12"></th>
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
@if (!isAdminConsoleActive) {
<th bitCell bitSortable="organizationId">
<th bitCell bitSortable="organizationId" class="tw-w-1/4">
{{ "owner" | i18n }}
</th>
}
<th bitCell class="tw-text-right" bitSortable="exposedXTimes">
<th bitCell class="tw-w-1/4 tw-text-right" bitSortable="exposedXTimes">
{{ "timesExposed" | i18n }}
</th>
</ng-container>
@@ -60,7 +60,7 @@
<td bitCell>
<app-vault-icon [cipher]="row"></app-vault-icon>
</td>
<td bitCell>
<td bitCell class="tw-truncate tw-max-w-0">
@if (!organization || canManageCipher(row)) {
<a
bitLink
@@ -72,7 +72,7 @@
{{ row.name }}
</a>
} @else {
<span>{{ row.name }}</span>
<span title="{{ row.name }}">{{ row.name }}</span>
}
@if (!organization && row.organizationId) {
<i

View File

@@ -45,20 +45,20 @@
></bit-chip-select>
}
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75">
@if (!isAdminConsoleActive) {
<ng-container header>
<th bitCell></th>
<th bitCell>{{ "name" | i18n }}</th>
<th bitCell>{{ "owner" | i18n }}</th>
<th bitCell></th>
</ng-container>
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75" layout="fixed">
<ng-container header>
<th bitCell class="tw-w-12"></th>
<th bitCell>{{ "name" | i18n }}</th>
@if (!isAdminConsoleActive) {
<th bitCell class="tw-w-1/4">{{ "owner" | i18n }}</th>
}
<th bitCell class="tw-w-1/4"></th>
</ng-container>
<ng-template bitRowDef let-row>
<td bitCell>
<app-vault-icon [cipher]="row"></app-vault-icon>
</td>
<td bitCell>
<td bitCell class="tw-truncate tw-max-w-0">
@if (!organization || canManageCipher(row)) {
<a
bitLink
@@ -69,7 +69,7 @@
>{{ row.name }}</a
>
} @else {
<span>{{ row.name }}</span>
<span title="{{ row.name }}">{{ row.name }}</span>
}
@if (!organization && row.organizationId) {
<i
@@ -92,16 +92,20 @@
<br />
<small>{{ row.subTitle }}</small>
</td>
<td bitCell>
@if (!organization) {
<app-org-badge
[disabled]="disabled"
[organizationId]="row.organizationId"
[organizationName]="row.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
/>
}
</td>
@if (!isAdminConsoleActive) {
<td bitCell>
@if (!organization) {
<app-org-badge
[disabled]="disabled"
[organizationId]="row.organizationId"
[organizationName]="
row.organizationId | orgNameFromId: (organizations$ | async)
"
appStopProp
/>
}
</td>
}
<td bitCell class="tw-text-right">
@if (cipherDocs.has(row.id)) {
<a bitBadge href="{{ cipherDocs.get(row.id) }}" target="_blank" rel="noreferrer">

View File

@@ -45,20 +45,20 @@
></bit-chip-select>
}
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75">
@if (!isAdminConsoleActive) {
<ng-container header>
<th bitCell></th>
<th bitCell>{{ "name" | i18n }}</th>
<th bitCell>{{ "owner" | i18n }}</th>
<th bitCell class="tw-text-right">{{ "timesReused" | i18n }}</th>
</ng-container>
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75" layout="fixed">
<ng-container header>
<th bitCell class="tw-w-12"></th>
<th bitCell>{{ "name" | i18n }}</th>
@if (!isAdminConsoleActive) {
<th bitCell class="tw-w-1/4">{{ "owner" | i18n }}</th>
}
<th bitCell class="tw-w-1/4 tw-text-right">{{ "timesReused" | i18n }}</th>
</ng-container>
<ng-template bitRowDef let-row>
<td bitCell>
<app-vault-icon [cipher]="row"></app-vault-icon>
</td>
<td bitCell>
<td bitCell class="tw-truncate tw-max-w-0">
@if (!organization || canManageCipher(row)) {
<a
bitLink
@@ -69,7 +69,7 @@
>{{ row.name }}</a
>
} @else {
<span>{{ row.name }}</span>
<span title="{{ row.name }}">{{ row.name }}</span>
}
@if (!organization && row.organizationId) {
<i
@@ -92,17 +92,21 @@
<br />
<small>{{ row.subTitle }}</small>
</td>
<td bitCell>
@if (!organization) {
<app-org-badge
[disabled]="disabled"
[organizationId]="row.organizationId"
[organizationName]="row.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
}
</td>
@if (!isAdminConsoleActive) {
<td bitCell>
@if (!organization) {
<app-org-badge
[disabled]="disabled"
[organizationId]="row.organizationId"
[organizationName]="
row.organizationId | orgNameFromId: (organizations$ | async)
"
appStopProp
>
</app-org-badge>
}
</td>
}
<td bitCell class="tw-text-right">
<span bitBadge variant="warning">
{{ "reusedXTimes" | i18n: passwordUseMap.get(row.login.password) }}

View File

@@ -45,19 +45,19 @@
></bit-chip-select>
}
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75">
@if (!isAdminConsoleActive) {
<ng-container header>
<th bitCell></th>
<th bitCell>{{ "name" | i18n }}</th>
<th bitCell>{{ "owner" | i18n }}</th>
</ng-container>
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="75" layout="fixed">
<ng-container header>
<th bitCell class="tw-w-12"></th>
<th bitCell>{{ "name" | i18n }}</th>
@if (!isAdminConsoleActive) {
<th bitCell class="tw-w-1/3">{{ "owner" | i18n }}</th>
}
</ng-container>
<ng-template bitRowDef let-row>
<td bitCell>
<app-vault-icon [cipher]="row"></app-vault-icon>
</td>
<td bitCell>
<td bitCell class="tw-truncate tw-max-w-0">
@if (!organization || canManageCipher(row)) {
<a
bitLink
@@ -68,7 +68,7 @@
>{{ row.name }}</a
>
} @else {
<span>{{ row.name }}</span>
<span title="{{ row.name }}">{{ row.name }}</span>
}
@if (!organization && row.organizationId) {
<i
@@ -91,17 +91,21 @@
<br />
<small>{{ row.subTitle }}</small>
</td>
<td bitCell>
@if (!organization) {
<app-org-badge
[disabled]="disabled"
[organizationId]="row.organizationId"
[organizationName]="row.organizationId | orgNameFromId: (organizations$ | async)"
appStopProp
>
</app-org-badge>
}
</td>
@if (!isAdminConsoleActive) {
<td bitCell>
@if (!organization) {
<app-org-badge
[disabled]="disabled"
[organizationId]="row.organizationId"
[organizationName]="
row.organizationId | orgNameFromId: (organizations$ | async)
"
appStopProp
>
</app-org-badge>
}
</td>
}
</ng-template>
</bit-table-scroll>
}

View File

@@ -45,12 +45,12 @@
></bit-chip-select>
}
}
<bit-table-scroll [dataSource]="dataSource" [rowSize]="54">
<bit-table-scroll [dataSource]="dataSource" [rowSize]="54" layout="fixed">
<ng-container header>
<th bitCell></th>
<th bitCell class="tw-w-12"></th>
<th bitCell bitSortable="name">{{ "name" | i18n }}</th>
@if (!isAdminConsoleActive) {
<th bitCell bitSortable="organizationId">
<th bitCell bitSortable="organizationId" class="tw-w-1/4">
{{ "owner" | i18n }}
</th>
}
@@ -62,7 +62,7 @@
<td bitCell>
<app-vault-icon [cipher]="row"></app-vault-icon>
</td>
<td bitCell>
<td bitCell class="tw-truncate tw-max-w-0">
@if (!organization || canManageCipher(row)) {
<a
bitLink
@@ -73,7 +73,7 @@
>{{ row.name }}</a
>
} @else {
<span>{{ row.name }}</span>
<span title="{{ row.name }}">{{ row.name }}</span>
}
@if (!organization && row.organizationId) {
<i