diff --git a/static/connections.html b/static/connections.html index ef9561a..f3873d3 100644 --- a/static/connections.html +++ b/static/connections.html @@ -118,6 +118,12 @@ .entries-table td:last-child { text-align: right; white-space: nowrap; } .entries-table .actions-cell { white-space: nowrap; text-align: right; } + /* Long names wrap inside their cell instead of widening the table past + the page: a dotted FQDN like db01.prod.example.internal has no + natural break point, so at any width a long enough one overran it + whatever the padding (#252). Buttons stay on one line. */ + .entries-table td { overflow-wrap: anywhere; } + .entries-table td.connect-cell, .entries-table td.actions-cell { overflow-wrap: normal; } .entries-table .actions-cell .btn-small { margin-left: var(--s-1); } /* Bulk selection (admins). */ diff --git a/static/recordings.html b/static/recordings.html index 64366c3..afa7471 100644 --- a/static/recordings.html +++ b/static/recordings.html @@ -102,6 +102,12 @@ #player-time { color: var(--text-muted); font-size: var(--fz-sm); white-space: nowrap; } .btn-delete { color: var(--primary); } .btn-delete:hover { color: var(--primary-hover); } + /* Long names, emails and folder paths wrap inside their cell instead + of widening the table past the page: a dotted FQDN has no natural + break point (#252). The action buttons stay on one line. */ + #recording-list td { overflow-wrap: anywhere; } + #recording-list td:last-child { overflow-wrap: normal; } + /* Narrow desktops (a 1710px MacBook display): the 18px table with 20px cell padding ran past the right edge. Same treatment as the connections table: 16px cells, tighter padding, 14px headers, and diff --git a/static/reports.html b/static/reports.html index fe7e78c..9a8eabe 100644 --- a/static/reports.html +++ b/static/reports.html @@ -48,6 +48,12 @@ font-size: var(--fz-sm); } .panel th { background: transparent; padding-top: 0; } + /* Long names, emails and folder paths wrap inside their cell instead + of widening the table past the page: a dotted FQDN has no natural + break point (#252). The action buttons stay on one line. */ + #history-table td { overflow-wrap: anywhere; } + #history-table td:last-child { overflow-wrap: normal; } + /* Narrow desktops (a 1710px MacBook display): the 18px table with 20px cell padding ran past the right edge. Same treatment as the connections table: 16px cells, tighter padding, 14px headers, and diff --git a/tests/e2e/README.md b/tests/e2e/README.md index 7b44a3a..0744087 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -23,18 +23,7 @@ One spec per change, named after the branch that introduced it: | spec | pins | |---|---| -| `tables-1710` | #252: no horizontal overflow, admin table fits at 1710px | -| `connections-sidebar` | #254: independent sidebar scroll, selected row marked and kept in view | -| `connections-folder-filter` | #254: live filter box, ARIA tree, arrow keys, `f` shortcut | -| `connections-app-shell` | #254: clipped body, two scroll panes, folder change scrolls the pane not the page | -| `role-nav` | one nav on every page, gated by `data-role-level` | -| `sessions-adhoc-modal` | ad-hoc form opens as a modal | -| `facet-filters` | shared facets narrow lists, persist, combine with search | -| `folder-display-name` | display name over the key, modal field round-trips | -| `themed-dialogs` | `rgConfirm`/`rgAlert` replace native dialogs | -| `global-search` | `/` opens the overlay on every page, folder view untouched | -| `search-under-modals` | a page modal covers the search box | -| `zero-click-fullscreen` | #257: same-tab iframe sized from the measured viewport | +| `tables-1710` | #252: no horizontal overflow at 1710px on Connections, Recordings and Reports, with short and with long unbreakable names | The fixture suppresses the first-visit onboarding tour (`options.onboarding` re-enables it) and defaults to the admin role; pass `role` to test gating. diff --git a/tests/e2e/specs/tables-1710.spec.mjs b/tests/e2e/specs/tables-1710.spec.mjs index 689239f..ee56412 100644 --- a/tests/e2e/specs/tables-1710.spec.mjs +++ b/tests/e2e/specs/tables-1710.spec.mjs @@ -2,7 +2,7 @@ // sideways. Each list page must fit its table inside the viewport at 1710px // with nothing cut off, and the page must not scroll horizontally. import { test, expect } from '@playwright/test'; -import { installApi, settle } from '../lib/api.mjs'; +import { installApi, settle, folder, entry } from '../lib/api.mjs'; test.use({ viewport: { width: 1710, height: 1112 } }); @@ -54,3 +54,75 @@ test('reports: the session history table fits at 1710px', async ({ page }) => { await tableFits(page, '#history-table'); await noHorizontalOverflow(page); }); + +// The fixture above is short enough to fit even without #252's changes, so +// these use the data that actually overran the page: dotted FQDNs, service +// accounts, long emails and folder paths. None of them has a natural break +// point, so they are what the wrapping has to cope with at any font width. +const LONG_HOST = 'sqlclusternode03.prod.melbourne.acmeholdings.internal'; +const LONG_USER = 'firstname.lastname@subsidiary.acmeholdings.com.au'; + +function wideFolder() { + return folder('wide', { + entries: [ + entry('sqlclusternode03', 'rdp', { + hostname: LONG_HOST, + username: 'svcbackupoperator', + fields: { jump_hosts: [{ hostname: 'bastion01.dmz.acmeholdings.net' }] } + }), + entry('fileserverreplica02', 'ssh', { + hostname: 'fileserverreplica02.dr.sydney.acmeholdings.internal', + username: 'ansibleautomation' + }) + ] + }); +} + +test('connections: long hostnames and a jump host still fit at 1710px', async ({ page }) => { + await installApi(page, { role: 'admin', folders: [wideFolder()] }); + await page.goto('/connections.html'); + await settle(page); + await page.locator('.folder-row').filter({ hasText: /wide/ }).first().click(); + await expect(page.locator('.entries-table tbody tr').first()).toBeVisible(); + await tableFits(page, '.entries-table'); + // The last admin action must be on screen, not just the table box. + const del = await page.locator('[data-delete-entry]').first().boundingBox(); + expect(del.x + del.width).toBeLessThanOrEqual(page.viewportSize().width); + await noHorizontalOverflow(page); +}); + +test('recordings: long entry names, users and folders still fit at 1710px', async ({ page }) => { + await installApi(page, { + recordings: [{ + name: `rdp-${LONG_HOST}-20261011-020000.guac`, modified: '2026-10-11T02:30:00Z', + size_bytes: 9_000_000, session_type: 'rdp', user: LONG_USER, + folder: 'shared/customers/acmeholdings/production', + address_book_entry: 'sqlclusternode03', entry_display_name: LONG_HOST + }] + }); + await page.goto('/recordings.html'); + await settle(page); + await expect(page.locator('#recording-list tbody tr').first()).toBeVisible(); + await tableFits(page, '#recording-list'); + await noHorizontalOverflow(page); +}); + +test('reports: long hostnames and users still fit at 1710px', async ({ page }) => { + await installApi(page, { + routes: { + 'GET /api/reports/sessions': { + total: 1, limit: 100, offset: 0, + sessions: [{ + session_id: '1', session_type: 'rdp', hostname: LONG_HOST, username: 'svcbackupoperator', + created_by: LONG_USER, status: 'Ended', started_at: '2026-10-11T02:00:00Z', + ended_at: '2026-10-11T02:30:00Z', duration_secs: 1800, entry_name: LONG_HOST + }] + } + } + }); + await page.goto('/reports.html'); + await settle(page); + await expect(page.locator('#history-body tr').first()).toBeVisible(); + await tableFits(page, '#history-table'); + await noHorizontalOverflow(page); +});