mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-11-29 02:42:51 +00:00
Compare commits
13 commits
2041b226e4
...
8d182f2c40
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d182f2c40 | ||
|
|
784ed39930 | ||
|
|
538f7fd5d7 | ||
|
|
3b262cf180 | ||
|
|
4c7249c451 | ||
|
|
edd8b12988 | ||
|
|
5e953bae45 | ||
|
|
747af376f2 | ||
|
|
a3ccccfe52 | ||
|
|
3299d15f28 | ||
|
|
ae82373457 | ||
|
|
d65233cc2c | ||
|
|
11dc06863e |
20 changed files with 692 additions and 93 deletions
|
|
@ -38,6 +38,7 @@ func initModels() error {
|
||||||
&model.InboundClientIps{},
|
&model.InboundClientIps{},
|
||||||
&xray.ClientTraffic{},
|
&xray.ClientTraffic{},
|
||||||
&model.HistoryOfSeeders{},
|
&model.HistoryOfSeeders{},
|
||||||
|
&model.Server{},
|
||||||
}
|
}
|
||||||
for _, model := range models {
|
for _, model := range models {
|
||||||
if err := db.AutoMigrate(model); err != nil {
|
if err := db.AutoMigrate(model); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -119,3 +119,12 @@ type Client struct {
|
||||||
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
|
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
|
||||||
UpdatedAt int64 `json:"updated_at,omitempty"` // Last update timestamp
|
UpdatedAt int64 `json:"updated_at,omitempty"` // Last update timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
|
Name string `json:"name" gorm:"unique;not null"`
|
||||||
|
Address string `json:"address" gorm:"not null"`
|
||||||
|
Port int `json:"port" gorm:"not null"`
|
||||||
|
APIKey string `json:"apiKey" gorm:"not null"`
|
||||||
|
Enable bool `json:"enable" gorm:"default:true"`
|
||||||
|
}
|
||||||
|
|
|
||||||
41
go.mod
41
go.mod
|
|
@ -1,45 +1,45 @@
|
||||||
module github.com/mhsanaei/3x-ui/v2
|
module github.com/mhsanaei/3x-ui/v2
|
||||||
|
|
||||||
go 1.25.2
|
go 1.25.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.4
|
github.com/gin-contrib/gzip v1.2.5
|
||||||
github.com/gin-contrib/sessions v1.0.4
|
github.com/gin-contrib/sessions v1.0.4
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/go-ldap/ldap/v3 v3.4.12
|
github.com/go-ldap/ldap/v3 v3.4.12
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mymmrac/telego v1.3.0
|
github.com/mymmrac/telego v1.3.1
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
github.com/nicksnyder/go-i18n/v2 v2.6.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.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.9
|
github.com/shirou/gopsutil/v4 v4.25.10
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/valyala/fasthttp v1.67.0
|
github.com/valyala/fasthttp v1.68.0
|
||||||
github.com/xlzd/gotp v0.1.0
|
github.com/xlzd/gotp v0.1.0
|
||||||
github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6
|
github.com/xtls/xray-core v1.251015.0
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/crypto v0.43.0
|
golang.org/x/crypto v0.43.0
|
||||||
golang.org/x/sys v0.37.0
|
golang.org/x/sys v0.38.0
|
||||||
golang.org/x/text v0.30.0
|
golang.org/x/text v0.30.0
|
||||||
google.golang.org/grpc v1.76.0
|
google.golang.org/grpc v1.76.0
|
||||||
gorm.io/driver/sqlite v1.6.0
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.31.0
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
github.com/Azure/go-ntlmssp v0.0.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.14.1 // indirect
|
github.com/bytedance/sonic v1.14.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
||||||
github.com/ebitengine/purego v0.9.0 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
|
@ -57,7 +57,7 @@ require (
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
|
@ -68,19 +68,20 @@ require (
|
||||||
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/pires/go-proxyproto v0.8.1 // indirect
|
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.55.0 // indirect
|
github.com/quic-go/quic-go v0.56.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.1 // indirect
|
github.com/refraction-networking/utls v1.8.1 // 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.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagernet/sing v0.7.12 // indirect
|
github.com/sagernet/sing v0.7.13 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
github.com/tklauser/numcpus v0.10.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.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
|
|
@ -89,15 +90,15 @@ require (
|
||||||
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 // indirect
|
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.22.0 // indirect
|
golang.org/x/arch v0.23.0 // indirect
|
||||||
golang.org/x/mod v0.29.0 // indirect
|
golang.org/x/mod v0.29.0 // indirect
|
||||||
golang.org/x/net v0.46.0 // indirect
|
golang.org/x/net v0.46.0 // indirect
|
||||||
golang.org/x/sync v0.17.0 // indirect
|
golang.org/x/sync v0.18.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.38.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-20250521234502-f333402bd9cb // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||||
google.golang.org/protobuf v1.36.10 // indirect
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
|
|
|
||||||
80
go.sum
80
go.sum
|
|
@ -1,5 +1,5 @@
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
github.com/Azure/go-ntlmssp v0.0.1 h1:NqbqUHiVYjwBDsxM1KrllG7rnoHpcp40EWrpffsgcUc=
|
||||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
github.com/Azure/go-ntlmssp v0.0.1/go.mod h1:P/Wrai1IsNvkfWRRN0jvRobt7ZJdz4sHQ3dOjiEGDt0=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||||
|
|
@ -8,10 +8,10 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||||
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
|
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
|
@ -23,14 +23,14 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
|
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
|
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/gin-contrib/gzip v1.2.4 h1:yNz4EhPC2kHSZJD1oc1zwp7MLEhEZ3goQeGM3a1b6jU=
|
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
||||||
github.com/gin-contrib/gzip v1.2.4/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
||||||
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||||
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
|
|
@ -107,8 +107,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
|
@ -130,8 +130,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
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 v1.3.0 h1:y2bDDCioLgkcs+5luUaPgTNHKel1Qh30iUxFcMUrowg=
|
github.com/mymmrac/telego v1.3.1 h1:dI5D8LKWBw241W02LmJqoSLZXW3tuLokxVoNbIZUYQg=
|
||||||
github.com/mymmrac/telego v1.3.0/go.mod h1:0D2l/IA/gUFn4oqsi1O4/tSnlezw5jNV/ReFRDUEKk8=
|
github.com/mymmrac/telego v1.3.1/go.mod h1:3D0h4jJ3OzubY/gI4xDIGx4jkY26fmcPyqx0Lq24+zI=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
|
@ -148,8 +148,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
github.com/quic-go/quic-go v0.56.0 h1:q/TW+OLismmXAehgFLczhCDTYB3bFmua4D9lsNBWxvY=
|
||||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
github.com/quic-go/quic-go v0.56.0/go.mod h1:9gx5KsFQtw2oZ6GZTyh+7YEvOxWCL9WZAepnHxgAo6c=
|
||||||
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
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=
|
||||||
|
|
@ -158,24 +158,26 @@ 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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagernet/sing v0.7.12 h1:MpMbO56crPRZTbltoj1wGk4Xj9+GiwH1wTO4s3fz1EA=
|
github.com/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM=
|
||||||
github.com/sagernet/sing v0.7.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
|
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
|
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.9 h1:JImNpf6gCVhKgZhtaAHJ0serfFGtlfIlSC08eaKdTrU=
|
github.com/shirou/gopsutil/v4 v4.25.10 h1:at8lk/5T1OgtuCp+AwrDofFRjnvosn0nkN2OLQ6g8tA=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.9/go.mod h1:gxIxoC+7nQRwUl/xNhutXlD8lq+jxTgpIkEf3rADHL8=
|
github.com/shirou/gopsutil/v4 v4.25.10/go.mod h1:+kSwyC8DRUD9XXEHCAFjK+0nuArFJM0lva+StQAcskM=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||||
|
|
@ -184,14 +186,14 @@ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfj
|
||||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
||||||
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.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.67.0 h1:tqKlJMUP6iuNG8hGjK/s9J4kadH7HLV4ijEcPGsezac=
|
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
|
||||||
github.com/valyala/fasthttp v1.67.0/go.mod h1:qYSIpqt/0XNmShgo/8Aq8E3UYWVVwNS2QYmzd8WIEPM=
|
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
|
|
@ -202,8 +204,8 @@ github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||||
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU=
|
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535 h1:nwobseOLLRtdbP6z7Z2aVI97u8ZptTgD1ofovhAKmeU=
|
||||||
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
|
github.com/xtls/reality v0.0.0-20251014195629-e4eec4520535/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
|
||||||
github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6 h1:gwgJxWb9OABUJAYxiS33nQzk3MRVjidzBnHBrzKnxOw=
|
github.com/xtls/xray-core v1.251015.0 h1:P7b3vt8ShhH31k4h6VJ/Pxar3tY9eK+7S8eygd6rsP0=
|
||||||
github.com/xtls/xray-core v1.250911.1-0.20251015080723-b69a376aa1b6/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik=
|
github.com/xtls/xray-core v1.251015.0/go.mod h1:72ZU/srfutsNPmw9y8SCGRy0iccvshIRk8BNGR8D2Ik=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
|
@ -226,24 +228,24 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||||
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.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.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
|
|
@ -256,8 +258,8 @@ golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+Z
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
|
@ -273,8 +275,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.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,13 @@ config_after_install() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
/usr/local/x-ui/x-ui migrate
|
/usr/local/x-ui/x-ui migrate
|
||||||
|
|
||||||
|
local existing_apiKey=$(/usr/local/x-ui/x-ui setting -show true | grep -oP 'ApiKey: \K.*')
|
||||||
|
if [[ -z "$existing_apiKey" ]]; then
|
||||||
|
local config_apiKey=$(gen_random_string 32)
|
||||||
|
/usr/local/x-ui/x-ui setting -apiKey "${config_apiKey}"
|
||||||
|
echo -e "${green}Generated random API Key: ${config_apiKey}${plain}"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_x-ui() {
|
install_x-ui() {
|
||||||
|
|
|
||||||
16
main.go
16
main.go
|
|
@ -248,7 +248,8 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
|
// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
|
||||||
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
|
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool, apiKey string) {
|
||||||
|
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Database initialization failed:", err)
|
fmt.Println("Database initialization failed:", err)
|
||||||
|
|
@ -258,6 +259,15 @@ func updateSetting(port int, username string, password string, webBasePath strin
|
||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
userService := service.UserService{}
|
userService := service.UserService{}
|
||||||
|
|
||||||
|
if apiKey != "" {
|
||||||
|
err := settingService.SetAPIKey(apiKey)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to set API Key:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("API Key set successfully: %v\n", apiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if port > 0 {
|
if port > 0 {
|
||||||
err := settingService.SetPort(port)
|
err := settingService.SetPort(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -424,9 +434,11 @@ func main() {
|
||||||
var show bool
|
var show bool
|
||||||
var getCert bool
|
var getCert bool
|
||||||
var resetTwoFactor bool
|
var resetTwoFactor bool
|
||||||
|
var apiKey string
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
||||||
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
|
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
|
||||||
|
settingCmd.StringVar(&apiKey, "apiKey", "", "Set API Key")
|
||||||
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 base path for Panel")
|
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
||||||
|
|
@ -476,7 +488,7 @@ func main() {
|
||||||
if reset {
|
if reset {
|
||||||
resetSetting()
|
resetSetting()
|
||||||
} else {
|
} else {
|
||||||
updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor)
|
updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor, apiKey)
|
||||||
}
|
}
|
||||||
if show {
|
if show {
|
||||||
showSetting(show)
|
showSetting(show)
|
||||||
|
|
|
||||||
|
|
@ -162,26 +162,43 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
|
serverService := service.MultiServerService{}
|
||||||
|
servers, err := serverService.GetServers()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get servers for subscription:", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var links []string
|
||||||
|
for _, server := range servers {
|
||||||
|
if !server.Enable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var link string
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
return s.genVmessLink(inbound, email)
|
link = s.genVmessLink(inbound, email, server)
|
||||||
case "vless":
|
case "vless":
|
||||||
return s.genVlessLink(inbound, email)
|
link = s.genVlessLink(inbound, email, server)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
return s.genTrojanLink(inbound, email)
|
link = s.genTrojanLink(inbound, email, server)
|
||||||
case "shadowsocks":
|
case "shadowsocks":
|
||||||
return s.genShadowsocksLink(inbound, email)
|
link = s.genShadowsocksLink(inbound, email, server)
|
||||||
}
|
}
|
||||||
return ""
|
if link != "" {
|
||||||
|
links = append(links, link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(links, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVmessLink(inbound *model.Inbound, email string, server *model.Server) string {
|
||||||
if inbound.Protocol != model.VMESS {
|
if inbound.Protocol != model.VMESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
obj := map[string]any{
|
obj := map[string]any{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"add": s.address,
|
"add": server.Address,
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
}
|
}
|
||||||
|
|
@ -294,7 +311,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
newObj[key] = value
|
newObj[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
|
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string), server.Name)
|
||||||
newObj["add"] = ep["dest"].(string)
|
newObj["add"] = ep["dest"].(string)
|
||||||
newObj["port"] = int(ep["port"].(float64))
|
newObj["port"] = int(ep["port"].(float64))
|
||||||
|
|
||||||
|
|
@ -310,14 +327,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||||
return links
|
return links
|
||||||
}
|
}
|
||||||
|
|
||||||
obj["ps"] = s.genRemark(inbound, email, "")
|
obj["ps"] = s.genRemark(inbound, email, "", server.Name)
|
||||||
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genVlessLink(inbound *model.Inbound, email string, server *model.Server) string {
|
||||||
address := s.address
|
address := server.Address
|
||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -497,7 +514,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string), server.Name)
|
||||||
|
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
|
|
@ -518,12 +535,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "", server.Name)
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, server *model.Server) string {
|
||||||
address := s.address
|
address := server.Address
|
||||||
if inbound.Protocol != model.Trojan {
|
if inbound.Protocol != model.Trojan {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -692,7 +709,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string), server.Name)
|
||||||
|
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
|
|
@ -714,12 +731,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "", server.Name)
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, server *model.Server) string {
|
||||||
address := s.address
|
address := server.Address
|
||||||
if inbound.Protocol != model.Shadowsocks {
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -859,7 +876,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string), server.Name)
|
||||||
|
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
links += "\n"
|
links += "\n"
|
||||||
|
|
@ -880,17 +897,18 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
url.Fragment = s.genRemark(inbound, email, "", server.Name)
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string, serverName string) string {
|
||||||
separationChar := string(s.remarkModel[0])
|
separationChar := string(s.remarkModel[0])
|
||||||
orderChars := s.remarkModel[1:]
|
orderChars := s.remarkModel[1:]
|
||||||
orders := map[byte]string{
|
orders := map[byte]string{
|
||||||
'i': "",
|
'i': "",
|
||||||
'e': "",
|
'e': "",
|
||||||
'o': "",
|
'o': "",
|
||||||
|
's': "",
|
||||||
}
|
}
|
||||||
if len(email) > 0 {
|
if len(email) > 0 {
|
||||||
orders['e'] = email
|
orders['e'] = email
|
||||||
|
|
@ -901,6 +919,9 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
||||||
if len(extra) > 0 {
|
if len(extra) > 0 {
|
||||||
orders['o'] = extra
|
orders['o'] = extra
|
||||||
}
|
}
|
||||||
|
if len(serverName) > 0 {
|
||||||
|
orders['s'] = serverName
|
||||||
|
}
|
||||||
|
|
||||||
var remark []string
|
var remark []string
|
||||||
for i := 0; i < len(orderChars); i++ {
|
for i := 0; i < len(orderChars); i++ {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||||
"github.com/mhsanaei/3x-ui/v2/web/service"
|
"github.com/mhsanaei/3x-ui/v2/web/service"
|
||||||
"github.com/mhsanaei/3x-ui/v2/web/session"
|
"github.com/mhsanaei/3x-ui/v2/web/session"
|
||||||
|
|
|
||||||
89
web/controller/multi_server_controller.go
Normal file
89
web/controller/multi_server_controller.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MultiServerController struct {
|
||||||
|
multiServerService service.MultiServerService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMultiServerController(g *gin.RouterGroup) *MultiServerController {
|
||||||
|
c := &MultiServerController{}
|
||||||
|
c.initRouter(g)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
|
||||||
|
g = g.Group("/server")
|
||||||
|
|
||||||
|
g.GET("/list", c.getServers)
|
||||||
|
g.POST("/add", c.addServer)
|
||||||
|
g.POST("/del/:id", c.delServer)
|
||||||
|
g.POST("/update/:id", c.updateServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultiServerController) getServers(ctx *gin.Context) {
|
||||||
|
servers, err := c.multiServerService.GetServers()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Failed to get servers", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(ctx, servers, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultiServerController) addServer(ctx *gin.Context) {
|
||||||
|
server := &model.Server{}
|
||||||
|
err := ctx.ShouldBind(server)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Invalid data", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.multiServerService.AddServer(server)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Failed to add server", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(ctx, "Server added successfully", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultiServerController) delServer(ctx *gin.Context) {
|
||||||
|
id, err := strconv.Atoi(ctx.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Invalid ID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.multiServerService.DeleteServer(id)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Failed to delete server", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(ctx, "Server deleted successfully", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MultiServerController) updateServer(ctx *gin.Context) {
|
||||||
|
id, err := strconv.Atoi(ctx.Param("id"))
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Invalid ID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
server := &model.Server{
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
err = ctx.ShouldBind(server)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Invalid data", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = c.multiServerService.UpdateServer(server)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(ctx, "Failed to update server", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonMsg(ctx, "Server updated successfully", nil)
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ func (a *XUIController) initRouter(g *gin.RouterGroup) {
|
||||||
|
|
||||||
g.GET("/", a.index)
|
g.GET("/", a.index)
|
||||||
g.GET("/inbounds", a.inbounds)
|
g.GET("/inbounds", a.inbounds)
|
||||||
|
g.GET("/servers", a.servers)
|
||||||
g.GET("/settings", a.settings)
|
g.GET("/settings", a.settings)
|
||||||
g.GET("/xray", a.xraySettings)
|
g.GET("/xray", a.xraySettings)
|
||||||
|
|
||||||
|
|
@ -52,3 +53,7 @@ func (a *XUIController) settings(c *gin.Context) {
|
||||||
func (a *XUIController) xraySettings(c *gin.Context) {
|
func (a *XUIController) xraySettings(c *gin.Context) {
|
||||||
html(c, "xray.html", "pages.xray.title", nil)
|
html(c, "xray.html", "pages.xray.title", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *XUIController) servers(c *gin.Context) {
|
||||||
|
html(c, "servers.html", "Servers", nil)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,11 @@
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
title: '{{ i18n "menu.inbounds"}}'
|
title: '{{ i18n "menu.inbounds"}}'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: '{{ .base_path }}panel/servers',
|
||||||
|
icon: 'cloud-server',
|
||||||
|
title: 'Servers'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: '{{ .base_path }}panel/settings',
|
key: '{{ .base_path }}panel/settings',
|
||||||
icon: 'setting',
|
icon: 'setting',
|
||||||
|
|
|
||||||
|
|
@ -844,9 +844,11 @@
|
||||||
text = `<td>${log.Email}</td>`;
|
text = `<td>${log.Email}</td>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { locale, timeZone } = Intl.DateTimeFormat().resolvedOptions();
|
||||||
|
|
||||||
formattedLogs += `
|
formattedLogs += `
|
||||||
<tr ${outboundColor}>
|
<tr ${outboundColor}>
|
||||||
<td><b>${new Date(log.DateTime).toLocaleString()}</b></td>
|
<td><b>${new Date(log.DateTime).toLocaleString(locale, { timeZone })}</b></td>
|
||||||
<td>${log.FromAddress}</td>
|
<td>${log.FromAddress}</td>
|
||||||
<td>${log.ToAddress}</td>
|
<td>${log.ToAddress}</td>
|
||||||
<td>${log.Inbound}</td>
|
<td>${log.Inbound}</td>
|
||||||
|
|
|
||||||
165
web/html/servers.html
Normal file
165
web/html/servers.html
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
{{template "header" .}}
|
||||||
|
|
||||||
|
<div id="app" class="row" v-cloak>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">Server Management</h3>
|
||||||
|
<div class="card-tools">
|
||||||
|
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Address</th>
|
||||||
|
<th>Port</th>
|
||||||
|
<th>Enabled</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(server, index) in servers">
|
||||||
|
<td>{{index + 1}}</td>
|
||||||
|
<td>{{server.name}}</td>
|
||||||
|
<td>{{server.address}}</td>
|
||||||
|
<td>{{server.port}}</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="server.enable" class="badge bg-success">Yes</span>
|
||||||
|
<span v-else class="badge bg-danger">No</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-info btn-sm" @click="showEditModal(server)">Edit</button>
|
||||||
|
<button class="btn btn-danger btn-sm" @click="deleteServer(server.id)">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add/Edit Modal -->
|
||||||
|
<div class="modal fade" id="serverModal" tabindex="-1" role="dialog">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{{modal.title}}</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Name</label>
|
||||||
|
<input type="text" class="form-control" v-model="modal.server.name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Address (IP or Domain)</label>
|
||||||
|
<input type="text" class="form-control" v-model="modal.server.address">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Port</label>
|
||||||
|
<input type="number" class="form-control" v-model.number="modal.server.port">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>API Key</label>
|
||||||
|
<input type="text" class="form-control" v-model="modal.server.apiKey">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" v-model="modal.server.enable">
|
||||||
|
<label class="form-check-label">Enabled</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||||
|
<button type="button" class="btn btn-primary" @click="saveServer">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
servers: [],
|
||||||
|
modal: {
|
||||||
|
title: '',
|
||||||
|
server: {
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
port: 0,
|
||||||
|
apiKey: '',
|
||||||
|
enable: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadServers() {
|
||||||
|
axios.get('{{.base_path}}server/list')
|
||||||
|
.then(response => {
|
||||||
|
this.servers = response.data.obj;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert(error.response.data.msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showAddModal() {
|
||||||
|
this.modal.title = 'Add Server';
|
||||||
|
this.modal.server = {
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
port: 0,
|
||||||
|
apiKey: '',
|
||||||
|
enable: true
|
||||||
|
};
|
||||||
|
$('#serverModal').modal('show');
|
||||||
|
},
|
||||||
|
showEditModal(server) {
|
||||||
|
this.modal.title = 'Edit Server';
|
||||||
|
this.modal.server = Object.assign({}, server);
|
||||||
|
$('#serverModal').modal('show');
|
||||||
|
},
|
||||||
|
saveServer() {
|
||||||
|
let url = '{{.base_path}}server/add';
|
||||||
|
if (this.modal.server.id) {
|
||||||
|
url = `{{.base_path}}server/update/${this.modal.server.id}`;
|
||||||
|
}
|
||||||
|
axios.post(url, this.modal.server)
|
||||||
|
.then(response => {
|
||||||
|
alert(response.data.msg);
|
||||||
|
$('#serverModal').modal('hide');
|
||||||
|
this.loadServers();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert(error.response.data.msg);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
deleteServer(id) {
|
||||||
|
if (!confirm('Are you sure you want to delete this server?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
axios.post(`{{.base_path}}server/del/${id}`)
|
||||||
|
.then(response => {
|
||||||
|
alert(response.data.msg);
|
||||||
|
this.loadServers();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
alert(error.response.data.msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadServers();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{template "footer" .}}
|
||||||
34
web/middleware/auth.go
Normal file
34
web/middleware/auth.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"x-ui/web/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApiAuth() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
apiKey := c.GetHeader("Api-Key")
|
||||||
|
if apiKey == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "API key is required"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
settingService := service.SettingService{}
|
||||||
|
panelAPIKey, err := settingService.GetAPIKey()
|
||||||
|
if err != nil || panelAPIKey == "" {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "API key not configured on the panel"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if apiKey != panelAPIKey {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,11 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -673,6 +676,11 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||||
}
|
}
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
body, _ := json.Marshal(data)
|
||||||
|
s.syncWithSlaves("POST", "/panel/inbound/api/addClient", bytes.NewReader(body))
|
||||||
|
}
|
||||||
|
|
||||||
return needRestart, tx.Save(oldInbound).Error
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -761,6 +769,11 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||||
s.xrayApi.Close()
|
s.xrayApi.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
s.syncWithSlaves("POST", fmt.Sprintf("/panel/inbound/api/%d/delClient/%s", inboundId, clientId), nil)
|
||||||
|
}
|
||||||
|
|
||||||
return needRestart, db.Save(oldInbound).Error
|
return needRestart, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -936,6 +949,12 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
logger.Debug("Client old email not found")
|
logger.Debug("Client old email not found")
|
||||||
needRestart = true
|
needRestart = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
body, _ := json.Marshal(data)
|
||||||
|
s.syncWithSlaves("POST", fmt.Sprintf("/panel/inbound/api/updateClient/%s", clientId), bytes.NewReader(body))
|
||||||
|
}
|
||||||
|
|
||||||
return needRestart, tx.Save(oldInbound).Error
|
return needRestart, tx.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2379,6 +2398,44 @@ func (s *InboundService) FilterAndSortClientEmails(emails []string) ([]string, [
|
||||||
|
|
||||||
return validEmails, extraEmails, nil
|
return validEmails, extraEmails, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) syncWithSlaves(method string, path string, body io.Reader) {
|
||||||
|
serverService := MultiServerService{}
|
||||||
|
servers, err := serverService.GetServers()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("Failed to get servers for syncing:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range servers {
|
||||||
|
if !server.Enable {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("http://%s:%d%s", server.Address, server.Port, path)
|
||||||
|
req, err := http.NewRequest(method, url, body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Failed to create request for server %s: %v", server.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Api-Key", server.APIKey)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warningf("Failed to send request to server %s: %v", server.Name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
|
logger.Warningf("Failed to sync with server %s. Status: %s, Body: %s", server.Name, resp.Status, string(bodyBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (bool, error) {
|
func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (bool, error) {
|
||||||
oldInbound, err := s.GetInbound(inboundId)
|
oldInbound, err := s.GetInbound(inboundId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -2470,4 +2527,5 @@ func (s *InboundService) DelInboundClientByEmail(inboundId int, email string) (b
|
||||||
}
|
}
|
||||||
|
|
||||||
return needRestart, db.Save(oldInbound).Error
|
return needRestart, db.Save(oldInbound).Error
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
web/service/inbound_service_sync_test.go
Normal file
72
web/service/inbound_service_sync_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInboundServiceSync(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
// Mock server to simulate a slave
|
||||||
|
var receivedApiKey string
|
||||||
|
var receivedBody []byte
|
||||||
|
mockSlave := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
receivedApiKey = r.Header.Get("Api-Key")
|
||||||
|
receivedBody, _ = io.ReadAll(r.Body)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
defer mockSlave.Close()
|
||||||
|
|
||||||
|
// Add the mock slave to the database
|
||||||
|
multiServerService := MultiServerService{}
|
||||||
|
mockSlaveURL, _ := url.Parse(mockSlave.URL)
|
||||||
|
mockSlavePort, _ := strconv.Atoi(mockSlaveURL.Port())
|
||||||
|
slaveServer := &model.Server{
|
||||||
|
Name: "mock-slave",
|
||||||
|
Address: mockSlaveURL.Hostname(),
|
||||||
|
Port: mockSlavePort,
|
||||||
|
APIKey: "slave-api-key",
|
||||||
|
Enable: true,
|
||||||
|
}
|
||||||
|
multiServerService.AddServer(slaveServer)
|
||||||
|
|
||||||
|
// Create a test inbound and client
|
||||||
|
inboundService := InboundService{}
|
||||||
|
db := database.GetDB()
|
||||||
|
testInbound := &model.Inbound{
|
||||||
|
UserId: 1,
|
||||||
|
Remark: "test-inbound",
|
||||||
|
Enable: true,
|
||||||
|
Settings: `{"clients":[]}`,
|
||||||
|
}
|
||||||
|
db.Create(testInbound)
|
||||||
|
|
||||||
|
clientData := model.Client{
|
||||||
|
Email: "test@example.com",
|
||||||
|
ID: "test-id",
|
||||||
|
}
|
||||||
|
clientBytes, _ := json.Marshal([]model.Client{clientData})
|
||||||
|
inboundData := &model.Inbound{
|
||||||
|
Id: testInbound.Id,
|
||||||
|
Settings: string(clientBytes),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test AddInboundClient sync
|
||||||
|
inboundService.AddInboundClient(inboundData)
|
||||||
|
|
||||||
|
assert.Equal(t, "slave-api-key", receivedApiKey)
|
||||||
|
var receivedInbound model.Inbound
|
||||||
|
json.Unmarshal(receivedBody, &receivedInbound)
|
||||||
|
assert.Equal(t, 1, receivedInbound.Id)
|
||||||
|
}
|
||||||
37
web/service/multi_server_service.go
Normal file
37
web/service/multi_server_service.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MultiServerService struct{}
|
||||||
|
|
||||||
|
func (s *MultiServerService) GetServers() ([]*model.Server, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var servers []*model.Server
|
||||||
|
err := db.Find(&servers).Error
|
||||||
|
return servers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MultiServerService) GetServer(id int) (*model.Server, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var server model.Server
|
||||||
|
err := db.First(&server, id).Error
|
||||||
|
return &server, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MultiServerService) AddServer(server *model.Server) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Create(server).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MultiServerService) UpdateServer(server *model.Server) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Save(server).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MultiServerService) DeleteServer(id int) error {
|
||||||
|
db := database.GetDB()
|
||||||
|
return db.Delete(&model.Server{}, id).Error
|
||||||
|
}
|
||||||
63
web/service/multi_server_service_test.go
Normal file
63
web/service/multi_server_service_test.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setup() {
|
||||||
|
dbPath := "test.db"
|
||||||
|
os.Remove(dbPath)
|
||||||
|
database.InitDB(dbPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func teardown() {
|
||||||
|
db, _ := database.GetDB().DB()
|
||||||
|
db.Close()
|
||||||
|
os.Remove("test.db")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultiServerService(t *testing.T) {
|
||||||
|
setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
service := MultiServerService{}
|
||||||
|
|
||||||
|
// Test AddServer
|
||||||
|
server := &model.Server{
|
||||||
|
Name: "test-server",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Port: 54321,
|
||||||
|
APIKey: "test-key",
|
||||||
|
Enable: true,
|
||||||
|
}
|
||||||
|
err := service.AddServer(server)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Test GetServer
|
||||||
|
retrievedServer, err := service.GetServer(server.Id)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, server.Name, retrievedServer.Name)
|
||||||
|
|
||||||
|
// Test GetServers
|
||||||
|
servers, err := service.GetServers()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, servers, 1)
|
||||||
|
|
||||||
|
// Test UpdateServer
|
||||||
|
retrievedServer.Name = "updated-server"
|
||||||
|
err = service.UpdateServer(retrievedServer)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
updatedServer, _ := service.GetServer(server.Id)
|
||||||
|
assert.Equal(t, "updated-server", updatedServer.Name)
|
||||||
|
|
||||||
|
// Test DeleteServer
|
||||||
|
err = service.DeleteServer(server.Id)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
_, err = service.GetServer(server.Id)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
@ -794,17 +794,17 @@ func (s *ServerService) GetXrayLogs(
|
||||||
for i, part := range parts {
|
for i, part := range parts {
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
dateTime, err := time.Parse("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1])
|
dateTime, err := time.ParseInLocation("2006/01/02 15:04:05.999999", parts[0]+" "+parts[1], time.Local)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
entry.DateTime = dateTime
|
entry.DateTime = dateTime.UTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
if part == "from" {
|
if part == "from" {
|
||||||
entry.FromAddress = parts[i+1]
|
entry.FromAddress = strings.TrimLeft(parts[i+1], "/")
|
||||||
} else if part == "accepted" {
|
} else if part == "accepted" {
|
||||||
entry.ToAddress = parts[i+1]
|
entry.ToAddress = strings.TrimLeft(parts[i+1], "/")
|
||||||
} else if strings.HasPrefix(part, "[") {
|
} else if strings.HasPrefix(part, "[") {
|
||||||
entry.Inbound = part[1:]
|
entry.Inbound = part[1:]
|
||||||
} else if strings.HasSuffix(part, "]") {
|
} else if strings.HasSuffix(part, "]") {
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,21 @@ func (s *SettingService) getSetting(key string) (*model.Setting, error) {
|
||||||
return setting, nil
|
return setting, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetAPIKey() (string, error) {
|
||||||
|
setting, err := s.getSetting("ApiKey")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if setting == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return setting.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetAPIKey(apiKey string) error {
|
||||||
|
return s.saveSetting("ApiKey", apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) saveSetting(key string, value string) error {
|
func (s *SettingService) saveSetting(key string, value string) error {
|
||||||
setting, err := s.getSetting(key)
|
setting, err := s.getSetting(key)
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue