From 5874df2fce5c31d1c330dcfe99dafaf047883696 Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Sun, 12 Jul 2026 18:49:45 +0300 Subject: [PATCH] Add initial server health check and update Harbor versions --- src/shared/versions.js | 4 +-- src/web/components/ServerPicker.jsx | 43 +++++++++++++++++++---------- src/web/styles.css | 43 ++++++++++++++++++++++++++++- test/web/server-picker.test.js | 8 ++++-- 4 files changed, 79 insertions(+), 19 deletions(-) diff --git a/src/shared/versions.js b/src/shared/versions.js index 164aa3a..d797557 100644 --- a/src/shared/versions.js +++ b/src/shared/versions.js @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.7.21', - gatewayClient: '0.7.21', + macClient: '0.7.22', + gatewayClient: '0.7.22', gatewayBackend: '0.7.1', }); diff --git a/src/web/components/ServerPicker.jsx b/src/web/components/ServerPicker.jsx index 2a9ae55..f276e79 100644 --- a/src/web/components/ServerPicker.jsx +++ b/src/web/components/ServerPicker.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { api } from '../api.js'; import { autoServer, @@ -89,6 +89,7 @@ export function ServerPicker({ const [collapsed, setCollapsed] = useState([]); const [pings, setPings] = useState({}); const [checking, setChecking] = useState(false); + const initialCheckStarted = useRef(false); const serverKey = servers.map(({ id }) => id).join('|'); useEffect(() => { @@ -156,14 +157,26 @@ export function ServerPicker({ } } + useEffect(() => { + if (initialCheckStarted.current || !servers.length) return; + initialCheckStarted.current = true; + checkVisible(); + }, [serverKey]); + if (servers.length === 1) { return
{prompt && Выберите сервер} +
+ +
{prompt && Выберите сервер} - +
+ + +
setView(id)} >{label})} -
diff --git a/src/web/styles.css b/src/web/styles.css index ce567d3..6f8f7c5 100644 --- a/src/web/styles.css +++ b/src/web/styles.css @@ -2728,13 +2728,26 @@ p { margin-bottom: 14px; } +.client-server-toolbar { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + margin-bottom: 8px; +} + +.client-server-toolbar.is-single { + display: flex; + justify-content: center; +} + .client-server-mode-toggle { min-height: 32px; display: flex; align-items: center; justify-content: center; gap: 7px; - margin: 0 auto 8px; + grid-column: 2; + margin: 0; padding: 5px 8px; border: 0; background: transparent; @@ -2744,6 +2757,34 @@ p { transition: color 220ms ease, text-shadow 320ms ease; } +.client-server-check { + grid-column: 3; + justify-self: start; + width: 88px; + min-height: 32px; + padding: 5px 7px; + border: 0; + background: transparent; + color: var(--client-muted); + font: 700 8px/1.2 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, monospace; + cursor: pointer; +} + +.client-server-check:hover:not(:disabled), +.client-server-check:focus-visible { + color: var(--client-accent); +} + +.client-server-check:focus-visible { + outline: 2px solid var(--client-accent); + outline-offset: 1px; +} + +.client-server-check:disabled { + cursor: default; + opacity: 0.55; +} + .client-server-mode-toggle:hover, .client-server-mode-toggle.is-open { color: var(--client-accent); diff --git a/test/web/server-picker.test.js b/test/web/server-picker.test.js index 3853289..9104335 100644 --- a/test/web/server-picker.test.js +++ b/test/web/server-picker.test.js @@ -36,8 +36,11 @@ test('server picker handles 1, 30 and 300 stable-ID servers with duplicate label assert.equal(SERVER_RESULT_WINDOW, 60); }); -test('server picker keeps health manual and the rendered result window bounded', () => { +test('server picker checks health once on load, keeps manual refresh and bounds the result window', () => { assert.doesNotMatch(overview, /pingAll|servers\.ping/); + assert.match(picker, /const initialCheckStarted = useRef\(false\)/); + 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, /\.slice\(page \* SERVER_RESULT_WINDOW, \(page \+ 1\) \* SERVER_RESULT_WINDOW\)/); assert.match(picker, /\.slice\(0, 30\)/); @@ -50,7 +53,8 @@ test('server picker keeps health manual and the rendered result window bounded', test('manual health remains visible for Auto and simple rows', () => { assert.match(picker, /autoActive \? serverHealthText\(pings\[selectedServerId\]\)/); - assert.match(picker, /disabled={checking \|\| \(!selectedServerId && !visible\.length\)}/); + assert.match(picker, /className="client-server-check"/); + assert.match(picker, /server={servers\[0\]}[\s\S]*?ping={pings\[servers\[0\]\.id\]}/); assert.match(picker, /simpleServers\.map[\s\S]*?ping={pings\[server\.id\]}/); });