diff --git a/README.md b/README.md
index ec1e5a15..b554353d 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,34 @@ To install your desired version, add the version to the end of the installation
```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2
```
+
+## SSL Certificate
+
+
+ Click for SSL Certificate
+
+### Cloudflare
+
+The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
+
+- Cloudflare registered email
+- Cloudflare Global API Key
+- The domain name has been resolved to the current server through cloudflare
+
+**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
+
+
+### Certbot
+```
+apt-get install certbot -y
+certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
+certbot renew --dry-run
+```
+
+***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
+
+
+
## Manual Install & Upgrade
@@ -201,34 +229,6 @@ Supports a variety of different architectures and devices. Here are some of the
-
-## SSL Certificate
-
-
- Click for SSL Certificate
-
-### Cloudflare
-
-The Management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
-
-- Cloudflare registered email
-- Cloudflare Global API Key
-- The domain name has been resolved to the current server through cloudflare
-
-**1:** Run the`x-ui`command on the terminal, then choose `Cloudflare SSL Certificate`.
-
-
-### Certbot
-```
-apt-get install certbot -y
-certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
-certbot renew --dry-run
-```
-
-***Tip:*** *Certbot is also built into the Management script. You can run the `x-ui` command, then choose `SSL Certificate Management`.*
-
-
-
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
diff --git a/database/db.go b/database/db.go
index 8bd0fb49..c75953f0 100644
--- a/database/db.go
+++ b/database/db.go
@@ -21,6 +21,7 @@ var db *gorm.DB
var initializers = []func() error{
initUser,
initInbound,
+ initOutbound,
initSetting,
initInboundClientIps,
initClientTraffic,
@@ -51,6 +52,10 @@ func initInbound() error {
return db.AutoMigrate(&model.Inbound{})
}
+func initOutbound() error {
+ return db.AutoMigrate(&model.OutboundTraffics{})
+}
+
func initSetting() error {
return db.AutoMigrate(&model.Setting{})
}
diff --git a/database/model/model.go b/database/model/model.go
index e2d54436..32ab255f 100644
--- a/database/model/model.go
+++ b/database/model/model.go
@@ -44,6 +44,15 @@ type Inbound struct {
Tag string `json:"tag" form:"tag" gorm:"unique"`
Sniffing string `json:"sniffing" form:"sniffing"`
}
+
+type OutboundTraffics struct {
+ Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
+ Tag string `json:"tag" form:"tag" gorm:"unique"`
+ Up int64 `json:"up" form:"up" gorm:"default:0"`
+ Down int64 `json:"down" form:"down" gorm:"default:0"`
+ Total int64 `json:"total" form:"total" gorm:"default:0"`
+}
+
type InboundClientIps struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
diff --git a/web/assets/js/model/outbound.js b/web/assets/js/model/outbound.js
index 7207561e..dc02d91b 100644
--- a/web/assets/js/model/outbound.js
+++ b/web/assets/js/model/outbound.js
@@ -861,13 +861,13 @@ Outbound.SocksSettings = class extends CommonClass {
}
static fromJson(json={}) {
- servers = json.servers;
+ let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.SocksSettings(
servers[0].address,
servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
- ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
+ ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
);
}
@@ -891,13 +891,13 @@ Outbound.HttpSettings = class extends CommonClass {
}
static fromJson(json={}) {
- servers = json.servers;
+ let servers = json.servers;
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
return new Outbound.HttpSettings(
servers[0].address,
servers[0].port,
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
- ObjectUtil.isArrEmpty(servers[0].pass) ? '' : servers[0].users[0].pass,
+ ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
);
}
@@ -914,8 +914,8 @@ Outbound.HttpSettings = class extends CommonClass {
Outbound.WireguardSettings = class extends CommonClass {
constructor(
- mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
- address=[''], workers=2, domainStrategy='ForceIP', reserved='',
+ mtu=1420, secretKey='',
+ address=[''], workers=2, domainStrategy='', reserved='',
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
super();
this.mtu = mtu;
@@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass {
};
Outbound.WireguardSettings.Peer = class extends CommonClass {
- constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
+ constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
super();
this.publicKey = publicKey;
this.psk = psk;
diff --git a/web/controller/xray_setting.go b/web/controller/xray_setting.go
index 09e9115f..430cc77b 100644
--- a/web/controller/xray_setting.go
+++ b/web/controller/xray_setting.go
@@ -10,6 +10,7 @@ type XraySettingController struct {
XraySettingService service.XraySettingService
SettingService service.SettingService
InboundService service.InboundService
+ OutboundService service.OutboundService
XrayService service.XrayService
}
@@ -27,6 +28,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
g.GET("/getXrayResult", a.getXrayResult)
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/warp/:action", a.warp)
+ g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
}
func (a *XraySettingController) getXraySetting(c *gin.Context) {
@@ -84,3 +86,12 @@ func (a *XraySettingController) warp(c *gin.Context) {
jsonObj(c, resp, err)
}
+
+func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
+ outboundsTraffic, err := a.OutboundService.GetOutboundsTraffic()
+ if err != nil {
+ jsonMsg(c, "Error getting traffics", err)
+ return
+ }
+ jsonObj(c, outboundsTraffic, nil)
+}
diff --git a/web/html/xui/form/stream/stream_ws.html b/web/html/xui/form/stream/stream_ws.html
index 1222124c..00b64167 100644
--- a/web/html/xui/form/stream/stream_ws.html
+++ b/web/html/xui/form/stream/stream_ws.html
@@ -7,7 +7,7 @@
- +
+ +
diff --git a/web/html/xui/xray.html b/web/html/xui/xray.html
index d6f0c0f8..d00b73cc 100644
--- a/web/html/xui/xray.html
+++ b/web/html/xui/xray.html
@@ -341,8 +341,15 @@
- {{ i18n "pages.xray.outbound.addOutbound" }}
- WARP
+
+
+ {{ i18n "pages.xray.outbound.addOutbound" }}
+ WARP
+
+
+
+
+
reality
+
+ [[ findOutboundTraffic(outbound) ]]
+
@@ -463,6 +473,7 @@
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
+ { title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
];
const reverseColumns = [
@@ -483,7 +494,9 @@
oldXraySetting: '',
xraySetting: '',
inboundTags: [],
+ outboundsTraffic: [],
saveBtnDisable: true,
+ refreshing: false,
restartResult: '',
isMobile: window.innerWidth <= 768,
advSettings: 'xraySetting',
@@ -581,6 +594,12 @@
loading(spinning = true) {
this.spinning = spinning;
},
+ async getOutboundsTraffic() {
+ const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
+ if (msg.success) {
+ this.outboundsTraffic = msg.obj;
+ }
+ },
async getXraySetting() {
this.loading(true);
const msg = await HttpUtil.post("/panel/xray/");
@@ -759,6 +778,14 @@
}
return true;
},
+ findOutboundTraffic(o) {
+ for (const otraffic of this.outboundsTraffic) {
+ if (otraffic.tag == o.tag) {
+ return sizeFormat(otraffic.up) + ' / ' + sizeFormat(otraffic.down);
+ }
+ }
+ return sizeFormat(0) + ' / ' + sizeFormat(0);
+ },
findOutboundAddress(o) {
serverObj = null;
switch(o.protocol){
@@ -816,6 +843,22 @@
outbounds.splice(index,1);
this.outboundSettings = JSON.stringify(outbounds);
},
+ async refreshOutboundTraffic() {
+ if (!this.refreshing) {
+ this.refreshing = true;
+ await this.getOutboundsTraffic();
+
+ data = []
+ if (this.templateSettings != null) {
+ this.templateSettings.outbounds.forEach((o, index) => {
+ data.push({'key': index, ...o});
+ });
+ }
+
+ this.outboundData = data;
+ this.refreshing = false;
+ }
+ },
addReverse(){
reverseModal.show({
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
@@ -949,6 +992,7 @@
async mounted() {
await this.getXraySetting();
await this.getXrayResult();
+ await this.getOutboundsTraffic();
while (true) {
await PromiseUtil.sleep(800);
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
diff --git a/web/job/xray_traffic_job.go b/web/job/xray_traffic_job.go
index 158930a4..c0de4428 100644
--- a/web/job/xray_traffic_job.go
+++ b/web/job/xray_traffic_job.go
@@ -6,8 +6,9 @@ import (
)
type XrayTrafficJob struct {
- xrayService service.XrayService
- inboundService service.InboundService
+ xrayService service.XrayService
+ inboundService service.InboundService
+ outboundService service.OutboundService
}
func NewXrayTrafficJob() *XrayTrafficJob {
@@ -24,11 +25,15 @@ func (j *XrayTrafficJob) Run() {
logger.Warning("get xray traffic failed:", err)
return
}
- err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
+ err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
if err != nil {
- logger.Warning("add traffic failed:", err)
+ logger.Warning("add inbound traffic failed:", err)
}
- if needRestart {
+ err, needRestart1 := j.outboundService.AddTraffic(traffics, clientTraffics)
+ if err != nil {
+ logger.Warning("add outbound traffic failed:", err)
+ }
+ if needRestart0 || needRestart1 {
j.xrayService.SetToNeedRestart()
}
diff --git a/web/service/inbound.go b/web/service/inbound.go
index f3445101..291c0dee 100644
--- a/web/service/inbound.go
+++ b/web/service/inbound.go
@@ -682,7 +682,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return needRestart, tx.Save(oldInbound).Error
}
-func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
+func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
var err error
db := database.GetDB()
tx := db.Begin()
@@ -694,7 +694,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
tx.Commit()
}
}()
- err = s.addInboundTraffic(tx, inboundTraffics)
+ err = s.addInboundTraffic(tx, traffics)
if err != nil {
return err, false
}
diff --git a/web/service/outbound.go b/web/service/outbound.go
new file mode 100644
index 00000000..dc0e0742
--- /dev/null
+++ b/web/service/outbound.go
@@ -0,0 +1,80 @@
+package service
+
+import (
+ "x-ui/database"
+ "x-ui/database/model"
+ "x-ui/logger"
+ "x-ui/xray"
+
+ "gorm.io/gorm"
+)
+
+type OutboundService struct {
+ xrayApi xray.XrayAPI
+}
+
+func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
+ var err error
+ db := database.GetDB()
+ tx := db.Begin()
+
+ defer func() {
+ if err != nil {
+ tx.Rollback()
+ } else {
+ tx.Commit()
+ }
+ }()
+
+ err = s.addOutboundTraffic(tx, traffics)
+ if err != nil {
+ return err, false
+ }
+
+ return nil, false
+}
+
+func (s *OutboundService) addOutboundTraffic(tx *gorm.DB, traffics []*xray.Traffic) error {
+ if len(traffics) == 0 {
+ return nil
+ }
+
+ var err error
+
+ for _, traffic := range traffics {
+ if traffic.IsOutbound {
+
+ var outbound model.OutboundTraffics
+
+ err = tx.Model(&model.OutboundTraffics{}).Where("tag = ?", traffic.Tag).
+ FirstOrCreate(&outbound).Error
+ if err != nil {
+ return err
+ }
+
+ outbound.Tag = traffic.Tag
+ outbound.Up = outbound.Up + traffic.Up
+ outbound.Down = outbound.Down + traffic.Down
+ outbound.Total = outbound.Up + outbound.Down
+
+ err = tx.Save(&outbound).Error
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, error) {
+ db := database.GetDB()
+ var traffics []*model.OutboundTraffics
+
+ err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
+ if err != nil {
+ logger.Warning(err)
+ return nil, err
+ }
+
+ return traffics, nil
+}
diff --git a/web/translation/translate.es_ES.toml b/web/translation/translate.es_ES.toml
index 6c817a30..ac9de9a4 100644
--- a/web/translation/translate.es_ES.toml
+++ b/web/translation/translate.es_ES.toml
@@ -57,7 +57,7 @@
"dashboard" = "Estado del Sistema"
"inbounds" = "Entradas"
"settings" = "Configuraciones"
-"xray" = "Configuración Xray"
+"xray" = "Ajustes Xray"
"logout" = "Cerrar Sesión"
"link" = "Gestionar"
diff --git a/xray/api.go b/xray/api.go
index 36b19875..1ce5afa1 100644
--- a/xray/api.go
+++ b/xray/api.go
@@ -213,6 +213,7 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
continue
}
isInbound := matchs[1] == "inbound"
+ isOutbound := matchs[1] == "outbound"
tag := matchs[2]
isDown := matchs[3] == "downlink"
if tag == "api" {
@@ -221,8 +222,9 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
traffic, ok := tagTrafficMap[tag]
if !ok {
traffic = &Traffic{
- IsInbound: isInbound,
- Tag: tag,
+ IsInbound: isInbound,
+ IsOutbound: isOutbound,
+ Tag: tag,
}
tagTrafficMap[tag] = traffic
traffics = append(traffics, traffic)
diff --git a/xray/traffic.go b/xray/traffic.go
index a1ef5186..7b907bae 100644
--- a/xray/traffic.go
+++ b/xray/traffic.go
@@ -1,8 +1,9 @@
package xray
type Traffic struct {
- IsInbound bool
- Tag string
- Up int64
- Down int64
+ IsInbound bool
+ IsOutbound bool
+ Tag string
+ Up int64
+ Down int64
}