Refine failover channel status layout
This commit is contained in:
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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 <div className="client-failover-channel-slot" key={channel}><article className="client-failover-channel" data-channel={channel}>
|
||||
<div className="client-failover-channel-title">
|
||||
<h3>{channel === 'primary' ? 'Основной' : 'Резервный'}</h3>
|
||||
<strong className={health === 'healthy' ? 'is-healthy' : health === 'unhealthy' || missing ? 'is-unhealthy' : ''}>{healthLabel}</strong>
|
||||
</div>
|
||||
<div className="client-failover-channel-status">
|
||||
<strong className={healthLabel === 'Работает' ? 'is-healthy' : healthLabel === 'Недоступен' || missing ? 'is-unhealthy' : ''}>{healthLabel}</strong>
|
||||
<span className="client-failover-channel-progress">{failureProgress}</span>
|
||||
</div>
|
||||
<FailoverPicker
|
||||
id={`client-failover-${channel}-options`}
|
||||
@@ -510,9 +518,6 @@ export function FailoverPanel({
|
||||
onOpenChange={(open) => setOpenPicker(open ? channel : '')}
|
||||
onChange={(optionId) => updateTarget(channel, optionId)}
|
||||
/>
|
||||
<span className={`client-failover-channel-checking${checking ? ' is-visible' : ''}`} aria-hidden="true">
|
||||
Проверяем {channel === 'primary' ? 'основной' : 'резервный'} канал
|
||||
</span>
|
||||
</article>{channel === 'primary' && <div className="client-failover-channel-actions" aria-label="Действия с каналами">
|
||||
<button
|
||||
className={`client-failover-check-action client-tooltip-anchor${checking ? ' is-running' : ''}`}
|
||||
|
||||
@@ -20,16 +20,16 @@
|
||||
.client-failover-channels { display: grid; grid-template-columns: minmax(0, 1fr) 52px minmax(0, 1fr); gap: 12px; }
|
||||
.client-failover-channel-slot { display: contents; }
|
||||
.client-failover-channel { position: relative; display: grid; align-content: start; gap: 7px; min-width: 0; }
|
||||
.client-failover-channel-title { min-height: 24px; display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
||||
.client-failover-channel-title { min-height: 24px; }
|
||||
.client-failover-channel-title h3 { margin: 0; font: var(--type-data); font-variant-numeric: var(--numeric-tabular); letter-spacing: var(--type-data-tracking); text-transform: var(--type-data-transform); }
|
||||
.client-failover-channel[data-channel='primary'] .client-failover-channel-title h3 { color: var(--harbor-connect); text-shadow: 0 0 9px color-mix(in oklch, var(--harbor-connect) 30%, transparent); }
|
||||
.client-failover-channel[data-channel='reserve'] .client-failover-channel-title h3 { color: var(--harbor-gateway); text-shadow: 0 0 9px color-mix(in oklch, var(--harbor-gateway) 30%, transparent); }
|
||||
.client-failover-channel-title strong { min-width: 0; color: var(--client-muted); font: var(--type-control); letter-spacing: var(--type-control-tracking); text-transform: var(--type-control-transform); text-align: right; white-space: nowrap; transition: color 600ms ease, filter 600ms ease; }
|
||||
.client-failover-channel-title strong.is-healthy { color: var(--client-accent); }
|
||||
.client-failover-channel-title strong.is-unhealthy { color: oklch(0.68 0.15 28); }
|
||||
.client-failover-channel-status { min-height: 32px; display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: repeat(2, minmax(0, 1fr)); align-items: baseline; color: var(--client-muted); font: var(--type-control); letter-spacing: var(--type-control-tracking); text-transform: var(--type-control-transform); }
|
||||
.client-failover-channel-status strong { min-width: 0; white-space: nowrap; transition: color 600ms ease, filter 600ms ease; }
|
||||
.client-failover-channel-status strong.is-healthy { color: var(--client-accent); }
|
||||
.client-failover-channel-status strong.is-unhealthy { color: oklch(0.68 0.15 28); }
|
||||
.client-failover-channel-progress { min-width: 0; font-variant-numeric: var(--numeric-tabular); text-align: left; white-space: nowrap; }
|
||||
.client-failover-picker { position: relative; min-width: 0; z-index: 1; }
|
||||
.client-failover-channel-checking { min-height: 16px; color: var(--client-muted); font: var(--type-control); letter-spacing: var(--type-control-tracking); text-transform: var(--type-control-transform); opacity: 0; visibility: hidden; transition: opacity 180ms ease; }
|
||||
.client-failover-channel-checking.is-visible { opacity: 1; visibility: visible; }
|
||||
.client-failover-channel-actions { grid-column: 2; grid-row: 1; min-height: 66px; display: grid; place-content: center; gap: 2px; }
|
||||
.client-failover-check-action, .client-failover-direction-action { width: 44px; height: 44px; display: grid; place-items: center; padding: 0; border: 0; background: transparent; color: var(--client-accent); cursor: pointer; }
|
||||
.client-failover-direction-action.is-primary { color: var(--harbor-connect); }
|
||||
@@ -101,5 +101,6 @@
|
||||
.client-failover-number-input:focus-visible { outline: 0; color: var(--client-accent); text-shadow: 0 0 9px color-mix(in oklch, var(--client-accent) 38%, transparent); }
|
||||
@keyframes failover-service-in { from { opacity: 0; filter: blur(5px); transform: translateY(-4px); } to { opacity: 1; filter: blur(0); transform: translateY(0); } }
|
||||
@keyframes failover-service-out { from { opacity: 1; filter: blur(0); transform: translateX(0) scale(1); } to { opacity: 0; filter: blur(6px); transform: translateX(16px) scale(.96); } }
|
||||
@media (max-width: 560px) { .client-failover-heading { align-items: flex-start; flex-wrap: wrap; } .client-failover-heading > h2 { min-width: 0; flex: 1 1 auto; } .client-failover-header-actions { flex: 1 1 100%; justify-content: flex-start; gap: 4px 10px; } .client-failover-channels { grid-template-columns: minmax(0, 1fr); gap: 12px; } .client-failover-channel-actions { grid-column: 1; grid-row: auto; min-height: 44px; display: flex; justify-content: center; gap: 8px; } .client-failover-service { grid-template-columns: minmax(0, 1fr) 32px; gap: 6px 10px; } .client-failover-service > .client-failover-number-setting { grid-column: 1; grid-row: 2; } .client-failover-remove-service { grid-column: 2; grid-row: 1 / span 2; } .client-failover-service-editor { grid-template-columns: 1fr; gap: 2px; } }
|
||||
@media (prefers-reduced-motion: reduce) { .client-failover-channel-title strong, .client-failover-channel-checking, .client-failover-switch::before, .client-failover-switch > span, .client-failover-picker-trigger, .client-failover-picker-trigger::before, .client-failover-picker-trigger svg, .client-failover-picker-list, .client-failover-picker-list button, .client-failover-number-steps, .client-failover-number-control:hover .client-failover-number-steps, .client-failover-number-control:focus-within .client-failover-number-steps, .client-failover-remove-service, .client-failover-remove-lid, .client-failover-direction-action svg { transition: none; } .client-failover-check-action.is-running svg { animation: none; } .client-failover-service, .client-failover-service-editor { animation: none; } }
|
||||
@media (max-width: 680px) { .client-failover-channels { grid-template-columns: minmax(0, 1fr); gap: 12px; } .client-failover-channel-actions { grid-column: 1; grid-row: auto; min-height: 44px; display: flex; justify-content: center; gap: 8px; } }
|
||||
@media (max-width: 560px) { .client-failover-heading { align-items: flex-start; flex-wrap: wrap; } .client-failover-heading > h2 { min-width: 0; flex: 1 1 auto; } .client-failover-header-actions { flex: 1 1 100%; justify-content: flex-start; gap: 4px 10px; } .client-failover-service { grid-template-columns: minmax(0, 1fr) 32px; gap: 6px 10px; } .client-failover-service > .client-failover-number-setting { grid-column: 1; grid-row: 2; } .client-failover-remove-service { grid-column: 2; grid-row: 1 / span 2; } .client-failover-service-editor { grid-template-columns: 1fr; gap: 2px; } }
|
||||
@media (prefers-reduced-motion: reduce) { .client-failover-channel-status strong, .client-failover-switch::before, .client-failover-switch > span, .client-failover-picker-trigger, .client-failover-picker-trigger::before, .client-failover-picker-trigger svg, .client-failover-picker-list, .client-failover-picker-list button, .client-failover-number-steps, .client-failover-number-control:hover .client-failover-number-steps, .client-failover-number-control:focus-within .client-failover-number-steps, .client-failover-remove-service, .client-failover-remove-lid, .client-failover-direction-action svg { transition: none; } .client-failover-check-action.is-running svg { animation: none; } .client-failover-service, .client-failover-service-editor { animation: none; } }
|
||||
|
||||
Reference in New Issue
Block a user