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:
Nikan Zeyaei
2026-07-05 02:27:03 +03:30
committed by GitHub
parent e11e587c60
commit b177e30714
19 changed files with 93 additions and 6 deletions
@@ -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;
+23 -1
View File
@@ -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>
);
})}