Update Harbor client and gateway integration workflows
Build and Deploy Gateway / build-and-push (push) Failing after 14s
Build and Deploy Gateway / deploy (push) Has been skipped

This commit is contained in:
2026-08-19 18:16:10 +03:00
parent 416b2b294a
commit daec12e013
63 changed files with 5016 additions and 108 deletions
+98 -3
View File
@@ -47,6 +47,13 @@ import {
InstructionsToggle,
useInstructionsFeature,
} from '../features/instructions/index.js';
import { FailoverPanel, FailoverToggle, useFailoverFeature } from '../features/failover/index.js';
import {
ActivityJournalPanel,
ActivityJournalToggle,
useActivityJournalFeature,
} from '../features/activity-journal/index.js';
import type { FailoverPolicy } from '../../shared/failover.js';
import {
HARBOR_VERSIONS,
parseVersion,
@@ -65,9 +72,33 @@ const VERSION_PARTS = [
] as const;
const DRAWER_SWITCH_MS = 620;
const DRAWER_ORDER = ['subscription', 'instructions', 'devices', 'diagnostics', 'routing'] as const;
const DRAWER_ORDER = ['subscription', 'failover', 'instructions', 'devices', 'diagnostics', 'routing', 'journal'] as const;
type DrawerKey = typeof DRAWER_ORDER[number];
const failoverReasonLabel = (reason: string | null) => ({
'primary-healthy': 'основной работает',
'health-unknown': 'ожидаем проверку',
'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-stopped': 'VPN выключен',
paused: 'автоматика на паузе',
disabled: 'резерв выключен',
'switch-failed': 'не удалось переключить',
'selector-unknown': 'текущий канал неизвестен',
'reconcile-failed': 'мониторинг временно недоступен',
'revalidation-required': 'условия проверяются заново',
'manual-check': 'оба канала проверены',
}[reason || ''] || 'наблюдение');
interface UiError {
context?: string;
profileId?: string;
@@ -93,6 +124,7 @@ interface ComponentActions {
setDevicePolicy: (id: string, mode: 'vpn' | 'direct', expectedRevision: number) => Promise<unknown>;
pingServers: (profileId: string, ids: string[]) => Promise<unknown>;
runConnectivityDiagnostics: (target?: unknown) => Promise<unknown>;
loadActivityJournal: (cursor?: string | null) => Promise<unknown>;
}
interface ClientViewState extends StateSnapshot {
@@ -119,6 +151,10 @@ interface ClientOverviewPageProps {
onSetGatewayAuto: (enabled: boolean) => Promise<unknown>;
onSaveRouteRules: (rules: RouteRule[], expectedRevision: number) => Promise<unknown>;
onUpdateDiagnosticsSettings: (settings: unknown) => Promise<unknown>;
onSaveFailover: (policy: FailoverPolicy) => Promise<unknown>;
onPauseFailover: (paused: boolean) => Promise<unknown>;
onSwitchFailover: (role: 'primary' | 'reserve') => Promise<unknown>;
onCheckFailover: () => Promise<unknown>;
onDismissError: () => void;
}
@@ -252,6 +288,7 @@ const operationProgress: Partial<Record<keyof OperationRegistrySnapshot, readonl
profileRefresh: ['subscription', 'Обновляем подписку…'],
profileDelete: ['subscription', 'Удаляем подписку…'],
routeRules: ['routing', 'Применяем локальные правила…'],
failover: ['failover', 'Применяем настройки резерва…'],
};
const canonicalOperationKeys: Record<string, OperationKey> = {
@@ -266,6 +303,10 @@ const canonicalOperationKeys: Record<string, OperationKey> = {
'profile-delete': 'profileDelete',
'gateway-auto': 'gatewayAuto',
'route-rules': 'routeRules',
'failover-save': 'failover',
'failover-pause': 'failover',
'failover-resume': 'failover',
'failover-switch': 'failover',
'subscription-import': 'profileAdd',
'subscription-refresh': 'profileRefresh',
'subscription-forget': 'profileDelete',
@@ -425,6 +466,10 @@ export function ClientOverviewPage({
onSetGatewayAuto,
onSaveRouteRules,
onUpdateDiagnosticsSettings,
onSaveFailover,
onPauseFailover,
onSwitchFailover,
onCheckFailover,
onDismissError,
}: ClientOverviewPageProps) {
const isGateway = state?.mode === 'gateway';
@@ -533,6 +578,8 @@ export function ClientOverviewPage({
port: state?.clientRuntime?.proxyPort || (isGateway ? 8080 : 8082),
controlHost,
});
const failoverFeature = useFailoverFeature();
const activityJournalFeature = useActivityJournalFeature();
const diagnosticsAvailable = hasSubscription;
const drawerControls = {
subscription: {
@@ -541,6 +588,12 @@ export function ClientOverviewPage({
show: subscriptionFeature.toggle,
close: subscriptionFeature.close,
},
failover: {
isOpen: failoverFeature.isOpen,
panelRef: failoverFeature.panelRef,
show: failoverFeature.toggle,
close: failoverFeature.close,
},
instructions: {
isOpen: instructionsFeature.isOpen,
panelRef: instructionsFeature.panelRef,
@@ -565,10 +618,16 @@ export function ClientOverviewPage({
show: routingFeature.open,
close: routingFeature.forceClose,
},
journal: {
isOpen: activityJournalFeature.isOpen,
panelRef: activityJournalFeature.panelRef,
show: activityJournalFeature.toggle,
close: activityJournalFeature.close,
},
};
const drawerOrder = isGateway
? DRAWER_ORDER
: DRAWER_ORDER.filter((drawer) => drawer !== 'devices');
: DRAWER_ORDER.filter((drawer) => !['devices', 'failover', 'journal'].includes(drawer));
const activeRailDrawer = drawerSwitchTarget && drawerControls[drawerSwitchTarget].isOpen
? drawerSwitchTarget
: drawerOrder.find((drawer) => drawerControls[drawer].isOpen) || null;
@@ -587,6 +646,8 @@ export function ClientOverviewPage({
instructionsFeature.close();
devicesFeature.close();
diagnosticsFeature.close();
failoverFeature.close();
activityJournalFeature.close();
}
}, [hasSubscription, isGateway]);
@@ -656,6 +717,7 @@ export function ClientOverviewPage({
routingFeature.requestClose();
return;
}
if (current === 'failover' && !failoverFeature.beforeCloseRef.current()) return;
if (!current) {
drawerControls[target].show();
return;
@@ -733,6 +795,13 @@ export function ClientOverviewPage({
: switchingServer && operationProfile && operationServer
? `Переключаем на ${subscriptionDomain(operationProfile.subscription.host)} · ${operationServer.label}`
: '';
const failoverIdentity = isGateway && state.failover.enabled
? `${state.failover.currentRole === 'reserve'
? 'Резервный канал'
: state.failover.currentRole === 'primary'
? 'Основной канал'
: 'Текущий канал вне резервной пары'} · ${failoverReasonLabel(state.failover.reason)}`
: '';
return (
<div
@@ -752,6 +821,11 @@ export function ClientOverviewPage({
open={activeRailDrawer === 'subscription'}
onToggle={() => switchDrawer('subscription')}
/>
{isGateway && <FailoverToggle
feature={failoverFeature}
open={activeRailDrawer === 'failover'}
onToggle={() => switchDrawer('failover')}
/>}
<InstructionsToggle
feature={instructionsFeature}
open={activeRailDrawer === 'instructions'}
@@ -775,6 +849,11 @@ export function ClientOverviewPage({
hasSubscription={hasSubscription}
onOpen={() => switchDrawer('routing')}
/>
{isGateway && <ActivityJournalToggle
feature={activityJournalFeature}
open={activeRailDrawer === 'journal'}
onToggle={() => switchDrawer('journal')}
/>}
</nav>}
<main className={`client-panel${showPower ? '' : ' is-setup'}${!isGateway && hasSubscription ? ' has-subscription' : ''}${isGateway && hasSubscription ? ' is-gateway-home' : ''}`}>
<ConnectionPanel
@@ -807,7 +886,7 @@ export function ClientOverviewPage({
blocked={connectionBlocked}
onRestart={onRestart}
/>}
serverSlot={<AppliedIdentity identity={mainIdentity} operation={switchIdentity} />}
serverSlot={<AppliedIdentity identity={mainIdentity} operation={switchIdentity || failoverIdentity} />}
statusSlot={<>
<InlineError error={error} context="connection" />
<InlineProgress operations={visibleOperations} context="connection" />
@@ -857,6 +936,22 @@ export function ClientOverviewPage({
<InlineProgress operations={visibleOperations} context="routing" />
</>}
/>}
{isGateway && hasSubscription && <FailoverPanel
feature={failoverFeature}
snapshot={state.failover}
profiles={profiles}
diagnostics={state.diagnostics}
blocked={operationBlocked(visibleOperations, 'failover')}
onSave={onSaveFailover}
onPause={onPauseFailover}
onSwitch={onSwitchFailover}
onCheck={onCheckFailover}
onUpdateDiagnostics={onUpdateDiagnosticsSettings}
/>}
{isGateway && hasSubscription && <ActivityJournalPanel
feature={activityJournalFeature}
loadPage={actions.loadActivityJournal}
/>}
<RoutingDiscardDialog feature={routingFeature} />
<SubscriptionDeleteDialog feature={subscriptionFeature} />
</div>