From 7e606cc8847735916eb81fc62baf92240890ac94 Mon Sep 17 00:00:00 2001 From: Dennis Nemec Date: Thu, 3 Sep 2026 19:30:03 +0200 Subject: [PATCH] Fix horizontal page overflow caused by wide tables Wide tables stretched the whole page because the shell's main is a flex item without min-width:0 and no table had a scroll container, so the header and the summary tiles scrolled out of view. Every table now scrolls inside its own overflow-x-auto container, the sidebar no longer shrinks, and long CVE ids, versions, targets and image references are kept on one line or truncated with a title tooltip. Co-Authored-By: Claude Opus 5 --- frontend/e2e/layout.spec.ts | 61 +++++ frontend/src/components/AppShell.vue | 4 +- frontend/src/components/FindingTable.test.ts | 12 + frontend/src/components/FindingTable.vue | 114 ++++---- frontend/src/components/PackageTable.vue | 96 +++---- frontend/src/components/WorkloadTable.vue | 106 ++++---- frontend/src/pages/BackupsPage.vue | 268 ++++++++++--------- frontend/src/pages/ClusterPage.vue | 50 ++-- frontend/src/pages/DashboardPage.vue | 98 +++---- frontend/src/pages/JobsPage.vue | 56 ++-- frontend/src/pages/UsersPage.vue | 78 +++--- 11 files changed, 526 insertions(+), 417 deletions(-) create mode 100644 frontend/e2e/layout.spec.ts diff --git a/frontend/e2e/layout.spec.ts b/frontend/e2e/layout.spec.ts new file mode 100644 index 0000000..5950714 --- /dev/null +++ b/frontend/e2e/layout.spec.ts @@ -0,0 +1,61 @@ +import { test, expect, type Page } from '@playwright/test' + +// Wide content (tables) must scroll inside its own container; the page itself must never +// scroll horizontally, otherwise the header and the tiles get cut off. +const PAGES = [ + '/', + '/updates', + '/cluster', + '/vulnerabilities', + '/backups', + '/jobs', + '/users', + '/settings', +] + +async function login(page: Page) { + await page.goto('/login') + await page.getByLabel('Email').fill('admin@example.com') + await page.getByLabel('Password').fill('admin-password-123') + await page.getByRole('button', { name: 'Sign in' }).click() + await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible() +} + +const pageOverflow = (page: Page) => + page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth) + +test('no page scrolls horizontally, with data and on a narrow window', async ({ page }) => { + await page.setViewportSize({ width: 1280, height: 800 }) + await login(page) + + // fill the app with the data that makes the tables wide + await page.goto('/updates') + await page.getByRole('button', { name: 'Refresh inventory' }).click() + await expect(page.getByTestId('os-name')).toContainText('Debian', { timeout: 15_000 }) + await page.goto('/vulnerabilities') + await page.getByRole('button', { name: 'Scan now' }).click() + await expect(page.getByTestId('count-critical')).not.toHaveText('0', { timeout: 20_000 }) + + for (const width of [1440, 1280, 1024, 900]) { + await page.setViewportSize({ width, height: 800 }) + for (const path of PAGES) { + await page.goto(path) + await expect(page.getByRole('heading').first()).toBeVisible() + expect(await pageOverflow(page), `${path} at ${width}px scrolls horizontally`).toBe(0) + } + } +}) + +test('the findings table scrolls inside its own container', async ({ page }) => { + await page.setViewportSize({ width: 900, height: 800 }) + await login(page) + await page.goto('/vulnerabilities') + await page.getByRole('button', { name: 'Scan now' }).click() + await expect(page.getByTestId('count-critical')).not.toHaveText('0', { timeout: 20_000 }) + + expect(await pageOverflow(page)).toBe(0) + const scrollable = await page + .getByTestId('findings-scroll') + .evaluate((el) => el.scrollWidth > el.clientWidth && getComputedStyle(el).overflowX === 'auto') + expect(scrollable).toBe(true) +}) diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index 5916a59..b16c091 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -23,7 +23,7 @@ async function signOut() { diff --git a/frontend/src/pages/ClusterPage.vue b/frontend/src/pages/ClusterPage.vue index 7407191..fd49287 100644 --- a/frontend/src/pages/ClusterPage.vue +++ b/frontend/src/pages/ClusterPage.vue @@ -104,29 +104,31 @@ function setImage(w: Workload) { />

Persistent volume claims

- - - - - - - - - - - - - - - - - - - -
NamespaceNameCapacityStorage classStatus
{{ p.namespace }}{{ p.name }}{{ p.capacity }}{{ p.storage_class }}{{ p.status }}
+
+ + + + + + + + + + + + + + + + + + + +
NamespaceNameCapacityStorage classStatus
{{ p.namespace }}{{ p.name }}{{ p.capacity }}{{ p.storage_class }}{{ p.status }}
+
diff --git a/frontend/src/pages/DashboardPage.vue b/frontend/src/pages/DashboardPage.vue index 59cdcf3..63aa16a 100644 --- a/frontend/src/pages/DashboardPage.vue +++ b/frontend/src/pages/DashboardPage.vue @@ -98,58 +98,64 @@ const tile = 'block rounded-lg border border-gray-200 bg-white p-4 hover:border- >Manage - - - - - - - - - - - -
{{ b.name }}{{ b.schedule }} - - -
No backup strategies configured.
+
+ + + + + + + + + + + +
{{ b.name }}{{ b.schedule }} + + +
No backup strategies configured.
+

Recent jobs

All jobs
- - - - - - - - - - - -
{{ j.kind }} - {{ j.status }} - {{ fmt(j.started_at) }} · {{ j.triggered_by }}
No jobs have run yet.
+
+ + + + + + + + + + + +
{{ j.kind }} + {{ j.status }} + + {{ fmt(j.started_at) }} · {{ j.triggered_by }} +
No jobs have run yet.
+
diff --git a/frontend/src/pages/JobsPage.vue b/frontend/src/pages/JobsPage.vue index 520c72b..fed18f5 100644 --- a/frontend/src/pages/JobsPage.vue +++ b/frontend/src/pages/JobsPage.vue @@ -74,33 +74,35 @@ const fmt = (d: string | null) => (d ? new Date(d).toLocaleString() : '–') - - - - - - - - - - - - - - - - - - - - - - - - -
JobStatusStartedFinishedTriggered by
{{ r.kind }}{{ fmt(r.started_at) }}{{ fmt(r.finished_at) }}{{ r.triggered_by }} - -
No job runs yet.
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
JobStatusStartedFinishedTriggered by
{{ r.kind }}{{ fmt(r.started_at) }}{{ fmt(r.finished_at) }}{{ r.triggered_by }} + +
No job runs yet.
+
diff --git a/frontend/src/pages/UsersPage.vue b/frontend/src/pages/UsersPage.vue index 713a4e5..9e87c60 100644 --- a/frontend/src/pages/UsersPage.vue +++ b/frontend/src/pages/UsersPage.vue @@ -86,44 +86,46 @@ async function resetPassword() {

Loading…

- - - - - - - - - - - - - - - - - - - -
EmailNameRoleStatus
{{ u.email }}{{ u.display_name }}{{ u.role }} - - - - -
+
+ + + + + + + + + + + + + + + + + + + +
EmailNameRoleStatus
{{ u.email }}{{ u.display_name }}{{ u.role }} + + + + +
+