Add runtime version reporting and display
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 16s
Build and Deploy Gateway / deploy (push) Successful in 12s

This commit is contained in:
2026-07-11 21:24:18 +03:00
parent c9223aa3a9
commit 7a6f9a26ac
12 changed files with 351 additions and 2 deletions

View File

@@ -13,6 +13,11 @@ import {
import { formatBytes } from '../utils/format.js';
import { instructionBlocks } from '../instructions.js';
import { createLatestRequest, operationBlocked } from '../state/operations.js';
import {
HARBOR_VERSIONS,
parseVersion,
versionCompatibility,
} from '../../shared/versions.js';
const SUBSCRIPTION_REVEAL_DELAY_MS = 1350;
const DURATION_MODE_STORAGE_KEY = 'harbor-duration-mode';
@@ -21,6 +26,96 @@ function CloudTooltip({ children }) {
return <span className="client-tooltip" role="tooltip">{children}</span>;
}
const VERSION_PARTS = [
['major', 'Major'],
['minor', 'Minor'],
['hotfix', 'Hotfix'],
];
function VersionBadge({ code, component, componentKey, version, singBox, incompatible = false }) {
const parsed = parseVersion(version);
const values = parsed ? VERSION_PARTS.map(([key]) => parsed[key]) : ['', '', ''];
function description(key) {
if (key === 'major') {
return 'Общий уровень совместимости Harbor. При его изменении обновляются Mac и вся Gateway-инфраструктура.';
}
if (key === 'minor' && componentKey === 'macClient') {
return 'Линия Mac-клиента. Gateway может менять minor без обязательного обновления Mac.';
}
if (key === 'minor') {
return 'Линия Gateway. Gateway client и backend должны совпадать по major.minor.';
}
return `Совместимое исправление только компонента ${component}; hotfix может обновляться независимо.`;
}
return (
<div className={`harbor-version${incompatible ? ' is-incompatible' : ''}`}>
<span className="harbor-version-code" aria-hidden="true">{code}</span>
<span className="harbor-version-number" aria-label={`${component} ${version || 'не определена'}`}>
{VERSION_PARTS.map(([key, label], index) => {
const tooltipId = `harbor-version-${componentKey}-${key}`;
return <React.Fragment key={key}>
{index > 0 && <span className="harbor-version-dot" aria-hidden="true">.</span>}
<span
className="harbor-version-part"
tabIndex="0"
aria-describedby={tooltipId}
>
{values[index]}
<span className="harbor-version-tooltip" id={tooltipId} role="tooltip">
<strong>{component} · {label} {values[index]}</strong>
<span>{description(key)}</span>
{singBox && <small>Runtime: sing-box {singBox}</small>}
{incompatible && <small className="is-warning">Версии Gateway несовместимы.</small>}
</span>
</span>
</React.Fragment>;
})}
</span>
</div>
);
}
function VersionDisplay({ isGateway, versionInfo }) {
const runtimeSingBox = versionInfo?.runtime?.singBox;
if (!isGateway) {
return <aside className="harbor-versions" aria-label="Версия Harbor">
<VersionBadge
code="M"
component="Mac client"
componentKey="macClient"
version={versionInfo?.components?.macClient || HARBOR_VERSIONS.macClient}
singBox={runtimeSingBox}
/>
</aside>;
}
const backendVersion = versionInfo?.components?.gatewayBackend;
const compatibility = backendVersion && versionCompatibility({
...HARBOR_VERSIONS,
gatewayBackend: backendVersion,
});
const incompatible = compatibility && !compatibility.compatible;
return <aside className="harbor-versions" aria-label="Версии Harbor Gateway">
<VersionBadge
code="C"
component="Gateway client"
componentKey="gatewayClient"
version={HARBOR_VERSIONS.gatewayClient}
incompatible={incompatible}
/>
<VersionBadge
code="B"
component="Gateway backend"
componentKey="gatewayBackend"
version={backendVersion}
singBox={runtimeSingBox}
incompatible={incompatible}
/>
</aside>;
}
function InlineError({ error, context }) {
if (!error || error.context !== context) return null;
return (
@@ -198,6 +293,7 @@ function HarborBrand({ isGateway, gatewayAvailable, gatewayDirect, blocked, onSe
export function ClientOverviewPage({
state,
versionInfo,
operations = {},
error,
subscriptionUrl,
@@ -553,6 +649,7 @@ export function ClientOverviewPage({
return (
<div className={`client-shell${hasSubscription ? '' : ' is-first-run'}${showIntro && !hasSubscription ? ' is-intro' : ''}`}>
<VersionDisplay isGateway={isGateway} versionInfo={versionInfo} />
<HarborBrand
isGateway={isGateway}
gatewayAvailable={gatewayAvailable}