Add initial server health check and update Harbor versions
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
export const HARBOR_VERSIONS = Object.freeze({
|
export const HARBOR_VERSIONS = Object.freeze({
|
||||||
macClient: '0.7.21',
|
macClient: '0.7.22',
|
||||||
gatewayClient: '0.7.21',
|
gatewayClient: '0.7.22',
|
||||||
gatewayBackend: '0.7.1',
|
gatewayBackend: '0.7.1',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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 { api } from '../api.js';
|
||||||
import {
|
import {
|
||||||
autoServer,
|
autoServer,
|
||||||
@@ -89,6 +89,7 @@ export function ServerPicker({
|
|||||||
const [collapsed, setCollapsed] = useState([]);
|
const [collapsed, setCollapsed] = useState([]);
|
||||||
const [pings, setPings] = useState({});
|
const [pings, setPings] = useState({});
|
||||||
const [checking, setChecking] = useState(false);
|
const [checking, setChecking] = useState(false);
|
||||||
|
const initialCheckStarted = useRef(false);
|
||||||
const serverKey = servers.map(({ id }) => id).join('|');
|
const serverKey = servers.map(({ id }) => id).join('|');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -156,14 +157,26 @@ export function ServerPicker({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialCheckStarted.current || !servers.length) return;
|
||||||
|
initialCheckStarted.current = true;
|
||||||
|
checkVisible();
|
||||||
|
}, [serverKey]);
|
||||||
|
|
||||||
if (servers.length === 1) {
|
if (servers.length === 1) {
|
||||||
return <section className="client-servers" aria-label="Выберите сервер">
|
return <section className="client-servers" aria-label="Выберите сервер">
|
||||||
{prompt && <span className="client-server-prompt">Выберите сервер</span>}
|
{prompt && <span className="client-server-prompt">Выберите сервер</span>}
|
||||||
|
<div className="client-server-toolbar is-single">
|
||||||
|
<button className="client-server-check" type="button" disabled={checking} onClick={checkVisible}>
|
||||||
|
{checking ? 'Проверяем…' : 'Проверить TCP'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div className="client-server-grid">
|
<div className="client-server-grid">
|
||||||
<ServerRow
|
<ServerRow
|
||||||
server={servers[0]}
|
server={servers[0]}
|
||||||
selected={servers[0].id === selectedServerId}
|
selected={servers[0].id === selectedServerId}
|
||||||
favorite={false}
|
favorite={false}
|
||||||
|
ping={pings[servers[0].id]}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
index={0}
|
index={0}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
@@ -193,16 +206,21 @@ export function ServerPicker({
|
|||||||
|
|
||||||
return <section className="client-servers is-scalable" aria-label="Выберите сервер">
|
return <section className="client-servers is-scalable" aria-label="Выберите сервер">
|
||||||
{prompt && <span className="client-server-prompt">Выберите сервер</span>}
|
{prompt && <span className="client-server-prompt">Выберите сервер</span>}
|
||||||
<button
|
<div className="client-server-toolbar">
|
||||||
className={`client-server-mode-toggle${advanced ? ' is-open' : ''}`}
|
<button
|
||||||
type="button"
|
className={`client-server-mode-toggle${advanced ? ' is-open' : ''}`}
|
||||||
aria-expanded={advanced}
|
type="button"
|
||||||
aria-label={advanced ? 'Скрыть поиск и фильтры' : 'Показать поиск и фильтры'}
|
aria-expanded={advanced}
|
||||||
onClick={() => setAdvanced((current) => !current)}
|
aria-label={advanced ? 'Скрыть поиск и фильтры' : 'Показать поиск и фильтры'}
|
||||||
>
|
onClick={() => setAdvanced((current) => !current)}
|
||||||
<span>Поиск и фильтры</span>
|
>
|
||||||
<svg viewBox="0 0 12 8" aria-hidden="true"><path d="m1 1 5 5 5-5" /></svg>
|
<span>Поиск и фильтры</span>
|
||||||
</button>
|
<svg viewBox="0 0 12 8" aria-hidden="true"><path d="m1 1 5 5 5-5" /></svg>
|
||||||
|
</button>
|
||||||
|
<button className="client-server-check" type="button" disabled={checking || !servers.length} onClick={checkVisible}>
|
||||||
|
{checking ? 'Проверяем…' : 'Проверить TCP'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="client-server-mode-panels">
|
<div className="client-server-mode-panels">
|
||||||
<div
|
<div
|
||||||
@@ -256,9 +274,6 @@ export function ServerPicker({
|
|||||||
key={id}
|
key={id}
|
||||||
onClick={() => setView(id)}
|
onClick={() => setView(id)}
|
||||||
>{label}</button>)}
|
>{label}</button>)}
|
||||||
<button type="button" disabled={checking || (!selectedServerId && !visible.length)} onClick={checkVisible}>
|
|
||||||
{checking ? 'Проверяем…' : 'Проверить TCP'}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2728,13 +2728,26 @@ p {
|
|||||||
margin-bottom: 14px;
|
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 {
|
.client-server-mode-toggle {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 7px;
|
gap: 7px;
|
||||||
margin: 0 auto 8px;
|
grid-column: 2;
|
||||||
|
margin: 0;
|
||||||
padding: 5px 8px;
|
padding: 5px 8px;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -2744,6 +2757,34 @@ p {
|
|||||||
transition: color 220ms ease, text-shadow 320ms ease;
|
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:hover,
|
||||||
.client-server-mode-toggle.is-open {
|
.client-server-mode-toggle.is-open {
|
||||||
color: var(--client-accent);
|
color: var(--client-accent);
|
||||||
|
|||||||
@@ -36,8 +36,11 @@ test('server picker handles 1, 30 and 300 stable-ID servers with duplicate label
|
|||||||
assert.equal(SERVER_RESULT_WINDOW, 60);
|
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.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, /onClick={checkVisible}/);
|
||||||
assert.match(picker, /\.slice\(page \* SERVER_RESULT_WINDOW, \(page \+ 1\) \* SERVER_RESULT_WINDOW\)/);
|
assert.match(picker, /\.slice\(page \* SERVER_RESULT_WINDOW, \(page \+ 1\) \* SERVER_RESULT_WINDOW\)/);
|
||||||
assert.match(picker, /\.slice\(0, 30\)/);
|
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', () => {
|
test('manual health remains visible for Auto and simple rows', () => {
|
||||||
assert.match(picker, /autoActive \? serverHealthText\(pings\[selectedServerId\]\)/);
|
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\]}/);
|
assert.match(picker, /simpleServers\.map[\s\S]*?ping={pings\[server\.id\]}/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user