Refactor VPN proxy components and update related behavior
Build and Deploy Gateway / build-and-push (push) Successful in 20s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-11 01:27:46 +03:00
parent c89e56942a
commit aa9c959368
58 changed files with 4234 additions and 2755 deletions
+19 -16
View File
@@ -22,7 +22,7 @@ function rule(selector, source = styles) {
return new RegExp(`^${escaped}\\s*\\{([\\s\\S]*?)\\n\\}`, 'm').exec(source)?.[1] || '';
}
test('desktop layout keeps the power control on a symmetric center axis', () => {
test('desktop layout keeps the power control on a symmetric center axis beside the subscription drawer', () => {
const panel = rule('.client-panel');
const power = rule('.client-power-section');
const form = rule('.client-form');
@@ -33,12 +33,11 @@ test('desktop layout keeps the power control on a symmetric center axis', () =>
assert.match(form, /position:\s*static/);
assert.match(form, /overflow:\s*visible/);
assert.doesNotMatch(form, /\bleft\s*:|translateX|transition:[^;]*(?:left|width|transform)/);
assert.match(styles, /\.client-panel\.has-subscription \.client-form \{[\s\S]*height:\s*var\(--client-work-height\)/);
assert.match(rule('.client-form-content'), /align-content:\s*start/);
assert.doesNotMatch(styles, /\.client-panel\.has-subscription \.client-form\b/);
assert.doesNotMatch(styles, /\.client-form-content\b/);
assert.match(styles, /--client-power-top:\s*calc\(\(var\(--client-work-height\) - 96px\) \/ 2\)/);
assert.match(styles, /\.client-panel\.has-subscription \{[\s\S]*transform:\s*translateY\(-9vh\)/);
assert.match(styles, /\.client-panel\.has-subscription \.client-power-section \{[\s\S]*height:\s*var\(--client-work-height\);[\s\S]*padding-top:\s*var\(--client-power-top\)/);
assert.match(styles, /\.client-panel\.has-subscription \.client-form-content \{[\s\S]*padding-top:\s*var\(--client-power-top\)/);
assert.match(rule('.client-panel.is-gateway-home'), /min-height:\s*max\(620px, calc\(100dvh - 80px\)\)[\s\S]*grid-template-rows:\s*minmax\(260px, 1fr\) auto[\s\S]*row-gap:\s*36px/);
assert.match(rule('.client-gateway-summary'), /grid-column:\s*1 \/ -1[\s\S]*grid-row:\s*2[\s\S]*justify-self:\s*center[\s\S]*width:\s*min\(860px, calc\(100% - 48px\)\)/);
assert.match(styles, /\.client-power-section\.is-gateway \.client-power-control,[\s\S]*width:\s*84px;[\s\S]*height:\s*84px/);
@@ -49,9 +48,11 @@ test('desktop layout keeps the power control on a symmetric center axis', () =>
assert.match(rule('.client-gateway-traffic-chart'), /min-height:\s*180px/);
assert.match(styles, /@media \(max-width: 560px\)[\s\S]*\.client-gateway-traffic-chart,[\s\S]*min-height:\s*130px;[\s\S]*height:\s*130px/);
assert.doesNotMatch(rule('.client-gateway-summary'), /background|border|box-shadow/);
assert.match(rule('.client-gateway-route-slot'), /min-height:\s*16px[\s\S]*overflow:\s*hidden[\s\S]*text-overflow:\s*ellipsis[\s\S]*white-space:\s*nowrap/);
assert.match(rule('.client-applied-operation'), /min-height:\s*15px[\s\S]*overflow:\s*hidden[\s\S]*text-overflow:\s*ellipsis[\s\S]*white-space:\s*nowrap/);
assert.match(component, /client-panel\$\{showPower \? '' : ' is-setup'\}[\s\S]*is-gateway-home/);
assert.match(component, /const showPower = isGateway \|\| \(hasSubscription && Boolean\(selectedServerId\)\)/);
assert.match(component, /const showPower = hasSubscription/);
assert.match(component, /\$\{!hasSubscription \? ' is-first-run' : ''\}/);
assert.match(component, /\$\{isGateway && hasSubscription \? ' is-gateway-home' : ''\}/);
});
test('page reload plays one stable startup sequence', () => {
@@ -66,7 +67,7 @@ test('page reload plays one stable startup sequence', () => {
assert.match(styles, /@media \(prefers-reduced-motion: reduce\)[\s\S]*\.client-shell\.is-intro \.client-panel,[\s\S]*animation: none/);
});
test('server rows scroll without moving the subscription column or showing a scrollbar', () => {
test('server rows scroll inside the subscription drawer without moving the main layout', () => {
const scroll = rule('.client-server-scroll');
const simpleScroll = rule('.client-server-mode-panel.is-simple .client-server-scroll');
const grid = rule('.client-server-grid');
@@ -77,7 +78,8 @@ test('server rows scroll without moving the subscription column or showing a scr
assert.match(simpleScroll, /max-height:\s*none/);
assert.match(simpleScroll, /overflow:\s*visible/);
assert.match(grid, /width:\s*min\(100%, 220px\)/);
assert.match(rule('.client-form-content'), /gap:\s*24px/);
assert.match(rule('.client-drawer'), /overflow-y:\s*auto/);
assert.match(subscription, /client-profile-list[\s\S]*feature\.profiles\.map\(\(profile\) => <ProfileGroup/);
assert.match(rule('.client-server-toolbar-title'), /grid-column:\s*2/);
assert.match(rule('.client-server-mode-toggle'), /grid-row:\s*2/);
assert.match(rule('.client-server-check'), /grid-column:\s*3/);
@@ -101,7 +103,7 @@ test('tablet and mobile regions use normal flow with viewport-safe widths', () =
assert.match(rule('.client-duration-toggle'), /width:\s*min\(290px, 100%\)/);
assert.match(mobile, /\.client-server-toolbar \{[\s\S]*grid-template-rows:\s*44px 44px/);
assert.match(mobile, /\.client-form-content \{[\s\S]*gap:\s*24px/);
assert.match(mobile, /\.client-secondary-menu \{[\s\S]*right:\s*8px/);
assert.match(mobile, /\.client-power-control > \.harbor-brand \{[\s\S]*scale\(1\.35\)/);
assert.match(mobile, /\.client-server-check \{[\s\S]*width:\s*44px/);
assert.match(styles, /\.client-instructions\s*\{[\s\S]*width:\s*min\(470px, 100vw\)/);
@@ -239,13 +241,14 @@ test('tooltips stay opaque, above adjacent content, and do not stick after point
assert.match(rule('.harbor-mode-tooltip'), /background:\s*oklch\(0\.14 0\.012 145\)/);
});
test('subscription validation waits for the provider and keeps diagnostics below errors', () => {
assert.match(subscription, /validateSubscription\(normalizedUrl/);
assert.match(subscription, /status: 'checking'/);
assert.match(subscription, /status: 'valid'/);
assert.match(subscription, /message: ERROR_DEFINITIONS\.SUBSCRIPTION_INVALID\.message/);
assert.match(subscription, /validationStatus === 'checking' \? '…' : '×'/);
assert.match(subscription, /if \(error\?\.context === 'subscription'\) onDismissError\(\)/);
test('subscription validation is local and provider errors keep stable detail slots', () => {
assert.match(subscription, /const validationStatus = !normalizedUrl[\s\S]*isSubscriptionUrlValid\(normalizedUrl\) \? 'valid' : 'invalid'/);
assert.doesNotMatch(subscription, /validateSubscription\(|AbortController|status: 'checking'/);
assert.match(subscription, /const message = feature\.duplicateLabel[\s\S]*feature\.validationStatus === 'invalid'[\s\S]*ERROR_DEFINITIONS\.SUBSCRIPTION_INVALID\.message[\s\S]*feature\.addError\?\.message/);
assert.match(subscription, /aria-invalid=\{feature\.validationStatus === 'invalid'\}/);
assert.match(subscription, /disabled=\{feature\.addBlocked \|\| !feature\.label\.trim\(\) \|\| feature\.duplicateLabel \|\| feature\.validationStatus !== 'valid'\}/);
assert.match(subscription, /if \(!await onAdd\(normalizedLabel, normalizedUrl\)\) return;[\s\S]*resetAdd\(\)/);
assert.match(subscription, /setUrl: \(value: string\) => \{[\s\S]*onDismissError\(\)/);
assert.match(component, /error\.retry[\s\S]*error\.correlationId/);
assert.match(rule('.client-inline-error.is-subscription small'), /flex-basis:\s*100%/);
assert.match(rule('.client-inline-error.is-subscription small'), /opacity:\s*0\.45/);