import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { flushSync } from 'react-dom'; import { api } from '../api.js'; import { accessTabForKey, connectionAction, connectionDurationParts, copyText, isSubscriptionUrlValid, localProxyUrls, subscriptionDomain, subscriptionDaysLeft, subscriptionUsage, } from '../utils/clientControls.js'; import { formatBytes } from '../utils/format.js'; import { instructionBlocks } from '../instructions.js'; import { operationBlocked } from '../state/operations.js'; import { ConfirmationPopup } from './ConfirmationPopup.jsx'; import { canAppendRouteRule } from '../../shared/routingRules.js'; import { HARBOR_VERSIONS, parseVersion, versionCompatibility, } from '../../shared/versions.js'; const SUBSCRIPTION_REVEAL_DELAY_MS = 1350; const DURATION_MODE_STORAGE_KEY = 'harbor-duration-mode'; const ACCESS_TABS = [ ['gateway', 'Gateway'], ['proxy', 'Gateway Proxy'], ]; function CloudTooltip({ children }) { return {children}; } const VERSION_PARTS = [ ['major', 'Major'], ['minor', 'Minor'], ['hotfix', 'Hotfix'], ]; function VersionBadge({ code, component, componentKey, version, runtime, incompatible = false }) { const parsed = parseVersion(version); const values = parsed ? VERSION_PARTS.map(([key]) => parsed[key]) : ['–', '–', '–']; function description(key) { if (key === 'major') { return 'Уровень совместимости всей экосистемы. Все совместно работающие компоненты и клиенты должны иметь одинаковый major.'; } if (key === 'minor') { return 'Уровень конкретного компонента и тесно связанной с ним части системы. Остальные клиенты с тем же major могут обновляться отдельно.'; } return 'Локальное совместимое исправление этой версии. Не требует обновлять связанные компоненты или другие клиенты.'; } return (
{VERSION_PARTS.map(([key, label], index) => { const tooltipId = `harbor-version-${componentKey}-${key}`; return {index > 0 && } {values[index]} {component} · {label} {values[index]} {description(key)} {runtime && {runtime}} {incompatible && Версии Gateway несовместимы.} ; })}
); } function VersionDisplay({ isGateway, versionInfo }) { const runtimeSingBox = versionInfo?.runtime?.singBox; if (!isGateway) { return ; } const backendVersion = versionInfo?.components?.gatewayBackend; const dataplaneVersion = versionInfo?.runtime?.dataplaneVersion; const compatibility = backendVersion && versionCompatibility({ ...HARBOR_VERSIONS, gatewayBackend: backendVersion, }); const incompatible = compatibility && !compatibility.compatible; return ; } function InlineError({ error, context }) { if (!error || error.context !== context) return null; return (
{error.message} {error.correlationId && ( Код: {error.correlationId.slice(0, 8)} )} {error.retry && }
); } const operationProgress = { connection: ['connection', 'Меняем состояние подключения…'], serverApply: ['connection', 'Применяем сервер…'], subscriptionImport: ['subscription', 'Загружаем подписку…'], subscriptionDelete: ['subscription', 'Удаляем подписку…'], routeRules: ['routing', 'Применяем локальные правила…'], }; function InlineProgress({ operations, context }) { const active = Object.entries(operationProgress).find(([key, [operationContext]]) => ( operationContext === context && operations[key]?.status === 'running' )); if (!active) return null; return (
{active[1][1]}
); } function InstructionStep({ step }) { if (typeof step === 'string') return step; return ( <> {step.before} {step.link[0]} {step.after} ); } function InstructionBlock({ block, open, onToggle }) { return (