diff --git a/src/shared/versions.js b/src/shared/versions.js index d797557..4be04d7 100644 --- a/src/shared/versions.js +++ b/src/shared/versions.js @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.7.22', - gatewayClient: '0.7.22', + macClient: '0.7.23', + gatewayClient: '0.7.23', gatewayBackend: '0.7.1', }); diff --git a/src/web/components/ServerPicker.jsx b/src/web/components/ServerPicker.jsx index f276e79..2926e46 100644 --- a/src/web/components/ServerPicker.jsx +++ b/src/web/components/ServerPicker.jsx @@ -38,14 +38,40 @@ function readAuto() { } function serverHealthText(ping) { - if (ping?.checking) return 'Проверяем TCP…'; if (ping?.error) return 'Проверка недоступна'; if (ping?.ok) return `TCP ${ping.latency} мс`; return ping ? 'TCP недоступен' : 'Не проверен'; } +function ServerHealth({ ping, fallback }) { + const health = fallback || serverHealthText(ping); + return + + + ; +} + +function ServerCheckButton({ checking, disabled, onClick }) { + return ; +} + function ServerRow({ server, selected, favorite, ping, disabled, index, onSelect, onFavorite }) { - const health = serverHealthText(ping); + const health = ping?.checking ? 'Проверяем TCP' : serverHealthText(ping); return
{onFavorite && +
Поиск и фильтры - +
@@ -286,9 +314,10 @@ export function ServerPicker({ onClick={() => select(autoServer(servers)?.id, true)} > Auto - - {autoActive ? serverHealthText(pings[selectedServerId]) : 'Первый стабильный сервер'} - + {selected && span { + grid-area: 1 / 1; + transition: opacity 420ms ease, filter 520ms cubic-bezier(0.16, 1, 0.3, 1), color 520ms ease; +} + +.client-server-health-checking { + color: var(--client-accent); + opacity: 0; + filter: blur(4px); + text-shadow: 0 0 8px color-mix(in oklch, var(--client-accent) 42%, transparent); +} + +.client-server-health.is-checking > span:first-child { + opacity: 0.32; + filter: blur(2px); +} + +.client-server-health.is-checking .client-server-health-checking { + opacity: 1; + filter: blur(0); +} + .client-servers.is-scalable { width: min(100%, 360px); margin-inline: auto; @@ -2766,8 +2794,37 @@ p { border: 0; background: transparent; color: var(--client-muted); - font: 700 8px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + font: 700 9px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; cursor: pointer; + transition: color 300ms ease, opacity 300ms ease, text-shadow 500ms ease; +} + +.client-server-check svg { + width: 13px; + height: 13px; + fill: none; + stroke: currentColor; + stroke-width: 1.6; + stroke-linecap: round; + stroke-linejoin: round; +} + +.client-server-check.is-checking { + color: var(--client-accent); + opacity: 1; + text-shadow: 0 0 9px color-mix(in oklch, var(--client-accent) 46%, transparent); +} + +.client-server-check.is-checking svg { + animation: client-server-check-spin 700ms linear infinite; +} + +@keyframes client-server-check-spin { + to { transform: rotate(360deg); } } .client-server-check:hover:not(:disabled), @@ -2782,6 +2839,9 @@ p { .client-server-check:disabled { cursor: default; +} + +.client-server-check:disabled:not(.is-checking) { opacity: 0.55; } @@ -3553,6 +3613,9 @@ p { .client-usage > strong, .client-server, .client-server-auto, + .client-server-health > span, + .client-server-check, + .client-server-check svg, .client-server-mode-toggle, .client-server-mode-toggle svg, .client-server-mode-panel, diff --git a/test/web/server-picker.test.js b/test/web/server-picker.test.js index 9104335..2d5a0f5 100644 --- a/test/web/server-picker.test.js +++ b/test/web/server-picker.test.js @@ -42,6 +42,9 @@ test('server picker checks health once on load, keeps manual refresh and bounds assert.match(picker, /if \(initialCheckStarted\.current \|\| !servers\.length\) return/); assert.match(picker, /initialCheckStarted\.current = true;\s*checkVisible\(\)/); assert.match(picker, /onClick={checkVisible}/); + assert.match(picker, /\{ \.\.\.current\[id\], checking: true \}/); + assert.match(picker, /700 - \(performance\.now\(\) - startedAt\)/); + assert.match(picker, /\{ \.\.\.current\[id\], checking: false \}/); assert.match(picker, /\.slice\(page \* SERVER_RESULT_WINDOW, \(page \+ 1\) \* SERVER_RESULT_WINDOW\)/); assert.match(picker, /\.slice\(0, 30\)/); assert.match(picker, /Math\.min\(index, 7\)/); @@ -52,8 +55,9 @@ test('server picker checks health once on load, keeps manual refresh and bounds }); test('manual health remains visible for Auto and simple rows', () => { - assert.match(picker, /autoActive \? serverHealthText\(pings\[selectedServerId\]\)/); - assert.match(picker, /className="client-server-check"/); + assert.match(picker, /function ServerHealth/); + assert.match(picker, /client-server-health-checking/); + assert.match(picker, /function ServerCheckButton/); assert.match(picker, /server={servers\[0\]}[\s\S]*?ping={pings\[servers\[0\]\.id\]}/); assert.match(picker, /simpleServers\.map[\s\S]*?ping={pings\[server\.id\]}/); });