diff --git a/frontend/src/pages/groups/GroupsPage.tsx b/frontend/src/pages/groups/GroupsPage.tsx index 45f144c9a..e8ce06957 100644 --- a/frontend/src/pages/groups/GroupsPage.tsx +++ b/frontend/src/pages/groups/GroupsPage.tsx @@ -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) => {count || 0}, }, + { + title: t('pages.groups.upload'), + dataIndex: 'up', + key: 'up', + width: 140, + render: (bytes: number) => {SizeFormatter.sizeFormat(bytes || 0)}, + }, + { + title: t('pages.groups.download'), + dataIndex: 'down', + key: 'down', + width: 140, + render: (bytes: number) => {SizeFormatter.sizeFormat(bytes || 0)}, + }, { title: t('pages.groups.trafficUsed'), dataIndex: 'trafficUsed', @@ -456,26 +480,30 @@ export default function GroupsPage() { - + } /> - + } /> - + } /> + + {SizeFormatter.sizeFormat(totalUpload)} + {SizeFormatter.sizeFormat(totalDownload)} + diff --git a/frontend/src/schemas/client.ts b/frontend/src/schemas/client.ts index 3dc73949c..916812280 100644 --- a/frontend/src/schemas/client.ts +++ b/frontend/src/schemas/client.ts @@ -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 ?? []); diff --git a/internal/web/service/client_groups.go b/internal/web/service/client_groups.go index 493f6e5f3..243b7d98d 100644 --- a/internal/web/service/client_groups.go +++ b/internal/web/service/client_groups.go @@ -14,6 +14,8 @@ type GroupSummary struct { Name string `json:"name"` ClientCount int `json:"clientCount"` TrafficUsed int64 `json:"trafficUsed"` + Up int64 `json:"up"` + Down int64 `json:"down"` } func (s *ClientService) ListGroups() ([]GroupSummary, error) { @@ -22,7 +24,7 @@ func (s *ClientService) ListGroups() ([]GroupSummary, error) { // never double-counts a client's traffic. var derived []GroupSummary if err := db.Table("clients AS c"). - Select("c.group_name AS name, COUNT(*) AS client_count, COALESCE(SUM(ct.up + ct.down), 0) AS traffic_used"). + Select("c.group_name AS name, COUNT(*) AS client_count, COALESCE(SUM(ct.up + ct.down), 0) AS traffic_used, COALESCE(SUM(ct.up), 0) AS up, COALESCE(SUM(ct.down), 0) AS down"). Joins("LEFT JOIN client_traffics ct ON ct.email = c.email"). Where("c.group_name <> ''"). Group("c.group_name"). @@ -36,17 +38,19 @@ func (s *ClientService) ListGroups() ([]GroupSummary, error) { type groupAgg struct { count int traffic int64 + up int64 + down int64 } merged := make(map[string]groupAgg, len(derived)+len(stored)) for _, g := range stored { merged[g.Name] = groupAgg{} } for _, g := range derived { - merged[g.Name] = groupAgg{count: g.ClientCount, traffic: g.TrafficUsed} + merged[g.Name] = groupAgg{count: g.ClientCount, traffic: g.TrafficUsed, up: g.Up, down: g.Down} } out := make([]GroupSummary, 0, len(merged)) for name, agg := range merged { - out = append(out, GroupSummary{Name: name, ClientCount: agg.count, TrafficUsed: agg.traffic}) + out = append(out, GroupSummary{Name: name, ClientCount: agg.count, TrafficUsed: agg.traffic, Up: agg.up, Down: agg.down}) } sort.Slice(out, func(i, j int) bool { return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name) diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index 0361e55c6..2956a042a 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -812,10 +812,12 @@ "groups": { "title": "المجموعات", "name": "الاسم", - "clientCount": "عملاء في المجموعة", + "clientCount": "العملاء", "totalGroups": "إجمالي المجموعات", "totalGroupedClients": "العملاء بمجموعة", "trafficUsed": "حركة المرور المستخدمة", + "upload": "رفع", + "download": "تنزيل", "totalTraffic": "إجمالي حركة المرور", "addGroup": "إضافة مجموعة", "createSuccess": "تم إنشاء المجموعة «{name}».", diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index 47a59fce7..c7dc19cd9 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -813,10 +813,12 @@ "groups": { "title": "Groups", "name": "Name", - "clientCount": "Clients in group", + "clientCount": "Clients", "totalGroups": "Total groups", "totalGroupedClients": "Clients with a group", "trafficUsed": "Traffic used", + "upload": "Upload", + "download": "Download", "totalTraffic": "Total traffic", "addGroup": "Add Group", "createSuccess": "Group \"{name}\" created.", diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index 7c788d3ee..2226aaf12 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -812,10 +812,12 @@ "groups": { "title": "Grupos", "name": "Nombre", - "clientCount": "Clientes en el grupo", + "clientCount": "Clientes", "totalGroups": "Total de grupos", "totalGroupedClients": "Clientes con grupo", "trafficUsed": "Tráfico usado", + "upload": "Subida", + "download": "Bajada", "totalTraffic": "Tráfico total", "addGroup": "Añadir grupo", "createSuccess": "Grupo «{name}» creado.", diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index 5c67bc3b2..1980cb79c 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -812,10 +812,12 @@ "groups": { "title": "گروه‌ها", "name": "نام", - "clientCount": "کاربران در گروه", + "clientCount": "کاربران", "totalGroups": "تعداد گروه‌ها", "totalGroupedClients": "کاربران دارای گروه", "trafficUsed": "ترافیک مصرف‌شده", + "upload": "آپلود", + "download": "دانلود", "totalTraffic": "مجموع ترافیک", "addGroup": "افزودن گروه", "createSuccess": "گروه «{name}» ایجاد شد.", diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index 30e5464b2..ad3075bb2 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -812,10 +812,12 @@ "groups": { "title": "Grup", "name": "Nama", - "clientCount": "Klien di grup", + "clientCount": "Klien", "totalGroups": "Total grup", "totalGroupedClients": "Klien dengan grup", "trafficUsed": "Trafik terpakai", + "upload": "Unggah", + "download": "Unduh", "totalTraffic": "Total trafik", "addGroup": "Tambah grup", "createSuccess": "Grup «{name}» dibuat.", diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index 414311de3..6d02d4ff7 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -812,10 +812,12 @@ "groups": { "title": "グループ", "name": "名前", - "clientCount": "グループ内のクライアント", + "clientCount": "クライアント", "totalGroups": "グループ合計", "totalGroupedClients": "グループのあるクライアント", "trafficUsed": "使用済みトラフィック", + "upload": "アップロード", + "download": "ダウンロード", "totalTraffic": "合計トラフィック", "addGroup": "グループ追加", "createSuccess": "グループ「{name}」を作成しました。", diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index b4410b4f6..3567ad0bd 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -812,10 +812,12 @@ "groups": { "title": "Grupos", "name": "Nome", - "clientCount": "Clientes no grupo", + "clientCount": "Clientes", "totalGroups": "Total de grupos", "totalGroupedClients": "Clientes com grupo", "trafficUsed": "Tráfego usado", + "upload": "Envio", + "download": "Recebimento", "totalTraffic": "Tráfego total", "addGroup": "Adicionar grupo", "createSuccess": "Grupo «{name}» criado.", diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index 88912957d..58f2b1c39 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -812,10 +812,12 @@ "groups": { "title": "Группы", "name": "Имя", - "clientCount": "Клиентов в группе", + "clientCount": "Клиенты", "totalGroups": "Всего групп", "totalGroupedClients": "Клиенты с группой", "trafficUsed": "Использованный трафик", + "upload": "Отправлено", + "download": "Получено", "totalTraffic": "Общий трафик", "addGroup": "Добавить группу", "createSuccess": "Группа «{name}» создана.", diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index bba16fd32..b55107647 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -813,10 +813,12 @@ "groups": { "title": "Gruplar", "name": "İsim", - "clientCount": "Gruptaki kullanıcılar", + "clientCount": "Kullanıcılar", "totalGroups": "Toplam grup", "totalGroupedClients": "Grubu olan kullanıcılar", "trafficUsed": "Kullanılan trafik", + "upload": "Yükleme", + "download": "İndirme", "totalTraffic": "Toplam trafik", "addGroup": "Grup ekle", "createSuccess": "«{name}» grubu oluşturuldu.", diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index 0bb3aca26..29a7fb06e 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -812,10 +812,12 @@ "groups": { "title": "Групи", "name": "Назва", - "clientCount": "Клієнтів у групі", + "clientCount": "Клієнти", "totalGroups": "Всього груп", "totalGroupedClients": "Клієнти з групою", "trafficUsed": "Використаний трафік", + "upload": "Вивантаження", + "download": "Завантаження", "totalTraffic": "Загальний трафік", "addGroup": "Додати групу", "createSuccess": "Групу «{name}» створено.", diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index 82acf8dab..aaa16155f 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -812,10 +812,12 @@ "groups": { "title": "Nhóm", "name": "Tên", - "clientCount": "Client trong nhóm", + "clientCount": "Client", "totalGroups": "Tổng số nhóm", "totalGroupedClients": "Client có nhóm", "trafficUsed": "Lưu lượng đã dùng", + "upload": "Tải lên", + "download": "Tải xuống", "totalTraffic": "Tổng lưu lượng", "addGroup": "Thêm nhóm", "createSuccess": "Đã tạo nhóm «{name}».", diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index a2d4a26e2..946678a0c 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -812,10 +812,12 @@ "groups": { "title": "分组", "name": "名称", - "clientCount": "分组中的客户端", + "clientCount": "客户端", "totalGroups": "分组总数", "totalGroupedClients": "有分组的客户端", "trafficUsed": "已用流量", + "upload": "上传", + "download": "下载", "totalTraffic": "总流量", "addGroup": "添加分组", "createSuccess": "已创建分组 “{name}”。", diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index 7b94fb5ca..5485693cb 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -812,10 +812,12 @@ "groups": { "title": "群組", "name": "名稱", - "clientCount": "群組中的客戶端", + "clientCount": "客戶端", "totalGroups": "群組總數", "totalGroupedClients": "有群組的客戶端", "trafficUsed": "已用流量", + "upload": "上傳", + "download": "下載", "totalTraffic": "總流量", "addGroup": "新增群組", "createSuccess": "已建立群組「{name}」。",