From 8c74a4eff533a645d13e8bb36474542eedb7f089 Mon Sep 17 00:00:00 2001 From: byang37 Date: Wed, 27 May 2026 00:58:04 +0800 Subject: [PATCH] feat: add inbound traffic multiplier --- database/model/model.go | 1 + frontend/public/openapi.json | 2 + frontend/src/models/dbinbound.ts | 3 + frontend/src/pages/api-docs/endpoints.ts | 4 +- .../src/pages/inbounds/InboundFormModal.tsx | 13 ++ frontend/src/pages/inbounds/InboundsPage.tsx | 1 + web/runtime/remote.go | 1 + web/service/inbound.go | 200 +++++++++++++++--- .../inbound_traffic_multiplier_test.go | 141 ++++++++++++ web/translation/ar-EG.json | 3 +- web/translation/en-US.json | 3 +- web/translation/es-ES.json | 3 +- web/translation/fa-IR.json | 3 +- web/translation/id-ID.json | 3 +- web/translation/ja-JP.json | 3 +- web/translation/pt-BR.json | 3 +- web/translation/ru-RU.json | 3 +- web/translation/tr-TR.json | 3 +- web/translation/uk-UA.json | 3 +- web/translation/vi-VN.json | 3 +- web/translation/zh-CN.json | 3 +- web/translation/zh-TW.json | 3 +- 22 files changed, 359 insertions(+), 46 deletions(-) create mode 100644 web/service/inbound_traffic_multiplier_test.go diff --git a/database/model/model.go b/database/model/model.go index 99566d9d..4636798d 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -55,6 +55,7 @@ type Inbound struct { ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` // Expiration timestamp TrafficReset string `json:"trafficReset" form:"trafficReset" gorm:"default:never;index:idx_enable_traffic_reset,priority:2"` // Traffic reset schedule LastTrafficResetTime int64 `json:"lastTrafficResetTime" form:"lastTrafficResetTime" gorm:"default:0"` // Last traffic reset timestamp + TrafficMultiplier float64 `json:"trafficMultiplier" form:"trafficMultiplier" gorm:"default:1"` // Multiplier for inbound/client traffic accounting ClientStats []xray.ClientTraffic `gorm:"foreignKey:InboundId;references:Id" json:"clientStats" form:"clientStats"` // Client traffic statistics // Xray configuration fields diff --git a/frontend/public/openapi.json b/frontend/public/openapi.json index fedcded9..ed7101f9 100644 --- a/frontend/public/openapi.json +++ b/frontend/public/openapi.json @@ -312,6 +312,7 @@ "remark": "VLESS-443", "enable": true, "expiryTime": 0, + "trafficMultiplier": 1, "listen": "", "port": 443, "protocol": "vless", @@ -500,6 +501,7 @@ "protocol": "vless", "expiryTime": 0, "total": 0, + "trafficMultiplier": 1, "settings": { "clients": [ { diff --git a/frontend/src/models/dbinbound.ts b/frontend/src/models/dbinbound.ts index 391838c2..e0e6008f 100644 --- a/frontend/src/models/dbinbound.ts +++ b/frontend/src/models/dbinbound.ts @@ -31,6 +31,7 @@ export type DBInboundInit = Partial<{ expiryTime: number; trafficReset: string; lastTrafficResetTime: number; + trafficMultiplier: number; listen: string; port: number; protocol: string; @@ -73,6 +74,7 @@ export class DBInbound { expiryTime: number; trafficReset: string; lastTrafficResetTime: number; + trafficMultiplier: number; listen: string; port: number; @@ -99,6 +101,7 @@ export class DBInbound { this.expiryTime = 0; this.trafficReset = "never"; this.lastTrafficResetTime = 0; + this.trafficMultiplier = 1; this.listen = ""; this.port = 0; diff --git a/frontend/src/pages/api-docs/endpoints.ts b/frontend/src/pages/api-docs/endpoints.ts index 6fc4e5a5..628dcd23 100644 --- a/frontend/src/pages/api-docs/endpoints.ts +++ b/frontend/src/pages/api-docs/endpoints.ts @@ -108,7 +108,7 @@ export const sections: readonly Section[] = [ path: '/panel/api/inbounds/list', summary: 'List every inbound owned by the authenticated user, including each inbound’s clientStats traffic counters. settings, streamSettings, and sniffing are returned as nested JSON objects (no escaped strings); legacy callers that send them back as JSON-encoded strings are still accepted on write.', response: - '{\n "success": true,\n "obj": [\n {\n "id": 1,\n "userId": 1,\n "up": 0,\n "down": 0,\n "total": 0,\n "remark": "VLESS-443",\n "enable": true,\n "expiryTime": 0,\n "listen": "",\n "port": 443,\n "protocol": "vless",\n "settings": {\n "clients": [],\n "decryption": "none"\n },\n "streamSettings": {\n "network": "tcp",\n "security": "reality",\n "realitySettings": { "show": false, "dest": "..." }\n },\n "tag": "inbound-443",\n "sniffing": {\n "enabled": true,\n "destOverride": ["http", "tls"]\n },\n "clientStats": []\n }\n ]\n}', + '{\n "success": true,\n "obj": [\n {\n "id": 1,\n "userId": 1,\n "up": 0,\n "down": 0,\n "total": 0,\n "remark": "VLESS-443",\n "enable": true,\n "expiryTime": 0,\n "trafficMultiplier": 1,\n "listen": "",\n "port": 443,\n "protocol": "vless",\n "settings": {\n "clients": [],\n "decryption": "none"\n },\n "streamSettings": {\n "network": "tcp",\n "security": "reality",\n "realitySettings": { "show": false, "dest": "..." }\n },\n "tag": "inbound-443",\n "sniffing": {\n "enabled": true,\n "destOverride": ["http", "tls"]\n },\n "clientStats": []\n }\n ]\n}', }, { method: 'GET', @@ -137,7 +137,7 @@ export const sections: readonly Section[] = [ path: '/panel/api/inbounds/add', summary: 'Create a new inbound. Send the full inbound payload (protocol, port, settings, streamSettings, sniffing, remark, expiryTime, total, enable). settings, streamSettings, and sniffing may be sent as nested JSON objects (preferred) or as JSON-encoded strings (legacy).', body: - '{\n "enable": true,\n "remark": "VLESS-443",\n "listen": "",\n "port": 443,\n "protocol": "vless",\n "expiryTime": 0,\n "total": 0,\n "settings": {\n "clients": [{ "id": "...", "email": "user1" }],\n "decryption": "none",\n "fallbacks": []\n },\n "streamSettings": {\n "network": "tcp",\n "security": "reality",\n "realitySettings": { "show": false, "dest": "..." }\n },\n "sniffing": {\n "enabled": true,\n "destOverride": ["http", "tls"]\n }\n}', + '{\n "enable": true,\n "remark": "VLESS-443",\n "listen": "",\n "port": 443,\n "protocol": "vless",\n "expiryTime": 0,\n "total": 0,\n "trafficMultiplier": 1,\n "settings": {\n "clients": [{ "id": "...", "email": "user1" }],\n "decryption": "none",\n "fallbacks": []\n },\n "streamSettings": {\n "network": "tcp",\n "security": "reality",\n "realitySettings": { "show": false, "dest": "..." }\n },\n "sniffing": {\n "enabled": true,\n "destOverride": ["http", "tls"]\n }\n}', errorResponse: '{\n "success": false,\n "msg": "Port 443 is already in use"\n}', }, diff --git a/frontend/src/pages/inbounds/InboundFormModal.tsx b/frontend/src/pages/inbounds/InboundFormModal.tsx index dd1c43c4..8ee46e70 100644 --- a/frontend/src/pages/inbounds/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/InboundFormModal.tsx @@ -940,6 +940,7 @@ export default function InboundFormModal({ expiryTime: form.expiryTime, trafficReset: form.trafficReset, lastTrafficResetTime: form.lastTrafficResetTime || 0, + trafficMultiplier: form.trafficMultiplier || 1, listen: ib.listen, port: ib.port, protocol: ib.protocol, @@ -1052,6 +1053,18 @@ export default function InboundFormModal({ }} /> + + { + const next = Number(v); + form.trafficMultiplier = next > 0 ? next : 1; + refresh(); + }} + /> +