Improve gateway traffic dashboard and connection branding
Build and Deploy Gateway / build-and-push (push) Successful in 19s
Build and Deploy Gateway / deploy (push) Successful in 6s

This commit is contained in:
2026-08-09 14:37:11 +03:00
parent 3204ecf4a5
commit d349ca5e29
11 changed files with 208 additions and 86 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
export const HARBOR_VERSIONS = Object.freeze({
macClient: '0.21.5',
gatewayClient: '0.22.0',
macClient: '0.21.6',
gatewayClient: '0.22.1',
gatewayBackend: '0.22.5',
});
+8 -8
View File
@@ -537,14 +537,6 @@ export function ClientOverviewPage({
<div className="client-live-region" role="status" aria-live="polite" aria-atomic="true">
<span key={copyAnnouncement.cycle}>{copyAnnouncement.text}</span>
</div>
<HarborBrand
isGateway={isGateway}
connected={connected}
gatewayAvailable={gatewayAvailable}
gatewayDirect={gatewayDirect}
blocked={gatewayAutoBlocked}
onSetGatewayAuto={onSetGatewayAuto}
/>
{(isGateway || (hasSubscription && subscriptionContentReady)) && <nav className="client-secondary-menu" aria-label="Дополнительные меню">
{isGateway && <SubscriptionToggle
feature={subscriptionFeature}
@@ -609,6 +601,14 @@ export function ClientOverviewPage({
proxyPort={state?.clientRuntime?.proxyPort}
now={now}
blocked={connectionBlocked}
brandSlot={<HarborBrand
isGateway={isGateway}
connected={connected}
gatewayAvailable={gatewayAvailable}
gatewayDirect={gatewayDirect}
blocked={gatewayAutoBlocked}
onSetGatewayAuto={onSetGatewayAuto}
/>}
copyFeedback={copyFeedback}
onCopyProxy={copyProxy}
onApply={onApply}
@@ -34,6 +34,7 @@ interface ConnectionPanelProps {
proxyPort?: number;
now: number;
blocked: boolean;
brandSlot?: ReactNode;
copyFeedback?: Partial<Record<CopyKind, CopyFeedback>>;
routingSlot?: ReactNode;
serverSlot?: ReactNode;
@@ -74,6 +75,7 @@ export function ConnectionPanel({
proxyPort,
now,
blocked,
brandSlot,
copyFeedback,
routingSlot,
serverSlot,
@@ -161,18 +163,19 @@ export function ConnectionPanel({
</button>;
return <>
{visible && <section className={`client-power-section${isGateway ? ' is-gateway' : ''}`} aria-labelledby="connection-title">
{isGateway ? <span
className="client-power-control client-tooltip-anchor"
{visible ? <section className={`client-power-section${isGateway ? ' is-gateway' : ''}`} aria-labelledby="connection-title">
<div
className={`client-power-control${isGateway ? ' client-tooltip-anchor' : ''}`}
tabIndex={powerUnavailable ? 0 : undefined}
aria-label={powerUnavailable ? 'VPN недоступен' : undefined}
aria-describedby={powerUnavailable ? 'gateway-power-unavailable' : undefined}
>
{brandSlot}
{powerButton}
{powerUnavailable && <span className="client-tooltip" id="gateway-power-unavailable" role="tooltip">
Сначала добавьте подписку и выберите сервер
</span>}
</span> : powerButton}
</div>
<div className="client-state-detail">
{connected ? (
<button
@@ -266,7 +269,7 @@ export function ConnectionPanel({
</div>
</section>
{statusSlot}
</section>}
</section> : brandSlot}
<ConfirmationDialog
open={confirmingStop}
+20 -6
View File
@@ -29,6 +29,7 @@
}
.client-power-control {
position: relative;
width: 96px;
height: 96px;
display: grid;
@@ -38,18 +39,31 @@
.client-power-section.is-gateway .client-power-control,
.client-power-section.is-gateway .client-power {
width: 68px;
height: 68px;
width: 84px;
height: 84px;
}
.client-power-section.is-gateway .client-power svg {
width: 30px;
height: 30px;
width: 36px;
height: 36px;
}
.client-power-section.is-gateway .client-power::before {
width: 44px;
height: 44px;
width: 52px;
height: 52px;
}
.client-power-control > .client-tooltip {
top: calc(100% + 12px);
right: auto;
bottom: auto;
left: 50%;
transform: translate(-50%, -2px);
}
.client-power-control.client-tooltip-anchor:hover > .client-tooltip,
.client-power-control.client-tooltip-anchor:focus-visible > .client-tooltip {
transform: translate(-50%, 0);
}
.client-power-control:focus-visible {
+19 -11
View File
@@ -152,6 +152,17 @@
pointer-events: none;
}
.client-power-control > .harbor-brand {
position: absolute;
top: auto;
right: auto;
bottom: calc(100% + 44px);
left: 50%;
width: max-content;
transform: translateX(-50%) scale(3);
transform-origin: center bottom;
}
.harbor-brand.is-switchable {
pointer-events: auto;
}
@@ -206,13 +217,6 @@
filter: drop-shadow(0 0 5px color-mix(in oklch, var(--client-accent) 44%, transparent));
}
.harbor-brand.is-connected .harbor-brand-content > svg,
.harbor-brand.is-connected .harbor-brand-name > strong,
.harbor-brand.is-connected .harbor-brand-name > em,
.harbor-brand.is-connected .harbor-mode-stack {
transform: translateY(-4px);
}
.harbor-brand.is-connected .harbor-brand-name > strong,
.harbor-brand.is-connected .harbor-brand-name > em,
.harbor-brand.is-connected .harbor-mode-stack {
@@ -397,10 +401,6 @@
animation: harbor-mode-arrows-discovered 260ms 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.harbor-brand.is-connected .harbor-mode-swap {
transform: translateY(calc(-50% - 4px));
}
@keyframes harbor-mode-arrows-discovered {
from {
opacity: 0;
@@ -662,6 +662,10 @@
transform: translateX(-50%) scale(1.55);
}
.client-power-control > .harbor-brand {
transform: translateX(-50%) scale(1.55);
}
.client-inline-error.is-connection,
.client-inline-error.is-subscription {
position: static;
@@ -719,6 +723,10 @@
transform: translateX(-50%) scale(1.35);
}
.client-power-control > .harbor-brand {
transform: translateX(-50%) scale(1.35);
}
.client-secondary-menu {
right: 8px;
}