Expose outbound traffic totals and simplify Direct chart breakdown
Build and Deploy Gateway / build-and-push (push) Successful in 20s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-12 22:43:40 +03:00
parent 9d43e74d97
commit 72c085a5b8
10 changed files with 173 additions and 69 deletions
+29 -5
View File
@@ -337,7 +337,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
<h2 id="client-devices-title">Устройства</h2>
<div className="client-instructions-intro">
<p>Устройства, которые Gateway видит в локальной таблице соседей.</p>
<p>Учитывается только трафик, который прошёл через Harbor.</p>
<p>Вход накопленные Gateway/Proxy. Выход оценка VPN/Direct с запуска текущего учёта.</p>
</div>
</header>
@@ -403,6 +403,24 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
const proxyTraffic = formatByteString(proxyTotal.toString());
const totalTraffic = formatByteString((gatewayTotal + proxyTotal).toString());
const hasProxyTraffic = proxyTotal > 0n;
const outboundAvailable = Boolean(
device.outboundTraffic?.singboxObservedAt || device.outboundTraffic?.directIpv4ObservedAt,
);
const vpnTotal = byteString(device.outboundTraffic?.vpnBytes);
const directTrackedTotal = byteString(device.outboundTraffic?.directTrackedBytes);
const directIpv4Total = byteString(device.outboundTraffic?.directIpv4Bytes);
const directTotal = directTrackedTotal + directIpv4Total;
const unknownTotal = byteString(device.outboundTraffic?.unknownBytes);
const outboundTotal = vpnTotal + directTotal + unknownTotal;
const displayedTotal = trafficView === 'outbound'
? outboundAvailable ? `${formatByteString(outboundTotal.toString())}` : '—'
: totalTraffic;
const trafficLabel = trafficView === 'outbound' ? 'Выход' : 'Вход';
const trafficAriaLabel = trafficView === 'outbound'
? outboundAvailable
? `Выход с запуска текущего учёта: примерно ${formatByteString(outboundTotal.toString())}. VPN ${formatByteString(vpnTotal.toString())}, Direct примерно ${formatByteString(directTotal.toString())}${unknownTotal > 0n ? `, неизвестно ${formatByteString(unknownTotal.toString())}` : ''}`
: 'Выход: ожидаем первые данные'
: `Вход накоплен: ${totalTraffic}. Gateway ${gatewayTraffic}${hasProxyTraffic ? `, Прокси ${proxyTraffic}` : ''}`;
const trafficDelta = trafficDeltas[device.id] || {};
const feedback = copyFeedback[device.id];
const policyBusy = device.policyStatus === 'applying';
@@ -538,14 +556,20 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
className="client-device-traffic"
role="group"
tabIndex={0}
aria-label={`Всего ${totalTraffic}. Gateway ${gatewayTraffic}${hasProxyTraffic ? `, Прокси ${proxyTraffic}` : ''}`}
aria-label={trafficAriaLabel}
>
<span className="client-device-traffic-total" aria-hidden="true">
<b>Всего</b><TrafficValue value={totalTraffic} delta={trafficDelta.total} />
<b>{trafficLabel}</b><TrafficValue value={displayedTotal} delta={trafficView === 'inbound' ? trafficDelta.total : undefined} />
</span>
<span className="client-device-traffic-breakdown" aria-hidden="true">
<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>}
{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><b>Gateway</b><TrafficValue value={gatewayTraffic} delta={trafficDelta.gateway} /></span>
{hasProxyTraffic && <span className="is-proxy"><b>Прокси</b><TrafficValue value={proxyTraffic} delta={trafficDelta.proxy} /></span>}
</>}
</span>
</span>}