mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-03-19 09:05:49 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59708d6410 | ||
|
|
be8e78797a | ||
|
|
6d364638ca | ||
|
|
c147636133 | ||
|
|
fa7a6a3f99 | ||
|
|
620da1784b | ||
|
|
b13e1d666d | ||
|
|
2935eeb36a | ||
|
|
eb382cd5b4 | ||
|
|
f02c82677d | ||
|
|
5a5f7afb68 |
@@ -28,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## Instalar una Versión Personalizada
|
## Instalar una Versión Personalizada
|
||||||
|
|
||||||
Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.2.6`:
|
Para instalar la versión deseada, agrega la versión al final del comando de instalación. Por ejemplo, ver `v2.2.8`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.6
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.8
|
||||||
```
|
```
|
||||||
|
|
||||||
## Certificado SSL
|
## Certificado SSL
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## Install Custom Version
|
## Install Custom Version
|
||||||
|
|
||||||
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.7`:
|
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.8`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.7
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.8
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL Certificate
|
## SSL Certificate
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||||||
|
|
||||||
## 安装指定版本
|
## 安装指定版本
|
||||||
|
|
||||||
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.2.6`:
|
要安装所需的版本,请将该版本添加到安装命令的末尾。 e.g., ver `v2.2.8`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.6
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.8
|
||||||
```
|
```
|
||||||
|
|
||||||
## SSL 认证
|
## SSL 认证
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.2.7
|
2.2.8
|
||||||
@@ -91,7 +91,7 @@ type Client struct {
|
|||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
Enable bool `json:"enable" form:"enable"`
|
||||||
TgID string `json:"tgId" form:"tgId"`
|
TgID int64 `json:"tgId" form:"tgId"`
|
||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
Reset int `json:"reset" form:"reset"`
|
Reset int `json:"reset" form:"reset"`
|
||||||
}
|
}
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -7,12 +7,12 @@ require (
|
|||||||
github.com/gin-contrib/gzip v1.0.0
|
github.com/gin-contrib/gzip v1.0.0
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
github.com/mymmrac/telego v0.29.1
|
github.com/mymmrac/telego v0.29.2
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.0
|
github.com/pelletier/go-toml/v2 v2.2.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.2
|
github.com/shirou/gopsutil/v3 v3.24.3
|
||||||
github.com/valyala/fasthttp v1.52.0
|
github.com/valyala/fasthttp v1.52.0
|
||||||
github.com/xtls/xray-core v1.8.10
|
github.com/xtls/xray-core v1.8.10
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -163,8 +163,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.29.1 h1:nsNnK0mS18OL+unoDjDI6BVfafJBbT8Wtj7rCzEWoM8=
|
github.com/mymmrac/telego v0.29.2 h1:5+fQ/b8d8Ld6ihCJ0OLe1CwUdT3t1sIUl3RaSaSvRJs=
|
||||||
github.com/mymmrac/telego v0.29.1/go.mod h1:ZLD1+L2TQRr97NPOCoN1V2w8y9kmFov33OfZ3qT8cF4=
|
github.com/mymmrac/telego v0.29.2/go.mod h1:BsKr+GF9BHqaVaLBwsZeDnfuJcJx2olWuDEtKm4zHMc=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
@@ -212,8 +212,8 @@ github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEo
|
|||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
|
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
|
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
@@ -346,7 +346,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
@@ -231,8 +231,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
obj["path"] = httpupgrade["path"].(string)
|
obj["path"] = httpupgrade["path"].(string)
|
||||||
obj["host"] = httpupgrade["host"].(string)
|
obj["host"] = httpupgrade["host"].(string)
|
||||||
|
if headers, ok := httpupgrade["headers"].(map[string]interface{}); ok {
|
||||||
|
hostFromHeaders := searchHost(headers)
|
||||||
|
if hostFromHeaders != "" {
|
||||||
|
obj["host"] = hostFromHeaders
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
@@ -347,8 +352,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
params["path"] = ws["path"].(string)
|
params["path"] = ws["path"].(string)
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
|
||||||
params["host"] = ws["host"].(string)
|
params["host"] = ws["host"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
if headers != nil {
|
if headers != nil {
|
||||||
hostFromHeaders := searchHost(headers)
|
hostFromHeaders := searchHost(headers)
|
||||||
if hostFromHeaders != "" {
|
if hostFromHeaders != "" {
|
||||||
@@ -376,8 +381,14 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
params["path"] = httpupgrade["path"].(string)
|
params["path"] = httpupgrade["path"].(string)
|
||||||
params["host"] = httpupgrade["host"].(string)
|
params["host"] = httpupgrade["host"].(string)
|
||||||
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
|
if headers != nil {
|
||||||
|
hostFromHeaders := searchHost(headers)
|
||||||
|
if hostFromHeaders != "" {
|
||||||
|
params["host"] = hostFromHeaders
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
@@ -570,8 +581,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
params["path"] = ws["path"].(string)
|
params["path"] = ws["path"].(string)
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
|
||||||
params["host"] = ws["host"].(string)
|
params["host"] = ws["host"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
if headers != nil {
|
if headers != nil {
|
||||||
hostFromHeaders := searchHost(headers)
|
hostFromHeaders := searchHost(headers)
|
||||||
if hostFromHeaders != "" {
|
if hostFromHeaders != "" {
|
||||||
@@ -599,8 +610,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
params["path"] = httpupgrade["path"].(string)
|
params["path"] = httpupgrade["path"].(string)
|
||||||
params["host"] = httpupgrade["host"].(string)
|
params["host"] = httpupgrade["host"].(string)
|
||||||
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
|
if headers != nil {
|
||||||
|
hostFromHeaders := searchHost(headers)
|
||||||
|
if hostFromHeaders != "" {
|
||||||
|
params["host"] = hostFromHeaders
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
@@ -794,8 +811,8 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
case "ws":
|
case "ws":
|
||||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
params["path"] = ws["path"].(string)
|
params["path"] = ws["path"].(string)
|
||||||
headers, _ := ws["headers"].(map[string]interface{})
|
|
||||||
params["host"] = ws["host"].(string)
|
params["host"] = ws["host"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
if headers != nil {
|
if headers != nil {
|
||||||
hostFromHeaders := searchHost(headers)
|
hostFromHeaders := searchHost(headers)
|
||||||
if hostFromHeaders != "" {
|
if hostFromHeaders != "" {
|
||||||
@@ -823,6 +840,13 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||||
params["path"] = httpupgrade["path"].(string)
|
params["path"] = httpupgrade["path"].(string)
|
||||||
params["host"] = httpupgrade["host"].(string)
|
params["host"] = httpupgrade["host"].(string)
|
||||||
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
|
if headers != nil {
|
||||||
|
hostFromHeaders := searchHost(headers)
|
||||||
|
if hostFromHeaders != "" {
|
||||||
|
params["host"] = hostFromHeaders
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ style attribute {
|
|||||||
.ant-layout-sider {
|
.ant-layout-sider {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.ant-card {
|
.ant-card, .ant-alert-error {
|
||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
.ant-tabs {
|
.ant-tabs {
|
||||||
@@ -384,6 +384,9 @@ style attribute {
|
|||||||
.ant-tabs {
|
.ant-tabs {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
.ant-alert-error {
|
||||||
|
margin-inline: 0.3rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fade-in-enter,
|
.fade-in-enter,
|
||||||
@@ -1344,7 +1347,7 @@ b, strong {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-menu-theme-switch:hover {
|
.ant-menu-theme-switch, .ant-menu-theme-switch:hover {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
@@ -1457,3 +1460,9 @@ b, strong {
|
|||||||
.ant-menu-theme-switch.ant-menu-item .ant-switch:not(.ant-switch-disabled):active:after, .ant-switch:not(.ant-switch-disabled):active:before {
|
.ant-menu-theme-switch.ant-menu-item .ant-switch:not(.ant-switch-disabled):active:after, .ant-switch:not(.ant-switch-disabled):active:before {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark .ant-select-disabled .ant-select-selection {
|
||||||
|
background: var(--dark-color-surface-100);
|
||||||
|
border-color: var(--dark-color-surface-200);
|
||||||
|
color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|||||||
@@ -290,6 +290,7 @@ class HttpUpgradeStreamSettings extends CommonClass {
|
|||||||
return new HttpUpgradeStreamSettings(
|
return new HttpUpgradeStreamSettings(
|
||||||
json.path,
|
json.path,
|
||||||
json.Host,
|
json.Host,
|
||||||
|
json.headers && !ObjectUtil.isEmpty(json.headers.Host) ? json.headers.Host : '',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,6 +298,7 @@ class HttpUpgradeStreamSettings extends CommonClass {
|
|||||||
return {
|
return {
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: this.host,
|
host: this.host,
|
||||||
|
headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -475,19 +475,38 @@ class GrpcStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HttpUpgradeStreamSettings extends XrayCommonClass {
|
class HTTPUpgradeStreamSettings extends XrayCommonClass {
|
||||||
constructor(acceptProxyProtocol=false, path='/', host='') {
|
constructor(acceptProxyProtocol=false, path='/', host='', headers=[]) {
|
||||||
super();
|
super();
|
||||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
|
this.headers = headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
addHeader(name, value) {
|
||||||
|
this.headers.push({ name: name, value: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeader(name) {
|
||||||
|
for (const header of this.headers) {
|
||||||
|
if (header.name.toLowerCase() === name.toLowerCase()) {
|
||||||
|
return header.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeHeader(index) {
|
||||||
|
this.headers.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new HttpUpgradeStreamSettings(
|
return new HTTPUpgradeStreamSettings(
|
||||||
json.acceptProxyProtocol,
|
json.acceptProxyProtocol,
|
||||||
json.path,
|
json.path,
|
||||||
json.host,
|
json.host,
|
||||||
|
XrayCommonClass.toHeaders(json.headers),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,6 +515,7 @@ class HttpUpgradeStreamSettings extends XrayCommonClass {
|
|||||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||||
path: this.path,
|
path: this.path,
|
||||||
host: this.host,
|
host: this.host,
|
||||||
|
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,7 +884,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
httpupgradeSettings=new HTTPUpgradeStreamSettings(),
|
||||||
sockopt = undefined,
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -943,7 +963,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||||
SockoptStreamSettings.fromJson(json.sockopt),
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1120,7 +1140,12 @@ class Inbound extends XrayCommonClass {
|
|||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.host[0];
|
return this.stream.http.host[0];
|
||||||
} else if (this.isHttpupgrade) {
|
} else if (this.isHttpupgrade) {
|
||||||
return this.stream.httpupgrade.host;
|
const hostHeader = this.stream.httpupgrade.getHeader("Host");
|
||||||
|
if (hostHeader !== null) {
|
||||||
|
return hostHeader;
|
||||||
|
} else {
|
||||||
|
return this.stream.httpupgrade.host;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1261,6 +1286,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
let httpupgrade = this.stream.httpupgrade;
|
let httpupgrade = this.stream.httpupgrade;
|
||||||
obj.path = httpupgrade.path;
|
obj.path = httpupgrade.path;
|
||||||
obj.host = httpupgrade.host;
|
obj.host = httpupgrade.host;
|
||||||
|
let index = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
obj.host = httpupgrade.headers[index].value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
@@ -1293,9 +1322,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (tcp.type === 'http') {
|
if (tcp.type === 'http') {
|
||||||
const request = tcp.request;
|
const request = tcp.request;
|
||||||
params.set("path", request.path.join(','));
|
params.set("path", request.path.join(','));
|
||||||
const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
const tcpIndex = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
if (index >= 0) {
|
if (tcpIndex >= 0) {
|
||||||
const host = request.headers[index].value;
|
const host = request.headers[tcpIndex].value;
|
||||||
params.set("host", host);
|
params.set("host", host);
|
||||||
}
|
}
|
||||||
params.set("headerType", 'http');
|
params.set("headerType", 'http');
|
||||||
@@ -1310,9 +1339,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
params.set("path", ws.path);
|
params.set("path", ws.path);
|
||||||
params.set("host", ws.host);
|
params.set("host", ws.host);
|
||||||
const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
const wsIndex = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
if (index >= 0) {
|
if (wsIndex >= 0) {
|
||||||
const host = ws.headers[index].value;
|
const host = ws.headers[wsIndex].value;
|
||||||
params.set("host", host);
|
params.set("host", host);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1336,9 +1365,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
const httpupgrade = this.stream.httpupgrade;
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
params.set("path", httpupgrade.path);
|
params.set("path", httpupgrade.path);
|
||||||
params.set("host", httpupgrade.host);
|
params.set("host", httpupgrade.host);
|
||||||
|
const httpupgradeIndex = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (httpupgradeIndex >= 0) {
|
||||||
|
const host = httpupgrade.headers[httpupgradeIndex].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1402,7 +1436,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genSSLink(address='', port=this.port, forceTls, remark='', clientPassword) {
|
genSSLink(address = '', port = this.port, forceTls, remark = '', clientPassword) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const type = this.stream.network;
|
const type = this.stream.network;
|
||||||
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
const security = forceTls == 'same' ? this.stream.security : forceTls;
|
||||||
@@ -1414,9 +1448,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (tcp.type === 'http') {
|
if (tcp.type === 'http') {
|
||||||
const request = tcp.request;
|
const request = tcp.request;
|
||||||
params.set("path", request.path.join(','));
|
params.set("path", request.path.join(','));
|
||||||
const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
const tcpIndex = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
if (index >= 0) {
|
if (tcpIndex >= 0) {
|
||||||
const host = request.headers[index].value;
|
const host = request.headers[tcpIndex].value;
|
||||||
params.set("host", host);
|
params.set("host", host);
|
||||||
}
|
}
|
||||||
params.set("headerType", 'http');
|
params.set("headerType", 'http');
|
||||||
@@ -1431,9 +1465,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
params.set("path", ws.path);
|
params.set("path", ws.path);
|
||||||
params.set("host", ws.host);
|
params.set("host", ws.host);
|
||||||
const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
const wsIndex = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
if (index >= 0) {
|
if (wsIndex >= 0) {
|
||||||
const host = ws.headers[index].value;
|
const host = ws.headers[wsIndex].value;
|
||||||
params.set("host", host);
|
params.set("host", host);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1452,26 +1486,31 @@ class Inbound extends XrayCommonClass {
|
|||||||
const grpc = this.stream.grpc;
|
const grpc = this.stream.grpc;
|
||||||
params.set("serviceName", grpc.serviceName);
|
params.set("serviceName", grpc.serviceName);
|
||||||
params.set("authority", grpc.authority);
|
params.set("authority", grpc.authority);
|
||||||
if(grpc.multiMode){
|
if (grpc.multiMode) {
|
||||||
params.set("mode", "multi");
|
params.set("mode", "multi");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
const httpupgrade = this.stream.httpupgrade;
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
params.set("path", httpupgrade.path);
|
params.set("path", httpupgrade.path);
|
||||||
params.set("host", httpupgrade.host);
|
params.set("host", httpupgrade.host);
|
||||||
|
const httpupgradeIndex = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (httpupgradeIndex >= 0) {
|
||||||
|
const host = httpupgrade.headers[httpupgradeIndex].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security === 'tls') {
|
if (security === 'tls') {
|
||||||
params.set("security", "tls");
|
params.set("security", "tls");
|
||||||
if (this.stream.isTls){
|
if (this.stream.isTls) {
|
||||||
params.set("fp" , this.stream.tls.settings.fingerprint);
|
params.set("fp", this.stream.tls.settings.fingerprint);
|
||||||
params.set("alpn", this.stream.tls.alpn);
|
params.set("alpn", this.stream.tls.alpn);
|
||||||
if(this.stream.tls.settings.allowInsecure){
|
if (this.stream.tls.settings.allowInsecure) {
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)){
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1502,9 +1541,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (tcp.type === 'http') {
|
if (tcp.type === 'http') {
|
||||||
const request = tcp.request;
|
const request = tcp.request;
|
||||||
params.set("path", request.path.join(','));
|
params.set("path", request.path.join(','));
|
||||||
const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
const tcpIndex = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
if (index >= 0) {
|
if (tcpIndex >= 0) {
|
||||||
const host = request.headers[index].value;
|
const host = request.headers[tcpIndex].value;
|
||||||
params.set("host", host);
|
params.set("host", host);
|
||||||
}
|
}
|
||||||
params.set("headerType", 'http');
|
params.set("headerType", 'http');
|
||||||
@@ -1519,9 +1558,9 @@ class Inbound extends XrayCommonClass {
|
|||||||
const ws = this.stream.ws;
|
const ws = this.stream.ws;
|
||||||
params.set("path", ws.path);
|
params.set("path", ws.path);
|
||||||
params.set("host", ws.host);
|
params.set("host", ws.host);
|
||||||
const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
const wsIndex = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
if (index >= 0) {
|
if (wsIndex >= 0) {
|
||||||
const host = ws.headers[index].value;
|
const host = ws.headers[wsIndex].value;
|
||||||
params.set("host", host);
|
params.set("host", host);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1545,9 +1584,14 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
const httpupgrade = this.stream.httpupgrade;
|
const httpupgrade = this.stream.httpupgrade;
|
||||||
params.set("path", httpupgrade.path);
|
params.set("path", httpupgrade.path);
|
||||||
params.set("host", httpupgrade.host);
|
params.set("host", httpupgrade.host);
|
||||||
|
const httpUpgradeIndex = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (httpUpgradeIndex >= 0) {
|
||||||
|
const host = httpupgrade.headers[httpUpgradeIndex].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@
|
|||||||
emailPrefix: "",
|
emailPrefix: "",
|
||||||
emailPostfix: "",
|
emailPostfix: "",
|
||||||
subId: "",
|
subId: "",
|
||||||
tgId: "",
|
tgId: 0,
|
||||||
flow: "",
|
flow: "",
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
reset: 0,
|
reset: 0,
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
this.emailPrefix = "";
|
this.emailPrefix = "";
|
||||||
this.emailPostfix = "";
|
this.emailPostfix = "";
|
||||||
this.subId = "";
|
this.subId = "";
|
||||||
this.tgId = "";
|
this.tgId = 0;
|
||||||
this.flow = "";
|
this.flow = "";
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="client.tgId"></a-input>
|
<a-input-number style="width: 50%" v-model="client.tgId" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="app.ipLimitEnable">
|
<a-form-item v-if="app.ipLimitEnable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
|
|||||||
@@ -290,8 +290,8 @@
|
|||||||
<a-input v-model="outbound.stream.ws.host"></a-input>
|
<a-input v-model="outbound.stream.ws.host"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- http -->
|
<!-- http -->
|
||||||
@@ -347,8 +347,8 @@
|
|||||||
<a-input v-model="outbound.stream.httpupgrade.host"></a-input>
|
<a-input v-model="outbound.stream.httpupgrade.host"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-form-item><a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -9,5 +9,18 @@
|
|||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||||
|
<a-button size="small" @click="inbound.stream.httpupgrade.addHeader('host', '')">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
|
<a-input-group compact v-for="(header, index) in inbound.stream.httpupgrade.headers">
|
||||||
|
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="inbound.stream.httpupgrade.removeHeader(index)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -10,7 +10,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('Host', '')">+</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">
|
||||||
|
|||||||
@@ -23,230 +23,231 @@
|
|||||||
{{ template "commonSider" . }}
|
{{ template "commonSider" . }}
|
||||||
<a-layout id="content-layout">
|
<a-layout id="content-layout">
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip"></a-spin>
|
<a-spin :spinning="spinning" :delay="200" :tip="loadingTip">
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
color="red"
|
color="red"
|
||||||
description='{{ i18n "secAlertSsl" }}'
|
description='{{ i18n "secAlertSsl" }}'
|
||||||
show-icon closable>
|
show-icon closable>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-card hoverable>
|
|
||||||
<a-row>
|
|
||||||
<a-col :sm="24" :md="12">
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.cpu.color"
|
|
||||||
:percent="status.cpu.percent"></a-progress>
|
|
||||||
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
|
||||||
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.mem.color"
|
|
||||||
:percent="status.mem.percent"></a-progress>
|
|
||||||
<div>
|
|
||||||
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :md="12">
|
|
||||||
<a-row>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.swap.color"
|
|
||||||
:percent="status.swap.percent"></a-progress>
|
|
||||||
<div>
|
|
||||||
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
<a-col :span="12" style="text-align: center">
|
|
||||||
<a-progress type="dashboard" status="normal"
|
|
||||||
:stroke-color="status.disk.color"
|
|
||||||
:percent="status.disk.percent"></a-progress>
|
|
||||||
<div>
|
|
||||||
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
|
||||||
</div>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
|
||||||
</a-card>
|
|
||||||
</a-row>
|
|
||||||
</transition>
|
|
||||||
<transition name="list" appear>
|
|
||||||
<a-row>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<b>3X-UI:</b>
|
<a-row>
|
||||||
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
<a-col :sm="24" :md="12">
|
||||||
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
<a-row>
|
||||||
|
<a-col :span="12" style="text-align: center">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.cpu.color"
|
||||||
|
:percent="status.cpu.percent"></a-progress>
|
||||||
|
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
|
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12" style="text-align: center">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.mem.color"
|
||||||
|
:percent="status.mem.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12" style="text-align: center">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.swap.color"
|
||||||
|
:percent="status.swap.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12" style="text-align: center">
|
||||||
|
<a-progress type="dashboard" status="normal"
|
||||||
|
:stroke-color="status.disk.color"
|
||||||
|
:percent="status.disk.percent"></a-progress>
|
||||||
|
<div>
|
||||||
|
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-row>
|
||||||
<a-col :sm="24" :lg="12">
|
</transition>
|
||||||
<a-card hoverable>
|
<transition name="list" appear>
|
||||||
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
<a-row>
|
||||||
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-col :sm="24" :lg="12">
|
||||||
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
<a-card hoverable>
|
||||||
</a-card>
|
<b>3X-UI:</b>
|
||||||
</a-col>
|
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
<a-col :sm="24" :lg="12">
|
<a rel="noopener" href="https://t.me/XrayUI" target="_blank"><a-tag color="green">@XrayUI</a-tag></a>
|
||||||
<a-card hoverable>
|
</a-card>
|
||||||
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
</a-col>
|
||||||
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]] </a-tag>
|
<a-col :sm="24" :lg="12">
|
||||||
<a-popover v-if="status.xray.state === State.Error" :overlay-class-name="themeSwitcher.currentTheme">
|
<a-card hoverable>
|
||||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||||
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag :color="status.xray.color">Xray: [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
</span>
|
<a-tag color="green">OS: [[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
<template slot="content">
|
</a-card>
|
||||||
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
</a-col>
|
||||||
</template>
|
<a-col :sm="24" :lg="12">
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-card hoverable>
|
||||||
</a-popover>
|
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]] </a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
<a-popover v-if="status.xray.state === State.Error" :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||||
</a-card>
|
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
</a-col>
|
</span>
|
||||||
<a-col :sm="24" :lg="12">
|
<template slot="content">
|
||||||
<a-card hoverable>
|
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||||
<b>{{ i18n "menu.link" }}:</b>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :lg="12">
|
|
||||||
<a-card hoverable>
|
|
||||||
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
|
||||||
<a-tag color="green">
|
|
||||||
<a-tooltip>
|
|
||||||
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
|
||||||
<template slot="title">
|
|
||||||
{{ i18n "pages.index.systemLoadDesc" }}
|
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tag>
|
</a-popover>
|
||||||
</a-card>
|
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||||
</a-col>
|
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
<a-card hoverable>
|
</a-card>
|
||||||
<b>{{ i18n "usage"}}:</b>
|
</a-col>
|
||||||
<a-tag color="green"> RAM: [[ sizeFormat(status.appStats.mem) ]] </a-tag>
|
<a-col :sm="24" :lg="12">
|
||||||
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
<a-card hoverable>
|
||||||
</a-card>
|
<b>{{ i18n "menu.link" }}:</b>
|
||||||
</a-col>
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<a-card hoverable>
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
<a-row>
|
</a-card>
|
||||||
<a-col :span="12">
|
</a-col>
|
||||||
<a-tag>
|
<a-col :sm="24" :lg="12">
|
||||||
<a-tooltip>
|
<a-card hoverable>
|
||||||
<a-icon type="global"></a-icon> IPv4
|
<b>{{ i18n "pages.index.systemLoad" }}:</b>
|
||||||
<template slot="title">
|
<a-tag color="green">
|
||||||
|
<a-tooltip>
|
||||||
|
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
|
</template>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card hoverable>
|
||||||
|
<b>{{ i18n "usage"}}:</b>
|
||||||
|
<a-tag color="green"> RAM: [[ sizeFormat(status.appStats.mem) ]] </a-tag>
|
||||||
|
<a-tag color="green"> Threads: [[ status.appStats.threads ]] </a-tag>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :lg="12">
|
||||||
|
<a-card hoverable>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-tag>
|
||||||
|
<a-tooltip>
|
||||||
|
<a-icon type="global"></a-icon> IPv4
|
||||||
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv4 ]]
|
[[ status.publicIP.ipv4 ]]
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="global"></a-icon> IPv6
|
<a-icon type="global"></a-icon> IPv6
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv6 ]]
|
[[ status.publicIP.ipv6 ]]
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="arrow-up"></a-icon> Up: [[ sizeFormat(status.netIO.up) ]]/s
|
<a-icon type="arrow-up"></a-icon> Up: [[ sizeFormat(status.netIO.up) ]]/s
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="arrow-down"></a-icon> Down: [[ sizeFormat(status.netIO.down) ]]/s
|
<a-icon type="arrow-down"></a-icon> Down: [[ sizeFormat(status.netIO.down) ]]/s
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :lg="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalSent" }}
|
{{ i18n "pages.index.totalSent" }}
|
||||||
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-tag>
|
<a-tag>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<a-icon type="cloud-download"></a-icon>
|
<a-icon type="cloud-download"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalReceive" }}
|
{{ i18n "pages.index.totalReceive" }}
|
||||||
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</transition>
|
</transition>
|
||||||
|
</a-spin>
|
||||||
</a-layout-content>
|
</a-layout-content>
|
||||||
</a-layout>
|
</a-layout>
|
||||||
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-alert type="error" v-if="confAlerts.length>0" style="margin: 10px 5px;"
|
<a-alert type="error" v-if="confAlerts.length>0" style="margin-bottom: 10px;"
|
||||||
message='{{ i18n "secAlertTitle" }}'
|
message='{{ i18n "secAlertTitle" }}'
|
||||||
color="red"
|
color="red"
|
||||||
show-icon closable>
|
show-icon closable>
|
||||||
@@ -376,319 +376,325 @@
|
|||||||
{{template "component/password" .}}
|
{{template "component/password" .}}
|
||||||
{{template "component/setting"}}
|
{{template "component/setting"}}
|
||||||
<script>
|
<script>
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
siderDrawer,
|
siderDrawer,
|
||||||
themeSwitcher,
|
themeSwitcher,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
changeSecret: false,
|
changeSecret: false,
|
||||||
oldAllSetting: new AllSetting(),
|
oldAllSetting: new AllSetting(),
|
||||||
allSetting: new AllSetting(),
|
allSetting: new AllSetting(),
|
||||||
saveBtnDisable: true,
|
saveBtnDisable: true,
|
||||||
user: {},
|
user: {},
|
||||||
lang: getLang(),
|
lang: getLang(),
|
||||||
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
remarkModels: { i: 'Inbound', e: 'Email', o: 'Other' },
|
||||||
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
remarkSeparators: [' ', '-', '_', '@', ':', '~', '|', ',', '.', '/'],
|
||||||
datepickerList: [{name:'Gregorian (Standard)', value: 'gregorian'}, {name:'Jalalian (شمسی)', value: 'jalalian'}],
|
datepickerList: [{ name: 'Gregorian (Standard)', value: 'gregorian' }, { name: 'Jalalian (شمسی)', value: 'jalalian' }],
|
||||||
remarkSample: '',
|
remarkSample: '',
|
||||||
defaultFragment: {
|
defaultFragment: {
|
||||||
tag: "fragment",
|
tag: "fragment",
|
||||||
protocol: "freedom",
|
protocol: "freedom",
|
||||||
settings: {
|
settings: {
|
||||||
domainStrategy: "AsIs",
|
domainStrategy: "AsIs",
|
||||||
fragment: {
|
fragment: {
|
||||||
packets: "tlshello",
|
packets: "tlshello",
|
||||||
length: "100-200",
|
length: "100-200",
|
||||||
interval: "10-20"
|
interval: "10-20"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
streamSettings: {
|
|
||||||
sockopt: {
|
|
||||||
tcpKeepAliveIdle: 100,
|
|
||||||
tcpNoDelay: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultMux: {
|
|
||||||
enabled: true,
|
|
||||||
concurrency: 8,
|
|
||||||
xudpConcurrency: 16,
|
|
||||||
xudpProxyUDP443: "reject"
|
|
||||||
},
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
type: "field",
|
|
||||||
outboundTag: "direct",
|
|
||||||
domain: [
|
|
||||||
"geosite:category-ir",
|
|
||||||
"geosite:cn"
|
|
||||||
],
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "field",
|
|
||||||
outboundTag: "direct",
|
|
||||||
ip: [
|
|
||||||
"geoip:private",
|
|
||||||
"geoip:ir",
|
|
||||||
"geoip:cn"
|
|
||||||
],
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
],
|
|
||||||
countryOptions: [
|
|
||||||
{ label: 'Private IP/Domain', value: 'private' },
|
|
||||||
{ label: '🇮🇷 Iran', value: 'ir' },
|
|
||||||
{ label: '🇨🇳 China', value: 'cn' },
|
|
||||||
{ label: '🇷🇺 Russia', value: 'ru' },
|
|
||||||
{ label: '🇻🇳 Vietnam', value: 'vn' },
|
|
||||||
],
|
|
||||||
get remarkModel() {
|
|
||||||
rm = this.allSetting.remarkModel;
|
|
||||||
return rm.length>1 ? rm.substring(1).split('') : [];
|
|
||||||
},
|
|
||||||
set remarkModel(value) {
|
|
||||||
rs = this.allSetting.remarkModel[0];
|
|
||||||
this.allSetting.remarkModel = rs + value.join('');
|
|
||||||
this.changeRemarkSample();
|
|
||||||
},
|
|
||||||
get remarkSeparator() {
|
|
||||||
return this.allSetting.remarkModel.length > 1 ? this.allSetting.remarkModel.charAt(0) : '-';
|
|
||||||
},
|
|
||||||
set remarkSeparator(value) {
|
|
||||||
this.allSetting.remarkModel = value + this.allSetting.remarkModel.substring(1);
|
|
||||||
this.changeRemarkSample();
|
|
||||||
},
|
|
||||||
get datepicker() {
|
|
||||||
return this.allSetting.datepicker ? this.allSetting.datepicker : 'gregorian';
|
|
||||||
},
|
|
||||||
set datepicker(value) {
|
|
||||||
this.allSetting.datepicker = value;
|
|
||||||
},
|
|
||||||
changeRemarkSample(){
|
|
||||||
sample = []
|
|
||||||
this.remarkModel.forEach(r => sample.push(this.remarkModels[r]));
|
|
||||||
this.remarkSample = sample.length == 0 ? '' : sample.join(this.remarkSeparator);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
streamSettings: {
|
||||||
loading(spinning = true) {
|
sockopt: {
|
||||||
this.spinning = spinning;
|
tcpKeepAliveIdle: 100,
|
||||||
},
|
tcpNoDelay: true
|
||||||
async getAllSetting() {
|
}
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/panel/setting/all");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.oldAllSetting = new AllSetting(msg.obj);
|
|
||||||
this.allSetting = new AllSetting(msg.obj);
|
|
||||||
app.changeRemarkSample();
|
|
||||||
this.saveBtnDisable = true;
|
|
||||||
}
|
|
||||||
await this.fetchUserSecret();
|
|
||||||
},
|
|
||||||
async updateAllSetting() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
await this.getAllSetting();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async updateUser() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.user = {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async restartPanel() {
|
|
||||||
await new Promise(resolve => {
|
|
||||||
this.$confirm({
|
|
||||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
|
||||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
|
||||||
class: themeSwitcher.currentTheme,
|
|
||||||
okText: '{{ i18n "sure" }}',
|
|
||||||
cancelText: '{{ i18n "cancel" }}',
|
|
||||||
onOk: () => resolve(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
|
||||||
this.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
this.loading(true);
|
|
||||||
await PromiseUtil.sleep(5000);
|
|
||||||
let { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
|
||||||
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
|
||||||
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
|
||||||
window.location.replace(url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchUserSecret() {
|
|
||||||
this.loading(true);
|
|
||||||
const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
|
|
||||||
if (userMessage.success) {
|
|
||||||
this.user = userMessage.obj;
|
|
||||||
}
|
|
||||||
this.loading(false);
|
|
||||||
},
|
|
||||||
async updateSecret() {
|
|
||||||
this.loading(true);
|
|
||||||
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
|
||||||
if (msg && msg.obj) {
|
|
||||||
this.user = msg.obj;
|
|
||||||
}
|
|
||||||
this.loading(false);
|
|
||||||
await this.updateAllSetting();
|
|
||||||
},
|
|
||||||
generateRandomString(length) {
|
|
||||||
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
|
||||||
let randomString = "";
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
randomString += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
return randomString;
|
|
||||||
},
|
|
||||||
async getNewSecret() {
|
|
||||||
if (!this.changeSecret) {
|
|
||||||
this.changeSecret = true;
|
|
||||||
this.user.loginSecret = '';
|
|
||||||
const newSecret = this.generateRandomString(64);
|
|
||||||
await PromiseUtil.sleep(1000);
|
|
||||||
this.user.loginSecret = newSecret;
|
|
||||||
this.changeSecret = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async toggleToken(value) {
|
|
||||||
if (value) {
|
|
||||||
await this.getNewSecret();
|
|
||||||
} else {
|
|
||||||
this.user.loginSecret = "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
fragment: {
|
|
||||||
get: function() { return this.allSetting?.subJsonFragment != ""; },
|
|
||||||
set: function (v) {
|
|
||||||
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fragmentPackets: {
|
|
||||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
|
|
||||||
set: function(v) {
|
|
||||||
if (v != ""){
|
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
|
||||||
newFragment.settings.fragment.packets = v;
|
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fragmentLength: {
|
|
||||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
|
||||||
set: function(v) {
|
|
||||||
if (v != ""){
|
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
|
||||||
newFragment.settings.fragment.length = v;
|
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fragmentInterval: {
|
|
||||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
|
||||||
set: function(v) {
|
|
||||||
if (v != ""){
|
|
||||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
|
||||||
newFragment.settings.fragment.interval = v;
|
|
||||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enableMux: {
|
|
||||||
get: function() { return this.allSetting?.subJsonMux != ""; },
|
|
||||||
set: function (v) {
|
|
||||||
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
muxConcurrency: {
|
|
||||||
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
|
|
||||||
set: function(v) {
|
|
||||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
|
||||||
newMux.concurrency = v;
|
|
||||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
muxXudpConcurrency: {
|
|
||||||
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
|
|
||||||
set: function(v) {
|
|
||||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
|
||||||
newMux.xudpConcurrency = v;
|
|
||||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
muxXudpProxyUDP443: {
|
|
||||||
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
|
|
||||||
set: function(v) {
|
|
||||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
|
||||||
newMux.xudpProxyUDP443 = v;
|
|
||||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enableDirect: {
|
|
||||||
get: function() { return this.allSetting?.subJsonRules != ""; },
|
|
||||||
set: function (v) {
|
|
||||||
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
directCountries: {
|
|
||||||
get: function() {
|
|
||||||
if (!this.enableDirect) return [];
|
|
||||||
rules = JSON.parse(this.allSetting.subJsonRules);
|
|
||||||
return Array.isArray(rules) ? rules[1].ip.map(d => d.replace("geoip:","")) : [];
|
|
||||||
},
|
|
||||||
set: function (v) {
|
|
||||||
rules = JSON.parse(this.allSetting.subJsonRules);
|
|
||||||
if (!Array.isArray(rules)) return;
|
|
||||||
rules[0].domain = [];
|
|
||||||
rules[1].ip = [];
|
|
||||||
v.forEach(d => {
|
|
||||||
category = ["cn","private"].includes(d) ? "" : "category-";
|
|
||||||
rules[0].domain.push("geosite:"+category+d);
|
|
||||||
rules[1].ip.push("geoip:"+d);
|
|
||||||
});
|
|
||||||
this.allSetting.subJsonRules = JSON.stringify(rules);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confAlerts: {
|
|
||||||
get: function() {
|
|
||||||
if (!this.allSetting) return [];
|
|
||||||
var alerts = []
|
|
||||||
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
|
||||||
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
|
||||||
panelPath = window.location.pathname.split('/').length<4
|
|
||||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
|
||||||
if (this.allSetting.subEnable) {
|
|
||||||
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
|
||||||
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
|
||||||
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
|
||||||
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
|
||||||
}
|
|
||||||
return alerts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
await this.getAllSetting();
|
|
||||||
while (true) {
|
|
||||||
await PromiseUtil.sleep(1000);
|
|
||||||
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
defaultMux: {
|
||||||
|
enabled: true,
|
||||||
|
concurrency: 8,
|
||||||
|
xudpConcurrency: 16,
|
||||||
|
xudpProxyUDP443: "reject"
|
||||||
|
},
|
||||||
|
defaultRules: [
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
domain: [
|
||||||
|
"geosite:category-ir",
|
||||||
|
"geosite:cn"
|
||||||
|
],
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "field",
|
||||||
|
outboundTag: "direct",
|
||||||
|
ip: [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir",
|
||||||
|
"geoip:cn"
|
||||||
|
],
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
countryOptions: [
|
||||||
|
{ label: 'Private IP/Domain', value: 'private' },
|
||||||
|
{ label: '🇮🇷 Iran', value: 'ir' },
|
||||||
|
{ label: '🇨🇳 China', value: 'cn' },
|
||||||
|
{ label: '🇷🇺 Russia', value: 'ru' },
|
||||||
|
],
|
||||||
|
get remarkModel() {
|
||||||
|
rm = this.allSetting.remarkModel;
|
||||||
|
return rm.length > 1 ? rm.substring(1).split('') : [];
|
||||||
|
},
|
||||||
|
set remarkModel(value) {
|
||||||
|
rs = this.allSetting.remarkModel[0];
|
||||||
|
this.allSetting.remarkModel = rs + value.join('');
|
||||||
|
this.changeRemarkSample();
|
||||||
|
},
|
||||||
|
get remarkSeparator() {
|
||||||
|
return this.allSetting.remarkModel.length > 1 ? this.allSetting.remarkModel.charAt(0) : '-';
|
||||||
|
},
|
||||||
|
set remarkSeparator(value) {
|
||||||
|
this.allSetting.remarkModel = value + this.allSetting.remarkModel.substring(1);
|
||||||
|
this.changeRemarkSample();
|
||||||
|
},
|
||||||
|
get datepicker() {
|
||||||
|
return this.allSetting.datepicker ? this.allSetting.datepicker : 'gregorian';
|
||||||
|
},
|
||||||
|
set datepicker(value) {
|
||||||
|
this.allSetting.datepicker = value;
|
||||||
|
},
|
||||||
|
changeRemarkSample() {
|
||||||
|
sample = []
|
||||||
|
this.remarkModel.forEach(r => sample.push(this.remarkModels[r]));
|
||||||
|
this.remarkSample = sample.length == 0 ? '' : sample.join(this.remarkSeparator);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loading(spinning = true) {
|
||||||
|
this.spinning = spinning;
|
||||||
|
},
|
||||||
|
async getAllSetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/all");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.oldAllSetting = new AllSetting(msg.obj);
|
||||||
|
this.allSetting = new AllSetting(msg.obj);
|
||||||
|
app.changeRemarkSample();
|
||||||
|
this.saveBtnDisable = true;
|
||||||
|
}
|
||||||
|
await this.fetchUserSecret();
|
||||||
|
},
|
||||||
|
async updateAllSetting() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/update", this.allSetting);
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
await this.getAllSetting();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async updateUser() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/updateUser", this.user);
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.user = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async restartPanel() {
|
||||||
|
await new Promise(resolve => {
|
||||||
|
this.$confirm({
|
||||||
|
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||||
|
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||||
|
class: themeSwitcher.currentTheme,
|
||||||
|
okText: '{{ i18n "sure" }}',
|
||||||
|
cancelText: '{{ i18n "cancel" }}',
|
||||||
|
onOk: () => resolve(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/restartPanel");
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.loading(true);
|
||||||
|
await PromiseUtil.sleep(5000);
|
||||||
|
let { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting;
|
||||||
|
const isTLS = webCertFile !== "" || webKeyFile !== "";
|
||||||
|
const url = buildURL({ host, port, isTLS, base, path: "panel/settings" });
|
||||||
|
window.location.replace(url);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchUserSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
const userMessage = await HttpUtil.post("/panel/setting/getUserSecret", this.user);
|
||||||
|
if (userMessage.success) {
|
||||||
|
this.user = userMessage.obj;
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
async updateSecret() {
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post("/panel/setting/updateUserSecret", this.user);
|
||||||
|
if (msg && msg.obj) {
|
||||||
|
this.user = msg.obj;
|
||||||
|
}
|
||||||
|
this.loading(false);
|
||||||
|
await this.updateAllSetting();
|
||||||
|
},
|
||||||
|
generateRandomString(length) {
|
||||||
|
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
|
||||||
|
let randomString = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
randomString += chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
},
|
||||||
|
async getNewSecret() {
|
||||||
|
if (!this.changeSecret) {
|
||||||
|
this.changeSecret = true;
|
||||||
|
this.user.loginSecret = '';
|
||||||
|
const newSecret = this.generateRandomString(64);
|
||||||
|
await PromiseUtil.sleep(1000);
|
||||||
|
this.user.loginSecret = newSecret;
|
||||||
|
this.changeSecret = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async toggleToken(value) {
|
||||||
|
if (value) {
|
||||||
|
await this.getNewSecret();
|
||||||
|
} else {
|
||||||
|
this.user.loginSecret = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
fragment: {
|
||||||
|
get: function () { return this.allSetting?.subJsonFragment != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentPackets: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.packets = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentLength: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.length = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fragmentInterval: {
|
||||||
|
get: function () { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
||||||
|
set: function (v) {
|
||||||
|
if (v != "") {
|
||||||
|
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||||
|
newFragment.settings.fragment.interval = v;
|
||||||
|
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableMux: {
|
||||||
|
get: function () { return this.allSetting?.subJsonMux != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxConcurrency: {
|
||||||
|
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
|
||||||
|
set: function (v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.concurrency = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxXudpConcurrency: {
|
||||||
|
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
|
||||||
|
set: function (v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.xudpConcurrency = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
muxXudpProxyUDP443: {
|
||||||
|
get: function () { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
|
||||||
|
set: function (v) {
|
||||||
|
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||||
|
newMux.xudpProxyUDP443 = v;
|
||||||
|
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enableDirect: {
|
||||||
|
get: function () { return this.allSetting?.subJsonRules != ""; },
|
||||||
|
set: function (v) {
|
||||||
|
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
directCountries: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.enableDirect) return [];
|
||||||
|
rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
return Array.isArray(rules) ? rules[1].ip.map(d => d.replace("geoip:", "")) : [];
|
||||||
|
},
|
||||||
|
set: function (v) {
|
||||||
|
rules = JSON.parse(this.allSetting.subJsonRules);
|
||||||
|
if (!Array.isArray(rules)) return;
|
||||||
|
rules[0].domain = [];
|
||||||
|
rules[1].ip = [];
|
||||||
|
v.forEach(d => {
|
||||||
|
let category = '';
|
||||||
|
if (["cn", "private"].includes(d)) {
|
||||||
|
category = "";
|
||||||
|
} else if (d === 'ru') {
|
||||||
|
category = "category-gov-";
|
||||||
|
} else {
|
||||||
|
category = "category-";
|
||||||
|
}
|
||||||
|
rules[0].domain.push("geosite:" + category + d);
|
||||||
|
rules[1].ip.push("geoip:" + d);
|
||||||
|
});
|
||||||
|
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confAlerts: {
|
||||||
|
get: function () {
|
||||||
|
if (!this.allSetting) return [];
|
||||||
|
var alerts = []
|
||||||
|
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
||||||
|
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
||||||
|
panelPath = window.location.pathname.split('/').length < 4
|
||||||
|
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||||
|
if (this.allSetting.subEnable) {
|
||||||
|
subPath = this.allSetting.subURI.length > 0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
||||||
|
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||||
|
subJsonPath = this.allSetting.subJsonURI.length > 0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
||||||
|
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||||
|
}
|
||||||
|
return alerts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.getAllSetting();
|
||||||
|
while (true) {
|
||||||
|
await PromiseUtil.sleep(1000);
|
||||||
|
this.saveBtnDisable = this.oldAllSetting.equals(this.allSetting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -572,9 +572,9 @@
|
|||||||
<a-tab-pane key="tpl-dns" tab='DNS' style="padding-top: 20px;" force-render="true">
|
<a-tab-pane key="tpl-dns" tab='DNS' style="padding-top: 20px;" force-render="true">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
|
||||||
<template v-if="enableDNS">
|
<template v-if="enableDNS">
|
||||||
<setting-list-item type="text" title='{{ i18n "pages.xray.dns.tag" }}' desc='{{ i18n "pages.xray.dns.tagDesc" }}' v-model="dnsTag"></setting-list-item>
|
<setting-list-item style="padding: 10px 20px" type="text" title='{{ i18n "pages.xray.dns.tag" }}' desc='{{ i18n "pages.xray.dns.tagDesc" }}' v-model="dnsTag"></setting-list-item>
|
||||||
<a-list-item>
|
<a-list-item style="padding: 10px 20px">
|
||||||
<a-row style="padding: 20px">
|
<a-row>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.dns.strategy" }}' description='{{ i18n "pages.xray.dns.strategyDesc" }}' />
|
<a-list-item-meta title='{{ i18n "pages.xray.dns.strategy" }}' description='{{ i18n "pages.xray.dns.strategyDesc" }}' />
|
||||||
</a-col>
|
</a-col>
|
||||||
@@ -632,8 +632,7 @@
|
|||||||
<template slot="action" slot-scope="text,fakedns,index">
|
<template slot="action" slot-scope="text,fakedns,index">
|
||||||
[[ index+1 ]]
|
[[ index+1 ]]
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="more"
|
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
||||||
style="font-size: 16px; text-decoration: bold;"></a-icon>
|
|
||||||
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
|
||||||
<a-menu-item @click="editFakedns(index)">
|
<a-menu-item @click="editFakedns(index)">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="random">Random</a-select-option>
|
<a-select-option value="random">Random</a-select-option>
|
||||||
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
||||||
<a-select-option value="leastload">Least Load</a-select-option>
|
<a-select-option value="leastLoad">Least Load</a-select-option>
|
||||||
<a-select-option value="leastping">Least Ping</a-select-option>
|
<a-select-option value="leastPing">Least Ping</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
||||||
|
|||||||
@@ -1186,7 +1186,7 @@ func (s *InboundService) GetClientByEmail(clientEmail string) (*xray.ClientTraff
|
|||||||
return nil, nil, common.NewError("Client Not Found In Inbound For Email:", clientEmail)
|
return nil, nil, common.NewError("Client Not Found In Inbound For Email:", clientEmail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId string) (bool, error) {
|
func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (bool, error) {
|
||||||
traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId)
|
traffic, inbound, err := s.GetClientInboundByTrafficID(trafficId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -1687,10 +1687,10 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetClientTrafficTgBot(tgId string) ([]*xray.ClientTraffic, error) {
|
func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": "%s"%%`, tgId)).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -39,8 +38,9 @@ var (
|
|||||||
type LoginStatus byte
|
type LoginStatus byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LoginSuccess LoginStatus = 1
|
LoginSuccess LoginStatus = 1
|
||||||
LoginFail LoginStatus = 0
|
LoginFail LoginStatus = 0
|
||||||
|
EmptyTelegramUserID = int64(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tgbot struct {
|
type Tgbot struct {
|
||||||
@@ -207,21 +207,20 @@ func (t *Tgbot) OnReceive() {
|
|||||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
||||||
if message.UsersShared != nil {
|
if message.UsersShared != nil {
|
||||||
if checkAdmin(message.From.ID) {
|
if checkAdmin(message.From.ID) {
|
||||||
userIDsStr := ""
|
for _, sharedUser := range message.UsersShared.Users {
|
||||||
for _, userID := range message.UsersShared.UserIDs {
|
userID := sharedUser.UserID
|
||||||
userIDsStr += strconv.FormatInt(userID, 10) + " "
|
needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userID)
|
||||||
|
if needRestart {
|
||||||
|
t.xrayService.SetToNeedRestart()
|
||||||
|
}
|
||||||
|
output := ""
|
||||||
|
if err != nil {
|
||||||
|
output += t.I18nBot("tgbot.messages.selectUserFailed")
|
||||||
|
} else {
|
||||||
|
output += t.I18nBot("tgbot.messages.userSaved")
|
||||||
|
}
|
||||||
|
t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove())
|
||||||
}
|
}
|
||||||
needRestart, err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userIDsStr)
|
|
||||||
if needRestart {
|
|
||||||
t.xrayService.SetToNeedRestart()
|
|
||||||
}
|
|
||||||
output := ""
|
|
||||||
if err != nil {
|
|
||||||
output += t.I18nBot("tgbot.messages.selectUserFailed")
|
|
||||||
} else {
|
|
||||||
output += t.I18nBot("tgbot.messages.userSaved")
|
|
||||||
}
|
|
||||||
t.SendMsgToTgbot(message.Chat.ID, output, tu.ReplyKeyboardRemove())
|
|
||||||
} else {
|
} else {
|
||||||
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove())
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.noResult"), tu.ReplyKeyboardRemove())
|
||||||
}
|
}
|
||||||
@@ -259,7 +258,9 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
|||||||
if isAdmin {
|
if isAdmin {
|
||||||
t.searchClient(chatId, commandArgs[0])
|
t.searchClient(chatId, commandArgs[0])
|
||||||
} else {
|
} else {
|
||||||
t.getClientUsage(chatId, strconv.FormatInt(message.From.ID, 10), commandArgs[0])
|
// Convert message.From.ID to int64
|
||||||
|
fromID := int64(message.From.ID)
|
||||||
|
t.getClientUsage(chatId, fromID, commandArgs[0])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msg += t.I18nBot("tgbot.commands.usage")
|
msg += t.I18nBot("tgbot.commands.usage")
|
||||||
@@ -726,7 +727,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
needRestart, err := t.inboundService.SetClientTelegramUserID(traffic.Id, "")
|
needRestart, err := t.inboundService.SetClientTelegramUserID(traffic.Id, EmptyTelegramUserID)
|
||||||
if needRestart {
|
if needRestart {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -783,8 +784,9 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getBanLogs"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.getBanLogs"))
|
||||||
t.sendBanLogs(chatId, true)
|
t.sendBanLogs(chatId, true)
|
||||||
case "client_traffic":
|
case "client_traffic":
|
||||||
|
tgUserID := callbackQuery.From.ID
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage"))
|
||||||
t.getClientUsage(chatId, strconv.FormatInt(callbackQuery.From.ID, 10))
|
t.getClientUsage(chatId, tgUserID)
|
||||||
case "client_commands":
|
case "client_commands":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands"))
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpClientCommands"))
|
||||||
@@ -1135,7 +1137,7 @@ func (t *Tgbot) clientInfoMsg(
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) getClientUsage(chatId int64, tgUserID string, email ...string) {
|
func (t *Tgbot) getClientUsage(chatId int64, tgUserID int64, email ...string) {
|
||||||
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
traffics, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
@@ -1145,7 +1147,7 @@ func (t *Tgbot) getClientUsage(chatId int64, tgUserID string, email ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(traffics) == 0 {
|
if len(traffics) == 0 {
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+tgUserID))
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.askToAddUserId", "TgUserID=="+strconv.FormatInt(tgUserID, 10)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1218,8 +1220,8 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgId := "None"
|
tgId := "None"
|
||||||
if len(client.TgID) > 0 {
|
if client.TgID != 0 {
|
||||||
tgId = client.TgID
|
tgId = strconv.FormatInt(client.TgID, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
output := ""
|
output := ""
|
||||||
@@ -1452,24 +1454,20 @@ func (t *Tgbot) notifyExhausted() {
|
|||||||
logger.Warning("Unable to load Inbounds", err)
|
logger.Warning("Unable to load Inbounds", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var chatIDsDone []string
|
var chatIDsDone []int64
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
if inbound.Enable {
|
if inbound.Enable {
|
||||||
if len(inbound.ClientStats) > 0 {
|
if len(inbound.ClientStats) > 0 {
|
||||||
clients, err := t.inboundService.GetClients(inbound)
|
clients, err := t.inboundService.GetClients(inbound)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.TgID != "" {
|
if client.TgID != 0 {
|
||||||
chatID, err := strconv.ParseInt(client.TgID, 10, 64)
|
chatID := client.TgID
|
||||||
if err != nil {
|
if !int64Contains(chatIDsDone, chatID) && !checkAdmin(chatID) {
|
||||||
logger.Warning("TgID is not a number: ", client.TgID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !slices.Contains(chatIDsDone, client.TgID) && !checkAdmin(chatID) {
|
|
||||||
var disabledClients []xray.ClientTraffic
|
var disabledClients []xray.ClientTraffic
|
||||||
var exhaustedClients []xray.ClientTraffic
|
var exhaustedClients []xray.ClientTraffic
|
||||||
traffics, err := t.inboundService.GetClientTrafficTgBot(client.TgID)
|
traffics, err := t.inboundService.GetClientTrafficTgBot(client.TgID)
|
||||||
if err == nil && len(traffics) > 0 {
|
if err == nil && len(traffics) > 0 {
|
||||||
output := t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
output := t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
||||||
for _, traffic := range traffics {
|
for _, traffic := range traffics {
|
||||||
if traffic.Enable {
|
if traffic.Enable {
|
||||||
@@ -1498,7 +1496,7 @@ func (t *Tgbot) notifyExhausted() {
|
|||||||
}
|
}
|
||||||
t.SendMsgToTgbot(chatID, output)
|
t.SendMsgToTgbot(chatID, output)
|
||||||
}
|
}
|
||||||
chatIDsDone = append(chatIDsDone, client.TgID)
|
chatIDsDone = append(chatIDsDone, chatID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1509,6 +1507,15 @@ func (t *Tgbot) notifyExhausted() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func int64Contains(slice []int64, item int64) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
||||||
if !p.IsRunning() {
|
if !p.IsRunning() {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user