diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/assets/js/model/models.js | 12 | ||||
| -rw-r--r-- | web/assets/js/model/xray.js | 38 | ||||
| -rw-r--r-- | web/controller/inbound.go | 21 | ||||
| -rw-r--r-- | web/html/login.html | 2 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/trojan.html | 35 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/vless.html | 36 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/vmess.html | 33 | ||||
| -rw-r--r-- | web/html/xui/inbound_modal.html | 24 | ||||
| -rw-r--r-- | web/html/xui/inbounds.html | 13 | ||||
| -rw-r--r-- | web/job/check_clinet_ip_job.go | 351 | ||||
| -rw-r--r-- | web/service/inbound.go | 22 | ||||
| -rw-r--r-- | web/service/server.go | 4 | ||||
| -rw-r--r-- | web/translation/translate.fa_IR.toml | 2 | ||||
| -rw-r--r-- | web/web.go | 3 |
14 files changed, 584 insertions, 12 deletions
diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js index 9fa5e7fc..80132a1e 100644 --- a/web/assets/js/model/models.js +++ b/web/assets/js/model/models.js @@ -36,7 +36,7 @@ class DBInbound { this.remark = ""; this.enable = true; this.expiryTime = 0; - + this.iplimit = 0; this.listen = ""; this.port = 0; this.protocol = ""; @@ -109,6 +109,10 @@ class DBInbound { get isExpiry() { return this.expiryTime < new Date().getTime(); } + get isDBInboundEmpty() { + const inbound = this.toInbound(); + return inbound.isInboundEmpty(); + } toInbound() { let settings = {}; @@ -151,10 +155,14 @@ class DBInbound { } } - genLink(clientIndex) { + genLink(clientIndex = 0) { const inbound = this.toInbound(); return inbound.genLink(this.address, this.remark, clientIndex); } + get genInboundLinks() { + const inbound = this.toInbound(); + return inbound.genInboundLinks(this.address, this.remark); + } } class AllSetting { diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index 23d73930..ea34e081 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -101,6 +101,7 @@ Object.freeze(XTLS_FLOW_CONTROL); Object.freeze(TLS_FLOW_CONTROL); Object.freeze(TLS_VERSION_OPTION); Object.freeze(TLS_CIPHER_OPTION); +Object.freeze(UTLS_FINGERPRINT); class XrayCommonClass { @@ -1065,7 +1066,6 @@ class Inbound extends XrayCommonClass { params.set("type", this.stream.network); if (this.xtls) { params.set("security", "xtls"); - address = this.stream.tls.server; } else { params.set("security", this.stream.security); } @@ -1119,7 +1119,10 @@ class Inbound extends XrayCommonClass { address = this.stream.tls.server; params.set("sni", address); } - params.set("flow", this.settings.vlesses[clientIndex].flow); + if (this.settings.vlesses[clientIndex].flow === "xtls-rprx-vision") { + params.set("flow", this.settings.vlesses[clientIndex].flow); + } + params.set("fp", this.settings.vlesses[clientIndex].fingerprint); } if (this.xtls) { @@ -1135,7 +1138,7 @@ class Inbound extends XrayCommonClass { return url.toString(); } - genSSLink(address = '', remark = '') { + genSSLink(address = '', remark = '',clientIndex) { let settings = this.settings; const server = this.stream.tls.server; if (!ObjectUtil.isEmpty(server)) { @@ -1245,6 +1248,22 @@ class Inbound extends XrayCommonClass { default: return ''; } } + genInboundLinks(address = '', remark = '') { + let link = ''; + JSON.parse(this.settings) + switch (this.protocol) { + case Protocols.VMESS: + case Protocols.VLESS: + case Protocols.TROJAN: + JSON.parse(this.settings).clients.forEach((client,index) => { + link += this.genLink(address, remark, index) + '\r\n'; + }); + return link; + case Protocols.SHADOWSOCKS: + return (this.genSSLink(address, remark) + '\r\n'); + default: return ''; + } +} static fromJson(json={}) { return new Inbound( @@ -1359,11 +1378,12 @@ Inbound.VmessSettings = class extends Inbound.Settings { } }; Inbound.VmessSettings.Vmess = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime='') { + constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { super(); this.id = id; this.alterId = alterId; this.email = email; + this.limitIp = limitIp; this.totalGB = totalGB; this.expiryTime = expiryTime; } @@ -1373,6 +1393,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { json.id, json.alterId, json.email, + json.limitIp, json.totalGB, json.expiryTime, @@ -1441,11 +1462,12 @@ Inbound.VLESSSettings = class extends Inbound.Settings { }; Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, expiryTime='') { + constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, fingerprint = UTLS_FINGERPRINT.UTLS_CHROME, expiryTime='') { super(); this.id = id; this.flow = flow; this.email = email; + this.limitIp = limitIp; this.totalGB = totalGB; this.fingerprint = fingerprint; this.expiryTime = expiryTime; @@ -1457,6 +1479,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { json.id, json.flow, json.email, + json.limitIp, json.totalGB, json.fingerprint, json.expiryTime, @@ -1557,11 +1580,12 @@ Inbound.TrojanSettings = class extends Inbound.Settings { } }; Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { - constructor(password=RandomUtil.randomSeq(10), flow ='', email=RandomUtil.randomText(), totalGB=0, expiryTime='') { + constructor(password=RandomUtil.randomSeq(10), flow ='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime='') { super(); this.password = password; this.flow = flow; this.email = email; + this.limitIp = limitIp; this.totalGB = totalGB; this.expiryTime = expiryTime; } @@ -1571,6 +1595,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { password: this.password, flow: this.flow, email: this.email, + limitIp: this.limitIp, totalGB: this.totalGB, expiryTime: this.expiryTime, }; @@ -1581,6 +1606,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { json.password, json.flow, json.email, + json.limitIp, json.totalGB, json.expiryTime, diff --git a/web/controller/inbound.go b/web/controller/inbound.go index 96c7ba18..8ec90ed4 100644 --- a/web/controller/inbound.go +++ b/web/controller/inbound.go @@ -31,6 +31,8 @@ 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("/clientIps/:email", a.getClientIps) + g.POST("/clearClientIps/:email", a.clearClientIps) g.POST("/resetClientTraffic/:email", a.resetClientTraffic) } @@ -122,7 +124,26 @@ func (a *InboundController) updateInbound(c *gin.Context) { a.xrayService.SetToNeedRestart() } } +func (a *InboundController) getClientIps(c *gin.Context) { + email := c.Param("email") + ips , err := a.inboundService.GetInboundClientIps(email) + if err != nil { + jsonObj(c, "No IP Record", nil) + return + } + jsonObj(c, ips, nil) +} +func (a *InboundController) clearClientIps(c *gin.Context) { + email := c.Param("email") + + err := a.inboundService.ClearClientIps(email) + if err != nil { + jsonMsg(c, "修改", err) + return + } + jsonMsg(c, "Log Cleared", nil) +} func (a *InboundController) resetClientTraffic(c *gin.Context) { email := c.Param("email") diff --git a/web/html/login.html b/web/html/login.html index 5138f15e..f2c2116c 100644 --- a/web/html/login.html +++ b/web/html/login.html @@ -39,7 +39,7 @@ <a-layout-content> <a-row type="flex" justify="center"> <a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8"> - <h1>{{ i18n "pages.login.title" }}</h1> + <h1>3x-ui {{ i18n "pages.login.title" }}</h1> </a-col> </a-row> <a-row type="flex" justify="center"> diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html index 6d43bc34..4bf57d7a 100644 --- a/web/html/xui/form/protocol/trojan.html +++ b/web/html/xui/form/protocol/trojan.html @@ -22,6 +22,41 @@ </span> <a-input v-model.trim="trojan.email"></a-input> </a-form-item> + <a-form-item> + <span slot="label"> + IP Count Limit + <a-tooltip> + <template slot="title"> + disable inbound if more than entered count (0 for disable limit ip) + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-input type="number" v-model.number="trojan.limitIp" min="0" ></a-input> + </a-form-item> + <a-form-item v-if="trojan.email && trojan.limitIp > 0 && isEdit"> + <span slot="label"> + IP log + <a-tooltip> + <template slot="title"> + IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log) + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + <a-tooltip> + <template slot="title"> + clear the log + </template> + <span style="color: #FF4D4F"> + <a-icon type="delete" @click="clearDBClientIps(trojan.email,$event)"></a-icon> + </span> + </a-tooltip> + </span> + <a-form layout="block"> + <a-textarea readonly @click="getDBClientIps(trojan.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }"> + </a-textarea> + </a-form> + </a-form-item> </a-form> <a-form-item label="Password"> <a-input v-model.trim="trojan.password"></a-input> diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html index fc7ffaa6..6d895f19 100644 --- a/web/html/xui/form/protocol/vless.html +++ b/web/html/xui/form/protocol/vless.html @@ -23,6 +23,42 @@ </span> <a-input v-model.trim="vless.email"></a-input> </a-form-item> + <a-form-item> + <span slot="label"> + IP Count Limit + <a-tooltip> + <template slot="title"> + disable inbound if more than entered count (0 for disable limit ip) + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-input type="number" v-model.number="vless.limitIp" min="0" ></a-input> + </a-form-item> + <a-form-item v-if="vless.email && vless.limitIp > 0 && isEdit"> + <span slot="label"> + IP log + <a-tooltip> + <template slot="title"> + IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log) + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + <a-tooltip> + <template slot="title"> + clear the log + </template> + <span style="color: #FF4D4F"> + <a-icon type="delete" @click="clearDBClientIps(vless.email,$event)"></a-icon> + </span> + </a-tooltip> + </span> + <a-form layout="block"> + + <a-textarea readonly @click="getDBClientIps(vless.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }"> + </a-textarea> + </a-form> + </a-form-item> </a-form> <a-form-item label="ID"> <a-input v-model.trim="vless.id"></a-input> diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html index f7050a5e..bab0cb8b 100644 --- a/web/html/xui/form/protocol/vmess.html +++ b/web/html/xui/form/protocol/vmess.html @@ -22,6 +22,39 @@ </span> <a-input v-model.trim="vmess.email"></a-input> </a-form-item> + <a-form-item> + <span slot="label"> + IP Count Limit + <a-tooltip> + <template slot="title"> + disable inbound if more than entered count (0 for disable limit ip) + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-input type="number" v-model.number="vmess.limitIp" min="0" ></a-input> + </a-form-item> + <a-form-item v-if="vmess.email && vmess.limitIp > 0 && isEdit"> + <span slot="label"> + IP Log + <a-tooltip> + <template slot="title"> + IPs history Log (before enabling inbound after it has been disabled by IP limit, you should clear the log) + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + <a-tooltip> + <template slot="title"> + clear the log + </template> + <span style="color: #FF4D4F"> + <a-icon type="delete" @click="clearDBClientIps(vmess.email,$event)"></a-icon> + </span> + </a-tooltip> + </span> + <a-textarea readonly @click="getDBClientIps(vmess.email,$event)" placeholder="Click To Get IPs" :auto-size="{ minRows: 0.5, maxRows: 10 }"> + </a-textarea> + </a-form-item> </a-form> <a-form-item label="ID"> <a-input v-model.trim="vmess.id"></a-input> diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index 80ea2286..54a64bf9 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -88,6 +88,30 @@ removeClient(index, clients) { clients.splice(index, 1); }, + async getDBClientIps(email,event) { + + const msg = await HttpUtil.post('/xui/inbound/clientIps/'+ email); + if (!msg.success) { + return; + } + try { + ips = JSON.parse(msg.obj) + ips = ips.join(",") + event.target.value = ips + } catch (error) { + // text + event.target.value = msg.obj + + } + + }, + async clearDBClientIps(email,event) { + const msg = await HttpUtil.post('/xui/inbound/clearClientIps/'+ email); + if (!msg.success) { + return; + } + event.target.value = "" + }, async resetClientTraffic(client,event) { const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email); if (!msg.success) { diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 14a61669..1f324846 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -45,6 +45,7 @@ <a-card hoverable> <div slot="title"> <a-button type="primary" @click="openAddInbound">Add Inbound</a-button> + <a-button type="primary" @click="exportAllLinks" class="copy-btn">Export Links</a-button> </div> <a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input> <a-table :columns="columns" :row-key="dbInbound => dbInbound.id" @@ -371,6 +372,18 @@ }, }); }, + exportAllLinks() { + let copyText = ''; + for (const dbInbound of this.dbInbounds) { + copyText += dbInbound.genInboundLinks + } + const clipboard = new ClipboardJS('.copy-btn', { + text: function () { + return copyText; + } + }); + clipboard.on('success', () => { this.$message.success('Export Links succeed'); }); + }, delInbound(dbInbound) { this.$confirm({ title: '{{ i18n "pages.inbounds.deleteInbound"}}', diff --git a/web/job/check_clinet_ip_job.go b/web/job/check_clinet_ip_job.go new file mode 100644 index 00000000..bf71116b --- /dev/null +++ b/web/job/check_clinet_ip_job.go @@ -0,0 +1,351 @@ +package job + +import ( + "x-ui/logger" + "x-ui/web/service" + "x-ui/database" + "x-ui/database/model" + "os" + ss "strings" + "regexp" + "encoding/json" + // "strconv" + "strings" + "time" + "net" + "github.com/go-cmd/cmd" + "sort" +) + +type CheckClientIpJob struct { + xrayService service.XrayService + inboundService service.InboundService +} +var job *CheckClientIpJob +var disAllowedIps []string + +func NewCheckClientIpJob() *CheckClientIpJob { + job = new(CheckClientIpJob) + return job +} + +func (j *CheckClientIpJob) Run() { + logger.Debug("Check Client IP Job...") + processLogFile() + + // disAllowedIps = []string{"192.168.1.183","192.168.1.197"} + blockedIps := []byte(ss.Join(disAllowedIps,",")) + err := os.WriteFile("./bin/blockedIPs", blockedIps, 0755) + checkError(err) + +} + +func processLogFile() { + accessLogPath := GetAccessLogPath() + if(accessLogPath == "") { + logger.Warning("xray log not init in config.json") + return + } + + data, err := os.ReadFile(accessLogPath) + InboundClientIps := make(map[string][]string) + checkError(err) + + // clean log + if err := os.Truncate(GetAccessLogPath(), 0); err != nil { + checkError(err) + } + + lines := ss.Split(string(data), "\n") + for _, line := range lines { + ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`) + emailRegx, _ := regexp.Compile(`email:.+`) + + matchesIp := ipRegx.FindString(line) + if(len(matchesIp) > 0) { + ip := string(matchesIp) + if( ip == "127.0.0.1" || ip == "1.1.1.1") { + continue + } + + matchesEmail := emailRegx.FindString(line) + if(matchesEmail == "") { + continue + } + matchesEmail = ss.Split(matchesEmail, "email: ")[1] + + if(InboundClientIps[matchesEmail] != nil) { + if(contains(InboundClientIps[matchesEmail],ip)){ + continue + } + InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip) + + + + }else{ + InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail],ip) + } + } + + } + disAllowedIps = []string{} + + for clientEmail, ips := range InboundClientIps { + inboundClientIps,err := GetInboundClientIps(clientEmail) + sort.Sort(sort.StringSlice(ips)) + if(err != nil){ + addInboundClientIps(clientEmail,ips) + + }else{ + updateInboundClientIps(inboundClientIps,clientEmail,ips) + } + + } + + + // check if inbound connection is more than limited ip and drop connection + LimitDevice := func() { LimitDevice() } + + stop := schedule(LimitDevice, 1000 *time.Millisecond) + time.Sleep(10 * time.Second) + stop <- true + +} +func GetAccessLogPath() string { + + config, err := os.ReadFile("bin/config.json") + checkError(err) + + jsonConfig := map[string]interface{}{} + err = json.Unmarshal([]byte(config), &jsonConfig) + checkError(err) + if(jsonConfig["log"] != nil) { + jsonLog := jsonConfig["log"].(map[string]interface{}) + if(jsonLog["access"] != nil) { + + accessLogPath := jsonLog["access"].(string) + + return accessLogPath + } + } + return "" + +} +func checkError(e error) { + if e != nil { + logger.Warning("client ip job err:", e) + } +} +func contains(s []string, str string) bool { + for _, v := range s { + if v == str { + return true + } + } + + return false +} +func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) { + db := database.GetDB() + InboundClientIps := &model.InboundClientIps{} + err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error + if err != nil { + return nil, err + } + return InboundClientIps, nil +} +func addInboundClientIps(clientEmail string,ips []string) error { + inboundClientIps := &model.InboundClientIps{} + jsonIps, err := json.Marshal(ips) + checkError(err) + + inboundClientIps.ClientEmail = clientEmail + inboundClientIps.Ips = string(jsonIps) + + + db := database.GetDB() + tx := db.Begin() + + defer func() { + if err == nil { + tx.Commit() + } else { + tx.Rollback() + } + }() + + err = tx.Save(inboundClientIps).Error + if err != nil { + return err + } + return nil +} +func updateInboundClientIps(inboundClientIps *model.InboundClientIps,clientEmail string,ips []string) error { + + jsonIps, err := json.Marshal(ips) + checkError(err) + + inboundClientIps.ClientEmail = clientEmail + inboundClientIps.Ips = string(jsonIps) + + // check inbound limitation + inbound, err := GetInboundByEmail(clientEmail) + checkError(err) + + if inbound.Settings == "" { + logger.Debug("wrong data ",inbound) + return nil + } + + settings := map[string][]model.Client{} + json.Unmarshal([]byte(inbound.Settings), &settings) + clients := settings["clients"] + + for _, client := range clients { + if client.Email == clientEmail { + + limitIp := client.LimitIP + + if(limitIp < len(ips) && limitIp != 0 && inbound.Enable) { + + disAllowedIps = append(disAllowedIps,ips[limitIp:]...) + } + } + } + logger.Debug("disAllowedIps ",disAllowedIps) + sort.Sort(sort.StringSlice(disAllowedIps)) + + db := database.GetDB() + err = db.Save(inboundClientIps).Error + if err != nil { + return err + } + return nil +} +func DisableInbound(id int) error{ + db := database.GetDB() + result := db.Model(model.Inbound{}). + Where("id = ? and enable = ?", id, true). + Update("enable", false) + err := result.Error + logger.Warning("disable inbound with id:",id) + + if err == nil { + job.xrayService.SetToNeedRestart() + } + + return err +} + +func GetInboundByEmail(clientEmail string) (*model.Inbound, error) { + db := database.GetDB() + var inbounds *model.Inbound + err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%" + clientEmail + "%").Find(&inbounds).Error + if err != nil { + return nil, err + } + return inbounds, nil +} + +func LimitDevice(){ + + localIp,err := LocalIP() + checkError(err) + + c := cmd.NewCmd("bash","-c","ss --tcp | grep -E '" + IPsToRegex(localIp) + "'| awk '{if($1==\"ESTAB\") print $4,$5;}'","| sort | uniq -c | sort -nr | head") + + <-c.Start() + if len(c.Status().Stdout) > 0 { + ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`) + portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`) + + for _, row := range c.Status().Stdout { + + data := strings.Split(row," ") + + destIp,destPort,srcIp,srcPort := "","","","" + + + destIp = string(ipRegx.FindString(data[0])) + + destPort = portRegx.FindString(data[0]) + destPort = strings.Replace(destPort,":","",-1) + + + srcIp = string(ipRegx.FindString(data[1])) + + srcPort = portRegx.FindString(data[1]) + srcPort = strings.Replace(srcPort,":","",-1) + + if(contains(disAllowedIps,srcIp)){ + dropCmd := cmd.NewCmd("bash","-c","ss -K dport = " + srcPort) + dropCmd.Start() + + logger.Debug("request droped : ",srcIp,srcPort,"to",destIp,destPort) + } + } + } + +} + +func LocalIP() ([]string, error) { + // get machine ips + + ifaces, err := net.Interfaces() + ips := []string{} + if err != nil { + return ips, err + } + for _, i := range ifaces { + addrs, err := i.Addrs() + if err != nil { + return ips, err + } + + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + + ips = append(ips,ip.String()) + + } + } + logger.Debug("System IPs : ",ips) + + return ips, nil +} + + +func IPsToRegex(ips []string) (string){ + + regx := "" + for _, ip := range ips { + regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")" + + } + regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")" + + return regx +} + +func schedule(LimitDevice func(), delay time.Duration) chan bool { + stop := make(chan bool) + + go func() { + for { + LimitDevice() + select { + case <-time.After(delay): + case <-stop: + return + } + } + }() + + return stop +} diff --git a/web/service/inbound.go b/web/service/inbound.go index 5c1cfedc..450c77f0 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -369,7 +369,29 @@ func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string) func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error { return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error } +func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) { + db := database.GetDB() + InboundClientIps := &model.InboundClientIps{} + err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error + if err != nil { + return "", err + } + return InboundClientIps.Ips, nil +} +func (s *InboundService) ClearClientIps(clientEmail string) (error) { + db := database.GetDB() + + result := db.Model(model.InboundClientIps{}). + Where("client_email = ?", clientEmail). + Update("ips", "") + err := result.Error + + if err != nil { + return err + } + return nil +} func (s *InboundService) ResetClientTraffic(clientEmail string) error { db := database.GetDB() diff --git a/web/service/server.go b/web/service/server.go index c0a8e9d2..d12c8e27 100644 --- a/web/service/server.go +++ b/web/service/server.go @@ -172,7 +172,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status { } func (s *ServerService) GetXrayVersions() ([]string, error) { - url := "https://api.github.com/repos/XTLS/Xray-core/releases" + url := "https://api.github.com/repos/mhsanaei/Xray-core/releases" resp, err := http.Get(url) if err != nil { return nil, err @@ -215,7 +215,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) { } fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch) - url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName) + url := fmt.Sprintf("https://github.com/mhsanaei/Xray-core/releases/download/%s/%s", version, fileName) resp, err := http.Get(url) if err != nil { return "", err diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 7d439fea..5751588b 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -54,7 +54,7 @@ "link" = "دیگر" [pages.login] -"title" = "ورود به سیستم X-UI" +"title" = "ورود به سیستم" "loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید" [pages.login.toasts] @@ -310,6 +310,9 @@ func (s *Server) startTask() { // Check the inbound traffic every 30 seconds that the traffic exceeds and expires s.cron.AddJob("@every 30s", job.NewCheckInboundJob()) + // check client ips from log file every 10 sec + s.cron.AddJob("@every 10s", job.NewCheckClientIpJob()) + // Make a traffic condition every day, 8:30 var entry cron.EntryID isTgbotenabled, err := s.settingService.GetTgbotenabled() |
