feat(groups): show upload/download breakdown in group traffic

Add per-group up/down to GroupSummary (backend + schema), surface them
as Upload/Download columns in the groups table, and fold upload/download
into the Total traffic summary card. Rename the group "Clients in group"
column to just "Clients" across all locales.
This commit is contained in:
MHSanaei
2026-06-12 15:30:30 +02:00
parent 7c698c4bcf
commit 1c5cb84492
16 changed files with 79 additions and 19 deletions
+31 -3
View File
@@ -22,6 +22,8 @@ import {
} from 'antd';
import type { MenuProps, TableColumnsType } from 'antd';
import {
ArrowDownOutlined,
ArrowUpOutlined,
ClockCircleOutlined,
DeleteOutlined,
EditOutlined,
@@ -165,6 +167,14 @@ export default function GroupsPage() {
() => groups.reduce((acc, g) => acc + (g.trafficUsed || 0), 0),
[groups],
);
const totalUpload = useMemo(
() => groups.reduce((acc, g) => acc + (g.up || 0), 0),
[groups],
);
const totalDownload = useMemo(
() => groups.reduce((acc, g) => acc + (g.down || 0), 0),
[groups],
);
function openCreate() {
setCreateName('');
@@ -417,6 +427,20 @@ export default function GroupsPage() {
width: 180,
render: (count: number) => <span>{count || 0}</span>,
},
{
title: t('pages.groups.upload'),
dataIndex: 'up',
key: 'up',
width: 140,
render: (bytes: number) => <span>{SizeFormatter.sizeFormat(bytes || 0)}</span>,
},
{
title: t('pages.groups.download'),
dataIndex: 'down',
key: 'down',
width: 140,
render: (bytes: number) => <span>{SizeFormatter.sizeFormat(bytes || 0)}</span>,
},
{
title: t('pages.groups.trafficUsed'),
dataIndex: 'trafficUsed',
@@ -456,26 +480,30 @@ export default function GroupsPage() {
<Col span={24}>
<Card size="small" hoverable className="summary-card">
<Row gutter={[16, isMobile ? 16 : 12]}>
<Col xs={12} sm={8} md={6}>
<Col xs={12} sm={8}>
<Statistic
title={t('pages.groups.totalGroups')}
value={String(totalGroups)}
prefix={<TagsOutlined />}
/>
</Col>
<Col xs={12} sm={8} md={6}>
<Col xs={12} sm={8}>
<Statistic
title={t('pages.groups.totalGroupedClients')}
value={String(totalClients)}
prefix={<TeamOutlined />}
/>
</Col>
<Col xs={12} sm={8} md={6}>
<Col xs={24} sm={8}>
<Statistic
title={t('pages.groups.totalTraffic')}
value={SizeFormatter.sizeFormat(totalTraffic)}
prefix={<RetweetOutlined />}
/>
<Space size={16} style={{ marginTop: 4, color: 'var(--ant-color-text-secondary)', fontSize: 13 }}>
<span><ArrowUpOutlined /> {SizeFormatter.sizeFormat(totalUpload)}</span>
<span><ArrowDownOutlined /> {SizeFormatter.sizeFormat(totalDownload)}</span>
</Space>
</Col>
</Row>
</Card>
+2
View File
@@ -129,6 +129,8 @@ export const GroupSummarySchema = z.object({
name: z.string(),
clientCount: z.number(),
trafficUsed: z.number().nullable().transform((v) => v ?? 0),
up: z.number().nullable().transform((v) => v ?? 0),
down: z.number().nullable().transform((v) => v ?? 0),
});
export const GroupSummaryListSchema = z.array(GroupSummarySchema).nullable().transform((v) => v ?? []);