diff --git a/.gitignore b/.gitignore index 83f4683..6dff8e9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ data/ # Local roadmap and task workspace /workpack/ +/design-qa.md # Node/Vite node_modules/ diff --git a/src/shared/versions.ts b/src/shared/versions.ts index 67876ea..37aab49 100644 --- a/src/shared/versions.ts +++ b/src/shared/versions.ts @@ -1,6 +1,6 @@ export const HARBOR_VERSIONS = Object.freeze({ - macClient: '0.35.0', - gatewayClient: '0.37.0', + macClient: '0.35.1', + gatewayClient: '0.37.1', gatewayBackend: '0.37.0', }); diff --git a/src/web/features/failover/FailoverFeature.tsx b/src/web/features/failover/FailoverFeature.tsx index 07e23c0..c75205b 100644 --- a/src/web/features/failover/FailoverFeature.tsx +++ b/src/web/features/failover/FailoverFeature.tsx @@ -482,6 +482,9 @@ export function FailoverPanel({ const selectedId = channelOptions.find(({ target }) => ( target.profileId === draft[channel].profileId && target.serverId === draft[channel].serverId ))?.id || ''; + const failureProgress = !checking && channel === 'primary' && snapshot.reason === 'failure-window' + ? `${seconds(Math.max(0, Date.now() - Date.parse(snapshot.primary.stateSince || new Date().toISOString())))} / ${seconds(draft.failureWindowMs)} с` + : ''; const healthLabel = missing ? 'Сервер не найден' : !draft.enabled @@ -490,15 +493,20 @@ export function FailoverPanel({ ? 'VPN выключен' : snapshot.activation === 'pending' ? 'Проверим после запуска VPN' - : channel === 'primary' && snapshot.reason === 'failure-window' - ? `Проверяем основной канал · ${seconds(Math.max(0, Date.now() - Date.parse(snapshot.primary.stateSince || new Date().toISOString())))} из ${seconds(draft.failureWindowMs)} с` + : checking + ? `Проверяем ${channel === 'primary' ? 'основной' : 'резервный'} канал` + : failureProgress + ? 'Проверяем основной канал' : channel === 'primary' && snapshot.currentRole === 'reserve' && health === 'healthy' && snapshot.reason === 'recovery-hold' ? 'Проверяем восстановление' : health === 'healthy' ? 'Работает' : health === 'unhealthy' ? 'Недоступен' : health === 'not-monitoring' ? 'Проверка выключена' : 'Ждёт проверки'; return

{channel === 'primary' ? 'Основной' : 'Резервный'}

- {healthLabel} +
+
+ {healthLabel} + {failureProgress}
setOpenPicker(open ? channel : '')} onChange={(optionId) => updateTarget(channel, optionId)} /> -
{channel === 'primary' &&