feat: improve UI contrast, dark mode, dialog sizing, color coherence, and add table sorting

- Fix dialog scrollability (flex layout + max-h-[90dvh]) and increase L4 dialog to lg width
- Add styled enable card to L4 dialog matching proxy host pattern
- Unify section colors across proxy host and L4 dialogs (cyan=LB, emerald=DNS, violet=upstream DNS, rose=geo, amber=mTLS)
- Improve light mode contrast: muted-foreground oklch 0.552→0.502, remove opacity modifiers on secondary text
- Improve dark mode: boost muted-foreground to 0.85, increase border opacity 10%→16%, input 15%→20%
- Add bg-card to DataTable wrapper and bg-muted/40 to table headers for surface hierarchy
- Add semantic badge variants (success, warning, info, muted) and StatusChip dark mode fix
- Add server-side sortable columns to Proxy Hosts and L4 Proxy Hosts (name, upstream, status, protocol, listen)
- Add sortKey to DataTable Column type with clickable sort headers (ArrowUp/Down indicators, URL param driven)
- Fix E2E test selectors for shadcn UI (label associations, combobox roles, dropdown menus, mobile drawer)
- Add htmlFor/id to proxy host form fields and aria-labels to select triggers for accessibility
- Add sorting E2E tests for both proxy host pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
fuomag9
2026-03-22 22:17:56 +01:00
parent 65753f6a8d
commit 9c60d11c2c
45 changed files with 1616 additions and 1052 deletions

View File

@@ -55,14 +55,14 @@ test.describe.serial('L4 TCP Proxy Routing', () => {
test('disabled TCP proxy host stops accepting connections', async ({ page }) => {
await page.goto('/l4-proxy-hosts');
const row = page.locator('tr', { hasText: 'L4 TCP Echo Test' });
await row.locator('input[type="checkbox"]').first().click({ force: true });
await row.getByRole('switch').click();
await page.waitForTimeout(3_000);
const connected = await tcpConnect('127.0.0.1', TCP_PORT, 2000);
expect(connected).toBe(false);
// Re-enable
await row.locator('input[type="checkbox"]').first().click({ force: true });
await row.getByRole('switch').click();
await page.waitForTimeout(2_000);
});
});
@@ -121,14 +121,14 @@ test.describe.serial('L4 UDP Proxy Routing', () => {
test('disabled UDP proxy host stops responding', async ({ page }) => {
await page.goto('/l4-proxy-hosts');
const row = page.locator('tr', { hasText: 'L4 UDP Echo Test' });
await row.locator('input[type="checkbox"]').first().click({ force: true });
await row.getByRole('switch').click();
await page.waitForTimeout(3_000);
const res = await udpSend('127.0.0.1', UDP_PORT, 'should-fail', 2000);
expect(res.received).toBe(false);
// Re-enable
await row.locator('input[type="checkbox"]').first().click({ force: true });
await row.getByRole('switch').click();
await page.waitForTimeout(2_000);
});
});

View File

@@ -30,7 +30,7 @@ test.describe.serial('Path Prefix Rewrite', () => {
await injectFormFields(page, { ssl_forced_present: 'on' });
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Functional Path Prefix Rewrite Test')).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('table').getByText('Functional Path Prefix Rewrite Test')).toBeVisible({ timeout: 10_000 });
await waitForRoute(DOMAIN);
});

View File

@@ -48,8 +48,8 @@ test.describe.serial('Proxy Routing', () => {
test('disabled proxy host stops routing traffic', async ({ page }) => {
await page.goto('/proxy-hosts');
const row = page.locator('tr', { hasText: 'Functional Proxy Test' });
// Toggle the enabled switch (first checkbox inside the row)
await row.locator('input[type="checkbox"]').first().click({ force: true });
// Toggle the enabled switch (shadcn Switch renders as button with role="switch")
await row.getByRole('switch').click();
// Give Caddy time to reload config
await page.waitForTimeout(3_000);
@@ -60,7 +60,7 @@ test.describe.serial('Proxy Routing', () => {
expect(res.body).not.toContain(ECHO_BODY);
// Re-enable
await row.locator('input[type="checkbox"]').first().click({ force: true });
await row.getByRole('switch').click();
await page.waitForTimeout(2_000);
});
});

View File

@@ -68,7 +68,7 @@ test.describe.serial('Redirect Rules full URLs, cross-domain, wildcards', ()
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Functional Advanced Redirects Test')).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('table').getByText('Functional Advanced Redirects Test')).toBeVisible({ timeout: 10_000 });
await waitForRoute(DOMAIN);
});

View File

@@ -40,7 +40,7 @@ test.describe.serial('Per-path Redirect Rules', () => {
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Functional Redirects Test')).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('table').getByText('Functional Redirects Test')).toBeVisible({ timeout: 10_000 });
await waitForRoute(DOMAIN);
});

View File

@@ -33,7 +33,7 @@ test.describe.serial('SSL Redirect (ssl_forced)', () => {
await page.getByRole('button', { name: /^create$/i }).click();
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 15_000 });
await expect(page.getByText('Functional SSL Redirect Test')).toBeVisible({ timeout: 10_000 });
await expect(page.getByRole('table').getByText('Functional SSL Redirect Test')).toBeVisible({ timeout: 10_000 });
await waitForRoute(DOMAIN);
});