Compare commits

..

33 Commits

Author SHA1 Message Date
mhsanaei
7417c52c8f fixed - sub show time when "Start After First Use" 2024-07-17 16:39:53 +02:00
mhsanaei
4d4eef8d8a Xray Core v1.8.19 2024-07-17 16:21:03 +02:00
mhsanaei
9de8b4acaf v2.3.9 2024-07-17 13:51:11 +02:00
mhsanaei
f21c293693 Xray core v1.8.18 2024-07-16 01:33:24 +02:00
mhsanaei
56159d9c52 Upload files to Artifacts 2024-07-15 11:09:24 +02:00
mhsanaei
6cbdf64013 update dependencies 2024-07-15 10:28:39 +02:00
mhsanaei
bb9b9100a8 [warp] enhanced + delete option
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-15 00:21:54 +02:00
mhsanaei
816adfc3ea fallback outbound in balancer
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-15 00:09:31 +02:00
mhsanaei
0a95b0c7b2 disable mux for vision flow
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:55:56 +02:00
mhsanaei
d298f4ffbd fix domain validator
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:55:04 +02:00
mhsanaei
315e8af025 fix observatory data
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:44:31 +02:00
mhsanaei
de985263f5 safe login
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
2024-07-14 23:37:43 +02:00
mhsanaei
dfe0bbd371 Refactor database initialization 2024-07-14 01:22:51 +02:00
mhsanaei
60cb328698 default - alpn h3, utls random 2024-07-14 00:03:07 +02:00
mhsanaei
3d7f13225a Refactor HttpUtil and Msg: optimize methods 2024-07-14 00:01:13 +02:00
mhsanaei
76fdfb2ef2 date format - jalalian 2024-07-13 01:38:51 +02:00
mhsanaei
8018023187 Xray core v1.8.17 + GO v1.22.5 2024-07-12 21:42:32 +02:00
mmmray
ea9d5dc2d5 fix wrong splithttp default (#2433)
This default is defined as 1MB, but maxUploadSize is to be specified in
bytes. This confusion could've come from poorly written documentation in
xray, but it has been updated.

in general I wish that panels would not set defaults at all and instead
just omit parameters (in sharelinks, inbounds, ...) that the user didn't
set explicitly. If I want to change the defaults in xray's codebase, it
seems that all the panels will have to update the default too.

I see marzban doing the same kind of things.
2024-07-09 15:24:06 +02:00
mhsanaei
96e43fa195 v2.3.8 2024-07-08 23:48:01 +02:00
mhsanaei
f1500a5d31 improved - message logs 2024-07-08 23:47:49 +02:00
mhsanaei
c9a218d060 axios v1.7.2 2024-07-08 21:03:20 +02:00
mhsanaei
6d18a15c4e Expand arch support in downloadXRay 2024-07-08 18:24:07 +02:00
dependabot[bot]
c0f86d2f38 Bump github.com/mymmrac/telego from 0.30.2 to 0.31.0 (#2432)
Bumps [github.com/mymmrac/telego](https://github.com/mymmrac/telego) from 0.30.2 to 0.31.0.
- [Release notes](https://github.com/mymmrac/telego/releases)
- [Commits](https://github.com/mymmrac/telego/compare/v0.30.2...v0.31.0)

---
updated-dependencies:
- dependency-name: github.com/mymmrac/telego
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 15:48:14 +02:00
mhsanaei
5227fefaeb update dependencies 2024-07-07 12:10:47 +02:00
mhsanaei
7a51d2f2cc Typo fixed 2024-07-07 12:10:24 +02:00
mhsanaei
02ae61fe6b change session name 2024-07-05 14:33:04 +02:00
mhsanaei
24b9e5bfa3 some changes 2024-07-04 15:04:04 +02:00
mhsanaei
9ff7f14b6e unnecessary log 2024-07-04 00:28:37 +02:00
mhsanaei
c3b42b8ea4 typo 2024-07-04 00:17:44 +02:00
mhsanaei
5afb8d85fc Optimize XrayAPI functionality and structure 2024-07-04 00:17:28 +02:00
dependabot[bot]
767ee4ec2b Bump google.golang.org/grpc from 1.64.0 to 1.65.0 (#2431)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.65.0.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.65.0)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 23:55:09 +02:00
mhsanaei
21b64beb96 tgbot - login notify (show password for failed login) 2024-07-03 21:53:45 +02:00
mhsanaei
b84e3ef338 improve bash menu 2024-07-02 00:34:25 +02:00
55 changed files with 937 additions and 730 deletions

View File

@@ -26,7 +26,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: '1.22' go-version-file: go.mod
- name: Install dependencies - name: Install dependencies
run: | run: |
@@ -83,7 +83,7 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # 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 if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip
@@ -126,6 +126,12 @@ jobs:
- name: Package - name: Package
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui 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 - name: Upload files to GH release
uses: svenstaro/upload-release-action@v2 uses: svenstaro/upload-release-action@v2
with: with:

View File

@@ -27,7 +27,7 @@ case $1 in
esac esac
mkdir -p build/bin mkdir -p build/bin
cd 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" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"

View File

@@ -26,10 +26,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Install Custom Version ## Install Custom Version
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.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 ## SSL Certificate

View File

@@ -1 +1 @@
2.3.7 2.3.9

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"io" "io"
"io/fs" "io/fs"
"log"
"os" "os"
"path" "path"
@@ -18,54 +19,51 @@ import (
var db *gorm.DB var db *gorm.DB
var initializers = []func() error{ const (
initUser, defaultUsername = "admin"
initInbound, defaultPassword = "admin"
initOutbound, defaultSecret = ""
initSetting, )
initInboundClientIps,
initClientTraffic, 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 { func initUser() error {
err := db.AutoMigrate(&model.User{}) empty, err := isTableEmpty("users")
if err != nil { if err != nil {
log.Printf("Error checking if users table is empty: %v", err)
return err return err
} }
var count int64 if empty {
err = db.Model(&model.User{}).Count(&count).Error
if err != nil {
return err
}
if count == 0 {
user := &model.User{ user := &model.User{
Username: "admin", Username: defaultUsername,
Password: "admin", Password: defaultPassword,
LoginSecret: "", LoginSecret: defaultSecret,
} }
return db.Create(user).Error return db.Create(user).Error
} }
return nil return nil
} }
func initInbound() error { func isTableEmpty(tableName string) (bool, error) {
return db.AutoMigrate(&model.Inbound{}) var count int64
} err := db.Table(tableName).Count(&count).Error
return count == 0, err
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 InitDB(dbPath string) error { func InitDB(dbPath string) error {
@@ -91,15 +89,27 @@ func InitDB(dbPath string) error {
return err return err
} }
for _, initialize := range initializers { if err := initModels(); err != nil {
if err := initialize(); err != nil { return err
return err }
} if err := initUser(); err != nil {
return err
} }
return nil 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 { func GetDB() *gorm.DB {
return db return db
} }

54
go.mod
View File

@@ -1,25 +1,25 @@
module x-ui module x-ui
go 1.22.4 go 1.22.5
require ( require (
github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/gzip v1.0.1
github.com/gin-contrib/sessions v1.0.1 github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/goccy/go-json v0.10.3 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/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.2.2 github.com/pelletier/go-toml/v2 v2.2.2
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v4 v4.24.6 github.com/shirou/gopsutil/v4 v4.24.6
github.com/valyala/fasthttp v1.55.0 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 go.uber.org/atomic v1.11.0
golang.org/x/text v0.16.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/driver/sqlite v1.5.6
gorm.io/gorm v1.25.10 gorm.io/gorm v1.25.11
) )
require ( require (
@@ -30,7 +30,7 @@ require (
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // 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/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // 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-playground/validator/v10 v10.22.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/btree v1.1.2 // 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/context v1.1.2 // indirect
github.com/gorilla/securecookie 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/gorilla/websocket v1.5.3 // indirect
github.com/grbit/go-json v0.11.0 // indirect github.com/grbit/go-json v0.11.0 // indirect
github.com/jinzhu/inflection v1.0.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/compress v1.17.9 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // 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-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/onsi/ginkgo/v2 v2.19.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/quic-go/quic-go v0.45.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/refraction-networking/utls v1.6.6 // 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/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 v0.4.1 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.8.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // 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/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // 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 github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.24.0 // indirect golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.26.0 // indirect golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.21.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/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/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // 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 google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect

104
go.sum
View File

@@ -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 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= 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/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.2 h1:ckJCCdV7hWkkrMeId3WfEhz+4Gyyf6QPwxi/RHIMZ6I=
github.com/fasthttp/router v1.5.1/go.mod h1:WrmsLo3mrerZP2VEXRV1E8nL8ymJFYCDTr4HmnB8+Zs= 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/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 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= 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/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/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-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-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g=
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= 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= 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/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 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= 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.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= 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 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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= 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/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 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 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-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= 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/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/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.30.2 h1:CqGlqX0hkgz9qMwdA3q+aZtSonqMOKQQrFLn/oUOTaw= github.com/mymmrac/telego v0.31.0 h1:vsN+JCNkh7Z9vfL/2/AHZ2xBsRk2GCMj3zydjCxkgIc=
github.com/mymmrac/telego v0.30.2/go.mod h1:U6cWJBgRCzGt+s0q77x/Dh2+i+u56VTAAYKlMenhuFc= 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/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
@@ -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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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/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/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/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/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig= github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA=
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= 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 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= 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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= 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.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 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/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 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= 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.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/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-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= 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/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 h1:9qqCSYF2pgOU+t+NgJtp7Co5+5mHF/HyKBUckySQL64=
github.com/shirou/gopsutil/v4 v4.24.6/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA= 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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/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.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 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= 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.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 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 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-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.16 h1:PhbpdREAIvDS7xmxR6Sdpkx0h5ugmf6wIoWECWtJ0kE= github.com/xtls/xray-core v1.8.19 h1:mml7smcO2FM5HyyKdqnf5F2BUvi3br2ldrqmeemEFRE=
github.com/xtls/xray-core v1.8.16/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M= 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 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= 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-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-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.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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= 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-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-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= 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-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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 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.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-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-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.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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-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-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 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-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-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.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= 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 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= 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= 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-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-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 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-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
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/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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.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.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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 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 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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= 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= 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 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= 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.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= 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 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=

View File

@@ -130,7 +130,7 @@ gen_random_string() {
# This function will be called when user installed x-ui out of security # This function will be called when user installed x-ui out of security
config_after_install() { config_after_install() {
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}" 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 if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
read -p "Please set up your username: " config_account read -p "Please set up your username: " config_account
echo -e "${yellow}Your username will be: ${config_account}${plain}" 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}Password: ${passwordTemp}${plain}"
echo -e "${green}WebBasePath: ${webBasePathTemp}${plain}" echo -e "${green}WebBasePath: ${webBasePathTemp}${plain}"
echo -e "###############################################" 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 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
fi fi
/usr/local/x-ui/x-ui migrate /usr/local/x-ui/x-ui migrate

98
main.go
View File

@@ -21,7 +21,7 @@ import (
) )
func runWebServer() { func runWebServer() {
log.Printf("%v %v", config.GetName(), config.GetVersion()) log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
switch config.GetLogLevel() { switch config.GetLogLevel() {
case config.Debug: case config.Debug:
@@ -35,31 +35,29 @@ func runWebServer() {
case config.Error: case config.Error:
logger.InitLogger(logging.ERROR) logger.InitLogger(logging.ERROR)
default: default:
log.Fatal("unknown log level:", config.GetLogLevel()) log.Fatalf("Unknown log level: %v", config.GetLogLevel())
} }
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
log.Fatal(err) log.Fatalf("Error initializing database: %v", err)
} }
var server *web.Server var server *web.Server
server = web.NewServer() server = web.NewServer()
global.SetWebServer(server) global.SetWebServer(server)
err = server.Start() err = server.Start()
if err != nil { if err != nil {
log.Println(err) log.Fatalf("Error starting web server: %v", err)
return return
} }
var subServer *sub.Server var subServer *sub.Server
subServer = sub.NewServer() subServer = sub.NewServer()
global.SetSubServer(subServer) global.SetSubServer(subServer)
err = subServer.Start() err = subServer.Start()
if err != nil { if err != nil {
log.Println(err) log.Fatalf("Error starting sub server: %v", err)
return return
} }
@@ -71,34 +69,39 @@ func runWebServer() {
switch sig { switch sig {
case syscall.SIGHUP: case syscall.SIGHUP:
logger.Info("Received SIGHUP signal. Restarting servers...")
err := server.Stop() err := server.Stop()
if err != nil { if err != nil {
logger.Warning("stop server err:", err) logger.Warning("Error stopping web server:", err)
} }
err = subServer.Stop() err = subServer.Stop()
if err != nil { if err != nil {
logger.Warning("stop server err:", err) logger.Warning("Error stopping sub server:", err)
} }
server = web.NewServer() server = web.NewServer()
global.SetWebServer(server) global.SetWebServer(server)
err = server.Start() err = server.Start()
if err != nil { if err != nil {
log.Println(err) log.Fatalf("Error restarting web server: %v", err)
return return
} }
log.Println("Web server restarted successfully.")
subServer = sub.NewServer() subServer = sub.NewServer()
global.SetSubServer(subServer) global.SetSubServer(subServer)
err = subServer.Start() err = subServer.Start()
if err != nil { if err != nil {
log.Println(err) log.Fatalf("Error restarting sub server: %v", err)
return return
} }
log.Println("Sub server restarted successfully.")
default: default:
server.Stop() server.Stop()
subServer.Stop() subServer.Stop()
log.Println("Shutting down servers.")
return return
} }
} }
@@ -107,16 +110,16 @@ func runWebServer() {
func resetSetting() { func resetSetting() {
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
fmt.Println(err) fmt.Println("Failed to initialize database:", err)
return return
} }
settingService := service.SettingService{} settingService := service.SettingService{}
err = settingService.ResetSettings() err = settingService.ResetSettings()
if err != nil { if err != nil {
fmt.Println("reset setting failed:", err) fmt.Println("Failed to reset settings:", err)
} else { } else {
fmt.Println("reset setting success") fmt.Println("Settings successfully reset.")
} }
} }
@@ -159,19 +162,19 @@ func showSetting(show bool) {
func updateTgbotEnableSts(status bool) { func updateTgbotEnableSts(status bool) {
settingService := service.SettingService{} settingService := service.SettingService{}
currentTgSts, err := settingService.GetTgbotenabled() currentTgSts, err := settingService.GetTgbotEnabled()
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} }
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status) logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
if currentTgSts != status { if currentTgSts != status {
err := settingService.SetTgbotenabled(status) err := settingService.SetTgbotEnabled(status)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
return return
} else { } 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) { func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
fmt.Println(err) fmt.Println("Error initializing database:", err)
return return
} }
@@ -188,68 +191,65 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
if tgBotToken != "" { if tgBotToken != "" {
err := settingService.SetTgBotToken(tgBotToken) err := settingService.SetTgBotToken(tgBotToken)
if err != nil { if err != nil {
fmt.Println(err) fmt.Printf("Error setting Telegram bot token: %v\n", err)
return return
} else {
logger.Info("updateTgbotSetting tgBotToken success")
} }
logger.Info("Successfully updated Telegram bot token.")
} }
if tgBotRuntime != "" { if tgBotRuntime != "" {
err := settingService.SetTgbotRuntime(tgBotRuntime) err := settingService.SetTgbotRuntime(tgBotRuntime)
if err != nil { if err != nil {
fmt.Println(err) fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
return return
} else {
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
} }
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
} }
if tgBotChatid != "" { if tgBotChatid != "" {
err := settingService.SetTgBotChatId(tgBotChatid) err := settingService.SetTgBotChatId(tgBotChatid)
if err != nil { if err != nil {
fmt.Println(err) fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
return 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) { func updateSetting(port int, username string, password string, webBasePath string) {
err := database.InitDB(config.GetDBPath()) err := database.InitDB(config.GetDBPath())
if err != nil { if err != nil {
fmt.Println(err) fmt.Println("Database initialization failed:", err)
return return
} }
settingService := service.SettingService{} settingService := service.SettingService{}
userService := service.UserService{}
if port > 0 { if port > 0 {
err := settingService.SetPort(port) err := settingService.SetPort(port)
if err != nil { if err != nil {
fmt.Println("set port failed:", err) fmt.Println("Failed to set port:", err)
} else { } else {
fmt.Printf("set port %v success", port) fmt.Printf("Port set successfully: %v\n", port)
} }
} }
if username != "" || password != "" { if username != "" || password != "" {
userService := service.UserService{}
err := userService.UpdateFirstUser(username, password) err := userService.UpdateFirstUser(username, password)
if err != nil { if err != nil {
fmt.Println("set username and password failed:", err) fmt.Println("Failed to update username and password:", err)
} else { } else {
fmt.Println("set username and password success") fmt.Println("Username and password updated successfully")
} }
} }
if webBasePath != "" { if webBasePath != "" {
err := settingService.SetBasePath(webBasePath) err := settingService.SetBasePath(webBasePath)
if err != nil { if err != nil {
fmt.Println("set base URI path failed:", err) fmt.Println("Failed to set base URI path:", err)
} else { } 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 reset bool
var show bool var show bool
var remove_secret bool var remove_secret bool
settingCmd.BoolVar(&reset, "reset", false, "reset all settings") settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
settingCmd.BoolVar(&show, "show", false, "show current settings") settingCmd.BoolVar(&show, "show", false, "Display current settings")
settingCmd.BoolVar(&remove_secret, "remove_secret", false, "remove secret") settingCmd.BoolVar(&remove_secret, "remove_secret", false, "Remove secret key")
settingCmd.IntVar(&port, "port", 0, "set panel port") settingCmd.IntVar(&port, "port", 0, "Set panel port number")
settingCmd.StringVar(&username, "username", "", "set login username") settingCmd.StringVar(&username, "username", "", "Set login username")
settingCmd.StringVar(&password, "password", "", "set login password") settingCmd.StringVar(&password, "password", "", "Set login password")
settingCmd.StringVar(&webBasePath, "webBasePath", "", "set web base path") settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
settingCmd.StringVar(&webCertFile, "webCert", "", "set web public key path") settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "set web private key path") settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token") settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time") settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id") settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify") settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
oldUsage := flag.Usage oldUsage := flag.Usage
flag.Usage = func() { flag.Usage = func() {

View File

@@ -163,13 +163,13 @@ func (s *Server) Start() (err error) {
} }
listener = network.NewAutoHttpsListener(listener) listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c) listener = tls.NewListener(listener, c)
logger.Info("sub server run https on", listener.Addr()) logger.Info("Sub server running HTTPS on", listener.Addr())
} else { } else {
logger.Error("error in loading certificates: ", err) logger.Error("Error loading certificates:", err)
logger.Info("sub server run http on", listener.Addr()) logger.Info("Sub server running HTTP on", listener.Addr())
} }
} else { } else {
logger.Info("sub server run http on", listener.Addr()) logger.Info("Sub server running HTTP on", listener.Addr())
} }
s.listener = listener s.listener = listener

View File

@@ -1023,10 +1023,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
remark = append(remark, fmt.Sprintf("%dM⏳", minutes)) remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
} }
case exp < 0: case exp < 0:
passedSeconds := now - exp days := exp / -86400
days := passedSeconds / 86400 hours := (exp % -86400) / 3600
hours := (passedSeconds % 86400) / 3600 minutes := (exp % -3600) / 60
minutes := (passedSeconds % 3600) / 60
if days > 0 { if days > 0 {
if hours > 0 { if hours > 0 {
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours)) remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))

File diff suppressed because one or more lines are too long

View File

@@ -577,6 +577,10 @@ class Outbound extends CommonClass {
} }
canEnableMux() { 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); return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, Protocols.Socks].includes(this.protocol);
} }

View File

@@ -523,7 +523,7 @@ class HTTPUpgradeStreamSettings extends XrayCommonClass {
} }
class SplitHTTPStreamSettings extends XrayCommonClass { class SplitHTTPStreamSettings extends XrayCommonClass {
constructor(path='/', host='', headers=[] , maxUploadSize= 1, maxConcurrentUploads= 10) { constructor(path='/', host='', headers=[] , maxUploadSize= 1000000, maxConcurrentUploads= 10) {
super(); super();
this.path = path; this.path = path;
this.host = host; this.host = host;
@@ -570,7 +570,7 @@ class TlsStreamSettings extends XrayCommonClass {
disableSystemRoot = false, disableSystemRoot = false,
enableSessionResumption = false, enableSessionResumption = false,
certificates=[new TlsStreamSettings.Cert()], 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()) { settings=new TlsStreamSettings.Settings()) {
super(); super();
this.sni = serverName; this.sni = serverName;
@@ -712,7 +712,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
class XtlsStreamSettings extends XrayCommonClass { class XtlsStreamSettings extends XrayCommonClass {
constructor(serverName='', constructor(serverName='',
certificates=[new XtlsStreamSettings.Cert()], 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()) { settings=new XtlsStreamSettings.Settings()) {
super(); super();
this.sni = serverName; this.sni = serverName;
@@ -892,7 +892,7 @@ class RealityStreamSettings extends XrayCommonClass {
} }
RealityStreamSettings.Settings = class 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(); super();
this.publicKey = publicKey; this.publicKey = publicKey;
this.fingerprint = fingerprint; this.fingerprint = fingerprint;

View File

@@ -128,7 +128,7 @@ Date.prototype.formatDateTime = function (split = ' ') {
}; };
class DateUtil { class DateUtil {
// String string to date object // String to date object
static parseDate(str) { static parseDate(str) {
return new Date(str.replace(/-/g, '/')); return new Date(str.replace(/-/g, '/'));
} }
@@ -143,4 +143,9 @@ class DateUtil {
date.setMinTime(); date.setMinTime();
return date; return date;
} }
static convertToJalalian(date) {
return date && moment.isMoment(date) ? date.format('jYYYY/jMM/jDD HH:mm:ss') : null;
}
} }

View File

@@ -1,73 +1,57 @@
class Msg { class Msg {
constructor(success, msg, obj) { constructor(success = false, msg = "", obj = null) {
this.success = false; this.success = success;
this.msg = ""; this.msg = msg;
this.obj = null; this.obj = obj;
if (success != null) {
this.success = success;
}
if (msg != null) {
this.msg = msg;
}
if (obj != null) {
this.obj = obj;
}
} }
} }
class HttpUtil { class HttpUtil {
static _handleMsg(msg) { static _handleMsg(msg) {
if (!(msg instanceof Msg)) { if (!(msg instanceof Msg) || msg.msg === "") {
return; return;
} }
if (msg.msg === "") { const messageType = msg.success ? 'success' : 'error';
return; Vue.prototype.$message[messageType](msg.msg);
}
if (msg.success) {
Vue.prototype.$message.success(msg.msg);
} else {
Vue.prototype.$message.error(msg.msg);
}
} }
static _respToMsg(resp) { static _respToMsg(resp) {
const data = resp.data; const { data } = resp;
if (data == null) { if (data == null) {
return new Msg(true); 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) { static async get(url, params, options = {}) {
let msg;
try { try {
const resp = await axios.get(url, data, options); const resp = await axios.get(url, { params, ...options });
msg = this._respToMsg(resp); const msg = this._respToMsg(resp);
} catch (e) { this._handleMsg(msg);
msg = new Msg(false, e.toString()); 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) { static async post(url, data, options = {}) {
let msg;
try { try {
const resp = await axios.post(url, data, options); const resp = await axios.post(url, data, options);
msg = this._respToMsg(resp); const msg = this._respToMsg(resp);
} catch (e) { this._handleMsg(msg);
msg = new Msg(false, e.toString()); 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) { static async postWithModal(url, data, modal) {

View File

@@ -232,14 +232,12 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
} }
email := c.Param("email") email := c.Param("email")
needRestart := true needRestart, err := a.inboundService.ResetClientTraffic(id, email)
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
if err != nil { if err != nil {
jsonMsg(c, "Something went wrong!", err) jsonMsg(c, "Something went wrong!", err)
return return
} }
jsonMsg(c, "traffic reseted", nil) jsonMsg(c, "Traffic has been reset", nil)
if needRestart { if needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
@@ -253,7 +251,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
} else { } else {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
jsonMsg(c, "All traffics reseted", nil) jsonMsg(c, "all traffic has been reset", nil)
} }
func (a *InboundController) resetAllClientTraffics(c *gin.Context) { func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
@@ -270,7 +268,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
} else { } else {
a.xrayService.SetToNeedRestart() 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) { func (a *InboundController) importInbound(c *gin.Context) {
@@ -313,9 +311,9 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
jsonMsg(c, "Something went wrong!", err) jsonMsg(c, "Something went wrong!", err)
return return
} }
jsonMsg(c, "All delpeted clients are deleted", nil) jsonMsg(c, "All depleted clients are deleted", nil)
} }
func (a *InboundController) onlines(c *gin.Context) { func (a *InboundController) onlines(c *gin.Context) {
jsonObj(c, a.inboundService.GetOnlineClinets(), nil) jsonObj(c, a.inboundService.GetOnlineClients(), nil)
} }

View File

@@ -2,6 +2,7 @@ package controller
import ( import (
"net/http" "net/http"
"text/template"
"time" "time"
"x-ui/logger" "x-ui/logger"
@@ -64,37 +65,40 @@ func (a *IndexController) login(c *gin.Context) {
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret) user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
timeStr := time.Now().Format("2006-01-02 15:04:05") 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 { if user == nil {
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) logger.Warningf("wrong username or password or secret: \"%s\" \"%s\" \"%s\"", safeUser, safePass, safeSecret)
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return return
} else { } else {
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c)) logger.Infof("%s logged in successfully, Ip Address: %s\n", safeUser, getRemoteIp(c))
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1) a.tgbot.UserLoginNotify(safeUser, ``, getRemoteIp(c), timeStr, 1)
} }
sessionMaxAge, err := a.settingService.GetSessionMaxAge() sessionMaxAge, err := a.settingService.GetSessionMaxAge()
if err != nil { 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 { if sessionMaxAge > 0 {
err = session.SetMaxAge(c, sessionMaxAge*60) err = session.SetMaxAge(c, sessionMaxAge*60)
if err != nil { 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) 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) jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
} }
func (a *IndexController) logout(c *gin.Context) { func (a *IndexController) logout(c *gin.Context) {
user := session.GetLoginUser(c) user := session.GetLoginUser(c)
if user != nil { if user != nil {
logger.Info("user", user.Id, "logout") logger.Infof("%s logged out successfully", user.Username)
} }
session.ClearSession(c) session.ClearSession(c)
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))

View File

@@ -105,7 +105,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
jsonMsg(c, "", err) jsonMsg(c, "", err)
return return
} }
jsonMsg(c, "Xray stoped", err) jsonMsg(c, "Xray stopped", err)
} }
func (a *ServerController) restartXrayService(c *gin.Context) { func (a *ServerController) restartXrayService(c *gin.Context) {

View File

@@ -12,6 +12,7 @@ type XraySettingController struct {
InboundService service.InboundService InboundService service.InboundService
OutboundService service.OutboundService OutboundService service.OutboundService
XrayService service.XrayService XrayService service.XrayService
WarpService service.WarpService
} }
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController { func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
@@ -72,16 +73,18 @@ func (a *XraySettingController) warp(c *gin.Context) {
var err error var err error
switch action { switch action {
case "data": case "data":
resp, err = a.XraySettingService.GetWarp() resp, err = a.WarpService.GetWarpData()
case "del":
err = a.WarpService.DelWarpData()
case "config": case "config":
resp, err = a.XraySettingService.GetWarpConfig() resp, err = a.WarpService.GetWarpConfig()
case "reg": case "reg":
skey := c.PostForm("privateKey") skey := c.PostForm("privateKey")
pkey := c.PostForm("publicKey") pkey := c.PostForm("publicKey")
resp, err = a.XraySettingService.RegWarp(skey, pkey) resp, err = a.WarpService.RegWarp(skey, pkey)
case "license": case "license":
license := c.PostForm("license") license := c.PostForm("license")
resp, err = a.XraySettingService.SetWarpLicence(license) resp, err = a.WarpService.SetWarpLicense(license)
} }
jsonObj(c, resp, err) jsonObj(c, resp, err)

View File

@@ -79,8 +79,8 @@
qrModal: qrModal, qrModal: qrModal,
}, },
methods: { methods: {
copyToClipboard(elmentId, content) { copyToClipboard(elementId, content) {
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, { this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content, text: () => content,
}); });
this.qrModal.clipboard.on('success', () => { this.qrModal.clipboard.on('success', () => {
@@ -88,9 +88,9 @@
this.qrModal.clipboard.destroy(); this.qrModal.clipboard.destroy();
}); });
}, },
setQrCode(elmentId, content) { setQrCode(elementId, content) {
new QRious({ new QRious({
element: document.querySelector('#' + elmentId), element: document.querySelector('#' + elementId),
size: 400, size: 400,
value: content, value: content,
background: 'white', background: 'white',

View File

@@ -108,15 +108,16 @@
format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="clientsBulkModal.expiryTime"></a-date-picker> v-model="clientsBulkModal.expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <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>
<a-form-item v-if="clientsBulkModal.expiryTime != 0"> <a-form-item v-if="clientsBulkModal.expiryTime != 0">
<template slot="label"> <template slot="label">
<span>{{ i18n "pages.client.renew" }}</span>
<a-tooltip> <a-tooltip>
<template slot="title"> <template slot="title">
<span>{{ i18n "pages.client.renewDesc" }}</span> <span>{{ i18n "pages.client.renewDesc" }}</span>
</template> </template>
{{ i18n "pages.client.renew" }}
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>

View File

@@ -54,12 +54,13 @@
<a-icon type="question-circle"></a-icon> <a-icon type="question-circle"></a-icon>
</a-tooltip> </a-tooltip>
</template> </template>
<a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" <a-date-picker style="width: 100%;" v-if="datepicker == 'gregorian'" :show-time="{ format: 'HH:mm:ss' }"
:dropdown-class-name="themeSwitcher.currentTheme" format="YYYY-MM-DD HH:mm:ss" :dropdown-class-name="themeSwitcher.currentTheme"
v-model="dbInbound._expiryTime"></a-date-picker> v-model="dbInbound._expiryTime"></a-date-picker>
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}' <persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
value="dbInbound._expiryTime" v-model="dbInbound._expiryTime"></persian-datepicker> value="dbInbound._expiryTime" v-model="dbInbound._expiryTime">
</a-form-item> </persian-datepicker>
</a-form-item>
</a-form> </a-form>
<!-- vmess settings --> <!-- vmess settings -->

View File

@@ -90,7 +90,14 @@
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }} <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span> </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> </template>
<table> <table>
<tr class="tr-table-box"> <tr class="tr-table-box">
@@ -108,7 +115,14 @@
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }} <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span> </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> </template>
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag> <a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
</a-popover> </a-popover>
@@ -201,7 +215,14 @@
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }} <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span> </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> </template>
<a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" /> <a-progress :show-info="false" :status="isClientEnabled(record, client.email)? 'exception' : ''" :percent="expireProgress(client.expiryTime, client.reset)" />
</a-popover> </a-popover>
@@ -214,7 +235,14 @@
<template slot="content"> <template slot="content">
<span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }} <span v-if="client.expiryTime < 0">{{ i18n "pages.client.delayedStart" }}
</span> </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> </template>
<a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag> <a-tag style="min-width: 50px; border: none;" :color="userExpiryColor(app.expireDiff, client, themeSwitcher.isDarkTheme)"> [[ remainedDays(client.expiryTime) ]] </a-tag>
</a-popover> </a-popover>

View File

@@ -221,7 +221,14 @@
</td> </td>
<td> <td>
<template v-if="infoModal.clientSettings.expiryTime > 0"> <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> </template>
<a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }} <a-tag v-else-if="infoModal.clientSettings.expiryTime < 0" color="green">[[ infoModal.clientSettings.expiryTime / -86400000 ]] {{ i18n "pages.client.days" }}
</a-tag> </a-tag>
@@ -494,8 +501,8 @@
}, },
}, },
methods: { methods: {
copyToClipboard(elmentId, content) { copyToClipboard(elementId, content) {
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, { this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
text: () => content, text: () => content,
}); });
this.infoModal.clipboard.on('success', () => { this.infoModal.clipboard.on('success', () => {

View File

@@ -403,9 +403,12 @@
</template> </template>
<template slot="expiryTime" slot-scope="text, dbInbound"> <template slot="expiryTime" slot-scope="text, dbInbound">
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme"> <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) ]] [[ DateUtil.formatMillis(dbInbound.expiryTime) ]]
</template> </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)"> <a-tag style="min-width: 50px;" :color="usageColor(new Date().getTime(), app.expireDiff, dbInbound._expiryTime)">
[[ remainedDays(dbInbound._expiryTime) ]] [[ remainedDays(dbInbound._expiryTime) ]]
</a-tag> </a-tag>
@@ -498,8 +501,14 @@
<tr> <tr>
<td>{{ i18n "pages.inbounds.expireDate" }}</td> <td>{{ i18n "pages.inbounds.expireDate" }}</td>
<td> <td>
<a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0" :color="dbInbound.isExpiry? 'red': 'blue'"> <a-tag style="min-width: 50px; text-align: center;" v-if="dbInbound.expiryTime > 0"
[[ DateUtil.formatMillis(dbInbound.expiryTime) ]] :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>
<a-tag v-else style="text-align: center;" color="purple" class="infinite-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"> <svg height="10px" width="14px" viewBox="0 0 640 512" fill="currentColor">

View File

@@ -503,6 +503,7 @@
this.loading(false); this.loading(false);
if (msg.success) { if (msg.success) {
this.user = {}; this.user = {};
window.location.replace(basePath + "logout");
} }
}, },
async restartPanel() { async restartPanel() {

View File

@@ -24,19 +24,22 @@
<td>[[ warpModal.warpData.private_key ]]</td> <td>[[ warpModal.warpData.private_key ]]</td>
</tr> </tr>
</table> </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-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
<a-collapse style="margin: 10px 0;"> <a-collapse style="margin: 10px 0;">
<a-collapse-panel header='WARP/WARP+ License Key'> <a-collapse-panel header='WARP/WARP+ License Key'>
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="Key"> <a-form-item label="Key">
<a-input v-model="warpPlus"></a-input> <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-item>
</a-form> </a-form>
</a-collapse-panel> </a-collapse-panel>
</a-collapse> </a-collapse>
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider> <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)"> <template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
<table style="width: 100%"> <table style="width: 100%">
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
@@ -51,39 +54,39 @@
<td>Device Enabled</td> <td>Device Enabled</td>
<td>[[ warpModal.warpConfig.enabled ]]</td> <td>[[ warpModal.warpConfig.enabled ]]</td>
</tr> </tr>
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)"> <template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
<tr> <tr>
<td>Account Type</td> <td>Account Type</td>
<td>[[ warpModal.warpConfig.account.account_type ]]</td> <td>[[ warpModal.warpConfig.account.account_type ]]</td>
</tr> </tr>
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
<td>Role</td> <td>Role</td>
<td>[[ warpModal.warpConfig.account.role ]]</td> <td>[[ warpModal.warpConfig.account.role ]]</td>
</tr> </tr>
<tr> <tr>
<td>WARP+ Data</td> <td>WARP+ Data</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td> <td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
</tr> </tr>
<tr class="client-table-odd-row"> <tr class="client-table-odd-row">
<td>Quota</td> <td>Quota</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td> <td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
</tr> </tr>
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)"> <tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
<td>Usage</td> <td>Usage</td>
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td> <td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
</tr> </tr>
</template> </template>
</table> </table>
<a-divider style="margin: 10px 0;">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider> <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} }"> <a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<template v-if="warpOutboundIndex>=0"> <template v-if="warpOutboundIndex>=0">
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag> <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> <a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
</template> </template>
<template v-else> <template v-else>
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag> <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> <a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
</template> </template>
</a-form-item> </a-form-item>
</a-form> </a-form>
</template> </template>
@@ -101,21 +104,20 @@
this.visible = true; this.visible = true;
this.warpConfig = null; this.warpConfig = null;
this.getData(); this.getData();
}, },
close() { close() {
this.visible = false; this.visible = false;
this.loading(false); this.loading(false);
}, },
loading(loading=true) { loading(loading = true) {
this.confirmLoading = loading; this.confirmLoading = loading;
}, },
async getData(){ async getData() {
this.loading(true); this.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/data'); const msg = await HttpUtil.post('/panel/xray/warp/data');
this.loading(false); this.loading(false);
if (msg.success) { 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() { collectConfig() {
config = warpModal.warpConfig.config; config = warpModal.warpConfig.config;
peer = config.peers[0]; peer = config.peers[0];
if(config){ if (config) {
warpModal.warpOutbound = Outbound.fromJson({ warpModal.warpOutbound = Outbound.fromJson({
tag: 'warp', tag: 'warp',
protocol: Protocols.Wireguard, protocol: Protocols.Wireguard,
settings: { settings: {
mtu: 1420, mtu: 1420,
secretKey: warpModal.warpData.private_key, 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', domainStrategy: 'ForceIP',
peers: [{ peers: [{
publicKey: peer.public_key, 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); warpModal.loading(true);
keys = Wireguard.generateKeypair(); 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) { if (msg.success) {
resp = JSON.parse(msg.obj); resp = JSON.parse(msg.obj);
warpModal.warpData = resp.data; warpModal.warpData = resp.data;
@@ -161,9 +186,9 @@
} }
warpModal.loading(false); warpModal.loading(false);
}, },
async updateLicense(l){ async updateLicense(l) {
warpModal.loading(true); 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) { if (msg.success) {
warpModal.warpData = JSON.parse(msg.obj); warpModal.warpData = JSON.parse(msg.obj);
warpModal.warpConfig = null; warpModal.warpConfig = null;
@@ -171,7 +196,7 @@
} }
warpModal.loading(false); warpModal.loading(false);
}, },
async getConfig(){ async getConfig() {
warpModal.loading(true); warpModal.loading(true);
const msg = await HttpUtil.post('/panel/xray/warp/config'); const msg = await HttpUtil.post('/panel/xray/warp/config');
warpModal.loading(false); warpModal.loading(false);
@@ -180,20 +205,37 @@
this.collectConfig(); 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.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds); app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close(); warpModal.close();
}, },
resetOutbound(){ resetOutbound() {
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson(); app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds); app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
warpModal.close(); warpModal.close();
},
delOutbound() {
if (this.warpOutboundIndex != -1) {
app.templateSettings.outbounds.splice(this.warpOutboundIndex, 1);
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
}
warpModal.close();
} }
}, },
computed: { computed: {
warpOutboundIndex: { warpOutboundIndex: {
get: function() { get: function () {
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1; return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
} }
} }

View File

@@ -1054,12 +1054,13 @@
}); });
}, },
changeObsCode() { changeObsCode() {
if (this.obsSettings == ''){
return
}
if(this.cm != null) { if(this.cm != null) {
this.cm.toTextArea(); this.cm.toTextArea();
} }
if (this.obsSettings == ''){
this.cm = null;
return
}
textAreaObj = document.getElementById('obsSetting'); textAreaObj = document.getElementById('obsSetting');
textAreaObj.value = this[this.obsSettings]; textAreaObj.value = this[this.obsSettings];
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
@@ -1267,7 +1268,8 @@
balancer: { balancer: {
tag: '', tag: '',
strategy: 'random', strategy: 'random',
selector: [] selector: [],
fallbackTag: ''
}, },
confirm: (balancer) => { confirm: (balancer) => {
balancerModal.loading(); balancerModal.loading();
@@ -1277,27 +1279,18 @@
} }
let tmpBalancer = { let tmpBalancer = {
'tag': balancer.tag, 'tag': balancer.tag,
'selector': balancer.selector 'selector': balancer.selector,
'fallbackTag': balancer.fallbackTag
}; };
if (balancer.strategy && balancer.strategy != 'random') { if (balancer.strategy && balancer.strategy != 'random') {
tmpBalancer.strategy = { tmpBalancer.strategy = {
'type': balancer.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); newTemplateSettings.routing.balancers.push(tmpBalancer);
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
if (balancer.strategy == 'leastPing' || balancer.strategy == 'leastLoad')
this.updateObservatorySelectors();
balancerModal.close(); balancerModal.close();
this.changeObsCode(); this.changeObsCode();
}, },
@@ -1317,7 +1310,8 @@
let tmpBalancer = { let tmpBalancer = {
'tag': balancer.tag, 'tag': balancer.tag,
'selector': balancer.selector 'selector': balancer.selector,
'fallbackTag': balancer.fallbackTag
}; };
// Remove old tag // Remove old tag
@@ -1332,18 +1326,6 @@
tmpBalancer.strategy = { tmpBalancer.strategy = {
'type': balancer.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; newTemplateSettings.routing.balancers[index] = tmpBalancer;
@@ -1356,14 +1338,49 @@
}); });
} }
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
if (balancer.strategy == 'leastPing' || balancer.strategy == 'leastLoad')
this.updateObservatorySelectors();
balancerModal.close(); balancerModal.close();
this.changeObsCode(); this.changeObsCode();
}, },
isEdit: true 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) { deleteBalancer(index) {
let newTemplateSettings = { ...this.templateSettings }; newTemplateSettings = this.templateSettings;
// Remove from balancers // Remove from balancers
const removedBalancer = this.balancersData.splice(index, 1)[0]; const removedBalancer = this.balancersData.splice(index, 1)[0];
@@ -1372,26 +1389,13 @@
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag); let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
newTemplateSettings.routing.balancers.splice(realIndex, 1); 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 // Update balancers property to an empty array if there are no more balancers
if (newTemplateSettings.routing.balancers.length === 0) { if (newTemplateSettings.routing.balancers.length === 0) {
delete newTemplateSettings.routing.balancers; delete newTemplateSettings.routing.balancers;
} }
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
this.updateObservatorySelectors();
this.obsSettings = '';
this.changeObsCode() this.changeObsCode()
}, },
addDNSServer(){ addDNSServer(){
@@ -1622,7 +1626,8 @@
'key': index, 'key': index,
'tag': o.tag ? o.tag : "", 'tag': o.tag ? o.tag : "",
'strategy': o.strategy?.type ?? "random", '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; this.templateSettings = newTemplateSettings;
}, },
}, },
observatoryEnable: { observatoryEnable: function () { return this.templateSettings != null && this.templateSettings.observatory != undefined },
get: function () { return this.templateSettings != null && this.templateSettings.observatory }, burstObservatoryEnable: function () { return this.templateSettings != null && this.templateSettings.burstObservatory != undefined },
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;
}
},
freedomStrategy: { freedomStrategy: {
get: function () { get: function () {
if (!this.templateSettings) return "AsIs"; if (!this.templateSettings) return "AsIs";

View File

@@ -26,12 +26,18 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback <a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback
:validate-status="balancerModal.emptySelector? 'warning' : 'success'"> :validate-status="balancerModal.emptySelector? 'warning' : 'success'">
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()" <a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option> <a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select> </a-select>
</a-form-item> </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> </table>
</a-form> </a-form>
</a-modal> </a-modal>
@@ -48,7 +54,8 @@
balancer: { balancer: {
tag: '', tag: '',
strategy: 'random', strategy: 'random',
selector: [] selector: [],
fallbackTag: ''
}, },
outboundTags: [], outboundTags: [],
balancerTags:[], balancerTags:[],
@@ -71,7 +78,8 @@
balancerModal.balancer = { balancerModal.balancer = {
tag: '', tag: '',
strategy: 'random', strategy: 'random',
selector: [] selector: [],
fallbackTag: ''
}; };
} }
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag); this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);

