Track outbound device traffic deltas
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
export const HARBOR_VERSIONS = Object.freeze({
|
||||
macClient: '0.25.7',
|
||||
gatewayClient: '0.26.6',
|
||||
macClient: '0.25.8',
|
||||
gatewayClient: '0.26.7',
|
||||
gatewayBackend: '0.26.5',
|
||||
});
|
||||
|
||||
|
||||
@@ -724,7 +724,6 @@ export function ClientOverviewPage({
|
||||
servers={profile.servers}
|
||||
selectedServerId={pickerState.selectedServerId}
|
||||
disabled={serverApplyBlocked || pickerState.disabled}
|
||||
prompt={!pickerState.selectedServerId}
|
||||
leaving={pickerState.leaving}
|
||||
revealVersion={pickerState.revealVersion}
|
||||
anchorServerId={pickerState.anchorServerId}
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -164,7 +164,6 @@ interface ServerPickerProps {
|
||||
servers: PickerServer[];
|
||||
selectedServerId: string;
|
||||
disabled: boolean;
|
||||
prompt: boolean;
|
||||
leaving: boolean;
|
||||
revealVersion: number;
|
||||
anchorServerId?: string;
|
||||
@@ -177,7 +176,6 @@ export function ServerPicker({
|
||||
servers,
|
||||
selectedServerId,
|
||||
disabled,
|
||||
prompt,
|
||||
leaving,
|
||||
revealVersion,
|
||||
anchorServerId = '',
|
||||
@@ -278,7 +276,6 @@ export function ServerPicker({
|
||||
|
||||
if (servers.length === 1) {
|
||||
return <section className="client-servers" aria-label="Выберите сервер">
|
||||
{prompt && <span className="client-server-prompt">Выберите сервер</span>}
|
||||
<div className="client-server-toolbar is-single">
|
||||
<span className="client-server-toolbar-title">Список серверов</span>
|
||||
<ServerCheckButton checking={checking} onClick={checkVisible} />
|
||||
@@ -317,7 +314,6 @@ export function ServerPicker({
|
||||
];
|
||||
|
||||
return <section className={`client-servers is-scalable${simpleServers.length <= INLINE_SERVER_ROWS ? ' is-short' : ''}`} aria-label="Выберите сервер">
|
||||
{prompt && <span className="client-server-prompt">Выберите сервер</span>}
|
||||
<div className="client-server-toolbar">
|
||||
<span className="client-server-toolbar-title">Список серверов</span>
|
||||
<ServerCheckButton checking={checking} disabled={!servers.length} onClick={checkVisible} />
|
||||
|
||||
@@ -475,8 +475,7 @@ function ProfileGroup({
|
||||
inert={!expanded ? true : undefined}
|
||||
>
|
||||
<div className="client-profile-body-inner">
|
||||
{!visibleServerId && <p className="client-profile-server-hint">Выберите сервер этой подписки</p>}
|
||||
{renderServerPicker(profile, {
|
||||
{!localStatus && renderServerPicker(profile, {
|
||||
disabled: controlsBlocked,
|
||||
leaving: false,
|
||||
revealVersion: feature.revealVersions[profile.id] || 0,
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
.client-devices-reset-wrap > .client-tooltip {
|
||||
right: 0;
|
||||
left: auto;
|
||||
white-space: nowrap;
|
||||
transform: translate(0, 2px);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.client-server-prompt {
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
color: var(--client-muted);
|
||||
font: var(--type-label);
|
||||
letter-spacing: var(--type-label-tracking);
|
||||
text-transform: var(--type-label-transform);
|
||||
text-align: center;
|
||||
animation: client-state-reveal 700ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
.client-server-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -385,7 +385,6 @@
|
||||
from { opacity: 0; }
|
||||
}
|
||||
|
||||
.client-profile-server-hint,
|
||||
.client-profile-local-status {
|
||||
min-height: 18px;
|
||||
margin: 0;
|
||||
|
||||
Reference in New Issue
Block a user