Track proxy traffic separately in device inventory
Build and Deploy Gateway / build-and-push (push) Successful in 15s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-07 17:08:24 +03:00
parent 9f31eaf396
commit 53e6cf2146
13 changed files with 714 additions and 146 deletions
+26 -4
View File
@@ -1,6 +1,7 @@
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { api } from '../api.js';
import {
byteString,
formatByteString,
formatLastSeen,
sortDevicesByTraffic,
@@ -210,6 +211,7 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
<h2 id="client-devices-title">Устройства</h2>
<div className="client-instructions-intro">
<p>Устройства, которые Gateway видит в локальной таблице соседей.</p>
<p>Учитывается только трафик, который прошёл через Harbor.</p>
</div>
</header>
@@ -223,6 +225,11 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
Трафик временно не обновляется. Показаны последние сохранённые значения.
</p>
)}
{snapshot?.source?.traffic?.proxy?.error && (
<p className="client-devices-source" role="status">
Прокси-трафик временно не обновляется. Показаны последние сохранённые значения.
</p>
)}
{snapshot?.source?.policy?.error && (
<p className="client-devices-source" role="status">
Маршруты устройств временно не обновляются. Показано последнее подтверждённое состояние.
@@ -248,6 +255,16 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
const online = device.status === 'online';
const download = formatByteString(device.downloadBytes);
const upload = formatByteString(device.uploadBytes);
const proxyDownload = formatByteString(device.proxyDownloadBytes);
const proxyUpload = formatByteString(device.proxyUploadBytes);
const gatewayTotal = byteString(device.downloadBytes) + byteString(device.uploadBytes);
const proxyTotal = byteString(device.proxyDownloadBytes) + byteString(device.proxyUploadBytes);
const gatewayTraffic = formatByteString(gatewayTotal.toString());
const proxyTraffic = formatByteString(proxyTotal.toString());
const trafficLabel = [
gatewayTotal > 0n ? `Gateway: получено ${download}, отдано ${upload}` : '',
proxyTotal > 0n ? `Прокси: получено ${proxyDownload}, отдано ${proxyUpload}` : '',
].filter(Boolean).join('. ');
const policyBusy = device.policyStatus === 'applying';
const policyFailed = device.policyStatus === 'failed';
const policyPending = device.policyStatus === 'pending';
@@ -314,11 +331,16 @@ export function DevicesPanel({ open, panelRef, closeRef, onClose }) {
</div>
)}
<span className="client-device-traffic-slot">
{device.trafficObservedAt && <span
className="client-device-traffic"
aria-label={`Получено ${download}, отдано ${upload}`}
{(gatewayTotal > 0n || proxyTotal > 0n) && <span
className="client-device-traffic client-tooltip-anchor"
tabIndex="0"
aria-label={trafficLabel}
>
<span aria-hidden="true"> {download} · {upload}</span>
<span className="client-device-traffic-sources" aria-hidden="true">
{gatewayTotal > 0n && <span>Gateway {gatewayTraffic}</span>}
{proxyTotal > 0n && <span className="is-proxy">Прокси {proxyTraffic}</span>}
</span>
<Tooltip>{trafficLabel}</Tooltip>
</span>}
</span>
<span className="client-device-pin-wrap client-tooltip-anchor">