View File

@@ -252,46 +252,55 @@ func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string)
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool { func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
jsonIps, err := json.Marshal(ips) 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.ClientEmail = clientEmail
inboundClientIps.Ips = string(jsonIps) inboundClientIps.Ips = string(jsonIps)
// check inbound limitation // Fetch inbound settings by client email
inbound, err := j.getInboundByEmail(clientEmail) inbound, err := j.getInboundByEmail(clientEmail)
j.checkError(err) if err != nil {
logger.Errorf("failed to fetch inbound settings for email %s: %s", clientEmail, err)
if inbound.Settings == "" {
logger.Debug("wrong data ", inbound)
return false return false
} }
if inbound.Settings == "" {
logger.Debug("wrong data:", inbound)
return false
}
// Unmarshal settings to get client limits
settings := map[string][]model.Client{} settings := map[string][]model.Client{}
json.Unmarshal([]byte(inbound.Settings), &settings) json.Unmarshal([]byte(inbound.Settings), &settings)
clients := settings["clients"] clients := settings["clients"]
shouldCleanLog := false shouldCleanLog := false
j.disAllowedIps = []string{} j.disAllowedIps = []string{}
// create iplimit log file channel // Open log file for IP limits
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil { 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() defer logIpFile.Close()
log.SetOutput(logIpFile) log.SetOutput(logIpFile)
log.SetFlags(log.LstdFlags) log.SetFlags(log.LstdFlags)
// Check client IP limits
for _, client := range clients { for _, client := range clients {
if client.Email == clientEmail { if client.Email == clientEmail {
limitIp := client.LimitIP limitIp := client.LimitIP
if limitIp != 0 { if limitIp > 0 && inbound.Enable {
shouldCleanLog = true shouldCleanLog = true
if limitIp < len(ips) && inbound.Enable { if limitIp < len(ips) {
j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...) j.disAllowedIps = append(j.disAllowedIps, ips[limitIp:]...)
for i := limitIp; i < len(ips); i++ { 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) sort.Strings(j.disAllowedIps)
if len(j.disAllowedIps) > 0 { if len(j.disAllowedIps) > 0 {
logger.Debug("disAllowedIps ", j.disAllowedIps) logger.Debug("disAllowedIps:", j.disAllowedIps)
} }
db := database.GetDB() db := database.GetDB()
err = db.Save(inboundClientIps).Error err = db.Save(inboundClientIps).Error
j.checkError(err) if err != nil {
logger.Error("failed to save inboundClientIps:", err)
return false
}
return shouldCleanLog return shouldCleanLog
} }

View File

@@ -19,10 +19,8 @@ func (j *XrayTrafficJob) Run() {
if !j.xrayService.IsXrayRunning() { if !j.xrayService.IsXrayRunning() {
return return
} }
traffics, clientTraffics, err := j.xrayService.GetXrayTraffic() traffics, clientTraffics, err := j.xrayService.GetXrayTraffic()
if err != nil { if err != nil {
logger.Warning("get xray traffic failed:", err)
return return
} }
err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics) err, needRestart0 := j.inboundService.AddTraffic(traffics, clientTraffics)

View File

@@ -3,6 +3,7 @@ package middleware
import ( import (
"net" "net"
"net/http" "net/http"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -14,12 +15,17 @@ func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
host = c.GetHeader("X-Real-IP") host = c.GetHeader("X-Real-IP")
} }
if host == "" { if host == "" {
host, _, _ := net.SplitHostPort(c.Request.Host) host = c.Request.Host
if host != domain { if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
c.AbortWithStatus(http.StatusForbidden) host, _, _ = net.SplitHostPort(host)
return
} }
c.Next()
} }
if host != domain {
c.AbortWithStatus(http.StatusForbidden)
return
}
c.Next()
} }
} }

View File

@@ -595,7 +595,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err return false, err
} }
inerfaceClients := settings["clients"].([]interface{}) interfaceClients := settings["clients"].([]interface{})
oldInbound, err := s.GetInbound(data.Id) oldInbound, err := s.GetInbound(data.Id)
if err != nil { if err != nil {
@@ -650,7 +650,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return false, err return false, err
} }
settingsClients := oldSettings["clients"].([]interface{}) settingsClients := oldSettings["clients"].([]interface{})
settingsClients[clientIndex] = inerfaceClients[0] settingsClients[clientIndex] = interfaceClients[0]
oldSettings["clients"] = settingsClients oldSettings["clients"] = settingsClients
newSettings, err := json.MarshalIndent(oldSettings, "", " ") 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 { func (s *InboundService) DelClientIPs(tx *gorm.DB, email string) error {
logger.Warning(email)
return tx.Where("client_email = ?", email).Delete(model.InboundClientIps{}).Error 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 var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error err = db.Model(xray.ClientTraffic{}).Where("id = ?", trafficId).Find(&traffics).Error
if err != nil { if err != nil {
logger.Warning(err) logger.Warningf("Error retrieving ClientTraffic with trafficId %d: %v", trafficId, err)
return nil, nil, err return nil, nil, err
} }
if len(traffics) > 0 { if len(traffics) > 0 {
@@ -1158,7 +1157,7 @@ func (s *InboundService) GetClientInboundByEmail(email string) (traffic *xray.Cl
var traffics []*xray.ClientTraffic var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil { if err != nil {
logger.Warning(err) logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, nil, err return nil, nil, err
} }
if len(traffics) > 0 { 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) { func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffic, error) {
db := database.GetDB() db := database.GetDB()
var inbounds []*model.Inbound var inbounds []*model.Inbound
err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"tgId": %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 { if err != nil && err != gorm.ErrRecordNotFound {
logger.Errorf("Error retrieving inbounds with tgId %d: %v", tgId, err)
return nil, err return nil, err
} }
var emails []string var emails []string
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.GetClients(inbound) clients, err := s.GetClients(inbound)
if err != nil { 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 { for _, client := range clients {
if client.TgID == tgId { if client.TgID == tgId {
@@ -1715,15 +1719,19 @@ func (s *InboundService) GetClientTrafficTgBot(tgId int64) ([]*xray.ClientTraffi
} }
} }
} }
var traffics []*xray.ClientTraffic var traffics []*xray.ClientTraffic
err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error err = db.Model(xray.ClientTraffic{}).Where("email IN ?", emails).Find(&traffics).Error
if err != nil { if err != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
logger.Warning(err) logger.Warning("No ClientTraffic records found for emails:", emails)
return nil, err 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) { 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 err = db.Model(xray.ClientTraffic{}).Where("email = ?", email).Find(&traffics).Error
if err != nil { if err != nil {
logger.Warning(err) logger.Warningf("Error retrieving ClientTraffic with email %s: %v", email, err)
return nil, err return nil, err
} }
if len(traffics) > 0 { if len(traffics) > 0 {
@@ -1747,38 +1755,51 @@ func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.Client
inbound := &model.Inbound{} inbound := &model.Inbound{}
traffic = &xray.ClientTraffic{} 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 != nil {
if err == gorm.ErrRecordNotFound { if err == gorm.ErrRecordNotFound {
logger.Warning(err) logger.Warningf("Inbound settings containing query %s not found: %v", query, err)
return nil, err return nil, err
} }
logger.Errorf("Error searching for inbound settings with query %s: %v", query, err)
return nil, err
} }
traffic.InboundId = inbound.Id traffic.InboundId = inbound.Id
// get settings clients // Unmarshal settings to get clients
settings := map[string][]model.Client{} 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"] clients := settings["clients"]
for _, client := range clients { for _, client := range clients {
if client.ID == query && client.Email != "" { if (client.ID == query || client.Password == query) && client.Email != "" {
traffic.Email = client.Email
break
}
if client.Password == query && client.Email != "" {
traffic.Email = client.Email traffic.Email = client.Email
break break
} }
} }
if traffic.Email == "" { 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 err = db.Model(xray.ClientTraffic{}).Where("email = ?", traffic.Email).First(traffic).Error
if err != nil { 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 nil, err
} }
return traffic, err
return traffic, nil
} }
func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) { func (s *InboundService) GetInboundClientIps(clientEmail string) (string, error) {
@@ -1948,6 +1969,6 @@ func (s *InboundService) MigrateDB() {
s.MigrationRemoveOrphanedTraffics() s.MigrationRemoveOrphanedTraffics()
} }
func (s *InboundService) GetOnlineClinets() []string { func (s *InboundService) GetOnlineClients() []string {
return p.GetOnlineClients() return p.GetOnlineClients()
} }

View File

@@ -70,7 +70,7 @@ func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, erro
err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error err := db.Model(model.OutboundTraffics{}).Find(&traffics).Error
if err != nil { if err != nil {
logger.Warning(err) logger.Warning("Error retrieving OutboundTraffics: ", err)
return nil, err return nil, err
} }

View File

@@ -19,7 +19,7 @@ func (s *PanelService) RestartPanel(delay time.Duration) error {
time.Sleep(delay) time.Sleep(delay)
err := p.Signal(syscall.SIGHUP) err := p.Signal(syscall.SIGHUP)
if err != nil { if err != nil {
logger.Error("send signal SIGHUP failed:", err) logger.Error("failed to send SIGHUP signal:", err)
} }
}() }()
return nil return nil

View File

@@ -312,6 +312,16 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
arch = "64" arch = "64"
case "arm64": case "arm64":
arch = "arm64-v8a" 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) fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)

View File

@@ -269,11 +269,11 @@ func (s *SettingService) SetTgBotChatId(chatIds string) error {
return s.setString("tgBotChatId", chatIds) return s.setString("tgBotChatId", chatIds)
} }
func (s *SettingService) GetTgbotenabled() (bool, error) { func (s *SettingService) GetTgbotEnabled() (bool, error) {
return s.getBool("tgBotEnable") return s.getBool("tgBotEnable")
} }
func (s *SettingService) SetTgbotenabled(value bool) error { func (s *SettingService) SetTgbotEnabled(value bool) error {
return s.setBool("tgBotEnable", value) return s.setBool("tgBotEnable", value)
} }
@@ -524,7 +524,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
"pageSize": func() (interface{}, error) { return s.GetPageSize() }, "pageSize": func() (interface{}, error) { return s.GetPageSize() },
"defaultCert": func() (interface{}, error) { return s.GetCertFile() }, "defaultCert": func() (interface{}, error) { return s.GetCertFile() },
"defaultKey": func() (interface{}, error) { return s.GetKeyFile() }, "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() }, "subEnable": func() (interface{}, error) { return s.GetSubEnable() },
"subURI": func() (interface{}, error) { return s.GetSubURI() }, "subURI": func() (interface{}, error) { return s.GetSubURI() },
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() }, "subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },

View File

@@ -64,52 +64,59 @@ func (t *Tgbot) GetHashStorage() *global.HashStorage {
} }
func (t *Tgbot) Start(i18nFS embed.FS) error { func (t *Tgbot) Start(i18nFS embed.FS) error {
// Initialize localizer
err := locale.InitLocalizer(i18nFS, &t.settingService) err := locale.InitLocalizer(i18nFS, &t.settingService)
if err != nil { if err != nil {
return err return err
} }
// init hash storage => store callback queries // Initialize hash storage to store callback queries
hashStorage = global.NewHashStorage(20 * time.Minute) hashStorage = global.NewHashStorage(20 * time.Minute)
t.SetHostname() t.SetHostname()
tgBottoken, err := t.settingService.GetTgBotToken()
if err != nil || tgBottoken == "" { // Get Telegram bot token
logger.Warning("Get TgBotToken failed:", err) tgBotToken, err := t.settingService.GetTgBotToken()
if err != nil || tgBotToken == "" {
logger.Warning("Failed to get Telegram bot token:", err)
return err return err
} }
tgBotid, err := t.settingService.GetTgBotChatId() // Get Telegram bot chat ID(s)
tgBotID, err := t.settingService.GetTgBotChatId()
if err != nil { if err != nil {
logger.Warning("Get GetTgBotChatId failed:", err) logger.Warning("Failed to get Telegram bot chat ID:", err)
return err return err
} }
if tgBotid != "" { // Parse admin IDs from comma-separated string
for _, adminId := range strings.Split(tgBotid, ",") { if tgBotID != "" {
id, err := strconv.Atoi(adminId) for _, adminID := range strings.Split(tgBotID, ",") {
id, err := strconv.Atoi(adminID)
if err != nil { 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 return err
} }
adminIds = append(adminIds, int64(id)) adminIds = append(adminIds, int64(id))
} }
} }
// Get Telegram bot proxy URL
tgBotProxy, err := t.settingService.GetTgBotProxy() tgBotProxy, err := t.settingService.GetTgBotProxy()
if err != nil { 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 { if err != nil {
fmt.Println("Get tgbot's api error:", err) logger.Error("Failed to initialize Telegram bot API:", err)
return err return err
} }
// listen for TG bot income messages // Start receiving Telegram bot messages
if !isRunning { if !isRunning {
logger.Info("Starting Telegram receiver ...") logger.Info("Telegram bot receiver started")
go t.OnReceive() go t.OnReceive()
isRunning = true isRunning = true
} }
@@ -201,7 +208,7 @@ func (t *Tgbot) OnReceive() {
}, th.AnyCommand()) }, th.AnyCommand())
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) { botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) {
t.asnwerCallback(&query, checkAdmin(query.From.ID)) t.answerCallback(&query, checkAdmin(query.From.ID))
}, th.AnyCallbackQueryWithMessage()) }, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) { 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 chatId := callbackQuery.Message.GetChat().ID
if isAdmin { if isAdmin {
@@ -964,7 +971,7 @@ func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
return info return info
} }
// Send server usage without an inline keyborad // Send server usage without an inline keyboard
func (t *Tgbot) sendServerUsage() string { func (t *Tgbot) sendServerUsage() string {
info := t.prepareServerUsageInfo() info := t.prepareServerUsageInfo()
return info return info
@@ -1019,7 +1026,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
return info 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() { if !t.IsRunning() {
return return
} }
@@ -1037,11 +1044,12 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
msg := "" msg := ""
if status == LoginSuccess { if status == LoginSuccess {
msg += t.I18nBot("tgbot.messages.loginSuccess") msg += t.I18nBot("tgbot.messages.loginSuccess")
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
} else if status == LoginFail { } else if status == LoginFail {
msg += t.I18nBot("tgbot.messages.loginFailed") 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.username", "Username=="+username)
msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip) msg += t.I18nBot("tgbot.messages.ip", "IP=="+ip)
msg += t.I18nBot("tgbot.messages.time", "Time=="+time) 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 { func (t *Tgbot) getInboundUsages() string {
info := "" info := ""
// get traffic // get traffic
inbouds, err := t.inboundService.GetAllInbounds() inbounds, err := t.inboundService.GetAllInbounds()
if err != nil { if err != nil {
logger.Warning("GetAllInbounds run failed:", err) logger.Warning("GetAllInbounds run failed:", err)
info += t.I18nBot("tgbot.answers.getInboundsFailed") info += t.I18nBot("tgbot.answers.getInboundsFailed")
} else { } else {
// NOTE:If there no any sessions here,need to notify here // NOTE:If there no any sessions here,need to notify here
// TODO:Sub-node push, automatic conversion format // 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.inbound", "Remark=="+inbound.Remark)
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)) 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)) 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) { func (t *Tgbot) searchInbound(chatId int64, remark string) {
inbouds, err := t.inboundService.SearchInbounds(remark) inbounds, err := t.inboundService.SearchInbounds(remark)
if err != nil { if err != nil {
logger.Warning(err) logger.Warning(err)
msg := t.I18nBot("tgbot.wentWrong") msg := t.I18nBot("tgbot.wentWrong")
t.SendMsgToTgbot(chatId, msg) t.SendMsgToTgbot(chatId, msg)
return return
} }
if len(inbouds) == 0 { if len(inbounds) == 0 {
msg := t.I18nBot("tgbot.noInbounds") msg := t.I18nBot("tgbot.noInbounds")
t.SendMsgToTgbot(chatId, msg) t.SendMsgToTgbot(chatId, msg)
return return
} }
for _, inbound := range inbouds { for _, inbound := range inbounds {
info := "" info := ""
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark) 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.port", "Port=="+strconv.Itoa(inbound.Port))

162
web/service/warp.go Normal file
View 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
}

View File

@@ -97,7 +97,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
if !clientTraffic.Enable { if !clientTraffic.Enable {
clients = RemoveIndex(clients, index-indexDecrease) clients = RemoveIndex(clients, index-indexDecrease)
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) { func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic, error) {
if !s.IsXrayRunning() { 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() 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 { func (s *XrayService) RestartXray(isForce bool) error {
@@ -202,7 +211,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
func (s *XrayService) StopXray() error { func (s *XrayService) StopXray() error {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
logger.Debug("stop xray") logger.Debug("Attempting to stop Xray...")
if s.IsXrayRunning() { if s.IsXrayRunning() {
return p.Stop() return p.Stop()
} }

View File

@@ -1,13 +1,8 @@
package service package service
import ( import (
"bytes"
_ "embed" _ "embed"
"encoding/json" "encoding/json"
"fmt"
"net/http"
"os"
"time"
"x-ui/util/common" "x-ui/util/common"
"x-ui/xray" "x-ui/xray"
@@ -32,142 +27,3 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
} }
return nil 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
}

View File

@@ -9,9 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
const ( const loginUser = "LOGIN_USER"
loginUser = "LOGIN_USER"
)
func init() { func init() {
gob.Register(model.User{}) gob.Register(model.User{})
@@ -19,6 +17,10 @@ func init() {
func SetLoginUser(c *gin.Context, user *model.User) error { func SetLoginUser(c *gin.Context, user *model.User) error {
s := sessions.Default(c) s := sessions.Default(c)
s.Options(sessions.Options{
Path: "/",
HttpOnly: true,
})
s.Set(loginUser, user) s.Set(loginUser, user)
return s.Save() return s.Save()
} }
@@ -34,24 +36,28 @@ func SetMaxAge(c *gin.Context, maxAge int) error {
func GetLoginUser(c *gin.Context) *model.User { func GetLoginUser(c *gin.Context) *model.User {
s := sessions.Default(c) s := sessions.Default(c)
obj := s.Get(loginUser) if obj := s.Get(loginUser); obj != nil {
if obj == nil { if user, ok := obj.(model.User); ok {
return nil return &user
}
} }
user := obj.(model.User) return nil
return &user
} }
func IsLogin(c *gin.Context) bool { func IsLogin(c *gin.Context) bool {
return GetLoginUser(c) != nil return GetLoginUser(c) != nil
} }
func ClearSession(c *gin.Context) { func ClearSession(c *gin.Context) error {
s := sessions.Default(c) s := sessions.Default(c)
s.Clear() s.Clear()
s.Options(sessions.Options{ s.Options(sessions.Options{
Path: "/", Path: "/",
MaxAge: -1, MaxAge: -1,
}) })
s.Save() if err := s.Save(); err != nil {
return err
}
c.SetCookie("3x-ui", "", -1, "/", "", false, true)
return nil
} }

View File

@@ -44,7 +44,7 @@
"monitor" = "Listen IP" "monitor" = "Listen IP"
"certificate" = "Digital Certificate" "certificate" = "Digital Certificate"
"fail" = " Failed" "fail" = " Failed"
"success" = " Successful" "success" = " Successfully"
"getVersion" = "Get Version" "getVersion" = "Get Version"
"install" = "Install" "install" = "Install"
"clients" = "Clients" "clients" = "Clients"
@@ -78,7 +78,7 @@
"invalidFormData" = "The Input data format is invalid." "invalidFormData" = "The Input data format is invalid."
"emptyUsername" = "Username is required" "emptyUsername" = "Username is required"
"emptyPassword" = "Password is required" "emptyPassword" = "Password is required"
"wrongUsernameOrPassword" = "Invalid username or password." "wrongUsernameOrPassword" = "Invalid username or password or secret."
"successLogin" = "Login" "successLogin" = "Login"
[pages.index] [pages.index]
@@ -544,7 +544,7 @@
"selectUserFailed" = "❌ Error in user selection!" "selectUserFailed" = "❌ Error in user selection!"
"userSaved" = "✅ Telegram User saved." "userSaved" = "✅ Telegram User saved."
"loginSuccess" = "✅ Logged in to the panel successfully.\r\n" "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" "report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n" "datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n" "hostname" = "💻 Host: {{ .Hostname }}\r\n"
@@ -562,6 +562,7 @@
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n" "xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Username: {{ .Username }}\r\n" "username" = "👤 Username: {{ .Username }}\r\n"
"password" = "👤 Password: {{ .Password }}\r\n"
"time" = "⏰ Time: {{ .Time }}\r\n" "time" = "⏰ Time: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n" "port" = "🔌 Port: {{ .Port }}\r\n"

View File

@@ -560,6 +560,7 @@
"traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Tráfico: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Estado de Xray: {{ .State }}\r\n" "xrayStatus" = " Estado de Xray: {{ .State }}\r\n"
"username" = "👤 Nombre de usuario: {{ .Username }}\r\n" "username" = "👤 Nombre de usuario: {{ .Username }}\r\n"
"password" = "👤 Contraseña: {{ .Password }}\r\n"
"time" = "⏰ Hora: {{ .Time }}\r\n" "time" = "⏰ Hora: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Puerto: {{ .Port }}\r\n" "port" = "🔌 Puerto: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " وضعیت‌ایکس‌ری: {{ .State }}\r\n" "xrayStatus" = " وضعیت‌ایکس‌ری: {{ .State }}\r\n"
"username" = "👤 نام‌کاربری: {{ .Username }}\r\n" "username" = "👤 نام‌کاربری: {{ .Username }}\r\n"
"password" = "👤 رمز عبور: {{ .Password }}\r\n"
"time" = "⏰ زمان: {{ .Time }}\r\n" "time" = "⏰ زمان: {{ .Time }}\r\n"
"inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n" "inbound" = "📍 نام‌ورودی: {{ .Remark }}\r\n"
"port" = "🔌 پورت: {{ .Port }}\r\n" "port" = "🔌 پورت: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n" "xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n" "username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
"password" = "👤 Kata Sandi: {{ .Password }}\r\n"
"time" = "⏰ Waktu: {{ .Time }}\r\n" "time" = "⏰ Waktu: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n" "port" = "🔌 Port: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Трафик: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Состояние Xray: {{ .State }}\r\n" "xrayStatus" = " Состояние Xray: {{ .State }}\r\n"
"username" = "👤 Имя пользователя: {{ .Username }}\r\n" "username" = "👤 Имя пользователя: {{ .Username }}\r\n"
"password" = "👤 Пароль: {{ .Password }}\r\n"
"time" = "⏰ Время: {{ .Time }}\r\n" "time" = "⏰ Время: {{ .Time }}\r\n"
"inbound" = "📍 Входящий поток: {{ .Remark }}\r\n" "inbound" = "📍 Входящий поток: {{ .Remark }}\r\n"
"port" = "🔌 Порт: {{ .Port }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Трафік: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Статус: {{ .State }}\r\n" "xrayStatus" = " Статус: {{ .State }}\r\n"
"username" = "👤 Ім'я користувача: {{ .Username }}\r\n" "username" = "👤 Ім'я користувача: {{ .Username }}\r\n"
"password" = "👤 Пароль: {{ .Password }}\r\n"
"time" = "⏰ Час: {{ .Time }}\r\n" "time" = "⏰ Час: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Порт: {{ .Port }}\r\n" "port" = "🔌 Порт: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 Lưu lượng: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Trạng thái Xray: {{ .State }}\r\n" "xrayStatus" = " Trạng thái Xray: {{ .State }}\r\n"
"username" = "👤 Tên người dùng: {{ .Username }}\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" "time" = "⏰ Thời gian: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n" "inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Cổng: {{ .Port }}\r\n" "port" = "🔌 Cổng: {{ .Port }}\r\n"

View File

@@ -562,6 +562,7 @@
"traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n" "traffic" = "🚦 流量:{{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Xray 状态:{{ .State }}\r\n" "xrayStatus" = " Xray 状态:{{ .State }}\r\n"
"username" = "👤 用户名:{{ .Username }}\r\n" "username" = "👤 用户名:{{ .Username }}\r\n"
"password" = "👤 密码: {{ .Password }}\r\n"
"time" = "⏰ 时间:{{ .Time }}\r\n" "time" = "⏰ 时间:{{ .Time }}\r\n"
"inbound" = "📍 入站:{{ .Remark }}\r\n" "inbound" = "📍 入站:{{ .Remark }}\r\n"
"port" = "🔌 端口:{{ .Port }}\r\n" "port" = "🔌 端口:{{ .Port }}\r\n"

View File

@@ -180,7 +180,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
assetsBasePath := basePath + "assets/" assetsBasePath := basePath + "assets/"
store := cookie.NewStore(secret) store := cookie.NewStore(secret)
engine.Use(sessions.Sessions("session", store)) engine.Use(sessions.Sessions("3x-ui", store))
engine.Use(func(c *gin.Context) { engine.Use(func(c *gin.Context) {
c.Set("base_path", basePath) c.Set("base_path", basePath)
}) })
@@ -268,7 +268,7 @@ func (s *Server) startTask() {
// Make a traffic condition every day, 8:30 // Make a traffic condition every day, 8:30
var entry cron.EntryID var entry cron.EntryID
isTgbotenabled, err := s.settingService.GetTgbotenabled() isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) { if (err == nil) && (isTgbotenabled) {
runtime, err := s.settingService.GetTgbotRuntime() runtime, err := s.settingService.GetTgbotRuntime()
if err != nil || runtime == "" { if err != nil || runtime == "" {
@@ -344,13 +344,13 @@ func (s *Server) Start() (err error) {
} }
listener = network.NewAutoHttpsListener(listener) listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c) listener = tls.NewListener(listener, c)
logger.Info("web server run https on", listener.Addr()) logger.Info("Web server running HTTPS on", listener.Addr())
} else { } else {
logger.Error("error in loading certificates: ", err) logger.Error("Error loading certificates:", err)
logger.Info("web server run http on", listener.Addr()) logger.Info("Web server running HTTP on", listener.Addr())
} }
} else { } else {
logger.Info("web server run http on", listener.Addr()) logger.Info("Web server running HTTP on", listener.Addr())
} }
s.listener = listener s.listener = listener
@@ -364,7 +364,7 @@ func (s *Server) Start() (err error) {
s.startTask() s.startTask()
isTgbotenabled, err := s.settingService.GetTgbotenabled() isTgbotenabled, err := s.settingService.GetTgbotEnabled()
if (err == nil) && (isTgbotenabled) { if (err == nil) && (isTgbotenabled) {
tgBot := s.tgbotService.NewTgbot() tgBot := s.tgbotService.NewTgbot()
tgBot.Start(i18nFS) tgBot.Start(i18nFS)

View File

@@ -262,10 +262,9 @@ reset_webbasepath() {
echo -e "${yellow}Resetting Web Base Path${plain}" echo -e "${yellow}Resetting Web Base Path${plain}"
# Prompt user to set a new web base path # 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 [[ $config_webBasePath == "y" ]]; then
if [[ -z $config_webBasePath ]]; then
config_webBasePath=$(gen_random_string 10) config_webBasePath=$(gen_random_string 10)
fi fi

View File

@@ -31,24 +31,27 @@ type XrayAPI struct {
isConnected bool isConnected bool
} }
func (x *XrayAPI) Init(apiPort int) (err error) { func (x *XrayAPI) Init(apiPort int) error {
if apiPort == 0 { if apiPort <= 0 {
return common.NewError("xray api port wrong:", apiPort) 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 { if err != nil {
return err return fmt.Errorf("failed to connect to Xray API: %w", err)
} }
x.grpcClient = conn x.grpcClient = conn
x.isConnected = true x.isConnected = true
hsClient := command.NewHandlerServiceClient(x.grpcClient) hsClient := command.NewHandlerServiceClient(conn)
ssClient := statsService.NewStatsServiceClient(x.grpcClient) ssClient := statsService.NewStatsServiceClient(conn)
x.HandlerServiceClient = &hsClient x.HandlerServiceClient = &hsClient
x.StatsServiceClient = &ssClient x.StatsServiceClient = &ssClient
return return nil
} }
func (x *XrayAPI) Close() { func (x *XrayAPI) Close() {
@@ -149,94 +152,101 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
return err return err
} }
func (x *XrayAPI) RemoveUser(inboundTag string, email string) error { func (x *XrayAPI) RemoveUser(inboundTag, email string) error {
client := *x.HandlerServiceClient ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
_, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{ defer cancel()
Tag: inboundTag,
Operation: serial.ToTypedMessage(&command.RemoveUserOperation{ op := &command.RemoveUserOperation{Email: email}
Email: email, req := &command.AlterInboundRequest{
}), Tag: inboundTag,
}) Operation: serial.ToTypedMessage(op),
return err }
_, 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) { func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
if x.grpcClient == nil { if x.grpcClient == nil {
return nil, nil, common.NewError("xray api is not initialized") 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) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel() defer cancel()
request := &statsService.QueryStatsRequest{
Reset_: reset, resp, err := (*x.StatsServiceClient).QueryStats(ctx, &statsService.QueryStatsRequest{Reset_: reset})
}
resp, err := client.QueryStats(ctx, request)
if err != nil { if err != nil {
logger.Debug("Failed to query Xray stats:", err)
return nil, nil, err return nil, nil, err
} }
tagTrafficMap := map[string]*Traffic{}
emailTrafficMap := map[string]*ClientTraffic{}
clientTraffics := make([]*ClientTraffic, 0) tagTrafficMap := make(map[string]*Traffic)
traffics := make([]*Traffic, 0) emailTrafficMap := make(map[string]*ClientTraffic)
for _, stat := range resp.GetStat() { for _, stat := range resp.GetStat() {
matchs := trafficRegex.FindStringSubmatch(stat.Name) if matches := trafficRegex.FindStringSubmatch(stat.Name); len(matches) == 4 {
if len(matchs) < 3 { processTraffic(matches, stat.Value, tagTrafficMap)
} else if matches := clientTrafficRegex.FindStringSubmatch(stat.Name); len(matches) == 3 {
matchs := ClientTrafficRegex.FindStringSubmatch(stat.Name) processClientTraffic(matches, stat.Value, emailTrafficMap)
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
} }
} }
return mapToSlice(tagTrafficMap), mapToSlice(emailTrafficMap), nil
return traffics, clientTraffics, 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
} }

View File

@@ -60,14 +60,14 @@ func GetAccessPersistentPrevLogPath() string {
func GetAccessLogPath() (string, error) { func GetAccessLogPath() (string, error) {
config, err := os.ReadFile(GetConfigPath()) config, err := os.ReadFile(GetConfigPath())
if err != nil { if err != nil {
logger.Warningf("Something went wrong: %s", err) logger.Warningf("Failed to read configuration file: %s", err)
return "", err return "", err
} }
jsonConfig := map[string]interface{}{} jsonConfig := map[string]interface{}{}
err = json.Unmarshal([]byte(config), &jsonConfig) err = json.Unmarshal([]byte(config), &jsonConfig)
if err != nil { if err != nil {
logger.Warningf("Something went wrong: %s", err) logger.Warningf("Failed to parse JSON configuration: %s", err)
return "", err return "", err
} }
@@ -206,7 +206,7 @@ func (p *process) Start() (err error) {
err = os.MkdirAll(config.GetLogFolder(), 0o770) err = os.MkdirAll(config.GetLogFolder(), 0o770)
if err != nil { if err != nil {
logger.Warningf("Something went wrong: %s", err) logger.Warningf("Failed to create log folder: %s", err)
} }
configPath := GetConfigPath() configPath := GetConfigPath()
@@ -224,7 +224,7 @@ func (p *process) Start() (err error) {
go func() { go func() {
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
logger.Error("Failure in running xray-core: ", err) logger.Error("Failure in running xray-core:", err)
p.exitErr = err p.exitErr = err
} }
}() }()