mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-09 03:36:19 +00:00
Merge branch 'MHSanaei:main' into IssueWithGeneratedVLESSConfig
This commit is contained in:
commit
0f6aa8d8f7
13 changed files with 209 additions and 52 deletions
56
README.md
56
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
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.1.2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## SSL Certificate
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for SSL Certificate</summary>
|
||||||
|
|
||||||
|
### 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`.*
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Manual Install & Upgrade
|
## Manual Install & Upgrade
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -201,34 +229,6 @@ Supports a variety of different architectures and devices. Here are some of the
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## SSL Certificate
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Click for SSL Certificate</summary>
|
|
||||||
|
|
||||||
### 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`.*
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
## [WARP Configuration](https://gitlab.com/fscarmen/warp)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
@ -21,6 +21,7 @@ var db *gorm.DB
|
||||||
var initializers = []func() error{
|
var initializers = []func() error{
|
||||||
initUser,
|
initUser,
|
||||||
initInbound,
|
initInbound,
|
||||||
|
initOutbound,
|
||||||
initSetting,
|
initSetting,
|
||||||
initInboundClientIps,
|
initInboundClientIps,
|
||||||
initClientTraffic,
|
initClientTraffic,
|
||||||
|
@ -51,6 +52,10 @@ func initInbound() error {
|
||||||
return db.AutoMigrate(&model.Inbound{})
|
return db.AutoMigrate(&model.Inbound{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func initOutbound() error {
|
||||||
|
return db.AutoMigrate(&model.OutboundTraffics{})
|
||||||
|
}
|
||||||
|
|
||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,15 @@ 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 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 {
|
type InboundClientIps struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
|
|
|
@ -861,13 +861,13 @@ Outbound.SocksSettings = class extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
servers = json.servers;
|
let servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.SocksSettings(
|
return new Outbound.SocksSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
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={}) {
|
static fromJson(json={}) {
|
||||||
servers = json.servers;
|
let servers = json.servers;
|
||||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||||
return new Outbound.HttpSettings(
|
return new Outbound.HttpSettings(
|
||||||
servers[0].address,
|
servers[0].address,
|
||||||
servers[0].port,
|
servers[0].port,
|
||||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
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 {
|
Outbound.WireguardSettings = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
mtu=1420, secretKey=Wireguard.generateKeypair().privateKey,
|
mtu=1420, secretKey='',
|
||||||
address=[''], workers=2, domainStrategy='ForceIP', reserved='',
|
address=[''], workers=2, domainStrategy='', reserved='',
|
||||||
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||||
super();
|
super();
|
||||||
this.mtu = mtu;
|
this.mtu = mtu;
|
||||||
|
@ -965,7 +965,7 @@ Outbound.WireguardSettings = class extends CommonClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
Outbound.WireguardSettings.Peer = 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();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
|
|
|
@ -10,6 +10,7 @@ type XraySettingController struct {
|
||||||
XraySettingService service.XraySettingService
|
XraySettingService service.XraySettingService
|
||||||
SettingService service.SettingService
|
SettingService service.SettingService
|
||||||
InboundService service.InboundService
|
InboundService service.InboundService
|
||||||
|
OutboundService service.OutboundService
|
||||||
XrayService service.XrayService
|
XrayService service.XrayService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
||||||
g.GET("/getXrayResult", a.getXrayResult)
|
g.GET("/getXrayResult", a.getXrayResult)
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/warp/:action", a.warp)
|
g.POST("/warp/:action", a.warp)
|
||||||
|
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
|
@ -84,3 +86,12 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
||||||
|
|
||||||
jsonObj(c, resp, err)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
<a-button size="small" @click="inbound.stream.ws.addHeader('host', '')">+</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
|
|
|
@ -341,8 +341,15 @@
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
<a-row>
|
||||||
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
<a-col :xs="12" :sm="12" :lg="12">
|
||||||
|
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||||
|
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
||||||
|
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"/>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
:row-key="r => r.key"
|
:row-key="r => r.key"
|
||||||
:data-source="outboundData"
|
:data-source="outboundData"
|
||||||
|
@ -378,6 +385,9 @@
|
||||||
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
<a-tag style="margin:0;" v-if="outbound.streamSettings.security=='reality'" color="green">reality</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="traffic" slot-scope="text, outbound, index">
|
||||||
|
<a-tag color="green">[[ findOutboundTraffic(outbound) ]]</a-tag>
|
||||||
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-4" tab='{{ i18n "pages.xray.outbound.reverse"}}' style="padding-top: 20px;" force-render="true">
|
||||||
|
@ -463,6 +473,7 @@
|
||||||
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
{ title: '{{ i18n "pages.xray.outbound.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
|
||||||
{ title: '{{ i18n "protocol"}}', align: 'center', width: 50, scopedSlots: { customRender: 'protocol' } },
|
{ 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.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||||
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', align: 'center', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const reverseColumns = [
|
const reverseColumns = [
|
||||||
|
@ -483,7 +494,9 @@
|
||||||
oldXraySetting: '',
|
oldXraySetting: '',
|
||||||
xraySetting: '',
|
xraySetting: '',
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
|
outboundsTraffic: [],
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
|
refreshing: false,
|
||||||
restartResult: '',
|
restartResult: '',
|
||||||
isMobile: window.innerWidth <= 768,
|
isMobile: window.innerWidth <= 768,
|
||||||
advSettings: 'xraySetting',
|
advSettings: 'xraySetting',
|
||||||
|
@ -581,6 +594,12 @@
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
this.spinning = spinning;
|
this.spinning = spinning;
|
||||||
},
|
},
|
||||||
|
async getOutboundsTraffic() {
|
||||||
|
const msg = await HttpUtil.get("/panel/xray/getOutboundsTraffic");
|
||||||
|
if (msg.success) {
|
||||||
|
this.outboundsTraffic = msg.obj;
|
||||||
|
}
|
||||||
|
},
|
||||||
async getXraySetting() {
|
async getXraySetting() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post("/panel/xray/");
|
const msg = await HttpUtil.post("/panel/xray/");
|
||||||
|
@ -759,6 +778,14 @@
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
findOutboundAddress(o) {
|
||||||
serverObj = null;
|
serverObj = null;
|
||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
|
@ -816,6 +843,22 @@
|
||||||
outbounds.splice(index,1);
|
outbounds.splice(index,1);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
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(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
|
@ -949,6 +992,7 @@
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getXraySetting();
|
await this.getXraySetting();
|
||||||
await this.getXrayResult();
|
await this.getXrayResult();
|
||||||
|
await this.getOutboundsTraffic();
|
||||||
while (true) {
|
while (true) {
|
||||||
await PromiseUtil.sleep(800);
|
await PromiseUtil.sleep(800);
|
||||||
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
this.saveBtnDisable = this.oldXraySetting === this.xraySetting;
|
||||||
|
|
|
@ -6,8 +6,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
|
outboundService service.OutboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXrayTrafficJob() *XrayTrafficJob {
|
func NewXrayTrafficJob() *XrayTrafficJob {
|
||||||
|
@ -24,11 +25,15 @@ func (j *XrayTrafficJob) Run() {
|
||||||
logger.Warning("get xray traffic failed:", err)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
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()
|
j.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -682,7 +682,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
return needRestart, tx.Save(oldInbound).Error
|
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
|
var err error
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
tx := db.Begin()
|
tx := db.Begin()
|
||||||
|
@ -694,7 +694,7 @@ func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraff
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
err = s.addInboundTraffic(tx, inboundTraffics)
|
err = s.addInboundTraffic(tx, traffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, false
|
return err, false
|
||||||
}
|
}
|
||||||
|
|
80
web/service/outbound.go
Normal file
80
web/service/outbound.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -57,7 +57,7 @@
|
||||||
"dashboard" = "Estado del Sistema"
|
"dashboard" = "Estado del Sistema"
|
||||||
"inbounds" = "Entradas"
|
"inbounds" = "Entradas"
|
||||||
"settings" = "Configuraciones"
|
"settings" = "Configuraciones"
|
||||||
"xray" = "Configuración Xray"
|
"xray" = "Ajustes Xray"
|
||||||
"logout" = "Cerrar Sesión"
|
"logout" = "Cerrar Sesión"
|
||||||
"link" = "Gestionar"
|
"link" = "Gestionar"
|
||||||
|
|
||||||
|
|
|
@ -213,6 +213,7 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isInbound := matchs[1] == "inbound"
|
isInbound := matchs[1] == "inbound"
|
||||||
|
isOutbound := matchs[1] == "outbound"
|
||||||
tag := matchs[2]
|
tag := matchs[2]
|
||||||
isDown := matchs[3] == "downlink"
|
isDown := matchs[3] == "downlink"
|
||||||
if tag == "api" {
|
if tag == "api" {
|
||||||
|
@ -221,8 +222,9 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||||
traffic, ok := tagTrafficMap[tag]
|
traffic, ok := tagTrafficMap[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
traffic = &Traffic{
|
traffic = &Traffic{
|
||||||
IsInbound: isInbound,
|
IsInbound: isInbound,
|
||||||
Tag: tag,
|
IsOutbound: isOutbound,
|
||||||
|
Tag: tag,
|
||||||
}
|
}
|
||||||
tagTrafficMap[tag] = traffic
|
tagTrafficMap[tag] = traffic
|
||||||
traffics = append(traffics, traffic)
|
traffics = append(traffics, traffic)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package xray
|
package xray
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
IsInbound bool
|
IsInbound bool
|
||||||
Tag string
|
IsOutbound bool
|
||||||
Up int64
|
Tag string
|
||||||
Down int64
|
Up int64
|
||||||
|
Down int64
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue