diff --git a/src/shared/versions.ts b/src/shared/versions.ts
index 08b5ce3..949de41 100644
--- a/src/shared/versions.ts
+++ b/src/shared/versions.ts
@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({
- macClient: '0.31.1',
- gatewayClient: '0.32.1',
+ macClient: '0.31.2',
+ gatewayClient: '0.32.2',
gatewayBackend: '0.32.2',
});
diff --git a/src/web/features/failover/FailoverFeature.tsx b/src/web/features/failover/FailoverFeature.tsx
index ad55b60..07e23c0 100644
--- a/src/web/features/failover/FailoverFeature.tsx
+++ b/src/web/features/failover/FailoverFeature.tsx
@@ -131,37 +131,6 @@ function FailoverNumberSetting({ before, after, value, min, max, step = 10, disa
;
}
-const reasonLabel = (reason: string | null) => ({
- 'primary-healthy': 'Основной канал работает',
- 'health-unknown': 'Ждём первую проверку',
- 'checking-channels': 'Проверяем оба канала',
- 'failure-window': 'Убеждаемся, что основной канал не работает',
- 'reserve-not-healthy': 'Ждём, пока резервный канал начнёт работать',
- 'both-unhealthy': 'Оба канала недоступны',
- 'primary-not-recovered': 'Основной канал ещё не восстановился',
- 'recovery-hold': 'Убеждаемся, что основной канал работает без сбоев',
- 'activity-unknown': 'Не удалось проверить передачу данных',
- 'active-traffic': 'Ждём паузу в передаче данных',
- 'quiet-window': 'Проверяем, что передача данных остановилась',
- 'primary-failed': 'Основной канал недоступен',
- 'primary-recovered': 'Основной канал восстановился',
- 'pending-activation': 'Настройка применится при запуске VPN',
- 'vpn-stopped': 'VPN выключен',
- paused: 'Автоматическое переключение приостановлено',
- disabled: 'Резервный канал выключен',
- 'switch-failed': 'Не удалось переключить канал',
- 'selector-unknown': 'Не удалось проверить выбранный канал',
- 'reconcile-failed': 'Настройки сохранены. Повторим проверку позже',
- 'revalidation-required': 'Проверяем условия ещё раз',
- 'manual-check': 'Оба канала проверены',
-}[reason || ''] || 'Следим за каналами');
-
-function targetLabel(profiles: ProfileSnapshot[], target: { profileId: string; serverId: string }) {
- const profile = profiles.find(({ id }) => id === target.profileId);
- const server = profile?.servers.find(({ id }) => id === target.serverId);
- return profile && server ? channelLabel(profile.label, server.label) : 'Не выбран';
-}
-
const channelLabel = (profile: string, server: string) => profile.trim() === server.trim()
? server
: `${profile} · ${server}`;
@@ -409,16 +378,15 @@ export function FailoverPanel({
? snapshot.currentRole
: null;
const switchRole = role === 'primary' ? 'reserve' : role === 'reserve' ? 'primary' : null;
+ const switchActionLabel = role && switchRole
+ ? `Сейчас ${role === 'primary' ? 'основной' : 'резервный'} канал. Переключить на ${switchRole === 'primary' ? 'основной' : 'резервный'} канал`
+ : 'Переключение недоступно';
const checking = manualChecking || snapshot.reason === 'checking-channels';
- const activity = snapshot.trafficActivity;
const targetExists = (target: FailoverPolicy['primary']) => profiles
.find(({ id }) => id === target.profileId)?.servers.some(({ id }) => id === target.serverId);
const targetsValid = targetExists(draft.primary) && targetExists(draft.reserve)
&& (draft.primary.profileId !== draft.reserve.profileId || draft.primary.serverId !== draft.reserve.serverId);
const draftValid = !draft.enabled || Boolean(targetsValid && draft.checks.length);
- const quietElapsed = activity?.quietSince
- ? Math.max(0, Date.now() - Date.parse(activity.quietSince))
- : 0;
const addService = async () => {
try {
const saved = await saveCustomDiagnosticService({
@@ -542,6 +510,9 @@ export function FailoverPanel({
onOpenChange={(open) => setOpenPicker(open ? channel : '')}
onChange={(optionId) => updateTarget(channel, optionId)}
/>
+
+ Проверяем {channel === 'primary' ? 'основной' : 'резервный'} канал
+
{channel === 'primary' &&
};
})}
-
- {(manualChecking || snapshot.reason === 'checking-channels' ? ['primary', 'reserve'] as const : []).map((checkingRole) => Проверяем {checkingRole === 'primary' ? 'основной' : 'резервный'} канал)}
-
-
-
-
- Сейчас используется
- {blocked
- ? 'Harbor завершает текущее действие'
- : role === 'primary'
- ? 'Основной канал'
- : role === 'reserve'
- ? 'Резервный канал'
- : 'Канал пока не выбран'}
- {blocked ? 'Подождите…' : reasonLabel(snapshot.reason)}
- {role
- ? `Новые подключения: ${targetLabel(profiles, snapshot[role].target)}`
- : snapshot.reason === 'vpn-stopped'
- ? `При запуске VPN будет выбран: ${targetLabel(profiles, draft.primary)}`
- : `После перезапуска VPN будет выбран: ${targetLabel(profiles, draft.primary)}`}
- {snapshot.nextDecisionAt && Проверим каналы снова через {seconds(Math.max(0, Date.parse(snapshot.nextDecisionAt) - Date.now()))} с}
- {activity && {activity.state === 'active' ? `Сейчас передаётся ${Math.round(activity.totalBytesPerSecond / 1024)} КБ/с. Активных подключений: ${activity.transmittingConnections}` : activity.state === 'quiet' ? `Передачи данных нет: ${Math.min(seconds(quietElapsed), seconds(draft.trafficGuard.quietWindowMs))} из ${seconds(draft.trafficGuard.quietWindowMs)} с` : 'Не удалось проверить передачу данных. Автоматическое переключение остановлено'}}
- {activity?.blockers.slice(0, 2).map((blocker) => {blocker.device} · {blocker.service} · {Math.round((blocker.uploadBytesPerSecond + blocker.downloadBytesPerSecond) / 1024)} КБ/с)}
- {activity && activity.blockers.length > 2 && Ещё {activity.blockers.length - 2}}
+ {checking ? 'Проверяем основной и резервный каналы' : ''}