mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-29 06:27:14 +00:00
feat(ui): show per-inbound live speed (#5261)
* feat(utils): add speedFormat utility and tests * feat(inbounds): add InboundSpeedEntry type * feat(inbounds): add speed column to inbound list * feat(inbounds): show speed in inbound stats modal * feat(inbounds): compute inbound speed from traffic deltas * feat(inbounds): wire inbound speed through page * feat(i18n): add speed translation for all locales * refactor(inbounds): dedupe live-speed UI and harden formatting Extract a shared InboundSpeedTag component and isActiveSpeed guard used by the speed column and stats modal, unify InboundSpeedEntry into a single type, and route speedFormat through sizeFormat. Also guard sizeFormat against non-finite input (no more "NaN PB/s") and clear stale per-inbound speeds when a traffic poll returns no deltas. --------- Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
This commit is contained in:
@@ -12,6 +12,8 @@ import { SlimInboundListSchema, LastOnlineMapSchema, InboundDetailSchema } from
|
||||
import { OnlinesSchema, OnlineByNodeSchema, ActiveInboundsByNodeSchema } from '@/schemas/client';
|
||||
import { DefaultsPayloadSchema, type DefaultsPayload } from '@/schemas/defaults';
|
||||
|
||||
import type { InboundSpeedEntry } from './list/types';
|
||||
|
||||
export interface SubSettings {
|
||||
enable: boolean;
|
||||
subTitle: string;
|
||||
@@ -26,6 +28,17 @@ export interface SubSettings {
|
||||
|
||||
type DBInboundInstance = InstanceType<typeof DBInbound>;
|
||||
|
||||
// Server-side traffic polling interval in seconds. XrayTrafficJob broadcasts
|
||||
// deltas accumulated over this window, so dividing by it yields bytes/sec.
|
||||
const TRAFFIC_POLL_INTERVAL_S = 5;
|
||||
|
||||
interface TrafficDelta {
|
||||
Tag: string;
|
||||
Up: number;
|
||||
Down: number;
|
||||
IsInbound?: boolean;
|
||||
}
|
||||
|
||||
interface ClientRollup {
|
||||
clients: number;
|
||||
active: string[];
|
||||
@@ -179,6 +192,8 @@ export function useInbounds() {
|
||||
const [clientCount, setClientCount] = useState<Record<number, ClientRollup>>({});
|
||||
const [statsVersion, setStatsVersion] = useState(0);
|
||||
|
||||
const [inboundSpeed, setInboundSpeed] = useState<Record<number, InboundSpeedEntry>>({});
|
||||
|
||||
const [onlineClients, setOnlineClients] = useState<string[]>([]);
|
||||
const onlineClientsRef = useRef<string[]>([]);
|
||||
onlineClientsRef.current = onlineClients;
|
||||
@@ -383,7 +398,13 @@ export function useInbounds() {
|
||||
const applyTrafficEvent = useCallback(
|
||||
(payload: unknown) => {
|
||||
if (!payload || typeof payload !== 'object') return;
|
||||
const p = payload as { onlineClients?: string[]; onlineByGuid?: Record<string, string[]>; activeInbounds?: Record<string, string[]>; lastOnlineMap?: Record<string, number> };
|
||||
const p = payload as {
|
||||
traffics?: TrafficDelta[];
|
||||
onlineClients?: string[];
|
||||
onlineByGuid?: Record<string, string[]>;
|
||||
activeInbounds?: Record<string, string[]>;
|
||||
lastOnlineMap?: Record<string, number>;
|
||||
};
|
||||
if (Array.isArray(p.onlineClients)) {
|
||||
onlineClientsRef.current = p.onlineClients;
|
||||
setOnlineClients(p.onlineClients);
|
||||
@@ -397,6 +418,26 @@ export function useInbounds() {
|
||||
if (p.lastOnlineMap && typeof p.lastOnlineMap === 'object') {
|
||||
setLastOnlineMap((prev) => ({ ...prev, ...p.lastOnlineMap! }));
|
||||
}
|
||||
// Full-replace each poll so idle inbounds (and an empty array after an
|
||||
// Xray stat reset) clear their speed instead of showing a stale value.
|
||||
if (Array.isArray(p.traffics)) {
|
||||
const byTag = new Map<string, TrafficDelta>();
|
||||
for (const tr of p.traffics) {
|
||||
if (!tr || typeof tr.Tag !== 'string') continue;
|
||||
if (tr.IsInbound === false) continue;
|
||||
byTag.set(tr.Tag, tr);
|
||||
}
|
||||
const nextSpeed: Record<number, InboundSpeedEntry> = {};
|
||||
for (const ib of dbInboundsRef.current) {
|
||||
const delta = byTag.get(ib.tag);
|
||||
if (!delta) continue;
|
||||
nextSpeed[ib.id] = {
|
||||
up: (delta.Up || 0) / TRAFFIC_POLL_INTERVAL_S,
|
||||
down: (delta.Down || 0) / TRAFFIC_POLL_INTERVAL_S,
|
||||
};
|
||||
}
|
||||
setInboundSpeed(nextSpeed);
|
||||
}
|
||||
rebuildClientCount();
|
||||
},
|
||||
[rebuildClientCount],
|
||||
@@ -481,6 +522,7 @@ export function useInbounds() {
|
||||
clientCount,
|
||||
onlineClients,
|
||||
lastOnlineMap,
|
||||
inboundSpeed,
|
||||
statsVersion,
|
||||
totals,
|
||||
expireDiff,
|
||||
|
||||
Reference in New Issue
Block a user