Track outbound device traffic deltas
Build and Deploy Gateway / build-and-push (push) Successful in 22s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-08-13 14:12:12 +03:00
parent 0290784526
commit 0ea2f9d548
12 changed files with 69 additions and 49 deletions
+42 -11
View File
@@ -29,6 +29,18 @@ interface TrafficDelta {
gateway?: string;
proxy?: string;
total?: string;
vpn?: string;
direct?: string;
unknown?: string;
outboundTotal?: string;
}
interface TrafficTotals {
gateway: bigint;
proxy: bigint;
vpn: bigint;
direct: bigint;
unknown: bigint;
}
interface PinCollapse {
@@ -95,7 +107,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
const previousPositions = useRef(new Map<string, DOMRect>());
const previousScrollTop = useRef(0);
const movementAnimations = useRef(new Map<string, Animation>());
const previousTraffic = useRef(new Map<string, { gateway: bigint; proxy: bigint }>());
const previousTraffic = useRef(new Map<string, TrafficTotals>());
const aliasBaseline = useRef({ id: '', value: '' });
const copyTimers = useRef(new Map<string, ReturnType<typeof setTimeout>>());
const copyAttempts = useRef(new Map<string, object>());
@@ -131,19 +143,38 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
return;
}
const next = new Map<string, { gateway: bigint; proxy: bigint }>();
const next = new Map<string, TrafficTotals>();
const deltas: Record<string, TrafficDelta> = {};
for (const device of snapshot?.devices || []) {
const gateway = byteString(device.downloadBytes) + byteString(device.uploadBytes);
const proxy = byteString(device.proxyDownloadBytes) + byteString(device.proxyUploadBytes);
const vpn = byteString(device.outboundTraffic?.vpnBytes);
const direct = byteString(device.outboundTraffic?.directTrackedBytes)
+ byteString(device.outboundTraffic?.directIpv4Bytes);
const unknown = byteString(device.outboundTraffic?.unknownBytes);
const previous = previousTraffic.current.get(device.id);
next.set(device.id, { gateway, proxy });
next.set(device.id, { gateway, proxy, vpn, direct, unknown });
if (!previous) continue;
const gatewayDelta = positiveByteDelta(previous.gateway, gateway);
const proxyDelta = positiveByteDelta(previous.proxy, proxy);
if (!gatewayDelta && !proxyDelta) continue;
const vpnDelta = positiveByteDelta(previous.vpn, vpn);
const directDelta = positiveByteDelta(previous.direct, direct);
const unknownDelta = positiveByteDelta(previous.unknown, unknown);
if (!gatewayDelta && !proxyDelta && !vpnDelta && !directDelta && !unknownDelta) continue;
const totalDelta = positiveByteDelta(previous.gateway + previous.proxy, gateway + proxy);
deltas[device.id] = { gateway: gatewayDelta, proxy: proxyDelta, total: totalDelta };
const outboundTotalDelta = positiveByteDelta(
previous.vpn + previous.direct + previous.unknown,
vpn + direct + unknown,
);
deltas[device.id] = {
gateway: gatewayDelta,
proxy: proxyDelta,
total: totalDelta,
vpn: vpnDelta,
direct: directDelta,
unknown: unknownDelta,
outboundTotal: outboundTotalDelta,
};
}
previousTraffic.current = next;
if (!Object.keys(deltas).length) return;
@@ -343,7 +374,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
<button
className="client-devices-reset"
type="button"
aria-label="Сбросить вход и выход всех устройств"
aria-label="Обнулить трафик устройств"
disabled={!snapshot || resetting}
onClick={requestTrafficReset}
>
@@ -351,7 +382,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
<path d="M4 12a8 8 0 1 0 2.3-5.7M4 5v7h7" />
</svg>
</button>
<Tooltip>Сбросить вход и выход всех устройств</Tooltip>
<Tooltip>Обнулить трафик устройств</Tooltip>
</span>
</div>
<h2 id="client-devices-title">Устройства</h2>
@@ -579,13 +610,13 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
aria-label={trafficAriaLabel}
>
<span className="client-device-traffic-total" aria-hidden="true">
<b>{trafficLabel}</b><TrafficValue value={displayedTotal} delta={trafficView === 'inbound' ? trafficDelta.total : undefined} />
<b>{trafficLabel}</b><TrafficValue value={displayedTotal} delta={trafficView === 'outbound' ? trafficDelta.outboundTotal : trafficDelta.total} />
</span>
<span className="client-device-traffic-breakdown" aria-hidden="true">
{trafficView === 'outbound' ? <>
<span className="is-vpn"><b>VPN</b><TrafficValue value={outboundAvailable ? formatByteString(vpnTotal.toString()) : '—'} /></span>
<span className="is-direct-total"><b>Direct</b><TrafficValue value={outboundAvailable ? formatByteString(directTotal.toString()) : '—'} /></span>
{unknownTotal > 0n && <span className="is-unknown"><b>Другое</b><TrafficValue value={formatByteString(unknownTotal.toString())} /></span>}
<span className="is-vpn"><b>VPN</b><TrafficValue value={outboundAvailable ? formatByteString(vpnTotal.toString()) : '—'} delta={trafficDelta.vpn} /></span>
<span className="is-direct-total"><b>Direct</b><TrafficValue value={outboundAvailable ? formatByteString(directTotal.toString()) : '—'} delta={trafficDelta.direct} /></span>
{unknownTotal > 0n && <span className="is-unknown"><b>Другое</b><TrafficValue value={formatByteString(unknownTotal.toString())} delta={trafficDelta.unknown} /></span>}
</> : <>
<span><b>Gateway</b><TrafficValue value={gatewayTraffic} delta={trafficDelta.gateway} /></span>
{hasProxyTraffic && <span className="is-proxy"><b>Прокси</b><TrafficValue value={proxyTraffic} delta={trafficDelta.proxy} /></span>}