Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7417c52c8f | ||
|
|
4d4eef8d8a | ||
|
|
9de8b4acaf | ||
|
|
f21c293693 | ||
|
|
56159d9c52 | ||
|
|
6cbdf64013 | ||
|
|
bb9b9100a8 | ||
|
|
816adfc3ea | ||
|
|
0a95b0c7b2 | ||
|
|
d298f4ffbd | ||
|
|
315e8af025 | ||
|
|
de985263f5 | ||
|
|
dfe0bbd371 | ||
|
|
60cb328698 | ||
|
|
3d7f13225a | ||
|
|
76fdfb2ef2 | ||
|
|
8018023187 | ||
|
|
ea9d5dc2d5 | ||
|
|
96e43fa195 | ||
|
|
f1500a5d31 | ||
|
|
c9a218d060 | ||
|
|
6d18a15c4e | ||
|
|
c0f86d2f38 | ||
|
|
5227fefaeb | ||
|
|
7a51d2f2cc | ||
|
|
02ae61fe6b | ||
|
|
24b9e5bfa3 | ||
|
|
9ff7f14b6e | ||
|
|
c3b42b8ea4 | ||
|
|
5afb8d85fc | ||
|
|
767ee4ec2b | ||
|
|
21b64beb96 | ||
|
|
b84e3ef338 |
12
.github/workflows/release.yml
vendored
12
.github/workflows/release.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version-file: go.mod
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.16/"
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.19/"
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
@@ -125,7 +125,13 @@ jobs:
|
||||
|
||||
- name: Package
|
||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x-ui-linux-${{ matrix.platform }}
|
||||
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
|
||||
@@ -27,7 +27,7 @@ case $1 in
|
||||
esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.16/Xray-linux-${ARCH}.zip"
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.19/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
|
||||
@@ -26,10 +26,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||
|
||||
## Install Custom Version
|
||||
|
||||
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.7`:
|
||||
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.3.9`:
|
||||
|
||||
```
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.7
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.3.9
|
||||
```
|
||||
|
||||
## SSL Certificate
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.3.7
|
||||
2.3.9
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
@@ -18,54 +19,51 @@ import (
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
var initializers = []func() error{
|
||||
initUser,
|
||||
initInbound,
|
||||
initOutbound,
|
||||
initSetting,
|
||||
initInboundClientIps,
|
||||
initClientTraffic,
|
||||
const (
|
||||
defaultUsername = "admin"
|
||||
defaultPassword = "admin"
|
||||
defaultSecret = ""
|
||||
)
|
||||
|
||||
func initModels() error {
|
||||
models := []interface{}{
|
||||
&model.User{},
|
||||
&model.Inbound{},
|
||||
&model.OutboundTraffics{},
|
||||
&model.Setting{},
|
||||
&model.InboundClientIps{},
|
||||
&xray.ClientTraffic{},
|
||||
}
|
||||
for _, model := range models {
|
||||
if err := db.AutoMigrate(model); err != nil {
|
||||
log.Printf("Error auto migrating model: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initUser() error {
|
||||
err := db.AutoMigrate(&model.User{})
|
||||
empty, err := isTableEmpty("users")
|
||||
if err != nil {
|
||||
log.Printf("Error checking if users table is empty: %v", err)
|
||||
return err
|
||||
}
|
||||
var count int64
|
||||
err = db.Model(&model.User{}).Count(&count).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
if empty {
|
||||
user := &model.User{
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
LoginSecret: "",
|
||||
Username: defaultUsername,
|
||||
Password: defaultPassword,
|
||||
LoginSecret: defaultSecret,
|
||||
}
|
||||
return db.Create(user).Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initInbound() error {
|
||||
return db.AutoMigrate(&model.Inbound{})
|
||||
}
|
||||
|
||||
func initOutbound() error {
|
||||
return db.AutoMigrate(&model.OutboundTraffics{})
|
||||
}
|
||||
|
||||
func initSetting() error {
|
||||
return db.AutoMigrate(&model.Setting{})
|
||||
}
|
||||
|
||||
func initInboundClientIps() error {
|
||||
return db.AutoMigrate(&model.InboundClientIps{})
|
||||
}
|
||||
|
||||
func initClientTraffic() error {
|
||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||
func isTableEmpty(tableName string) (bool, error) {
|
||||
var count int64
|
||||
err := db.Table(tableName).Count(&count).Error
|
||||
return count == 0, err
|
||||
}
|
||||
|
||||
func InitDB(dbPath string) error {
|
||||
@@ -91,15 +89,27 @@ func InitDB(dbPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, initialize := range initializers {
|
||||
if err := initialize(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := initModels(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := initUser(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloseDB() error {
|
||||
if db != nil {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDB() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
54
go.mod
54
go.mod
@@ -1,25 +1,25 @@
|
||||
module x-ui
|
||||
|
||||
go 1.22.4
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-contrib/sessions v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/mymmrac/telego v0.30.2
|
||||
github.com/mymmrac/telego v0.31.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.2.2
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.24.6
|
||||
github.com/valyala/fasthttp v1.55.0
|
||||
github.com/xtls/xray-core v1.8.16
|
||||
github.com/xtls/xray-core v1.8.19
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.16.0
|
||||
google.golang.org/grpc v1.64.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.10
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -30,7 +30,7 @@ require (
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/fasthttp/router v1.5.1 // indirect
|
||||
github.com/fasthttp/router v1.5.2 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
@@ -40,10 +40,10 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
||||
github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2 // indirect
|
||||
github.com/gorilla/sessions v1.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grbit/go-json v0.11.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
@@ -52,25 +52,26 @@ require (
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/quic-go/quic-go v0.45.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.6 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/quic-go v0.45.1 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/sagernet/sing v0.4.1 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
@@ -78,21 +79,22 @@ require (
|
||||
github.com/valyala/fastjson v1.6.4 // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc // indirect
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
|
||||
|
||||
104
go.sum
104
go.sum
@@ -37,8 +37,8 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fasthttp/router v1.5.1 h1:uViy8UYYhm5npJSKEZ4b/ozM//NGzVCfJbh6VJ0VKr8=
|
||||
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs=
|
||||
github.com/fasthttp/router v1.5.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
|
||||
github.com/fasthttp/router v1.5.2/go.mod h1:C8EY53ozOwpONyevc/V7Gr8pqnEjwnkFFqPo1alAGs0=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
@@ -97,8 +97,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g=
|
||||
github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
@@ -106,8 +106,8 @@ github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
|
||||
@@ -139,8 +139,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@@ -157,8 +157,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.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mymmrac/telego v0.30.2 h1:CqGlqX0hkgz9qMwdA3q+aZtSonqMOKQQrFLn/oUOTaw=
|
||||
github.com/mymmrac/telego v0.30.2/go.mod h1:U6cWJBgRCzGt+s0q77x/Dh2+i+u56VTAAYKlMenhuFc=
|
||||
github.com/mymmrac/telego v0.31.0 h1:vsN+JCNkh7Z9vfL/2/AHZ2xBsRk2GCMj3zydjCxkgIc=
|
||||
github.com/mymmrac/telego v0.31.0/go.mod h1:MuqgVf2xXnIOWZs0prvsp3f4Yss80kCSjVEj4CRl7Ig=
|
||||
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/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||
@@ -179,31 +179,33 @@ github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
|
||||
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
|
||||
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA=
|
||||
github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
|
||||
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
|
||||
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
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/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
|
||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v4 v4.24.6 h1:9qqCSYF2pgOU+t+NgJtp7Co5+5mHF/HyKBUckySQL64=
|
||||
github.com/shirou/gopsutil/v4 v4.24.6/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA=
|
||||
@@ -250,10 +252,10 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
@@ -273,10 +275,10 @@ github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mo
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
|
||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/xtls/xray-core v1.8.16 h1:PhbpdREAIvDS7xmxR6Sdpkx0h5ugmf6wIoWECWtJ0kE=
|
||||
github.com/xtls/xray-core v1.8.16/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/xtls/xray-core v1.8.19 h1:mml7smcO2FM5HyyKdqnf5F2BUvi3br2ldrqmeemEFRE=
|
||||
github.com/xtls/xray-core v1.8.19/go.mod h1:0CwyMPNA5Cs+ukPXHbYQGgne/ug0PuXOSVqBu7zyXOc=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
@@ -294,16 +296,16 @@ golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -312,8 +314,8 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -337,10 +339,8 @@ golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
@@ -353,8 +353,8 @@ golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
@@ -371,14 +371,14 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 h1:1GBuWVLM/KMVUv1t1En5Gs+gFZCNd360GGb4sSxtrhU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -395,8 +395,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
|
||||
@@ -130,7 +130,7 @@ gen_random_string() {
|
||||
# This function will be called when user installed x-ui out of security
|
||||
config_after_install() {
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
read -p "Do you want to continue with the modification [y/n]?": config_confirm
|
||||
read -p "Would you like to customize the panel settings? (If not, random settings will be applied) [y/n]: " config_confirm
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
read -p "Please set up your username: " config_account
|
||||
echo -e "${yellow}Your username will be: ${config_account}${plain}"
|
||||
@@ -160,9 +160,9 @@ config_after_install() {
|
||||
echo -e "${green}Password: ${passwordTemp}${plain}"
|
||||
echo -e "${green}WebBasePath: ${webBasePathTemp}${plain}"
|
||||
echo -e "###############################################"
|
||||
echo -e "${red}If you forgot your login info, you can type x-ui and then type 8 to check after installation${plain}"
|
||||
echo -e "${yellow}If you forgot your login info, you can type "x-ui settings" to check after installation${plain}"
|
||||
else
|
||||
echo -e "${red}This is your upgrade, will keep old settings. If you forgot your login info, you can type x-ui and then type 8 to check${plain}"
|
||||
echo -e "${yellow}This is your upgrade, will keep old settings. If you forgot your login info, you can type "x-ui settings" to check${plain}"
|
||||
fi
|
||||
fi
|
||||
/usr/local/x-ui/x-ui migrate
|
||||
|
||||
98
main.go
98
main.go
@@ -21,7 +21,7 @@ import (
|
||||
)
|
||||
|
||||
func runWebServer() {
|
||||
log.Printf("%v %v", config.GetName(), config.GetVersion())
|
||||
log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
|
||||
|
||||
switch config.GetLogLevel() {
|
||||
case config.Debug:
|
||||
@@ -35,31 +35,29 @@ func runWebServer() {
|
||||
case config.Error:
|
||||
logger.InitLogger(logging.ERROR)
|
||||
default:
|
||||
log.Fatal("unknown log level:", config.GetLogLevel())
|
||||
log.Fatalf("Unknown log level: %v", config.GetLogLevel())
|
||||
}
|
||||
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Fatalf("Error initializing database: %v", err)
|
||||
}
|
||||
|
||||
var server *web.Server
|
||||
|
||||
server = web.NewServer()
|
||||
global.SetWebServer(server)
|
||||
err = server.Start()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Fatalf("Error starting web server: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var subServer *sub.Server
|
||||
subServer = sub.NewServer()
|
||||
global.SetSubServer(subServer)
|
||||
|
||||
err = subServer.Start()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Fatalf("Error starting sub server: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,34 +69,39 @@ func runWebServer() {
|
||||
|
||||
switch sig {
|
||||
case syscall.SIGHUP:
|
||||
logger.Info("Received SIGHUP signal. Restarting servers...")
|
||||
|
||||
err := server.Stop()
|
||||
if err != nil {
|
||||
logger.Warning("stop server err:", err)
|
||||
logger.Warning("Error stopping web server:", err)
|
||||
}
|
||||
err = subServer.Stop()
|
||||
if err != nil {
|
||||
logger.Warning("stop server err:", err)
|
||||
logger.Warning("Error stopping sub server:", err)
|
||||
}
|
||||
|
||||
server = web.NewServer()
|
||||
global.SetWebServer(server)
|
||||
err = server.Start()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Fatalf("Error restarting web server: %v", err)
|
||||
return
|
||||
}
|
||||
log.Println("Web server restarted successfully.")
|
||||
|
||||
subServer = sub.NewServer()
|
||||
global.SetSubServer(subServer)
|
||||
|
||||
err = subServer.Start()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
log.Fatalf("Error restarting sub server: %v", err)
|
||||
return
|
||||
}
|
||||
log.Println("Sub server restarted successfully.")
|
||||
|
||||
default:
|
||||
server.Stop()
|
||||
subServer.Stop()
|
||||
log.Println("Shutting down servers.")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -107,16 +110,16 @@ func runWebServer() {
|
||||
func resetSetting() {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Failed to initialize database:", err)
|
||||
return
|
||||
}
|
||||
|
||||
settingService := service.SettingService{}
|
||||
err = settingService.ResetSettings()
|
||||
if err != nil {
|
||||
fmt.Println("reset setting failed:", err)
|
||||
fmt.Println("Failed to reset settings:", err)
|
||||
} else {
|
||||
fmt.Println("reset setting success")
|
||||
fmt.Println("Settings successfully reset.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,19 +162,19 @@ func showSetting(show bool) {
|
||||
|
||||
func updateTgbotEnableSts(status bool) {
|
||||
settingService := service.SettingService{}
|
||||
currentTgSts, err := settingService.GetTgbotenabled()
|
||||
currentTgSts, err := settingService.GetTgbotEnabled()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
|
||||
if currentTgSts != status {
|
||||
err := settingService.SetTgbotenabled(status)
|
||||
err := settingService.SetTgbotEnabled(status)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
} else {
|
||||
logger.Infof("SetTgbotenabled[%v] success", status)
|
||||
logger.Infof("SetTgbotEnabled[%v] success", status)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +182,7 @@ func updateTgbotEnableSts(status bool) {
|
||||
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Error initializing database:", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,68 +191,65 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||
if tgBotToken != "" {
|
||||
err := settingService.SetTgBotToken(tgBotToken)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("Error setting Telegram bot token: %v\n", err)
|
||||
return
|
||||
} else {
|
||||
logger.Info("updateTgbotSetting tgBotToken success")
|
||||
}
|
||||
logger.Info("Successfully updated Telegram bot token.")
|
||||
}
|
||||
|
||||
if tgBotRuntime != "" {
|
||||
err := settingService.SetTgbotRuntime(tgBotRuntime)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
|
||||
return
|
||||
} else {
|
||||
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
|
||||
}
|
||||
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
|
||||
}
|
||||
|
||||
if tgBotChatid != "" {
|
||||
err := settingService.SetTgBotChatId(tgBotChatid)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
|
||||
return
|
||||
} else {
|
||||
logger.Info("updateTgbotSetting tgBotChatid success")
|
||||
}
|
||||
logger.Info("Successfully updated Telegram bot chat ID.")
|
||||
}
|
||||
}
|
||||
|
||||
func updateSetting(port int, username string, password string, webBasePath string) {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Database initialization failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
settingService := service.SettingService{}
|
||||
userService := service.UserService{}
|
||||
|
||||
if port > 0 {
|
||||
err := settingService.SetPort(port)
|
||||
if err != nil {
|
||||
fmt.Println("set port failed:", err)
|
||||
fmt.Println("Failed to set port:", err)
|
||||
} else {
|
||||
fmt.Printf("set port %v success", port)
|
||||
fmt.Printf("Port set successfully: %v\n", port)
|
||||
}
|
||||
}
|
||||
|
||||
if username != "" || password != "" {
|
||||
userService := service.UserService{}
|
||||
err := userService.UpdateFirstUser(username, password)
|
||||
if err != nil {
|
||||
fmt.Println("set username and password failed:", err)
|
||||
fmt.Println("Failed to update username and password:", err)
|
||||
} else {
|
||||
fmt.Println("set username and password success")
|
||||
fmt.Println("Username and password updated successfully")
|
||||
}
|
||||
}
|
||||
|
||||
if webBasePath != "" {
|
||||
err := settingService.SetBasePath(webBasePath)
|
||||
if err != nil {
|
||||
fmt.Println("set base URI path failed:", err)
|
||||
fmt.Println("Failed to set base URI path:", err)
|
||||
} else {
|
||||
fmt.Println("set base URI path success")
|
||||
fmt.Println("Base URI path set successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,19 +348,19 @@ func main() {
|
||||
var reset bool
|
||||
var show bool
|
||||
var remove_secret bool
|
||||
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "remove secret")
|
||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||
settingCmd.StringVar(&webBasePath, "webBasePath", "", "set web base path")
|
||||
settingCmd.StringVar(&webCertFile, "webCert", "", "set web public key path")
|
||||
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "set web private key path")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
|
||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
|
||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
|
||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
||||
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
|
||||
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
|
||||
settingCmd.StringVar(&username, "username", "", "Set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "Set login password")
|
||||
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
||||
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
||||
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
|
||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
|
||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
|
||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
|
||||
|
||||
oldUsage := flag.Usage
|
||||
flag.Usage = func() {
|
||||
|
||||
@@ -163,13 +163,13 @@ func (s *Server) Start() (err error) {
|
||||
}
|
||||
listener = network.NewAutoHttpsListener(listener)
|
||||
listener = tls.NewListener(listener, c)
|
||||
logger.Info("sub server run https on", listener.Addr())
|
||||
logger.Info("Sub server running HTTPS on", listener.Addr())
|
||||
} else {
|
||||
logger.Error("error in loading certificates: ", err)
|
||||
logger.Info("sub server run http on", listener.Addr())
|
||||
logger.Error("Error loading certificates:", err)
|
||||
logger.Info("Sub server running HTTP on", listener.Addr())
|
||||
}
|
||||
} else {
|
||||
logger.Info("sub server run http on", listener.Addr())
|
||||
logger.Info("Sub server running HTTP on", listener.Addr())
|
||||
}
|
||||
s.listener = listener
|
||||
|
||||
|
||||
@@ -1023,10 +1023,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
||||
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
|
||||
}
|
||||
case exp < 0:
|
||||
passedSeconds := now - exp
|
||||
days := passedSeconds / 86400
|
||||
hours := (passedSeconds % 86400) / 3600
|
||||
minutes := (passedSeconds % 3600) / 60
|
||||
days := exp / -86400
|
||||
hours := (exp % -86400) / 3600
|
||||
minutes := (exp % -3600) / 60
|
||||
if days > 0 {
|
||||
if hours > 0 {
|
||||
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
|
||||
|
||||
3
web/assets/axios/axios.min.js
vendored
3
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -577,6 +577,10 @@ class Outbound extends CommonClass {
|
||||
}
|
||||
|
||||
canEnableMux() {
|
||||
if (this.settings.flow && this.settings.flow != ''){
|
||||
this.mux.enabled = false;
|
||||
return false;
|
||||
}
|
||||
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, Protocols.Socks].includes(this.protocol);
|
||||
}
|
||||
|
||||
|
||||
@@ -523,7 +523,7 @@ class HTTPUpgradeStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class SplitHTTPStreamSettings extends XrayCommonClass {
|
||||
constructor(path='/', host='', headers=[] , maxUploadSize= 1, maxConcurrentUploads= 10) {
|
||||
constructor(path='/', host='', headers=[] , maxUploadSize= 1000000, maxConcurrentUploads= 10) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
@@ -570,7 +570,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
disableSystemRoot = false,
|
||||
enableSessionResumption = false,
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
alpn=[ALPN_OPTION.H3,ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
settings=new TlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.sni = serverName;
|
||||
@@ -712,7 +712,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||
class XtlsStreamSettings extends XrayCommonClass {
|
||||
constructor(serverName='',
|
||||
certificates=[new XtlsStreamSettings.Cert()],
|
||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
alpn=[ALPN_OPTION.H3,ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
settings=new XtlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.sni = serverName;
|
||||
@@ -892,7 +892,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') {
|
||||
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_RANDOM, serverName = '', spiderX= '/') {
|
||||
super();
|
||||
this.publicKey = publicKey;
|
||||
this.fingerprint = fingerprint;
|
||||
@@ -2608,4 +2608,4 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||
keepAlive: this.keepAlive?? undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -128,7 +128,7 @@ Date.prototype.formatDateTime = function (split = ' ') {
|
||||
};
|
||||
|
||||
class DateUtil {
|
||||
// String string to date object
|
||||
// String to date object
|
||||
static parseDate(str) {
|
||||
return new Date(str.replace(/-/g, '/'));
|
||||
}
|
||||
@@ -143,4 +143,9 @@ class DateUtil {
|
||||
date.setMinTime();
|
||||
return date;
|
||||
}
|
||||
|
||||
static convertToJalalian(date) {
|
||||
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,73 +1,57 @@
|
||||
class Msg {
|
||||
constructor(success, msg, obj) {
|
||||
this.success = false;
|
||||
this.msg = "";
|
||||
this.obj = null;
|
||||
|
||||
if (success != null) {
|
||||
this.success = success;
|
||||
}
|
||||
if (msg != null) {
|
||||
this.msg = msg;
|
||||
}
|
||||
if (obj != null) {
|
||||
this.obj = obj;
|
||||
}
|
||||
constructor(success = false, msg = "", obj = null) {
|
||||
this.success = success;
|
||||
this.msg = msg;
|
||||
this.obj = obj;
|
||||
}
|
||||
}
|
||||
|
||||
class HttpUtil {
|
||||
static _handleMsg(msg) {
|
||||
if (!(msg instanceof Msg)) {
|
||||
if (!(msg instanceof Msg) || msg.msg === "") {
|
||||
return;
|
||||
}
|
||||
if (msg.msg === "") {
|
||||
return;
|
||||
}
|
||||
if (msg.success) {
|
||||
Vue.prototype.$message.success(msg.msg);
|
||||
} else {
|
||||
Vue.prototype.$message.error(msg.msg);
|
||||
}
|
||||
const messageType = msg.success ? 'success' : 'error';
|
||||
Vue.prototype.$message[messageType](msg.msg);
|
||||
}
|
||||
|
||||
static _respToMsg(resp) {
|
||||
const data = resp.data;
|
||||
const { data } = resp;
|
||||
if (data == null) {
|
||||
return new Msg(true);
|
||||
} else if (typeof data === 'object') {
|
||||
if (data.hasOwnProperty('success')) {
|
||||
return new Msg(data.success, data.msg, data.obj);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
} else {
|
||||
return new Msg(false, 'unknown data:', data);
|
||||
}
|
||||
if (typeof data === 'object' && 'success' in data) {
|
||||
return new Msg(data.success, data.msg, data.obj);
|
||||
}
|
||||
return typeof data === 'object' ? data : new Msg(false, 'unknown data:', data);
|
||||
}
|
||||
|
||||
static async get(url, data, options) {
|
||||
let msg;
|
||||
static async get(url, params, options = {}) {
|
||||
try {
|
||||
const resp = await axios.get(url, data, options);
|
||||
msg = this._respToMsg(resp);
|
||||
} catch (e) {
|
||||
msg = new Msg(false, e.toString());
|
||||
const resp = await axios.get(url, { params, ...options });
|
||||
const msg = this._respToMsg(resp);
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
} catch (error) {
|
||||
console.error('GET request failed:', error);
|
||||
const errorMsg = new Msg(false, error.response?.data?.message || error.message);
|
||||
this._handleMsg(errorMsg);
|
||||
return errorMsg;
|
||||
}
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static async post(url, data, options) {
|
||||
let msg;
|
||||
static async post(url, data, options = {}) {
|
||||
try {
|
||||
const resp = await axios.post(url, data, options);
|
||||
msg = this._respToMsg(resp);
|
||||
} catch (e) {
|
||||
msg = new Msg(false, e.toString());
|
||||
const msg = this._respToMsg(resp);
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
} catch (error) {
|
||||
console.error('POST request failed:', error);
|
||||
const errorMsg = new Msg(false, error.response?.data?.message || error.message);
|
||||
this._handleMsg(errorMsg);
|
||||
return errorMsg;
|
||||
}
|
||||
this._handleMsg(msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
static async postWithModal(url, data, modal) {
|
||||
|
||||
@@ -232,14 +232,12 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
}
|
||||
email := c.Param("email")
|
||||
|
||||
needRestart := true
|
||||
|
||||
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
|
||||
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "traffic reseted", nil)
|
||||
jsonMsg(c, "Traffic has been reset", nil)
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
@@ -253,7 +251,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
||||
} else {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
jsonMsg(c, "All traffics reseted", nil)
|
||||
jsonMsg(c, "all traffic has been reset", nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||
@@ -270,7 +268,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||
} else {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
jsonMsg(c, "All traffics of client reseted", nil)
|
||||
jsonMsg(c, "All traffic from the client has been reset.", nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) importInbound(c *gin.Context) {
|
||||
@@ -313,9 +311,9 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "All delpeted clients are deleted", nil)
|
||||
jsonMsg(c, "All depleted clients are deleted", nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) onlines(c *gin.Context) {
|
||||
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
|
||||
jsonObj(c, a.inboundService.GetOnlineClients(), nil)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"x-ui/logger"
|
||||
@@ -64,37 +65,40 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
|
||||
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||
safeUser := template.HTMLEscapeString(form.Username)
|
||||
safePass := template.HTMLEscapeString(form.Password)
|
||||
safeSecret := template.HTMLEscapeString(form.LoginSecret)
|
||||
if user == nil {
|
||||
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||
logger.Warningf("wrong username or password or secret: \"%s\" \"%s\" \"%s\"", safeUser, safePass, safeSecret)
|
||||
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
return
|
||||
} else {
|
||||
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
||||
logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
|
||||
}
|
||||
|
||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||
if err != nil {
|
||||
logger.Warningf("Unable to get session's max age from DB")
|
||||
logger.Warning("Unable to get session's max age from DB")
|
||||
}
|
||||
|
||||
if sessionMaxAge > 0 {
|
||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||
if err != nil {
|
||||
logger.Warningf("Unable to set session's max age")
|
||||
logger.Warning("Unable to set session's max age")
|
||||
}
|
||||
}
|
||||
|
||||
err = session.SetLoginUser(c, user)
|
||||
logger.Info("user", user.Id, "login success")
|
||||
logger.Infof("%s logged in successfully", user.Username)
|
||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||
}
|
||||
|
||||
func (a *IndexController) logout(c *gin.Context) {
|
||||
user := session.GetLoginUser(c)
|
||||
if user != nil {
|
||||
logger.Info("user", user.Id, "logout")
|
||||
logger.Infof("%s logged out successfully", user.Username)
|
||||
}
|
||||
session.ClearSession(c)
|
||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||
|
||||
@@ -105,7 +105,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||
jsonMsg(c, "", err)
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Xray stoped", err)
|
||||
jsonMsg(c, "Xray stopped", err)
|
||||
}
|
||||
|
||||
func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||
|
||||
@@ -12,6 +12,7 @@ type XraySettingController struct {
|
||||
InboundService service.InboundService
|
||||
OutboundService service.OutboundService
|
||||
XrayService service.XrayService
|
||||
WarpService service.WarpService
|
||||
}
|
||||
|
||||
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
||||
@@ -72,16 +73,18 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
||||
var err error
|
||||
switch action {
|
||||
case "data":
|
||||
resp, err = a.XraySettingService.GetWarp()
|
||||
resp, err = a.WarpService.GetWarpData()
|
||||
case "del":
|
||||
err = a.WarpService.DelWarpData()
|
||||
case "config":
|
||||
resp, err = a.XraySettingService.GetWarpConfig()
|
||||
resp, err = a.WarpService.GetWarpConfig()
|
||||
case "reg":
|
||||
skey := c.PostForm("privateKey")
|
||||
pkey := c.PostForm("publicKey")
|
||||
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
||||
resp, err = a.WarpService.RegWarp(skey, pkey)
|
||||
case "license":
|
||||
license := c.PostForm("license")
|
||||
resp, err = a.XraySettingService.SetWarpLicence(license)
|
||||
resp, err = a.WarpService.SetWarpLicense(license)
|
||||
}
|
||||
|
||||
jsonObj(c, resp, err)
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
qrModal: qrModal,
|
||||
},
|
||||
methods: {
|
||||
copyToClipboard(elmentId, content) {
|
||||
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||
copyToClipboard(elementId, content) {
|
||||
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
|
||||
text: () => content,
|
||||
});
|
||||
this.qrModal.clipboard.on('success', () => {
|
||||
@@ -88,9 +88,9 @@
|
||||
this.qrModal.clipboard.destroy();
|
||||
});
|
||||
},
|
||||
setQrCode(elmentId, content) {
|
||||
setQrCode(elementId, content) {
|
||||
new QRious({
|
||||
element: document.querySelector('#' + elmentId),
|
||||
element: document.querySelector('#' + elementId),
|
||||
size: 400,
|
||||
value: content,
|
||||
background: 'white',
|
||||
|
||||
@@ -108,15 +108,16 @@
|
||||
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime"></persian-datepicker>
|
||||
value="clientsBulkModal.expiryTime" v-model="clientsBulkModal.expiryTime">
|
||||
</persian-datepicker>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
||||
<template slot="label">
|
||||
<span>{{ i18n "pages.client.renew" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.client.renew" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
@@ -54,12 +54,13 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker>
|
||||
</a-form-item>
|
||||
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime">
|
||||
</persian-datepicker>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess settings -->
|
||||
|
||||
@@ -90,7 +90,14 @@
|
||||
<template slot="content">
|
||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
|
||||
</span>
|
||||
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||
<span v-else>
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(client._expiryTime)) ]]
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<table>
|
||||
<tr class="tr-table-box">
|
||||
@@ -108,7 +115,14 @@
|
||||
<template slot="content">
|
||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
|
||||
</span>
|
||||
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||
<span v-else>
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(client._expiryTime)) ]]
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||
</a-popover>
|
||||
@@ -201,7 +215,14 @@
|
||||
<template slot="content">
|
||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
|
||||
</span>
|
||||
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||
<span v-else>
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(client._expiryTime)) ]]
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
|
||||
</a-popover>
|
||||
@@ -214,7 +235,14 @@
|
||||
<template slot="content">
|
||||
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
|
||||
</span>
|
||||
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||
<span v-else>
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(client._expiryTime)) ]]
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
|
||||
</a-popover>
|
||||
|
||||
@@ -221,7 +221,14 @@
|
||||
</td>
|
||||
<td>
|
||||
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)"> [[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]] </a-tag>
|
||||
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(infoModal.clientSettings.expiryTime)) ]]
|
||||
</template>
|
||||
</a-tag>
|
||||
</template>
|
||||
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}
|
||||
</a-tag>
|
||||
@@ -494,8 +501,8 @@
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
copyToClipboard(elmentId, content) {
|
||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||
copyToClipboard(elementId, content) {
|
||||
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
|
||||
text: () => content,
|
||||
});
|
||||
this.infoModal.clipboard.on('success', () => {
|
||||
|
||||
@@ -403,9 +403,12 @@
|
||||
</template>
|
||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<template slot="content" v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else slot="content">
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
<a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
|
||||
[[ remainedDays(dbInbound._expiryTime) ]]
|
||||
</a-tag>
|
||||
@@ -498,8 +501,14 @@
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.expireDate" }}</td>
|
||||
<td>
|
||||
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0"
|
||||
:color="dbInbound.isExpiry? 'red': 'blue'">
|
||||
<template v-if="app.datepicker === 'gregorian'">
|
||||
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
|
||||
</template>
|
||||
<template v-else>
|
||||
[[ DateUtil.convertToJalalian(moment(dbInbound.expiryTime)) ]]
|
||||
</template>
|
||||
</a-tag>
|
||||
<a-tag v-else style="text-align: center;" color="purple" class="infinite-tag">
|
||||
<svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">
|
||||
|
||||
@@ -503,6 +503,7 @@
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.user = {};
|
||||
window.location.replace(basePath + "logout");
|
||||
}
|
||||
},
|
||||
async restartPanel() {
|
||||
|
||||
@@ -24,19 +24,22 @@
|
||||
<td>[[ warpModal.warpData.private_key ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-button @click="delConfig" :loading="warpModal.confirmLoading" type="danger">{{ i18n "delete" }}</a-button>
|
||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
||||
<a-collapse style="margin: 10px 0;">
|
||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Key">
|
||||
<a-input v-model="warpPlus"></a-input>
|
||||
<a-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
|
||||
<a-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26"
|
||||
:loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider>
|
||||
<a-button icon="sync" @click="getConfig" style="margin-top: 5px; margin-bottom: 10px;" :loading="warpModal.confirmLoading" type="primary">{{ i18n "info" }}</a-button>
|
||||
<a-button icon="sync" @click="getConfig" style="margin-top: 5px; margin-bottom: 10px;"
|
||||
:loading="warpModal.confirmLoading" type="primary">{{ i18n "info" }}</a-button>
|
||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
|
||||
<table style="width: 100%">
|
||||
<tr class="client-table-odd-row">
|
||||
@@ -51,39 +54,39 @@
|
||||
<td>Device Enabled</td>
|
||||
<td>[[ warpModal.warpConfig.enabled ]]</td>
|
||||
</tr>
|
||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
|
||||
<tr>
|
||||
<td>Account Type</td>
|
||||
<td>[[ warpModal.warpConfig.account.account_type ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Role</td>
|
||||
<td>[[ warpModal.warpConfig.account.role ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WARP+ Data</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Quota</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
|
||||
<td>Usage</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
|
||||
<tr>
|
||||
<td>Account Type</td>
|
||||
<td>[[ warpModal.warpConfig.account.account_type ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Role</td>
|
||||
<td>[[ warpModal.warpConfig.account.role ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WARP+ Data</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Quota</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
|
||||
<td>Usage</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
<a-divider style="margin: 10px 0;">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider>
|
||||
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<template v-if="warpOutboundIndex>=0">
|
||||
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag>
|
||||
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag>
|
||||
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||
</template>
|
||||
<template v-if="warpOutboundIndex>=0">
|
||||
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag>
|
||||
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag>
|
||||
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
@@ -101,21 +104,20 @@
|
||||
this.visible = true;
|
||||
this.warpConfig = null;
|
||||
this.getData();
|
||||
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
this.loading(false);
|
||||
},
|
||||
loading(loading=true) {
|
||||
loading(loading = true) {
|
||||
this.confirmLoading = loading;
|
||||
},
|
||||
async getData(){
|
||||
async getData() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/data');
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
|
||||
this.warpData = msg.obj.length > 0 ? JSON.parse(msg.obj) : null;
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -131,14 +133,15 @@
|
||||
collectConfig() {
|
||||
config = warpModal.warpConfig.config;
|
||||
peer = config.peers[0];
|
||||
if(config){
|
||||
if (config) {
|
||||
warpModal.warpOutbound = Outbound.fromJson({
|
||||
tag: 'warp',
|
||||
protocol: Protocols.Wireguard,
|
||||
settings: {
|
||||
mtu: 1420,
|
||||
secretKey: warpModal.warpData.private_key,
|
||||
address: Object.values(config.interface.addresses),
|
||||
address: this.getAddresses(config.interface.addresses),
|
||||
reserved: this.getResolved(config.client_id),
|
||||
domainStrategy: 'ForceIP',
|
||||
peers: [{
|
||||
publicKey: peer.public_key,
|
||||
@@ -149,10 +152,32 @@
|
||||
});
|
||||
}
|
||||
},
|
||||
async register(){
|
||||
getAddresses(addrs) {
|
||||
let addresses = [];
|
||||
if (addrs.v4) addresses.push(addrs.v4 + "/32");
|
||||
if (addrs.v6) addresses.push(addrs.v6 + "/128");
|
||||
return addresses;
|
||||
},
|
||||
getResolved(client_id) {
|
||||
let reserved = [];
|
||||
let decoded = atob(client_id);
|
||||
let hexString = '';
|
||||
for (let i = 0; i < decoded.length; i++) {
|
||||
let hex = decoded.charCodeAt(i).toString(16);
|
||||
hexString += (hex.length === 1 ? '0' : '') + hex;
|
||||
}
|
||||
|
||||
for (let i = 0; i < hexString.length; i += 2) {
|
||||
let hexByte = hexString.slice(i, i + 2);
|
||||
let decValue = parseInt(hexByte, 16);
|
||||
reserved.push(decValue);
|
||||
}
|
||||
return reserved;
|
||||
},
|
||||
async register() {
|
||||
warpModal.loading(true);
|
||||
keys = Wireguard.generateKeypair();
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/reg',keys);
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/reg', keys);
|
||||
if (msg.success) {
|
||||
resp = JSON.parse(msg.obj);
|
||||
warpModal.warpData = resp.data;
|
||||
@@ -161,9 +186,9 @@
|
||||
}
|
||||
warpModal.loading(false);
|
||||
},
|
||||
async updateLicense(l){
|
||||
async updateLicense(l) {
|
||||
warpModal.loading(true);
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/license',{license: l});
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/license', { license: l });
|
||||
if (msg.success) {
|
||||
warpModal.warpData = JSON.parse(msg.obj);
|
||||
warpModal.warpConfig = null;
|
||||
@@ -171,7 +196,7 @@
|
||||
}
|
||||
warpModal.loading(false);
|
||||
},
|
||||
async getConfig(){
|
||||
async getConfig() {
|
||||
warpModal.loading(true);
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/config');
|
||||
warpModal.loading(false);
|
||||
@@ -180,20 +205,37 @@
|
||||
this.collectConfig();
|
||||
}
|
||||
},
|
||||
addOutbound(){
|
||||
async delConfig() {
|
||||
warpModal.loading(true);
|
||||
const msg = await HttpUtil.post('/panel/xray/warp/del');
|
||||
warpModal.loading(false);
|
||||
if (msg.success) {
|
||||
warpModal.warpData = null;
|
||||
warpModal.warpConfig = null;
|
||||
this.delOutbound();
|
||||
}
|
||||
},
|
||||
addOutbound() {
|
||||
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
|
||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||
warpModal.close();
|
||||
},
|
||||
resetOutbound(){
|
||||
resetOutbound() {
|
||||
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
|
||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||
warpModal.close();
|
||||
},
|
||||
delOutbound() {
|
||||
if (this.warpOutboundIndex != -1) {
|
||||
app.templateSettings.outbounds.splice(this.warpOutboundIndex, 1);
|
||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||
}
|
||||
warpModal.close();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
warpOutboundIndex: {
|
||||
get: function() {
|
||||
get: function () {
|
||||
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
|
||||
}
|
||||
}
|
||||
@@ -201,4 +243,4 @@
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -1054,12 +1054,13 @@
|
||||
});
|
||||
},
|
||||
changeObsCode() {
|
||||
if (this.obsSettings == ''){
|
||||
return
|
||||
}
|
||||
if(this.cm != null) {
|
||||
this.cm.toTextArea();
|
||||
}
|
||||
if (this.obsSettings == ''){
|
||||
this.cm = null;
|
||||
return
|
||||
}
|
||||
textAreaObj = document.getElementById('obsSetting');
|
||||
textAreaObj.value = this[this.obsSettings];
|
||||
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||
@@ -1267,7 +1268,8 @@
|
||||
balancer: {
|
||||
tag: '',
|
||||
strategy: 'random',
|
||||
selector: []
|
||||
selector: [],
|
||||
fallbackTag: ''
|
||||
},
|
||||
confirm: (balancer) => {
|
||||
balancerModal.loading();
|
||||
@@ -1277,27 +1279,18 @@
|
||||
}
|
||||
let tmpBalancer = {
|
||||
'tag': balancer.tag,
|
||||
'selector': balancer.selector
|
||||
'selector': balancer.selector,
|
||||
'fallbackTag': balancer.fallbackTag
|
||||
};
|
||||
if (balancer.strategy && balancer.strategy != 'random') {
|
||||
tmpBalancer.strategy = {
|
||||
'type': balancer.strategy
|
||||
};
|
||||
if (balancer.strategy == 'leastPing'){
|
||||
if (!newTemplateSettings.observatory)
|
||||
newTemplateSettings.observatory = this.defaultObservatory;
|
||||
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
|
||||
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
|
||||
}
|
||||
if (balancer.strategy == 'leastLoad'){
|
||||
if (!newTemplateSettings.burstObservatory)
|
||||
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
||||
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
|
||||
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
|
||||
}
|
||||
}
|
||||
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
||||
this.templateSettings = newTemplateSettings;
|
||||
if (balancer.strategy == 'leastPing' || balancer.strategy == 'leastLoad')
|
||||
this.updateObservatorySelectors();
|
||||
balancerModal.close();
|
||||
this.changeObsCode();
|
||||
},
|
||||
@@ -1317,7 +1310,8 @@
|
||||
|
||||
let tmpBalancer = {
|
||||
'tag': balancer.tag,
|
||||
'selector': balancer.selector
|
||||
'selector': balancer.selector,
|
||||
'fallbackTag': balancer.fallbackTag
|
||||
};
|
||||
|
||||
// Remove old tag
|
||||
@@ -1332,18 +1326,6 @@
|
||||
tmpBalancer.strategy = {
|
||||
'type': balancer.strategy
|
||||
};
|
||||
if (balancer.strategy == 'leastPing'){
|
||||
if (!newTemplateSettings.observatory)
|
||||
newTemplateSettings.observatory = this.defaultObservatory;
|
||||
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
|
||||
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
|
||||
}
|
||||
if (balancer.strategy == 'leastLoad'){
|
||||
if (!newTemplateSettings.burstObservatory)
|
||||
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
||||
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
|
||||
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
|
||||
}
|
||||
}
|
||||
|
||||
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
||||
@@ -1356,14 +1338,49 @@
|
||||
});
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
if (balancer.strategy == 'leastPing' || balancer.strategy == 'leastLoad')
|
||||
this.updateObservatorySelectors();
|
||||
balancerModal.close();
|
||||
this.changeObsCode();
|
||||
},
|
||||
isEdit: true
|
||||
});
|
||||
},
|
||||
updateObservatorySelectors(){
|
||||
newTemplateSettings = this.templateSettings;
|
||||
const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
|
||||
const leastLoads = this.balancersData.filter((b) => b.strategy == 'leastLoad');
|
||||
if (leastPings.length>0){
|
||||
if (!newTemplateSettings.observatory)
|
||||
newTemplateSettings.observatory = this.defaultObservatory;
|
||||
newTemplateSettings.observatory.subjectSelector = [];
|
||||
leastPings.forEach((b) => {
|
||||
b.selector.forEach((s) => {
|
||||
if (!newTemplateSettings.observatory.subjectSelector.includes(s))
|
||||
newTemplateSettings.observatory.subjectSelector.push(s);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
delete newTemplateSettings.observatory
|
||||
}
|
||||
if (leastLoads.length>0){
|
||||
if (!newTemplateSettings.burstObservatory)
|
||||
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
||||
newTemplateSettings.burstObservatory.subjectSelector = [];
|
||||
leastLoads.forEach((b) => {
|
||||
b.selector.forEach((s) => {
|
||||
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(s))
|
||||
newTemplateSettings.burstObservatory.subjectSelector.push(s);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
delete newTemplateSettings.burstObservatory
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
this.changeObsCode();
|
||||
},
|
||||
deleteBalancer(index) {
|
||||
let newTemplateSettings = { ...this.templateSettings };
|
||||
newTemplateSettings = this.templateSettings;
|
||||
|
||||
// Remove from balancers
|
||||
const removedBalancer = this.balancersData.splice(index, 1)[0];
|
||||
@@ -1371,27 +1388,14 @@
|
||||
// Remove from settings
|
||||
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
||||
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
||||
|
||||
// Remove tag from observatory
|
||||
if (newTemplateSettings.observatory){
|
||||
newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != removedBalancer.tag);
|
||||
}
|
||||
if (newTemplateSettings.burstObservatory){
|
||||
newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != removedBalancer.tag);
|
||||
}
|
||||
|
||||
// Remove related routing rules
|
||||
newTemplateSettings.routing.rules.forEach((rule) => {
|
||||
if (rule.balancerTag === removedBalancer.tag) {
|
||||
delete rule.balancerTag;
|
||||
}
|
||||
});
|
||||
|
||||
// Update balancers property to an empty array if there are no more balancers
|
||||
if (newTemplateSettings.routing.balancers.length === 0) {
|
||||
delete newTemplateSettings.routing.balancers;
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
this.updateObservatorySelectors();
|
||||
this.obsSettings = '';
|
||||
this.changeObsCode()
|
||||
},
|
||||
addDNSServer(){
|
||||
@@ -1622,7 +1626,8 @@
|
||||
'key': index,
|
||||
'tag': o.tag ? o.tag : "",
|
||||
'strategy': o.strategy?.type ?? "random",
|
||||
'selector': o.selector ? o.selector : []
|
||||
'selector': o.selector ? o.selector : [],
|
||||
'fallbackTag': o.fallbackTag?? '',
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1649,22 +1654,8 @@
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
observatoryEnable: {
|
||||
get: function () { return this.templateSettings != null && this.templateSettings.observatory },
|
||||
set: function (v) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.observatory = v ? this.defaultObservatory : undefined;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
burstObservatoryEnable: {
|
||||
get: function () { return this.templateSettings != null && this.templateSettings.burstObservatory },
|
||||
set: function (v) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.burstObservatory = v ? this.defaultBurstObservatory : undefined;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
observatoryEnable: function () { return this.templateSettings != null && this.templateSettings.observatory != undefined },
|
||||
burstObservatoryEnable: function () { return this.templateSettings != null && this.templateSettings.burstObservatory != undefined },
|
||||
freedomStrategy: {
|
||||
get: function () {
|
||||
if (!this.templateSettings) return "AsIs";
|
||||
|
||||
@@ -25,13 +25,19 @@
|
||||
<a-select-option value="leastPing">Least Ping</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
||||
:validate-status="balancerModal.emptySelector? 'warning' : 'success'">
|
||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
|
||||
:validate-status="balancerModal.emptySelector? 'warning' : 'success'">
|
||||
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Fallback">
|
||||
<a-select v-model="balancerModal.balancer.fallbackTag" clearable
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in [ '', ...balancerModal.outboundTags]" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -48,7 +54,8 @@
|
||||
balancer: {
|
||||
tag: '',
|
||||
strategy: 'random',
|
||||
selector: []
|
||||
selector: [],
|
||||
fallbackTag: ''
|
||||
},
|
||||
outboundTags: [],
|
||||
balancerTags:[],
|
||||
@@ -71,7 +78,8 @@
|
||||
balancerModal.balancer = {
|
||||
tag: '',
|
||||
strategy: 'random',
|
||||
selector: []
|
||||
selector: [],
|
||||
fallbackTag: ''
|
||||
};
|
||||
}
|
||||
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
||||
|
||||
@@ -252,46 +252,55 @@ func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string)
|
||||
|
||||
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
|
||||
jsonIps, err := json.Marshal(ips)
|
||||
j.checkError(err)
|
||||
if err != nil {
|
||||
logger.Error("failed to marshal IPs to JSON:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
inboundClientIps.ClientEmail = clientEmail
|
||||
inboundClientIps.Ips = string(jsonIps)
|
||||
|
||||
// check inbound limitation
|
||||
// Fetch inbound settings by client email
|
||||
inbound, err := j.getInboundByEmail(clientEmail)
|
||||
j.checkError(err)
|
||||
|
||||
if inbound.Settings == "" {
|
||||
logger.Debug("wrong data ", inbound)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if inbound.Settings == "" {
|
||||
logger.Debug("wrong data:", inbound)
|
||||
return false
|
||||
}
|
||||
|
||||
// Unmarshal settings to get client limits
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
clients := settings["clients"]
|
||||
shouldCleanLog := false
|
||||
j.disAllowedIps = []string{}
|
||||
|
||||
// create iplimit log file channel
|
||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
// Open log file for IP limits
|
||||
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
||||
logger.Errorf("failed to open IP limit log file: %s", err)
|
||||
return false
|
||||
}
|
||||
defer logIpFile.Close()
|
||||
log.SetOutput(logIpFile)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
|
||||
// Check client IP limits
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
limitIp := client.LimitIP
|
||||
|
||||
if limitIp != 0 {
|
||||
if limitIp > 0 && inbound.Enable {
|
||||
shouldCleanLog = true
|
||||
|
||||
if limitIp < len(ips) && inbound.Enable {
|
||||
if limitIp < len(ips) {
|
||||
j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
|
||||
for i := limitIp; i < len(ips); i++ {
|
||||
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
||||
logger.Debugf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,12 +310,15 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
|
||||
sort.Strings(j.disAllowedIps)
|
||||
|
||||
if len(j.disAllowedIps) > 0 {
|
||||
logger.Debug("disAllowedIps ", j.disAllowedIps)
|
||||
logger.Debug("disAllowedIps:", j.disAllowedIps)
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
err = db.Save(inboundClientIps).Error
|
||||
j.checkError(err)
|
||||
if err != nil {
|
||||
logger.Error("failed to save inboundClientIps:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return shouldCleanLog
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@ func (j *XrayTrafficJob) Run() {
|
||||
if !j.xrayService.IsXrayRunning() {
|
||||
return
|
||||
}
|
||||
|
||||
traffics, clientTraffics, err := j.xrayService.GetXrayTraffic()
|
||||
if err != nil {
|
||||
logger.Warning("get xray traffic failed:", err)
|
||||
return
|
||||
}
|
||||
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||
|
||||
@@ -3,6 +3,7 @@ package middleware
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -14,12 +15,17 @@ func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
|
||||
host = c.GetHeader("X-Real-IP")
|
||||
}
|
||||
if host == "" {
|
||||
host, _, _ := net.SplitHostPort(c.Request.Host)
|
||||
if host != domain {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
host = c.Request.Host
|
||||
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||
host, _, _ = net.SplitHostPort(host)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
if host != domain {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +595,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
return false, err
|
||||
}
|
||||
|
||||
inerfaceClients := settings["clients"].([]interface{})
|
||||
interfaceClients := settings["clients"].([]interface{})
|
||||
|
||||
oldInbound, err := s.GetInbound(data.Id)
|
||||
if err != nil {
|
||||
@@ -650,7 +650,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
return false, err
|
||||
}
|
||||
settingsClients := oldSettings["clients"].([]interface{})
|
||||
settingsClients[clientIndex] = inerfaceClients[0]
|
||||
settingsClients[clientIndex] = interfaceClients[0]
|
||||
oldSettings["clients"] = settingsClients
|
||||
|
||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||
@@ -1134,7 +1134,6 @@ func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
|
||||
}
|
||||
|
||||
func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error {
|
||||
logger.Warning(email)
|
||||
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error
|
||||
}
|
||||
|
||||
@@ -1143,7 +1142,7 @@ func (s *InboundService) GetClientInboundByTrafficID(trafficId int) (traffic *xr
|
||||
var traffics []*xray.ClientTraffic
|
||||
err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
logger.Warningf("Error retrieving ClientTraffic with trafficId %d: %v", trafficId, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(traffics) > 0 {
|
||||
@@ -1158,7 +1157,7 @@ func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.Cl
|
||||
var traffics []*xray.ClientTraffic
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(traffics) > 0 {
|
||||
@@ -1699,15 +1698,20 @@ func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
|
||||
|
||||
// Retrieve inbounds where settings contain the given tgId
|
||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", fmt.Sprintf(`%%"tgId": %d%%`, tgId)).Find(&inbounds).Error
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var emails []string
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
logger.Error("Unable to get clients from inbound")
|
||||
logger.Errorf("Error retrieving clients for inbound %d: %v", inbound.Id, err)
|
||||
continue
|
||||
}
|
||||
for _, client := range clients {
|
||||
if client.TgID == tgId {
|
||||
@@ -1715,15 +1719,19 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var traffics []*xray.ClientTraffic
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
logger.Warning(err)
|
||||
return nil, err
|
||||
logger.Warning("No ClientTraffic records found for emails:", emails)
|
||||
return nil, nil
|
||||
}
|
||||
logger.Errorf("Error retrieving ClientTraffic for emails %v: %v", emails, err)
|
||||
return nil, err
|
||||
}
|
||||
return traffics, err
|
||||
|
||||
return traffics, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.ClientTraffic, err error) {
|
||||
@@ -1732,7 +1740,7 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
|
||||
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
|
||||
return nil, err
|
||||
}
|
||||
if len(traffics) > 0 {
|
||||
@@ -1747,38 +1755,51 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
|
||||
inbound := &model.Inbound{}
|
||||
traffic = &xray.ClientTraffic{}
|
||||
|
||||
err = db.Model(model.Inbound{}).Where("settings like ?", "%\""+query+"\"%").First(inbound).Error
|
||||
// Search for inbound settings that contain the query
|
||||
err = db.Model(model.Inbound{}).Where("settings LIKE ?", "%\""+query+"\"%").First(inbound).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
logger.Warning(err)
|
||||
logger.Warningf("Inbound settings containing query %s not found: %v", query, err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Errorf("Error searching for inbound settings with query %s: %v", query, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
traffic.InboundId = inbound.Id
|
||||
|
||||
// get settings clients
|
||||
// Unmarshal settings to get clients
|
||||
settings := map[string][]model.Client{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||
logger.Errorf("Error unmarshalling inbound settings for inbound ID %d: %v", inbound.Id, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clients := settings["clients"]
|
||||
for _, client := range clients {
|
||||
if client.ID == query && client.Email != "" {
|
||||
traffic.Email = client.Email
|
||||
break
|
||||
}
|
||||
if client.Password == query && client.Email != "" {
|
||||
if (client.ID == query || client.Password == query) && client.Email != "" {
|
||||
traffic.Email = client.Email
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if traffic.Email == "" {
|
||||
return nil, err
|
||||
logger.Warningf("No client found with query %s in inbound ID %d", query, inbound.Id)
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
|
||||
// Retrieve ClientTraffic based on the found email
|
||||
err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
logger.Warningf("ClientTraffic for email %s not found: %v", traffic.Email, err)
|
||||
return nil, err
|
||||
}
|
||||
logger.Errorf("Error retrieving ClientTraffic for email %s: %v", traffic.Email, err)
|
||||
return nil, err
|
||||
}
|
||||
return traffic, err
|
||||
|
||||
return traffic, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
|
||||
@@ -1948,6 +1969,6 @@ func (s *InboundService) MigrateDB() {
|
||||
s.MigrationRemoveOrphanedTraffics()
|
||||
}
|
||||
|
||||
func (s *InboundService) GetOnlineClinets() []string {
|
||||
func (s *InboundService) GetOnlineClients() []string {
|
||||
return p.GetOnlineClients()
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, erro
|
||||
|
||||
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
logger.Warning("Error retrieving OutboundTraffics: ", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ func (s *PanelService) RestartPanel(delay time.Duration) error {
|
||||
time.Sleep(delay)
|
||||
err := p.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
logger.Error("send signal SIGHUP failed:", err)
|
||||
logger.Error("failed to send SIGHUP signal:", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
|
||||
@@ -312,6 +312,16 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
arch = "64"
|
||||
case "arm64":
|
||||
arch = "arm64-v8a"
|
||||
case "armv7":
|
||||
arch = "arm32-v7a"
|
||||
case "armv6":
|
||||
arch = "arm32-v6"
|
||||
case "armv5":
|
||||
arch = "arm32-v5"
|
||||
case "386":
|
||||
arch = "32"
|
||||
case "s390x":
|
||||
arch = "s390x"
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||
|
||||
@@ -269,11 +269,11 @@ func (s *SettingService) SetTgBotChatId(chatIds string) error {
|
||||
return s.setString("tgBotChatId", chatIds)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetTgbotenabled() (bool, error) {
|
||||
func (s *SettingService) GetTgbotEnabled() (bool, error) {
|
||||
return s.getBool("tgBotEnable")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetTgbotenabled(value bool) error {
|
||||
func (s *SettingService) SetTgbotEnabled(value bool) error {
|
||||
return s.setBool("tgBotEnable", value)
|
||||
}
|
||||
|
||||
@@ -524,7 +524,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
"pageSize": func() (interface{}, error) { return s.GetPageSize() },
|
||||
"defaultCert": func() (interface{}, error) { return s.GetCertFile() },
|
||||
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() },
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotEnabled() },
|
||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||
|
||||
@@ -64,52 +64,59 @@ func (t *Tgbot) GetHashStorage() *global.HashStorage {
|
||||
}
|
||||
|
||||
func (t *Tgbot) Start(i18nFS embed.FS) error {
|
||||
// Initialize localizer
|
||||
err := locale.InitLocalizer(i18nFS, &t.settingService)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// init hash storage => store callback queries
|
||||
// Initialize hash storage to store callback queries
|
||||
hashStorage = global.NewHashStorage(20 * time.Minute)
|
||||
|
||||
t.SetHostname()
|
||||
tgBottoken, err := t.settingService.GetTgBotToken()
|
||||
if err != nil || tgBottoken == "" {
|
||||
logger.Warning("Get TgBotToken failed:", err)
|
||||
|
||||
// Get Telegram bot token
|
||||
tgBotToken, err := t.settingService.GetTgBotToken()
|
||||
if err != nil || tgBotToken == "" {
|
||||
logger.Warning("Failed to get Telegram bot token:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
tgBotid, err := t.settingService.GetTgBotChatId()
|
||||
// Get Telegram bot chat ID(s)
|
||||
tgBotID, err := t.settingService.GetTgBotChatId()
|
||||
if err != nil {
|
||||
logger.Warning("Get GetTgBotChatId failed:", err)
|
||||
logger.Warning("Failed to get Telegram bot chat ID:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if tgBotid != "" {
|
||||
for _, adminId := range strings.Split(tgBotid, ",") {
|
||||
id, err := strconv.Atoi(adminId)
|
||||
// Parse admin IDs from comma-separated string
|
||||
if tgBotID != "" {
|
||||
for _, adminID := range strings.Split(tgBotID, ",") {
|
||||
id, err := strconv.Atoi(adminID)
|
||||
if err != nil {
|
||||
logger.Warning("Failed to get IDs from GetTgBotChatId:", err)
|
||||
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
|
||||
return err
|
||||
}
|
||||
adminIds = append(adminIds, int64(id))
|
||||
}
|
||||
}
|
||||
|
||||
// Get Telegram bot proxy URL
|
||||
tgBotProxy, err := t.settingService.GetTgBotProxy()
|
||||
if err != nil {
|
||||
logger.Warning("Failed to get ProxyUrl:", err)
|
||||
logger.Warning("Failed to get Telegram bot proxy URL:", err)
|
||||
}
|
||||
|
||||
bot, err = t.NewBot(tgBottoken, tgBotProxy)
|
||||
// Create new Telegram bot instance
|
||||
bot, err = t.NewBot(tgBotToken, tgBotProxy)
|
||||
if err != nil {
|
||||
fmt.Println("Get tgbot's api error:", err)
|
||||
logger.Error("Failed to initialize Telegram bot API:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// listen for TG bot income messages
|
||||
// Start receiving Telegram bot messages
|
||||
if !isRunning {
|
||||
logger.Info("Starting Telegram receiver ...")
|
||||
logger.Info("Telegram bot receiver started")
|
||||
go t.OnReceive()
|
||||
isRunning = true
|
||||
}
|
||||
@@ -201,7 +208,7 @@ func (t *Tgbot) OnReceive() {
|
||||
}, th.AnyCommand())
|
||||
|
||||
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) {
|
||||
t.asnwerCallback(&query, checkAdmin(query.From.ID))
|
||||
t.answerCallback(&query, checkAdmin(query.From.ID))
|
||||
}, th.AnyCallbackQueryWithMessage())
|
||||
|
||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
||||
@@ -286,7 +293,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
||||
func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
||||
chatId := callbackQuery.Message.GetChat().ID
|
||||
|
||||
if isAdmin {
|
||||
@@ -964,7 +971,7 @@ func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
|
||||
return info
|
||||
}
|
||||
|
||||
// Send server usage without an inline keyborad
|
||||
// Send server usage without an inline keyboard
|
||||
func (t *Tgbot) sendServerUsage() string {
|
||||
info := t.prepareServerUsageInfo()
|
||||
return info
|
||||
@@ -1019,7 +1026,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
|
||||
return info
|
||||
}
|
||||
|
||||
func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status LoginStatus) {
|
||||
func (t *Tgbot) UserLoginNotify(username string, password string, ip string, time string, status LoginStatus) {
|
||||
if !t.IsRunning() {
|
||||
return
|
||||
}
|
||||
@@ -1037,11 +1044,12 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
|
||||
msg := ""
|
||||
if status == LoginSuccess {
|
||||
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
} else if status == LoginFail {
|
||||
msg += t.I18nBot("tgbot.messages.loginFailed")
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
|
||||
}
|
||||
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
msg += t.I18nBot("tgbot.messages.username", "Username=="+username)
|
||||
msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip)
|
||||
msg += t.I18nBot("tgbot.messages.time", "Time=="+time)
|
||||
@@ -1051,14 +1059,14 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
|
||||
func (t *Tgbot) getInboundUsages() string {
|
||||
info := ""
|
||||
// get traffic
|
||||
inbouds, err := t.inboundService.GetAllInbounds()
|
||||
inbounds, err := t.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
logger.Warning("GetAllInbounds run failed:", err)
|
||||
info += t.I18nBot("tgbot.answers.getInboundsFailed")
|
||||
} else {
|
||||
// NOTE:If there no any sessions here,need to notify here
|
||||
// TODO:Sub-node push, automatic conversion format
|
||||
for _, inbound := range inbouds {
|
||||
for _, inbound := range inbounds {
|
||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||
@@ -1331,20 +1339,20 @@ func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
||||
}
|
||||
|
||||
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||
inbouds, err := t.inboundService.SearchInbounds(remark)
|
||||
inbounds, err := t.inboundService.SearchInbounds(remark)
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
msg := t.I18nBot("tgbot.wentWrong")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
if len(inbouds) == 0 {
|
||||
if len(inbounds) == 0 {
|
||||
msg := t.I18nBot("tgbot.noInbounds")
|
||||
t.SendMsgToTgbot(chatId, msg)
|
||||
return
|
||||
}
|
||||
|
||||
for _, inbound := range inbouds {
|
||||
for _, inbound := range inbounds {
|
||||
info := ""
|
||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||
|
||||
162
web/service/warp.go
Normal file
162
web/service/warp.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"x-ui/logger"
|
||||
)
|
||||
|
||||
type WarpService struct {
|
||||
SettingService
|
||||
}
|
||||
|
||||
func (s *WarpService) GetWarpData() (string, error) {
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return warp, nil
|
||||
}
|
||||
|
||||
func (s *WarpService) DelWarpData() error {
|
||||
err := s.SettingService.SetWarp("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WarpService) GetWarpConfig() (string, error) {
|
||||
var warpData map[string]string
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal([]byte(warp), &warpData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error) {
|
||||
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
||||
hostName, _ := os.Hostname()
|
||||
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
|
||||
|
||||
url := "https://api.cloudflareclient.com/v0a2158/reg"
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("CF-Client-Version", "a-7.21-0721")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var rspData map[string]interface{}
|
||||
err = json.Unmarshal(buffer.Bytes(), &rspData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deviceId := rspData["id"].(string)
|
||||
token := rspData["token"].(string)
|
||||
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
|
||||
if !ok {
|
||||
logger.Debug("Error accessing license value.")
|
||||
return "", err
|
||||
}
|
||||
|
||||
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
|
||||
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
|
||||
|
||||
s.SettingService.SetWarp(warpData)
|
||||
|
||||
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
var warpData map[string]string
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal([]byte(warp), &warpData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
|
||||
data := fmt.Sprintf(`{"license": "%s"}`, license)
|
||||
|
||||
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
warpData["license_key"] = license
|
||||
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.SettingService.SetWarp(string(newWarpData))
|
||||
println(string(newWarpData))
|
||||
|
||||
return string(newWarpData), nil
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
if !clientTraffic.Enable {
|
||||
clients = RemoveIndex(clients, index-indexDecrease)
|
||||
indexDecrease++
|
||||
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
|
||||
logger.Infof("Remove Inbound User %s due to expiration or traffic limit", c["email"])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,11 +165,20 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
|
||||
func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
|
||||
if !s.IsXrayRunning() {
|
||||
return nil, nil, errors.New("xray is not running")
|
||||
err := errors.New("xray is not running")
|
||||
logger.Debug("Attempted to fetch Xray traffic, but Xray is not running:", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
s.xrayAPI.Init(p.GetAPIPort())
|
||||
apiPort := p.GetAPIPort()
|
||||
s.xrayAPI.Init(apiPort)
|
||||
defer s.xrayAPI.Close()
|
||||
return s.xrayAPI.GetTraffic(true)
|
||||
|
||||
traffic, clientTraffic, err := s.xrayAPI.GetTraffic(true)
|
||||
if err != nil {
|
||||
logger.Debug("Failed to fetch Xray traffic:", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return traffic, clientTraffic, nil
|
||||
}
|
||||
|
||||
func (s *XrayService) RestartXray(isForce bool) error {
|
||||
@@ -202,7 +211,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
||||
func (s *XrayService) StopXray() error {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
logger.Debug("stop xray")
|
||||
logger.Debug("Attempting to stop Xray...")
|
||||
if s.IsXrayRunning() {
|
||||
return p.Stop()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"x-ui/util/common"
|
||||
"x-ui/xray"
|
||||
@@ -32,142 +27,3 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *XraySettingService) GetWarpData() (string, error) {
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return warp, nil
|
||||
}
|
||||
|
||||
func (s *XraySettingService) GetWarpConfig() (string, error) {
|
||||
var warpData map[string]string
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal([]byte(warp), &warpData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string, error) {
|
||||
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
||||
hostName, _ := os.Hostname()
|
||||
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
|
||||
|
||||
url := "https://api.cloudflareclient.com/v0a2158/reg"
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Add("CF-Client-Version", "a-7.21-0721")
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var rspData map[string]interface{}
|
||||
err = json.Unmarshal(buffer.Bytes(), &rspData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deviceId := rspData["id"].(string)
|
||||
token := rspData["token"].(string)
|
||||
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
|
||||
if !ok {
|
||||
fmt.Println("Error accessing license value.")
|
||||
return "", err
|
||||
}
|
||||
|
||||
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
|
||||
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
|
||||
|
||||
s.SettingService.SetWarp(warpData)
|
||||
|
||||
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *XraySettingService) SetWarpLicence(license string) (string, error) {
|
||||
var warpData map[string]string
|
||||
warp, err := s.SettingService.GetWarp()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = json.Unmarshal([]byte(warp), &warpData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
|
||||
data := fmt.Sprintf(`{"license": "%s"}`, license)
|
||||
|
||||
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
warpData["license_key"] = license
|
||||
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s.SettingService.SetWarp(string(newWarpData))
|
||||
println(string(newWarpData))
|
||||
|
||||
return string(newWarpData), nil
|
||||
}
|
||||
|
||||
@@ -9,9 +9,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
loginUser = "LOGIN_USER"
|
||||
)
|
||||
const loginUser = "LOGIN_USER"
|
||||
|
||||
func init() {
|
||||
gob.Register(model.User{})
|
||||
@@ -19,6 +17,10 @@ func init() {
|
||||
|
||||
func SetLoginUser(c *gin.Context, user *model.User) error {
|
||||
s := sessions.Default(c)
|
||||
s.Options(sessions.Options{
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
})
|
||||
s.Set(loginUser, user)
|
||||
return s.Save()
|
||||
}
|
||||
@@ -34,24 +36,28 @@ func SetMaxAge(c *gin.Context, maxAge int) error {
|
||||
|
||||
func GetLoginUser(c *gin.Context) *model.User {
|
||||
s := sessions.Default(c)
|
||||
obj := s.Get(loginUser)
|
||||
if obj == nil {
|
||||
return nil
|
||||
if obj := s.Get(loginUser); obj != nil {
|
||||
if user, ok := obj.(model.User); ok {
|
||||
return &user
|
||||
}
|
||||
}
|
||||
user := obj.(model.User)
|
||||
return &user
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsLogin(c *gin.Context) bool {
|
||||
return GetLoginUser(c) != nil
|
||||
}
|
||||
|
||||
func ClearSession(c *gin.Context) {
|
||||
func ClearSession(c *gin.Context) error {
|
||||
s := sessions.Default(c)
|
||||
s.Clear()
|
||||
s.Options(sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
})
|
||||
s.Save()
|
||||
if err := s.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.SetCookie("3x-ui", "", -1, "/", "", false, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"monitor" = "Listen IP"
|
||||
"certificate" = "Digital Certificate"
|
||||
"fail" = " Failed"
|
||||
"success" = " Successful"
|
||||
"success" = " Successfully"
|
||||
"getVersion" = "Get Version"
|
||||
"install" = "Install"
|
||||
"clients" = "Clients"
|
||||
@@ -78,7 +78,7 @@
|
||||
"invalidFormData" = "The Input data format is invalid."
|
||||
"emptyUsername" = "Username is required"
|
||||
"emptyPassword" = "Password is required"
|
||||
"wrongUsernameOrPassword" = "Invalid username or password."
|
||||
"wrongUsernameOrPassword" = "Invalid username or password or secret."
|
||||
"successLogin" = "Login"
|
||||
|
||||
[pages.index]
|
||||
@@ -544,7 +544,7 @@
|
||||
"selectUserFailed" = "❌ Error in user selection!"
|
||||
"userSaved" = "✅ Telegram User saved."
|
||||
"loginSuccess" = "✅ Logged in to the panel successfully.\r\n"
|
||||
"loginFailed" = "❗️ Log in to the panel failed.\r\n"
|
||||
"loginFailed" = "❗️Login attempt to the panel failed.\r\n"
|
||||
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n"
|
||||
"username" = "👤 Username: {{ .Username }}\r\n"
|
||||
"password" = "👤 Password: {{ .Password }}\r\n"
|
||||
"time" = "⏰ Time: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Port: {{ .Port }}\r\n"
|
||||
|
||||
@@ -560,6 +560,7 @@
|
||||
"traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Estado de Xray: {{ .State }}\r\n"
|
||||
"username" = "👤 Nombre de usuario: {{ .Username }}\r\n"
|
||||
"password" = "👤 Contraseña: {{ .Password }}\r\n"
|
||||
"time" = "⏰ Hora: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Puerto: {{ .Port }}\r\n"
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ وضعیتایکسری: {{ .State }}\r\n"
|
||||
"username" = "👤 نامکاربری: {{ .Username }}\r\n"
|
||||
"password" = "👤 رمز عبور: {{ .Password }}\r\n"
|
||||
"time" = "⏰ زمان: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 نامورودی: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 پورت: {{ .Port }}\r\n"
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Status: {{ .State }}\r\n"
|
||||
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
|
||||
"password" = "👤 Kata Sandi: {{ .Password }}\r\n"
|
||||
"time" = "⏰ Waktu: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Port: {{ .Port }}\r\n"
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Состояние Xray: {{ .State }}\r\n"
|
||||
"username" = "👤 Имя пользователя: {{ .Username }}\r\n"
|
||||
"password" = "👤 Пароль: {{ .Password }}\r\n"
|
||||
"time" = "⏰ Время: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Порт: {{ .Port }}\r\n"
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Статус: {{ .State }}\r\n"
|
||||
"username" = "👤 Ім'я користувача: {{ .Username }}\r\n"
|
||||
"password" = "👤 Пароль: {{ .Password }}\r\n"
|
||||
"time" = "⏰ Час: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Порт: {{ .Port }}\r\n"
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Trạng thái Xray: {{ .State }}\r\n"
|
||||
"username" = "👤 Tên người dùng: {{ .Username }}\r\n"
|
||||
"password" = "👤 Mật khẩu: {{ .Password }}\r\n"
|
||||
"time" = "⏰ Thời gian: {{ .Time }}\r\n"
|
||||
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||
"port" = "🔌 Cổng: {{ .Port }}\r\n"
|
||||
|
||||
@@ -562,6 +562,7 @@
|
||||
"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||
"xrayStatus" = "ℹ️ Xray 状态:{{ .State }}\r\n"
|
||||
"username" = "👤 用户名:{{ .Username }}\r\n"
|
||||
"password" = "👤 密码: {{ .Password }}\r\n"
|
||||
"time" = "⏰ 时间:{{ .Time }}\r\n"
|
||||
"inbound" = "📍 入站:{{ .Remark }}\r\n"
|
||||
"port" = "🔌 端口:{{ .Port }}\r\n"
|
||||
|
||||
14
web/web.go
14
web/web.go
@@ -180,7 +180,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
assetsBasePath := basePath + "assets/"
|
||||
|
||||
store := cookie.NewStore(secret)
|
||||
engine.Use(sessions.Sessions("session", store))
|
||||
engine.Use(sessions.Sessions("3x-ui", store))
|
||||
engine.Use(func(c *gin.Context) {
|
||||
c.Set("base_path", basePath)
|
||||
})
|
||||
@@ -268,7 +268,7 @@ func (s *Server) startTask() {
|
||||
|
||||
// Make a traffic condition every day, 8:30
|
||||
var entry cron.EntryID
|
||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
|
||||
if (err == nil) && (isTgbotenabled) {
|
||||
runtime, err := s.settingService.GetTgbotRuntime()
|
||||
if err != nil || runtime == "" {
|
||||
@@ -344,13 +344,13 @@ func (s *Server) Start() (err error) {
|
||||
}
|
||||
listener = network.NewAutoHttpsListener(listener)
|
||||
listener = tls.NewListener(listener, c)
|
||||
logger.Info("web server run https on", listener.Addr())
|
||||
logger.Info("Web server running HTTPS on", listener.Addr())
|
||||
} else {
|
||||
logger.Error("error in loading certificates: ", err)
|
||||
logger.Info("web server run http on", listener.Addr())
|
||||
logger.Error("Error loading certificates:", err)
|
||||
logger.Info("Web server running HTTP on", listener.Addr())
|
||||
}
|
||||
} else {
|
||||
logger.Info("web server run http on", listener.Addr())
|
||||
logger.Info("Web server running HTTP on", listener.Addr())
|
||||
}
|
||||
s.listener = listener
|
||||
|
||||
@@ -364,7 +364,7 @@ func (s *Server) Start() (err error) {
|
||||
|
||||
s.startTask()
|
||||
|
||||
isTgbotenabled, err := s.settingService.GetTgbotenabled()
|
||||
isTgbotenabled, err := s.settingService.GetTgbotEnabled()
|
||||
if (err == nil) && (isTgbotenabled) {
|
||||
tgBot := s.tgbotService.NewTgbot()
|
||||
tgBot.Start(i18nFS)
|
||||
|
||||
5
x-ui.sh
5
x-ui.sh
@@ -262,10 +262,9 @@ reset_webbasepath() {
|
||||
echo -e "${yellow}Resetting Web Base Path${plain}"
|
||||
|
||||
# Prompt user to set a new web base path
|
||||
read -rp "Please set the new web base path [default is a random path]: " config_webBasePath
|
||||
read -rp "Please set the new web base path [press 'y' for a random path]: " config_webBasePath
|
||||
|
||||
# If user input is empty, generate a random path
|
||||
if [[ -z $config_webBasePath ]]; then
|
||||
if [[ $config_webBasePath == "y" ]]; then
|
||||
config_webBasePath=$(gen_random_string 10)
|
||||
fi
|
||||
|
||||
|
||||
174
xray/api.go
174
xray/api.go
@@ -31,24 +31,27 @@ type XrayAPI struct {
|
||||
isConnected bool
|
||||
}
|
||||
|
||||
func (x *XrayAPI) Init(apiPort int) (err error) {
|
||||
if apiPort == 0 {
|
||||
return common.NewError("xray api port wrong:", apiPort)
|
||||
func (x *XrayAPI) Init(apiPort int) error {
|
||||
if apiPort <= 0 {
|
||||
return fmt.Errorf("invalid Xray API port: %d", apiPort)
|
||||
}
|
||||
conn, err := grpc.NewClient(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
||||
addr := fmt.Sprintf("127.0.0.1:%d", apiPort)
|
||||
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to connect to Xray API: %w", err)
|
||||
}
|
||||
|
||||
x.grpcClient = conn
|
||||
x.isConnected = true
|
||||
|
||||
hsClient := command.NewHandlerServiceClient(x.grpcClient)
|
||||
ssClient := statsService.NewStatsServiceClient(x.grpcClient)
|
||||
hsClient := command.NewHandlerServiceClient(conn)
|
||||
ssClient := statsService.NewStatsServiceClient(conn)
|
||||
|
||||
x.HandlerServiceClient = &hsClient
|
||||
x.StatsServiceClient = &ssClient
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XrayAPI) Close() {
|
||||
@@ -149,94 +152,101 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
|
||||
return err
|
||||
}
|
||||
|
||||
func (x *XrayAPI) RemoveUser(inboundTag string, email string) error {
|
||||
client := *x.HandlerServiceClient
|
||||
_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{
|
||||
Tag: inboundTag,
|
||||
Operation: serial.ToTypedMessage(&command.RemoveUserOperation{
|
||||
Email: email,
|
||||
}),
|
||||
})
|
||||
return err
|
||||
func (x *XrayAPI) RemoveUser(inboundTag, email string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
op := &command.RemoveUserOperation{Email: email}
|
||||
req := &command.AlterInboundRequest{
|
||||
Tag: inboundTag,
|
||||
Operation: serial.ToTypedMessage(op),
|
||||
}
|
||||
|
||||
_, err := (*x.HandlerServiceClient).AlterInbound(ctx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove user: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
|
||||
if x.grpcClient == nil {
|
||||
return nil, nil, common.NewError("xray api is not initialized")
|
||||
}
|
||||
trafficRegex := regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||
ClientTrafficRegex := regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
|
||||
|
||||
client := *x.StatsServiceClient
|
||||
trafficRegex := regexp.MustCompile(`(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)`)
|
||||
clientTrafficRegex := regexp.MustCompile(`user>>>([^>]+)>>>traffic>>>(downlink|uplink)`)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
request := &statsService.QueryStatsRequest{
|
||||
Reset_: reset,
|
||||
}
|
||||
resp, err := client.QueryStats(ctx, request)
|
||||
|
||||
resp, err := (*x.StatsServiceClient).QueryStats(ctx, &statsService.QueryStatsRequest{Reset_: reset})
|
||||
if err != nil {
|
||||
logger.Debug("Failed to query Xray stats:", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
tagTrafficMap := map[string]*Traffic{}
|
||||
emailTrafficMap := map[string]*ClientTraffic{}
|
||||
|
||||
clientTraffics := make([]*ClientTraffic, 0)
|
||||
traffics := make([]*Traffic, 0)
|
||||
tagTrafficMap := make(map[string]*Traffic)
|
||||
emailTrafficMap := make(map[string]*ClientTraffic)
|
||||
|
||||
for _, stat := range resp.GetStat() {
|
||||
matchs := trafficRegex.FindStringSubmatch(stat.Name)
|
||||
if len(matchs) < 3 {
|
||||
|
||||
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name)
|
||||
if len(matchs) < 3 {
|
||||
continue
|
||||
} else {
|
||||
|
||||
isUser := matchs[1] == "user"
|
||||
email := matchs[2]
|
||||
isDown := matchs[3] == "downlink"
|
||||
if !isUser {
|
||||
continue
|
||||
}
|
||||
traffic, ok := emailTrafficMap[email]
|
||||
if !ok {
|
||||
traffic = &ClientTraffic{
|
||||
Email: email,
|
||||
}
|
||||
emailTrafficMap[email] = traffic
|
||||
clientTraffics = append(clientTraffics, traffic)
|
||||
}
|
||||
if isDown {
|
||||
traffic.Down = stat.Value
|
||||
} else {
|
||||
traffic.Up = stat.Value
|
||||
}
|
||||
|
||||
}
|
||||
continue
|
||||
}
|
||||
isInbound := matchs[1] == "inbound"
|
||||
isOutbound := matchs[1] == "outbound"
|
||||
tag := matchs[2]
|
||||
isDown := matchs[3] == "downlink"
|
||||
if tag == "api" {
|
||||
continue
|
||||
}
|
||||
traffic, ok := tagTrafficMap[tag]
|
||||
if !ok {
|
||||
traffic = &Traffic{
|
||||
IsInbound: isInbound,
|
||||
IsOutbound: isOutbound,
|
||||
Tag: tag,
|
||||
}
|
||||
tagTrafficMap[tag] = traffic
|
||||
traffics = append(traffics, traffic)
|
||||
}
|
||||
if isDown {
|
||||
traffic.Down = stat.Value
|
||||
} else {
|
||||
traffic.Up = stat.Value
|
||||
if matches := trafficRegex.FindStringSubmatch(stat.Name); len(matches) == 4 {
|
||||
processTraffic(matches, stat.Value, tagTrafficMap)
|
||||
} else if matches := clientTrafficRegex.FindStringSubmatch(stat.Name); len(matches) == 3 {
|
||||
processClientTraffic(matches, stat.Value, emailTrafficMap)
|
||||
}
|
||||
}
|
||||
|
||||
return traffics, clientTraffics, nil
|
||||
return mapToSlice(tagTrafficMap), mapToSlice(emailTrafficMap), nil
|
||||
}
|
||||
|
||||
func processTraffic(matches []string, value int64, trafficMap map[string]*Traffic) {
|
||||
isInbound := matches[1] == "inbound"
|
||||
tag := matches[2]
|
||||
isDown := matches[3] == "downlink"
|
||||
|
||||
if tag == "api" {
|
||||
return
|
||||
}
|
||||
|
||||
traffic, ok := trafficMap[tag]
|
||||
if !ok {
|
||||
traffic = &Traffic{
|
||||
IsInbound: isInbound,
|
||||
IsOutbound: !isInbound,
|
||||
Tag: tag,
|
||||
}
|
||||
trafficMap[tag] = traffic
|
||||
}
|
||||
|
||||
if isDown {
|
||||
traffic.Down = value
|
||||
} else {
|
||||
traffic.Up = value
|
||||
}
|
||||
}
|
||||
|
||||
func processClientTraffic(matches []string, value int64, clientTrafficMap map[string]*ClientTraffic) {
|
||||
email := matches[1]
|
||||
isDown := matches[2] == "downlink"
|
||||
|
||||
traffic, ok := clientTrafficMap[email]
|
||||
if !ok {
|
||||
traffic = &ClientTraffic{Email: email}
|
||||
clientTrafficMap[email] = traffic
|
||||
}
|
||||
|
||||
if isDown {
|
||||
traffic.Down = value
|
||||
} else {
|
||||
traffic.Up = value
|
||||
}
|
||||
}
|
||||
|
||||
func mapToSlice[T any](m map[string]*T) []*T {
|
||||
result := make([]*T, 0, len(m))
|
||||
for _, v := range m {
|
||||
result = append(result, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -60,14 +60,14 @@ func GetAccessPersistentPrevLogPath() string {
|
||||
func GetAccessLogPath() (string, error) {
|
||||
config, err := os.ReadFile(GetConfigPath())
|
||||
if err != nil {
|
||||
logger.Warningf("Something went wrong: %s", err)
|
||||
logger.Warningf("Failed to read configuration file: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
jsonConfig := map[string]interface{}{}
|
||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
||||
if err != nil {
|
||||
logger.Warningf("Something went wrong: %s", err)
|
||||
logger.Warningf("Failed to parse JSON configuration: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ func (p *process) Start() (err error) {
|
||||
|
||||
err = os.MkdirAll(config.GetLogFolder(), 0o770)
|
||||
if err != nil {
|
||||
logger.Warningf("Something went wrong: %s", err)
|
||||
logger.Warningf("Failed to create log folder: %s", err)
|
||||
}
|
||||
|
||||
configPath := GetConfigPath()
|
||||
@@ -224,7 +224,7 @@ func (p *process) Start() (err error) {
|
||||
go func() {
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logger.Error("Failure in running xray-core: ", err)
|
||||
logger.Error("Failure in running xray-core:", err)
|
||||
p.exitErr = err
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user