Simplify failover settings UI
This commit is contained in:
@@ -76,27 +76,26 @@ function pulseNumber(event: FormEvent<HTMLInputElement>) {
|
||||
], { duration: 320, easing: 'cubic-bezier(0.16, 1, 0.3, 1)' });
|
||||
}
|
||||
|
||||
function FailoverNumberSetting({ before, after, value, min, max, step = 10, disabled, ariaLabel, onChange, compact = false }: {
|
||||
function FailoverNumberSetting({ before, after, value, min, max, disabled, ariaLabel, onChange }: {
|
||||
before: string;
|
||||
after: string;
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
disabled?: boolean;
|
||||
ariaLabel: string;
|
||||
onChange: (value: number) => void;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const set = (next: number) => onChange(Math.min(max, Math.max(min, next)));
|
||||
return <div className={`client-failover-number-setting${compact ? ' is-compact' : ''}`}>
|
||||
return <label className="client-failover-number-setting">
|
||||
<span>{before}</span>
|
||||
<input
|
||||
className="client-failover-number-input"
|
||||
type="number"
|
||||
min={min}
|
||||
max={max}
|
||||
aria-label={ariaLabel}
|
||||
value={value}
|
||||
style={{ width: `${Math.max(2, String(value).length + 0.6)}ch` }}
|
||||
disabled={disabled}
|
||||
onInput={pulseNumber}
|
||||
onChange={(event) => {
|
||||
@@ -105,11 +104,7 @@ function FailoverNumberSetting({ before, after, value, min, max, step = 10, disa
|
||||
}}
|
||||
/>
|
||||
<span>{after}</span>
|
||||
<span className="client-failover-number-steps">
|
||||
<button type="button" aria-label={`${ariaLabel}: уменьшить на ${step}`} disabled={disabled || value <= min} onClick={() => set(value - step)}>−{step}</button>
|
||||
<button type="button" aria-label={`${ariaLabel}: увеличить на ${step}`} disabled={disabled || value >= max} onClick={() => set(value + step)}>+{step}</button>
|
||||
</span>
|
||||
</div>;
|
||||
</label>;
|
||||
}
|
||||
|
||||
const reasonLabel = (reason: string | null) => ({
|
||||
@@ -315,8 +310,6 @@ export function FailoverPanel({
|
||||
const [serviceError, setServiceError] = useState('');
|
||||
const [openPicker, setOpenPicker] = useState('');
|
||||
const [removingCheckId, setRemovingCheckId] = useState('');
|
||||
const [advancedOpen, setAdvancedOpen] = useState(false);
|
||||
const [advancedClosing, setAdvancedClosing] = useState(false);
|
||||
const [manualChecking, setManualChecking] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!dirty) setDraft(snapshot.policy);
|
||||
@@ -422,15 +415,6 @@ export function FailoverPanel({
|
||||
if (!document.startViewTransition || matchMedia('(prefers-reduced-motion: reduce)').matches) finish();
|
||||
else document.startViewTransition(finish);
|
||||
};
|
||||
const toggleAdvanced = () => {
|
||||
if (!advancedOpen) {
|
||||
setAdvancedOpen(true);
|
||||
return;
|
||||
}
|
||||
if (matchMedia('(prefers-reduced-motion: reduce)').matches) setAdvancedOpen(false);
|
||||
else setAdvancedClosing(true);
|
||||
};
|
||||
|
||||
return <Drawer
|
||||
panelRef={feature.panelRef}
|
||||
closeRef={feature.closeRef}
|
||||
@@ -510,7 +494,7 @@ export function FailoverPanel({
|
||||
</section>
|
||||
|
||||
<fieldset className="client-failover-services">
|
||||
<legend>Что проверять</legend>
|
||||
<legend><span className="client-failover-section-header"><strong>Проверка доступности</strong><small>Harbor открывает эти адреса через оба канала и сравнивает результат.</small></span></legend>
|
||||
{draft.checks.map((check) => {
|
||||
const service = services.find(({ id }) => check.serviceId === id);
|
||||
const removing = removingCheckId === check.serviceId;
|
||||
@@ -525,12 +509,10 @@ export function FailoverPanel({
|
||||
>
|
||||
<span className="client-failover-service-name">{service?.label || 'Сервис недоступен'}</span>
|
||||
<FailoverNumberSetting
|
||||
compact
|
||||
before="Ждать ответ не дольше"
|
||||
after="секунд"
|
||||
min={2}
|
||||
max={30}
|
||||
step={1}
|
||||
ariaLabel={`Время ожидания ответа: ${service?.label || 'сервис недоступен'}`}
|
||||
value={seconds(check.timeoutMs)}
|
||||
disabled={blocked || !draft.enabled}
|
||||
@@ -578,6 +560,10 @@ export function FailoverPanel({
|
||||
</fieldset>
|
||||
|
||||
<section className="client-failover-timing" aria-label="Пороги переключения">
|
||||
<header className="client-failover-section-header">
|
||||
<h3>Когда переключать канал</h3>
|
||||
<p>Harbor сначала подтверждает сбой и только затем переводит новые соединения на рабочий канал.</p>
|
||||
</header>
|
||||
<FailoverNumberSetting before="Проверять каналы каждые" after="секунд" min={15} max={900} ariaLabel="Интервал проверки каналов" value={seconds(draft.intervalMs)} disabled={blocked || !draft.enabled} onChange={(value) => update({ intervalMs: value * 1_000 })} />
|
||||
<FailoverNumberSetting before="Считать основной недоступным после" after="секунд непрерывного сбоя" min={seconds(draft.intervalMs * 2)} max={1800} ariaLabel="Длительность сбоя основного канала" value={seconds(draft.failureWindowMs)} disabled={blocked || !draft.enabled} onChange={(value) => update({ failureWindowMs: value * 1_000 })} />
|
||||
<FailoverNumberSetting before="Вернуться на основной после" after="секунд стабильной работы" min={60} max={86400} ariaLabel="Время стабильной работы основного канала" value={seconds(draft.recoveryWindowMs)} disabled={blocked || !draft.enabled} onChange={(value) => update({ recoveryWindowMs: value * 1_000 })} />
|
||||
@@ -594,26 +580,17 @@ export function FailoverPanel({
|
||||
<FailoverNumberSetting before="Считать работу активной при скорости от" after="КБ/с" min={1} max={102400} ariaLabel="Скорость активной работы" value={Math.round(draft.trafficGuard.thresholdBytesPerSecond / 1024)} disabled={blocked || !draft.enabled || !draft.trafficGuard.enabled} onChange={(value) => update({ trafficGuard: { ...draft.trafficGuard, thresholdBytesPerSecond: value * 1024 } })} />
|
||||
</section>
|
||||
|
||||
<section className={`client-failover-advanced${advancedOpen ? ' is-open' : ''}${advancedClosing ? ' is-closing' : ''}`}>
|
||||
<button type="button" aria-expanded={advancedOpen && !advancedClosing} aria-controls="client-failover-advanced-options" onClick={toggleAdvanced}>
|
||||
<i aria-hidden="true" />
|
||||
<span>Защита от повторных сбоев</span>
|
||||
</button>
|
||||
{advancedOpen && <div
|
||||
id="client-failover-advanced-options"
|
||||
className="client-failover-advanced-options"
|
||||
inert={advancedClosing ? true : undefined}
|
||||
onAnimationEnd={(event) => {
|
||||
if (!advancedClosing || event.target !== event.currentTarget) return;
|
||||
setAdvancedClosing(false);
|
||||
setAdvancedOpen(false);
|
||||
}}
|
||||
>
|
||||
<section className="client-failover-protection" aria-labelledby="client-failover-protection-title">
|
||||
<header className="client-failover-section-header">
|
||||
<h3 id="client-failover-protection-title">Защита от частых переключений</h3>
|
||||
<p>Если основной канал падает снова, Harbor остаётся на резервном и даёт основному время восстановиться.</p>
|
||||
</header>
|
||||
<div className="client-failover-protection-options">
|
||||
<FailoverNumberSetting before="Оставаться на резервном не меньше" after="секунд" min={60} max={86400} ariaLabel="Минимальное время на резервном канале" value={seconds(draft.minimumReserveMs)} disabled={blocked || !draft.enabled} onChange={(value) => update({ minimumReserveMs: value * 1_000 })} />
|
||||
<FailoverNumberSetting before="Отправлять основной в карантин после" after="повторных сбоев" min={2} max={10} step={1} ariaLabel="Количество повторных сбоев" value={draft.flapProtection.count} disabled={blocked || !draft.enabled} onChange={(value) => update({ flapProtection: { ...draft.flapProtection, count: value } })} />
|
||||
<FailoverNumberSetting before="Считать повторными сбои за последние" after="часов" min={1} max={72} step={1} ariaLabel="Период повторных сбоев" value={Math.round(draft.flapProtection.windowMs / 3_600_000)} disabled={blocked || !draft.enabled} onChange={(value) => update({ flapProtection: { ...draft.flapProtection, windowMs: value * 3_600_000 } })} />
|
||||
<FailoverNumberSetting before="Отправлять основной в карантин после" after="повторных сбоев" min={2} max={10} ariaLabel="Количество повторных сбоев" value={draft.flapProtection.count} disabled={blocked || !draft.enabled} onChange={(value) => update({ flapProtection: { ...draft.flapProtection, count: value } })} />
|
||||
<FailoverNumberSetting before="Считать повторными сбои за последние" after="часов" min={1} max={72} ariaLabel="Период повторных сбоев" value={Math.round(draft.flapProtection.windowMs / 3_600_000)} disabled={blocked || !draft.enabled} onChange={(value) => update({ flapProtection: { ...draft.flapProtection, windowMs: value * 3_600_000 } })} />
|
||||
<FailoverNumberSetting before="Не возвращаться на основной после карантина" after="минут" min={10} max={10080} ariaLabel="Длительность карантина основного канала" value={Math.round(draft.flapProtection.quarantineMs / 60_000)} disabled={blocked || !draft.enabled} onChange={(value) => update({ flapProtection: { ...draft.flapProtection, quarantineMs: value * 60_000 } })} />
|
||||
</div>}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="client-failover-runtime">
|
||||
|
||||
Reference in New Issue
Block a user