From 146b4d78730dde9aa678c6fe49881afa98e4c2e6 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Wed, 22 Feb 2023 15:13:45 +0100 Subject: [PATCH] Separate client module --- config/version | 2 +- web/controller/inbound.go | 62 +++++++++++ web/html/xui/client_modal.html | 102 +++++++++++++++++ web/html/xui/form/client.html | 79 ++++++++++++++ web/html/xui/form/inbound.html | 2 +- web/html/xui/form/protocol/trojan.html | 64 ++++------- web/html/xui/form/protocol/vless.html | 64 ++++------- web/html/xui/form/protocol/vmess.html | 68 ++++-------- web/html/xui/inbound_client_table.html | 2 + web/html/xui/inbound_info_modal.html | 19 +--- web/html/xui/inbound_modal.html | 39 +------ web/html/xui/inbounds.html | 101 ++++++++++++++--- web/service/inbound.go | 145 ++++++++++++++++++++----- web/service/xray.go | 2 +- web/translation/translate.en_US.toml | 7 ++ web/translation/translate.fa_IR.toml | 7 ++ web/translation/translate.zh_Hans.toml | 7 ++ 17 files changed, 539 insertions(+), 233 deletions(-) create mode 100644 web/html/xui/client_modal.html create mode 100644 web/html/xui/form/client.html diff --git a/config/version b/config/version index abd41058..0d91a54c 100644 --- a/config/version +++ b/config/version @@ -1 +1 @@ -0.2.4 +0.3.0 diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 96c7ba18..63314897 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -31,6 +31,9 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) { g.POST("/add", a.addInbound) g.POST("/del/:id", a.delInbound) g.POST("/update/:id", a.updateInbound) + g.POST("/addClient/", a.addInboundClient) + g.POST("/delClient/:inboundId/:index", a.delInboundClient) + g.POST("/updateClient/:index", a.updateInboundClient) g.POST("/resetClientTraffic/:email", a.resetClientTraffic) } @@ -123,6 +126,65 @@ func (a *InboundController) updateInbound(c *gin.Context) { } } +func (a *InboundController) addInboundClient(c *gin.Context) { + inbound := &model.Inbound{} + err := c.ShouldBind(inbound) + if err != nil { + jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) + return + } + + err = a.inboundService.AddInboundClient(inbound) + if err != nil { + jsonMsg(c, "something worng!", err) + return + } + jsonMsg(c, "Client added", nil) +} + +func (a *InboundController) delInboundClient(c *gin.Context) { + inboundId, err := strconv.Atoi(c.Param("inboundId")) + if err != nil { + jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) + return + } + + index, err := strconv.Atoi(c.Param("index")) + if err != nil { + jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) + return + } + + err = a.inboundService.DelInboundClient(inboundId, index) + if err != nil { + jsonMsg(c, "something worng!", err) + return + } + jsonMsg(c, "Client deleted", nil) +} + +func (a *InboundController) updateInboundClient(c *gin.Context) { + index, err := strconv.Atoi(c.Param("index")) + if err != nil { + jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) + return + } + + inbound := &model.Inbound{} + err = c.ShouldBind(inbound) + if err != nil { + jsonMsg(c, I18n(c, "pages.inbounds.revise"), err) + return + } + + err = a.inboundService.UpdateInboundClient(inbound, index) + if err != nil { + jsonMsg(c, "something worng!", err) + return + } + jsonMsg(c, "Client updated", nil) +} + func (a *InboundController) resetClientTraffic(c *gin.Context) { email := c.Param("email") diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html new file mode 100644 index 00000000..9f49f30b --- /dev/null +++ b/web/html/xui/client_modal.html @@ -0,0 +1,102 @@ +{{define "clientsModal"}} + + {{template "form/client"}} + + +{{end}} \ No newline at end of file diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html new file mode 100644 index 00000000..b4604971 --- /dev/null +++ b/web/html/xui/form/client.html @@ -0,0 +1,79 @@ +{{define "form/client"}} + + + + + Email + + + + + + + + + + + + + + + + [[ key ]] + + + + + + + + {{ i18n "none" }} + [[ key ]] + + + + + {{ i18n "none" }} + [[ key ]] + + + + + {{ i18n "pages.inbounds.totalFlow" }}(GB) + + + + + + + + + + + {{ i18n "pages.inbounds.expireDate" }} + + + + + + + Expired + + +{{end}} \ No newline at end of file diff --git a/web/html/xui/form/inbound.html b/web/html/xui/form/inbound.html index 1a2e7ca1..bf244561 100644 --- a/web/html/xui/form/inbound.html +++ b/web/html/xui/form/inbound.html @@ -8,7 +8,7 @@ - + [[ p ]] diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html index 52bd646b..30f5889d 100644 --- a/web/html/xui/form/protocol/trojan.html +++ b/web/html/xui/form/protocol/trojan.html @@ -1,11 +1,7 @@ {{define "form/trojan"}} - - - - - Account is (Expired|Traffic Ended) And Disabled + + @@ -14,20 +10,17 @@ - - + - + - + - + {{ i18n "none" }} [[ key ]] @@ -42,7 +35,7 @@ - + @@ -55,39 +48,22 @@ + v-model="client._expiryTime" style="width: 300px;"> - - - - - - - - [[ sizeFormat(getUpStats(trojan.email)) ]] / [[ sizeFormat(getDownStats(trojan.email)) ]] - used : [[ sizeFormat(getUpStats(trojan.email) + getDownStats(trojan.email)) ]] - - - - - - - - - - - - - - + + + + + + + + + +
[[ col ]]
[[ col ]]
+
+
diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html index b9128b59..fe60fcfe 100644 --- a/web/html/xui/form/protocol/vless.html +++ b/web/html/xui/form/protocol/vless.html @@ -1,12 +1,7 @@ {{define "form/vless"}} - - - - - Account is (Expired|Traffic Ended) And Disabled - + + @@ -15,17 +10,14 @@ - - + - + - + @@ -54,7 +46,7 @@ - + @@ -67,40 +59,22 @@ + v-model="client._expiryTime" style="width: 300px;"> - - - - - - - - [[ sizeFormat(getUpStats(vless.email)) ]] / [[ sizeFormat(getDownStats(vless.email)) ]] - used : [[ sizeFormat(getUpStats(vless.email) + getDownStats(vless.email)) ]] - - - - - - - - - - - - - - - + + + + + + + + + +
[[ col ]]
[[ col ]]
+
+
diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html index bd46f009..a3c43fff 100644 --- a/web/html/xui/form/protocol/vmess.html +++ b/web/html/xui/form/protocol/vmess.html @@ -1,11 +1,7 @@ {{define "form/vmess"}} - - - - Account is (Expired|Traffic Ended) And Disabled - + + @@ -14,20 +10,17 @@ - - + - + - + - + @@ -39,7 +32,7 @@ - + @@ -52,43 +45,22 @@ + v-model="client._expiryTime" style="width: 300px;"> - - - - - - - - [[ sizeFormat(getUpStats(vmess.email)) ]] / [[ sizeFormat(getDownStats(vmess.email)) ]] - used : [[ sizeFormat(getUpStats(vmess.email) + getDownStats(vmess.email)) ]] - - - - - - - - - - - - - - - - - - + + + + + + + + + +
[[ col ]]
[[ col ]]
+
+
diff --git a/web/html/xui/inbound_client_table.html b/web/html/xui/inbound_client_table.html index 1f29e372..0b50b220 100644 --- a/web/html/xui/inbound_client_table.html +++ b/web/html/xui/inbound_client_table.html @@ -5,8 +5,10 @@ diff --git a/web/html/xui/inbound_info_modal.html b/web/html/xui/inbound_info_modal.html index 06cdf717..8f0a475c 100644 --- a/web/html/xui/inbound_info_modal.html +++ b/web/html/xui/inbound_info_modal.html @@ -58,12 +58,11 @@ {{ i18n "pages.inbounds.client" }} - - - - + + +
[[ Object.keys(infoModal.clientSettings)[0] ]][[ Object.keys(infoModal.clientSettings)[1] ]][[ Object.keys(infoModal.clientSettings)[2] ]]
[[ Object.values(infoModal.clientSettings)[0] ]][[ Object.values(infoModal.clientSettings)[1] ]][[ Object.values(infoModal.clientSettings)[2] ]][[ col ]]
[[ col ]]
@@ -119,18 +118,8 @@ this.dbInbound = new DBInbound(dbInbound); this.link = dbInbound.genLink(index); this.clientSettings = Object.values(JSON.parse(this.inbound.settings).clients)[index]; - this.clientStats = dbInbound.clientStats; this.isExpired = this.inbound.isExpiry(index); - if(dbInbound.clientStats.length > 0) - { - for (const key in dbInbound.clientStats) { - if (Object.hasOwnProperty.call(dbInbound.clientStats, key)) { - if(dbInbound.clientStats[key]['email'] == this.clientSettings.email) - this.clientStats = dbInbound.clientStats[key]; - - } - } - } + this.clientStats = this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email); this.visible = true; infoModalApp.$nextTick(() => { if (this.clipboard === null) { diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index fdd1328f..297c1115 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -109,53 +109,18 @@ isExpiry(index) { return this.inbound.isExpiry(index) }, - getUpStats(email) { - clientStats = this.inbound.clientStats - if(clientStats.length > 0) - { - for (const key in clientStats) { - if (Object.hasOwnProperty.call(clientStats, key)) { - if(clientStats[key]['email'] == email) - return clientStats[key]['up'] - - } - } - } - }, - getDownStats(email) { - clientStats = this.inbound.clientStats - if(clientStats.length > 0) - { - for (const key in clientStats) { - if (Object.hasOwnProperty.call(clientStats, key)) { - if(clientStats[key]['email'] == email) - return clientStats[key]['down'] - - } - } - } - }, isClientEnable(email) { clientStats = this.dbInbound.clientStats ? this.dbInbound.clientStats.find(stats => stats.email === email) : null return clientStats ? clientStats['enable'] : true }, - getHeaderText(email) { - if(email == "") - return "Add Client" - - return email + (this.isClientEnable(email) == true ? ' Active' : ' Deactive') - }, - getHeaderStyle(email) { - return (this.isClientEnable(email) == true ? '' : 'deactive-client') - }, getNewEmail(client) { var chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; var string = ''; - var len = 6 + Math.floor(Math.random() * 5) + var len = 6 + Math.floor(Math.random() * 5); for(var ii=0; ii
- + {{ i18n "pages.inbounds.addInbound" }}
{{ i18n "edit" }} + + + {{ i18n "pages.client.add"}} + {{ i18n "pages.inbounds.resetTraffic" }} @@ -128,15 +132,6 @@ > {{template "client_table"}} - - {{template "client_table"}} -
@@ -211,10 +206,6 @@ { title: 'Password', width: 150, dataIndex: "password" }, ]; - const innerOneColumns = [ - { title: '', width: 50, scopedSlots: { customRender: 'actions' } }, - ]; - const app = new Vue({ delimiters: ['[[', ']]'], el: '#app', @@ -238,6 +229,7 @@ return; } this.setInbounds(msg.obj); + this.searchKey = ''; }, setInbounds(dbInbounds) { this.inbounds.splice(0); @@ -281,6 +273,9 @@ case "edit": this.openEditInbound(dbInbound.id); break; + case "addClient": + this.openAddClient(dbInbound) + break; case "resetTraffic": this.resetTraffic(dbInbound); break; @@ -355,6 +350,71 @@ }; await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal); }, + openAddClient(inbound) { + clientModal.show({ + title: '{{ i18n "pages.client.add"}}', + okText: '{{ i18n "pages.client.submitAdd"}}', + dbInbound: inbound, + confirm: async (inbound, dbInbound, index) => { + clientModal.loading(); + await this.addClient(inbound, dbInbound); + clientModal.close(); + }, + isEdit: false + }); + }, + openEditClient(inbound, index) { + clientModal.show({ + title: '{{ i18n "pages.client.edit"}}', + okText: '{{ i18n "pages.client.submitEdit"}}', + dbInbound: inbound, + index: index, + confirm: async (inbound, dbInbound, index) => { + clientModal.loading(); + await this.updateClient(inbound, dbInbound, index); + clientModal.close(); + }, + isEdit: true + }); + }, + async addClient(inbound, dbInbound) { + const data = { + id: dbInbound.id, + up: dbInbound.up, + down: dbInbound.down, + total: dbInbound.total, + remark: dbInbound.remark, + enable: dbInbound.enable, + expiryTime: dbInbound.expiryTime, + + listen: inbound.listen, + port: inbound.port, + protocol: inbound.protocol, + settings: inbound.settings.toString(), + streamSettings: inbound.stream.toString(), + sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}', + }; + await this.submit('/xui/inbound/addClient', data, clientModal); + }, + async updateClient(inbound, dbInbound, index) { + const data = { + id: dbInbound.id, + up: dbInbound.up, + down: dbInbound.down, + total: dbInbound.total, + remark: dbInbound.remark, + enable: dbInbound.enable, + expiryTime: dbInbound.expiryTime, + + listen: inbound.listen, + port: inbound.port, + protocol: inbound.protocol, + settings: inbound.settings.toString(), + streamSettings: inbound.stream.toString(), + sniffing: inbound.canSniffing() ? inbound.sniffing.toString() : '{}', + }; + await this.submit(`/xui/inbound/updateClient/${index}`, data, clientModal); + }, resetTraffic(dbInbound) { this.$confirm({ title: '{{ i18n "pages.inbounds.resetTraffic"}}', @@ -378,6 +438,15 @@ onOk: () => this.submit('/xui/inbound/del/' + dbInbound.id), }); }, + delClient(dbInboundId,index) { + this.$confirm({ + title: '{{ i18n "pages.inbounds.deleteInbound"}}', + content: '{{ i18n "pages.inbounds.deleteInboundContent"}}', + okText: '{{ i18n "delete"}}', + cancelText: '{{ i18n "cancel"}}', + onOk: () => this.submit('/xui/inbound/delClient/' + dbInboundId + '/' + index), + }); + }, showQrcode(dbInbound, clientIndex) { const link = dbInbound.genLink(clientIndex); qrModal.show('{{ i18n "qrCode"}}', link, dbInbound); @@ -478,6 +547,9 @@ clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null return clientStats ? clientStats['enable'] : true }, + isRemovable(dbInbound_id){ + return this.getInboundClients(this.dbInbounds.find(row => row.id === dbInbound_id)).length > 1 + } }, watch: { searchKey: debounce(function (newVal) { @@ -509,5 +581,6 @@ {{template "qrcodeModal"}} {{template "textModal"}} {{template "inboundInfoModal"}} +{{template "clientsModal"}} \ No newline at end of file diff --git a/web/service/inbound.go b/web/service/inbound.go index 9c24a9b3..c9859f86 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -125,11 +125,16 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, err return inbound, common.NewError("Duplicate email:", existEmail) } + clients, err := s.getClients(inbound) + if err != nil { + return inbound, err + } + db := database.GetDB() err = db.Save(inbound).Error if err == nil { - s.UpdateClientStat(inbound.Id, inbound.Settings) + s.AddClientStat(inbound.Id, &clients[0]) } return inbound, err } @@ -168,6 +173,10 @@ func (s *InboundService) AddInbounds(inbounds []*model.Inbound) error { func (s *InboundService) DelInbound(id int) error { db := database.GetDB() + err := db.Where("inbound_id = ?", id).Delete(xray.ClientTraffic{}).Error + if err != nil { + return err + } return db.Delete(model.Inbound{}, id).Error } @@ -216,11 +225,89 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, oldInbound.Sniffing = inbound.Sniffing oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port) - s.UpdateClientStat(inbound.Id, inbound.Settings) db := database.GetDB() return inbound, db.Save(oldInbound).Error } +func (s *InboundService) AddInboundClient(inbound *model.Inbound) error { + existEmail, err := s.checkEmailExistForInbound(inbound) + if err != nil { + return err + } + + if existEmail != "" { + return common.NewError("Duplicate email:", existEmail) + } + + clients, err := s.getClients(inbound) + if err != nil { + return err + } + + oldInbound, err := s.GetInbound(inbound.Id) + if err != nil { + return err + } + + oldInbound.Settings = inbound.Settings + + s.AddClientStat(inbound.Id, &clients[len(clients)-1]) + db := database.GetDB() + return db.Save(oldInbound).Error +} + +func (s *InboundService) DelInboundClient(inboundId int, index int) error { + oldInbound, err := s.GetInbound(inboundId) + if err != nil { + return err + } + settings := map[string][]model.Client{} + json.Unmarshal([]byte(oldInbound.Settings), &settings) + clients := settings["clients"] + settings["clients"] = append(clients[:index], clients[index+1:]...) + + newSetting, err := json.Marshal(settings) + if err != nil { + return err + } + oldInbound.Settings = string(newSetting) + + db := database.GetDB() + s.DelClientStat(db, clients[index].Email) + return db.Save(oldInbound).Error +} + +func (s *InboundService) UpdateInboundClient(inbound *model.Inbound, index int) error { + existEmail, err := s.checkEmailExistForInbound(inbound) + if err != nil { + return err + } + if existEmail != "" { + return common.NewError("Duplicate email:", existEmail) + } + + clients, err := s.getClients(inbound) + if err != nil { + return err + } + + oldInbound, err := s.GetInbound(inbound.Id) + if err != nil { + return err + } + + oldClients, err := s.getClients(oldInbound) + if err != nil { + return err + } + + oldInbound.Settings = inbound.Settings + + s.UpdateClientStat(oldClients[index].Email, &clients[index]) + db := database.GetDB() + return db.Save(oldInbound).Error +} + func (s *InboundService) AddTraffic(traffics []*xray.Traffic) (err error) { if len(traffics) == 0 { return nil @@ -297,7 +384,7 @@ func (s *InboundService) AddClientTraffic(traffics []*xray.ClientTraffic) (err e traffic.Total = client.TotalGB } } - if tx.Where("inbound_id = ?", inbound.Id).Where("email = ?", traffic.Email). + if tx.Where("inbound_id = ?, email = ?", inbound.Id, traffic.Email). UpdateColumns(map[string]interface{}{ "enable": true, "expiry_time": traffic.ExpiryTime, @@ -336,33 +423,37 @@ func (s *InboundService) DisableInvalidClients() (int64, error) { count := result.RowsAffected return count, err } -func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string) error { +func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error { db := database.GetDB() - // get settings clients - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inboundSettings), &settings) - clients := settings["clients"] - for _, client := range clients { - result := db.Model(xray.ClientTraffic{}). - Where("inbound_id = ? and email = ?", inboundId, client.Email). - Updates(map[string]interface{}{"enable": true, "total": client.TotalGB, "expiry_time": client.ExpiryTime}) - if result.RowsAffected == 0 { - clientTraffic := xray.ClientTraffic{} - clientTraffic.InboundId = inboundId - clientTraffic.Email = client.Email - clientTraffic.Total = client.TotalGB - clientTraffic.ExpiryTime = client.ExpiryTime - clientTraffic.Enable = true - clientTraffic.Up = 0 - clientTraffic.Down = 0 - db.Create(&clientTraffic) - } - err := result.Error - if err != nil { - return err - } + clientTraffic := xray.ClientTraffic{} + clientTraffic.InboundId = inboundId + clientTraffic.Email = client.Email + clientTraffic.Total = client.TotalGB + clientTraffic.ExpiryTime = client.ExpiryTime + clientTraffic.Enable = true + clientTraffic.Up = 0 + clientTraffic.Down = 0 + result := db.Create(&clientTraffic) + err := result.Error + if err != nil { + return err + } + return nil +} +func (s *InboundService) UpdateClientStat(email string, client *model.Client) error { + db := database.GetDB() + result := db.Model(xray.ClientTraffic{}). + Where("email = ?", email). + Updates(map[string]interface{}{ + "enable": true, + "email": client.Email, + "total": client.TotalGB, + "expiry_time": client.ExpiryTime}) + err := result.Error + if err != nil { + return err } return nil } diff --git a/web/service/xray.go b/web/service/xray.go index 33425c3c..d9e65f8d 100644 --- a/web/service/xray.go +++ b/web/service/xray.go @@ -160,5 +160,5 @@ func (s *XrayService) SetToNeedRestart() { } func (s *XrayService) IsNeedRestartAndSetFalse() bool { - return isNeedXrayRestart.CAS(true, false) + return isNeedXrayRestart.CompareAndSwap(true, false) } diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index e11dcc12..59b01869 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -127,6 +127,13 @@ "clickOnQRcode" = "Click on QR Code to Copy" "client" = "Client" +[pages.client] +"add" = "Add client" +"edit" = "Edit client" +"submitAdd" = "Add client" +"submitEdit" = "Save changes" +"clientCount" = "Number of clients" + [pages.inbounds.toasts] "obtain" = "Obtain" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 413afa8e..f5a72747 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -127,6 +127,13 @@ "clickOnQRcode" = "برای کپی بر روی کد تصویری کلیک کنید" "client" = "کاربر" +[pages.client] +"add" = "کاربر جدید" +"edit" = "ویرایش کاربر" +"submitAdd" = "اضافه کردن" +"submitEdit" = "ذخیره تغییرات" +"clientCount" = "تعداد کاربران" + [pages.inbounds.toasts] "obtain" = "Obtain" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 0e0be0a0..46c66bac 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -127,6 +127,13 @@ "clickOnQRcode" = "点击二维码复制" "client" = "客户" +[pages.client] +"add" = "添加客户端" +"edit" = "编辑客户" +"submitAdd" = "添加客户端" +"submitEdit" = "保存修改" +"clientCount" = "客户数量" + [pages.inbounds.toasts] "obtain" = "获取"
{{ i18n "usage" }}{{ i18n "pages.inbounds.totalFlow" }}{{ i18n "pages.inbounds.expireDate" }}{{ i18n "enable" }}