mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-11 05:40:59 +00:00
feat(ui): client-realtime-speed (#5687)
* refactor(inbounds): extract TRAFFIC_POLL_INTERVAL_S to shared util * feat(clients): derive per-client live speed from traffic WebSocket deltas * feat(clients): render speed column and mobile card line * i18n(clients): add pages.clients.speed key to all 13 locales
This commit is contained in:
@@ -162,6 +162,12 @@
|
||||
background: color-mix(in srgb, var(--ant-color-primary) 6%, transparent);
|
||||
}
|
||||
|
||||
.client-card-speed {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
|
||||
.card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -61,6 +61,7 @@ import { useNodesQuery } from '@/api/queries/useNodesQuery';
|
||||
import { useDatepicker } from '@/hooks/useDatepicker';
|
||||
import type { ClientRecord, InboundOption, ExternalLink, ExternalLinkInput } from '@/hooks/useClients';
|
||||
import ClientTrafficCell from '@/components/clients/ClientTrafficCell';
|
||||
import ClientSpeedTag, { isActiveSpeed } from '@/components/clients/ClientSpeedTag';
|
||||
import AppSidebar from '@/layouts/AppSidebar';
|
||||
import { IntlUtil, SizeFormatter } from '@/utils';
|
||||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
@@ -209,6 +210,7 @@ export default function ClientsPage() {
|
||||
tgBotEnable, expireDiff, trafficDiff, pageSize,
|
||||
create, update, remove, bulkDelete, bulkAdjust, bulkEnable, bulkDisable, bulkAddToGroup, bulkRemoveFromGroup, attach, setExternalLinks, bulkAttach, detach, bulkDetach,
|
||||
resetTraffic, resetAllTraffics, delDepleted, delOrphans, exportClients, importClients, setEnable,
|
||||
clientSpeed,
|
||||
applyTrafficEvent, applyClientStatsEvent,
|
||||
refresh,
|
||||
hydrate,
|
||||
@@ -902,6 +904,17 @@ export default function ClientsPage() {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('pages.clients.speed'),
|
||||
key: 'speed',
|
||||
width: 110,
|
||||
align: 'center',
|
||||
render: (_v, record) => {
|
||||
const speed = clientSpeed[record.email];
|
||||
if (!isActiveSpeed(speed)) return <Tag color="default">—</Tag>;
|
||||
return <ClientSpeedTag speed={speed} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('pages.clients.remaining'),
|
||||
key: 'remaining',
|
||||
@@ -919,7 +932,7 @@ export default function ClientsPage() {
|
||||
),
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
], [t, togglingEmail, clientBucket, isOnline, inboundsById, filters, allGroups, datepicker, trafficDiff]);
|
||||
], [t, togglingEmail, clientBucket, isOnline, inboundsById, filters, allGroups, datepicker, trafficDiff, clientSpeed]);
|
||||
|
||||
const tablePagination = {
|
||||
current: currentPage,
|
||||
@@ -1428,6 +1441,15 @@ export default function ClientsPage() {
|
||||
enabled={row.enable}
|
||||
trafficDiff={trafficDiff}
|
||||
/>
|
||||
{(() => {
|
||||
const speed = clientSpeed[row.email];
|
||||
if (!isActiveSpeed(speed)) return null;
|
||||
return (
|
||||
<div className="client-card-speed">
|
||||
<ClientSpeedTag speed={speed} />
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { OnlinesSchema, OnlineByNodeSchema, ActiveInboundsByNodeSchema } from '@
|
||||
import { DefaultsPayloadSchema, type DefaultsPayload } from '@/schemas/defaults';
|
||||
|
||||
import type { InboundSpeedEntry } from './list/types';
|
||||
import { TRAFFIC_POLL_INTERVAL_S } from '@/lib/traffic/poll-interval';
|
||||
|
||||
export interface SubSettings {
|
||||
enable: boolean;
|
||||
@@ -28,10 +29,6 @@ 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;
|
||||
|
||||
// Speed is delta-derived, so it can't be recomputed until the first poll after
|
||||
// mount; navigating away and back would otherwise blank the column for up to one
|
||||
// poll. Cache the last speed map across mounts (module scope) and reseed from it
|
||||
|
||||
Reference in New Issue
Block a user