mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-04-19 13:32:24 +00:00
ip limit + export links
This commit is contained in:
parent
1a4ba4afd6
commit
5317df21f3
20 changed files with 604 additions and 14 deletions
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
mv xui-release x-ui
|
mv xui-release x-ui
|
||||||
mkdir bin
|
mkdir bin
|
||||||
cd bin
|
cd bin
|
||||||
wget https://github.com/XTLS/Xray-core/releases/latest/download/Xray-linux-64.zip
|
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
|
@ -40,3 +40,4 @@ jobs:
|
||||||
file: x-ui-linux-amd64.tar.gz
|
file: x-ui-linux-amd64.tar.gz
|
||||||
asset_name: x-ui-linux-amd64.tar.gz
|
asset_name: x-ui-linux-amd64.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
overwrite: true
|
||||||
|
|
|
@ -42,6 +42,9 @@ func initInbound() error {
|
||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
func initInboundClientIps() error {
|
||||||
|
return db.AutoMigrate(&model.InboundClientIps{})
|
||||||
|
}
|
||||||
func initClientTraffic() error {
|
func initClientTraffic() error {
|
||||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
@ -81,6 +84,10 @@ func InitDB(dbPath string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = initInboundClientIps()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
err = initClientTraffic()
|
err = initClientTraffic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -43,6 +43,11 @@ type Inbound struct {
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
}
|
}
|
||||||
|
type InboundClientIps struct {
|
||||||
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
|
Ips string `json:"ips" form:"ips"`
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||||
listen := i.Listen
|
listen := i.Listen
|
||||||
|
@ -70,6 +75,7 @@ type Client struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
AlterIds uint16 `json:"alterId"`
|
AlterIds uint16 `json:"alterId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
LimitIP int `json:"limitIp"`
|
||||||
Security string `json:"security"`
|
Security string `json:"security"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
||||||
github.com/Workiva/go-datastructures v1.0.53
|
github.com/Workiva/go-datastructures v1.0.53
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/gin-gonic/gin v1.9.0
|
||||||
|
github.com/go-cmd/cmd v1.4.1
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -28,6 +28,8 @@ github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjX
|
||||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc=
|
||||||
|
github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
@ -44,6 +46,7 @@ github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVL
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
|
|
@ -36,7 +36,7 @@ class DBInbound {
|
||||||
this.remark = "";
|
this.remark = "";
|
||||||
this.enable = true;
|
this.enable = true;
|
||||||
this.expiryTime = 0;
|
this.expiryTime = 0;
|
||||||
|
this.iplimit = 0;
|
||||||
this.listen = "";
|
this.listen = "";
|
||||||
this.port = 0;
|
this.port = 0;
|
||||||
this.protocol = "";
|
this.protocol = "";
|
||||||
|
@ -109,6 +109,10 @@ class DBInbound {
|
||||||
get isExpiry() {
|
get isExpiry() {
|
||||||
return this.expiryTime < new Date().getTime();
|
return this.expiryTime < new Date().getTime();
|
||||||
}
|
}
|
||||||
|
get isDBInboundEmpty() {
|
||||||
|
const inbound = this.toInbound();
|
||||||
|
return inbound.isInboundEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
toInbound() {
|
toInbound() {
|
||||||
let settings = {};
|
let settings = {};
|
||||||
|
@ -151,10 +155,14 @@ class DBInbound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(clientIndex) {
|
genLink(clientIndex = 0) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(this.address, this.remark, clientIndex);
|
return inbound.genLink(this.address, this.remark, clientIndex);
|
||||||
}
|
}
|
||||||
|
get genInboundLinks() {
|
||||||
|
const inbound = this.toInbound();
|
||||||
|
return inbound.genInboundLinks(this.address, this.remark);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AllSetting {
|
class AllSetting {
|
||||||
|
|
|
@ -101,6 +101,7 @@ Object.freeze(XTLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_FLOW_CONTROL);
|
Object.freeze(TLS_FLOW_CONTROL);
|
||||||
Object.freeze(TLS_VERSION_OPTION);
|
Object.freeze(TLS_VERSION_OPTION);
|
||||||
Object.freeze(TLS_CIPHER_OPTION);
|
Object.freeze(TLS_CIPHER_OPTION);
|
||||||
|
Object.freeze(UTLS_FINGERPRINT);
|
||||||
|
|
||||||
class XrayCommonClass {
|
class XrayCommonClass {
|
||||||
|
|
||||||
|
@ -1065,7 +1066,6 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("type", this.stream.network);
|
params.set("type", this.stream.network);
|
||||||
if (this.xtls) {
|
if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
address = this.stream.tls.server;
|
|
||||||
} else {
|
} else {
|
||||||
params.set("security", this.stream.security);
|
params.set("security", this.stream.security);
|
||||||
}
|
}
|
||||||
|
@ -1119,7 +1119,10 @@ class Inbound extends XrayCommonClass {
|
||||||
address = this.stream.tls.server;
|
address = this.stream.tls.server;
|
||||||
params.set("sni", address);
|
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) {
|
if (this.xtls) {
|
||||||
|
@ -1135,7 +1138,7 @@ class Inbound extends XrayCommonClass {
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address = '', remark = '') {
|
genSSLink(address = '', remark = '',clientIndex) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const server = this.stream.tls.server;
|
const server = this.stream.tls.server;
|
||||||
if (!ObjectUtil.isEmpty(server)) {
|
if (!ObjectUtil.isEmpty(server)) {
|
||||||
|
@ -1245,6 +1248,22 @@ class Inbound extends XrayCommonClass {
|
||||||
default: return '';
|
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={}) {
|
static fromJson(json={}) {
|
||||||
return new Inbound(
|
return new Inbound(
|
||||||
|
@ -1359,11 +1378,12 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.alterId = alterId;
|
this.alterId = alterId;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
}
|
}
|
||||||
|
@ -1373,6 +1393,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
json.id,
|
json.id,
|
||||||
json.alterId,
|
json.alterId,
|
||||||
json.email,
|
json.email,
|
||||||
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
|
||||||
|
@ -1441,11 +1462,12 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
|
@ -1457,6 +1479,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
json.id,
|
json.id,
|
||||||
json.flow,
|
json.flow,
|
||||||
json.email,
|
json.email,
|
||||||
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
@ -1557,11 +1580,12 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
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();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
this.expiryTime = expiryTime;
|
this.expiryTime = expiryTime;
|
||||||
}
|
}
|
||||||
|
@ -1571,6 +1595,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
password: this.password,
|
password: this.password,
|
||||||
flow: this.flow,
|
flow: this.flow,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
limitIp: this.limitIp,
|
||||||
totalGB: this.totalGB,
|
totalGB: this.totalGB,
|
||||||
expiryTime: this.expiryTime,
|
expiryTime: this.expiryTime,
|
||||||
};
|
};
|
||||||
|
@ -1581,6 +1606,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
json.password,
|
json.password,
|
||||||
json.flow,
|
json.flow,
|
||||||
json.email,
|
json.email,
|
||||||
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
json.expiryTime,
|
json.expiryTime,
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,8 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/add", a.addInbound)
|
g.POST("/add", a.addInbound)
|
||||||
g.POST("/del/:id", a.delInbound)
|
g.POST("/del/:id", a.delInbound)
|
||||||
g.POST("/update/:id", a.updateInbound)
|
g.POST("/update/:id", a.updateInbound)
|
||||||
|
g.POST("/clientIps/:email", a.getClientIps)
|
||||||
|
g.POST("/clearClientIps/:email", a.clearClientIps)
|
||||||
g.POST("/resetClientTraffic/:email", a.resetClientTraffic)
|
g.POST("/resetClientTraffic/:email", a.resetClientTraffic)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -122,7 +124,26 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
a.xrayService.SetToNeedRestart()
|
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) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
<a-col :xs="22" :sm="20" :md="16" :lg="12" :xl="8">
|
<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-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row type="flex" justify="center">
|
<a-row type="flex" justify="center">
|
||||||
|
|
|
@ -21,6 +21,41 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="trojan.email"></a-input>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Password">
|
||||||
|
|
|
@ -22,6 +22,42 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="vless.email"></a-input>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
|
|
|
@ -21,6 +21,39 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="vmess.email"></a-input>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
|
|
|
@ -88,6 +88,30 @@
|
||||||
removeClient(index, clients) {
|
removeClient(index, clients) {
|
||||||
clients.splice(index, 1);
|
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) {
|
async resetClientTraffic(client,event) {
|
||||||
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
|
const msg = await HttpUtil.post('/xui/inbound/resetClientTraffic/'+ client.email);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<div slot="title">
|
<div slot="title">
|
||||||
<a-button type="primary" @click="openAddInbound">Add Inbound</a-button>
|
<a-button type="primary" @click="openAddInbound">Add Inbound</a-button>
|
||||||
|
<a-button type="primary" @click="exportAllLinks" class="copy-btn">Export Links</a-button>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder="{{ i18n "search" }}" autofocus style="max-width: 300px"></a-input>
|
<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"
|
<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) {
|
delInbound(dbInbound) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
title: '{{ i18n "pages.inbounds.deleteInbound"}}',
|
||||||
|
|
351
web/job/check_clinet_ip_job.go
Normal file
351
web/job/check_clinet_ip_job.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -369,7 +369,29 @@ func (s *InboundService) UpdateClientStat(inboundId int, inboundSettings string)
|
||||||
func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
|
func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
|
||||||
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).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 {
|
func (s *InboundService) ResetClientTraffic(clientEmail string) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
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)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -215,7 +215,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
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)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
"link" = "دیگر"
|
"link" = "دیگر"
|
||||||
|
|
||||||
[pages.login]
|
[pages.login]
|
||||||
"title" = "ورود به سیستم X-UI"
|
"title" = "ورود به سیستم"
|
||||||
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
|
"loginAgain" = "مدت زمان استفاده به اتمام رسیده ، لطفا دوباره وارد شوید"
|
||||||
|
|
||||||
[pages.login.toasts]
|
[pages.login.toasts]
|
||||||
|
|
|
@ -310,6 +310,9 @@ func (s *Server) startTask() {
|
||||||
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
|
// Check the inbound traffic every 30 seconds that the traffic exceeds and expires
|
||||||
s.cron.AddJob("@every 30s", job.NewCheckInboundJob())
|
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
|
// Make a traffic condition every day, 8:30
|
||||||
var entry cron.EntryID
|
var entry cron.EntryID
|
||||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||||
|
|
|
@ -162,7 +162,7 @@ func (p *process) Start() (err error) {
|
||||||
return common.NewErrorf("Failed to write configuration file: %v", err)
|
return common.NewErrorf("Failed to write configuration file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(GetBinaryPath(), "-c", configPath)
|
cmd := exec.Command(GetBinaryPath(), "-c", configPath, "-restrictedIPsPath", "./bin/blockedIPs")
|
||||||
p.cmd = cmd
|
p.cmd = cmd
|
||||||
|
|
||||||
stdReader, err := cmd.StdoutPipe()
|
stdReader, err := cmd.StdoutPipe()
|
||||||
|
|
Loading…
Reference in a new issue