mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-04-17 21:15:50 +00:00
Compare commits
9 commits
c57e3b021e
...
fd8c17868d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd8c17868d | ||
|
|
0e006653f2 | ||
|
|
a2097ad062 | ||
|
|
52fdf5d429 | ||
|
|
34d8885075 | ||
|
|
5740996436 | ||
|
|
874aae8080 | ||
|
|
842fae18d7 | ||
|
|
cb83e57b2a |
40 changed files with 413 additions and 188 deletions
|
|
@ -1 +1 @@
|
|||
2.8.10
|
||||
2.8.11
|
||||
|
|
@ -117,6 +117,7 @@ type Client struct {
|
|||
Enable bool `json:"enable" form:"enable"` // Whether the client is enabled
|
||||
TgID int64 `json:"tgId" form:"tgId"` // Telegram user ID for notifications
|
||||
SubID string `json:"subId" form:"subId"` // Subscription identifier
|
||||
SubHost string `json:"subHost,omitempty"` // Optional host/IP override for exported client links
|
||||
Comment string `json:"comment" form:"comment"` // Client comment
|
||||
Reset int `json:"reset" form:"reset"` // Reset period in days
|
||||
CreatedAt int64 `json:"created_at,omitempty"` // Creation timestamp
|
||||
|
|
|
|||
21
go.mod
21
go.mod
|
|
@ -5,18 +5,18 @@ go 1.26.0
|
|||
require (
|
||||
github.com/gin-contrib/gzip v1.2.5
|
||||
github.com/gin-contrib/sessions v1.0.4
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mymmrac/telego v1.6.0
|
||||
github.com/mymmrac/telego v1.7.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.26.1
|
||||
github.com/shirou/gopsutil/v4 v4.26.2
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/valyala/fasthttp v1.69.0
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
|
|
@ -39,7 +39,7 @@ require (
|
|||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
|
|
@ -60,7 +60,7 @@ require (
|
|||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.34 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
|
|
@ -72,29 +72,30 @@ require (
|
|||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sagernet/sing v0.7.18 // indirect
|
||||
github.com/sagernet/sing v0.8.1 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fastjson v1.6.7 // indirect
|
||||
github.com/valyala/fastjson v1.6.10 // indirect
|
||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
|
|
|
|||
42
go.sum
42
go.sum
|
|
@ -23,8 +23,8 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||
|
|
@ -35,8 +35,8 @@ github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kb
|
|||
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/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
||||
|
|
@ -117,8 +117,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
|
|
@ -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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/mymmrac/telego v1.6.0 h1:Zc8rgyHozvd/7ZgyrigyHdAF9koHYMfilYfyB6wlFC0=
|
||||
github.com/mymmrac/telego v1.6.0/go.mod h1:xt6ZWA8zi8KmuzryE1ImEdl9JSwjHNpM4yhC7D8hU4Y=
|
||||
github.com/mymmrac/telego v1.7.0 h1:yRO/l00tFGG4nY66ufUKb4ARqv7qx9+LsjQv/b0NEyo=
|
||||
github.com/mymmrac/telego v1.7.0/go.mod h1:pdLV346EgVuq7Xrh3kMggeBiazeHhsdEoK0RTEOPXRM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
|
|
@ -156,12 +156,12 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E=
|
||||
github.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.8.1 h1:Li+zg4xdiMsvdX4j50TPqmSG8LF/TB9US2qlAN40izU=
|
||||
github.com/sagernet/sing v0.8.1/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/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
|
@ -187,8 +187,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM=
|
||||
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
||||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
|
|
@ -203,6 +203,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
|
|||
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/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
|
|
@ -227,12 +229,12 @@ golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
|
|||
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -255,8 +257,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=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ is_port_in_use() {
|
|||
install_base() {
|
||||
case "${release}" in
|
||||
ubuntu | debian | armbian)
|
||||
apt-get update && apt-get install -y -q curl tar tzdata socat ca-certificates
|
||||
apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates
|
||||
;;
|
||||
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
|
||||
dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
|||
if err != nil || len(inbounds) == 0 {
|
||||
return "", "", err
|
||||
}
|
||||
requestHost := strings.TrimSpace(host)
|
||||
defaultClientHost := s.SubService.getDefaultClientHost()
|
||||
|
||||
var header string
|
||||
var traffic xray.ClientTraffic
|
||||
|
|
@ -103,7 +105,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
|
|||
for _, client := range clients {
|
||||
if client.Enable && client.SubID == subId {
|
||||
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
||||
newConfigs := s.getConfig(inbound, client, host)
|
||||
clientHost := s.SubService.ResolveClientHostWithDefault(inbound, client, requestHost, defaultClientHost)
|
||||
newConfigs := s.getConfig(inbound, client, clientHost)
|
||||
configArray = append(configArray, newConfigs...)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,8 @@ import (
|
|||
|
||||
// SubService provides business logic for generating subscription links and managing subscription data.
|
||||
type SubService struct {
|
||||
address string
|
||||
showInfo bool
|
||||
remarkModel string
|
||||
datepicker string
|
||||
inboundService service.InboundService
|
||||
settingService service.SettingService
|
||||
}
|
||||
|
|
@ -40,7 +38,8 @@ func NewSubService(showInfo bool, remarkModel string) *SubService {
|
|||
|
||||
// GetSubs retrieves subscription links for a given subscription ID and host.
|
||||
func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.ClientTraffic, error) {
|
||||
s.address = host
|
||||
requestHost := strings.TrimSpace(host)
|
||||
defaultClientHost := s.getDefaultClientHost()
|
||||
var result []string
|
||||
var traffic xray.ClientTraffic
|
||||
var lastOnline int64
|
||||
|
|
@ -54,10 +53,6 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
|||
return nil, 0, traffic, common.NewError("No inbounds found with ", subId)
|
||||
}
|
||||
|
||||
s.datepicker, err = s.settingService.GetDatepicker()
|
||||
if err != nil {
|
||||
s.datepicker = "gregorian"
|
||||
}
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.GetClients(inbound)
|
||||
if err != nil {
|
||||
|
|
@ -76,7 +71,7 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, int64, xray.C
|
|||
}
|
||||
for _, client := range clients {
|
||||
if client.Enable && client.SubID == subId {
|
||||
link := s.getLink(inbound, client.Email)
|
||||
link := s.getLink(inbound, client.Email, requestHost, defaultClientHost)
|
||||
result = append(result, link)
|
||||
ct := s.getClientTraffics(inbound.ClientStats, client.Email)
|
||||
clientTraffics = append(clientTraffics, ct)
|
||||
|
|
@ -161,33 +156,87 @@ func (s *SubService) getFallbackMaster(dest string, streamSettings string) (stri
|
|||
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
||||
}
|
||||
|
||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||
func (s *SubService) getDefaultClientHost() string {
|
||||
defaultHost, err := s.settingService.GetSubDefaultHost()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(defaultHost)
|
||||
}
|
||||
|
||||
func isWildcardListen(listen string) bool {
|
||||
switch strings.ToLower(strings.TrimSpace(listen)) {
|
||||
case "", "0.0.0.0", "::", "::0":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubService) getInboundSubHost(inbound *model.Inbound) string {
|
||||
var settings map[string]any
|
||||
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
||||
return ""
|
||||
}
|
||||
subHost, _ := settings["subHost"].(string)
|
||||
return strings.TrimSpace(subHost)
|
||||
}
|
||||
|
||||
func (s *SubService) resolveAddress(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
|
||||
if host := strings.TrimSpace(client.SubHost); host != "" {
|
||||
return host
|
||||
}
|
||||
if host := s.getInboundSubHost(inbound); host != "" {
|
||||
return host
|
||||
}
|
||||
if host := strings.TrimSpace(defaultClientHost); host != "" {
|
||||
return host
|
||||
}
|
||||
if !isWildcardListen(inbound.Listen) {
|
||||
return inbound.Listen
|
||||
}
|
||||
return requestHost
|
||||
}
|
||||
|
||||
func (s *SubService) ResolveClientHost(inbound *model.Inbound, client model.Client, requestHost string) string {
|
||||
return s.ResolveClientHostWithDefault(inbound, client, requestHost, s.getDefaultClientHost())
|
||||
}
|
||||
|
||||
func (s *SubService) ResolveClientHostWithDefault(inbound *model.Inbound, client model.Client, requestHost string, defaultClientHost string) string {
|
||||
host := strings.TrimSpace(requestHost)
|
||||
return s.resolveAddress(inbound, client, host, defaultClientHost)
|
||||
}
|
||||
|
||||
func findClientByEmail(clients []model.Client, email string) (model.Client, int) {
|
||||
for i, client := range clients {
|
||||
if client.Email == email {
|
||||
return client, i
|
||||
}
|
||||
}
|
||||
return model.Client{}, -1
|
||||
}
|
||||
|
||||
func (s *SubService) getLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||
switch inbound.Protocol {
|
||||
case "vmess":
|
||||
return s.genVmessLink(inbound, email)
|
||||
return s.genVmessLink(inbound, email, requestHost, defaultClientHost)
|
||||
case "vless":
|
||||
return s.genVlessLink(inbound, email)
|
||||
return s.genVlessLink(inbound, email, requestHost, defaultClientHost)
|
||||
case "trojan":
|
||||
return s.genTrojanLink(inbound, email)
|
||||
return s.genTrojanLink(inbound, email, requestHost, defaultClientHost)
|
||||
case "shadowsocks":
|
||||
return s.genShadowsocksLink(inbound, email)
|
||||
return s.genShadowsocksLink(inbound, email, requestHost, defaultClientHost)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||
if inbound.Protocol != model.VMESS {
|
||||
return ""
|
||||
}
|
||||
var address string
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
address = s.address
|
||||
} else {
|
||||
address = inbound.Listen
|
||||
}
|
||||
obj := map[string]any{
|
||||
"v": "2",
|
||||
"add": address,
|
||||
"add": "",
|
||||
"port": inbound.Port,
|
||||
"type": "none",
|
||||
}
|
||||
|
|
@ -274,15 +323,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||
}
|
||||
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
clientIndex := -1
|
||||
for i, client := range clients {
|
||||
if client.Email == email {
|
||||
clientIndex = i
|
||||
break
|
||||
}
|
||||
client, clientIndex := findClientByEmail(clients, email)
|
||||
if clientIndex < 0 {
|
||||
return ""
|
||||
}
|
||||
obj["id"] = clients[clientIndex].ID
|
||||
obj["scy"] = clients[clientIndex].Security
|
||||
obj["add"] = s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||
obj["id"] = client.ID
|
||||
obj["scy"] = client.Security
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
||||
|
|
@ -319,28 +366,18 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||
}
|
||||
|
||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
var address string
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
address = s.address
|
||||
} else {
|
||||
address = inbound.Listen
|
||||
}
|
||||
|
||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||
if inbound.Protocol != model.VLESS {
|
||||
return ""
|
||||
}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
clientIndex := -1
|
||||
for i, client := range clients {
|
||||
if client.Email == email {
|
||||
clientIndex = i
|
||||
break
|
||||
}
|
||||
client, clientIndex := findClientByEmail(clients, email)
|
||||
if clientIndex < 0 {
|
||||
return ""
|
||||
}
|
||||
uuid := clients[clientIndex].ID
|
||||
uuid := client.ID
|
||||
port := inbound.Port
|
||||
streamNetwork := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
|
|
@ -430,8 +467,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||
}
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
if streamNetwork == "tcp" && len(client.Flow) > 0 {
|
||||
params["flow"] = client.Flow
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -464,8 +501,8 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||
params["spx"] = "/" + random.Seq(15)
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
if streamNetwork == "tcp" && len(client.Flow) > 0 {
|
||||
params["flow"] = client.Flow
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -508,6 +545,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||
return strings.Join(links, "\n")
|
||||
}
|
||||
|
||||
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||
url, _ := url.Parse(link)
|
||||
q := url.Query()
|
||||
|
|
@ -523,27 +561,18 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||
return url.String()
|
||||
}
|
||||
|
||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
||||
var address string
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
address = s.address
|
||||
} else {
|
||||
address = inbound.Listen
|
||||
}
|
||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||
if inbound.Protocol != model.Trojan {
|
||||
return ""
|
||||
}
|
||||
var stream map[string]any
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
clientIndex := -1
|
||||
for i, client := range clients {
|
||||
if client.Email == email {
|
||||
clientIndex = i
|
||||
break
|
||||
}
|
||||
client, clientIndex := findClientByEmail(clients, email)
|
||||
if clientIndex < 0 {
|
||||
return ""
|
||||
}
|
||||
password := clients[clientIndex].Password
|
||||
password := client.Password
|
||||
port := inbound.Port
|
||||
streamNetwork := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
|
|
@ -656,8 +685,8 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||
params["spx"] = "/" + random.Seq(15)
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
params["flow"] = clients[clientIndex].Flow
|
||||
if streamNetwork == "tcp" && len(client.Flow) > 0 {
|
||||
params["flow"] = client.Flow
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -703,6 +732,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||
return links
|
||||
}
|
||||
|
||||
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||
|
||||
url, _ := url.Parse(link)
|
||||
|
|
@ -719,13 +749,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||
return url.String()
|
||||
}
|
||||
|
||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
||||
var address string
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
address = s.address
|
||||
} else {
|
||||
address = inbound.Listen
|
||||
}
|
||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string, requestHost string, defaultClientHost string) string {
|
||||
if inbound.Protocol != model.Shadowsocks {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -737,12 +761,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||
inboundPassword := settings["password"].(string)
|
||||
method := settings["method"].(string)
|
||||
clientIndex := -1
|
||||
for i, client := range clients {
|
||||
if client.Email == email {
|
||||
clientIndex = i
|
||||
break
|
||||
}
|
||||
client, clientIndex := findClientByEmail(clients, email)
|
||||
if clientIndex < 0 {
|
||||
return ""
|
||||
}
|
||||
streamNetwork := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
|
|
@ -827,9 +848,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||
}
|
||||
}
|
||||
|
||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||
encPart := fmt.Sprintf("%s:%s", method, client.Password)
|
||||
if method[0] == '2' {
|
||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, client.Password)
|
||||
}
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]any)
|
||||
|
|
@ -870,6 +891,7 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||
return links
|
||||
}
|
||||
|
||||
address := s.resolveAddress(inbound, client, requestHost, defaultClientHost)
|
||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||
url, _ := url.Parse(link)
|
||||
q := url.Query()
|
||||
|
|
@ -1161,8 +1183,8 @@ func (s *SubService) BuildPageData(subId string, hostHeader string, traffic xray
|
|||
remained = common.FormatTraffic(left)
|
||||
}
|
||||
|
||||
datepicker := s.datepicker
|
||||
if datepicker == "" {
|
||||
datepicker, err := s.settingService.GetDatepicker()
|
||||
if err != nil || datepicker == "" {
|
||||
datepicker = "gregorian"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ class DBInbound {
|
|||
}
|
||||
}
|
||||
|
||||
genInboundLinks(remarkModel) {
|
||||
genInboundLinks(remarkModel, defaultHost = '') {
|
||||
const inbound = this.toInbound();
|
||||
return inbound.genInboundLinks(this.remark, remarkModel);
|
||||
return inbound.genInboundLinks(this.remark, remarkModel, defaultHost);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1725,10 +1725,25 @@ class Inbound extends XrayCommonClass {
|
|||
}
|
||||
}
|
||||
|
||||
genAllLinks(remark = '', remarkModel = '-ieo', client) {
|
||||
resolveLinkHost(client, defaultHost = '') {
|
||||
if (client?.subHost && client.subHost.trim().length > 0) {
|
||||
return client.subHost.trim();
|
||||
}
|
||||
if (this.settings?.subHost && this.settings.subHost.trim().length > 0) {
|
||||
return this.settings.subHost.trim();
|
||||
}
|
||||
if (defaultHost && defaultHost.trim().length > 0) {
|
||||
return defaultHost.trim();
|
||||
}
|
||||
return !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" && this.listen !== "::" && this.listen !== "::0"
|
||||
? this.listen
|
||||
: location.hostname;
|
||||
}
|
||||
|
||||
genAllLinks(remark = '', remarkModel = '-ieo', client, defaultHost = '') {
|
||||
let result = [];
|
||||
let email = client ? client.email : '';
|
||||
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||
let addr = this.resolveLinkHost(client, defaultHost);
|
||||
let port = this.port;
|
||||
const separationChar = remarkModel.charAt(0);
|
||||
const orderChars = remarkModel.slice(1);
|
||||
|
|
@ -1756,12 +1771,12 @@ class Inbound extends XrayCommonClass {
|
|||
return result;
|
||||
}
|
||||
|
||||
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||
genInboundLinks(remark = '', remarkModel = '-ieo', defaultHost = '') {
|
||||
let addr = this.resolveLinkHost(null, defaultHost);
|
||||
if (this.clients) {
|
||||
let links = [];
|
||||
this.clients.forEach((client) => {
|
||||
this.genAllLinks(remark, remarkModel, client).forEach(l => {
|
||||
this.genAllLinks(remark, remarkModel, client, defaultHost).forEach(l => {
|
||||
links.push(l.link);
|
||||
})
|
||||
});
|
||||
|
|
@ -1811,9 +1826,10 @@ class Inbound extends XrayCommonClass {
|
|||
}
|
||||
|
||||
Inbound.Settings = class extends XrayCommonClass {
|
||||
constructor(protocol) {
|
||||
constructor(protocol, subHost = '') {
|
||||
super();
|
||||
this.protocol = protocol;
|
||||
this.subHost = subHost;
|
||||
}
|
||||
|
||||
static getSettings(protocol) {
|
||||
|
|
@ -1877,15 +1893,18 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.VmessSettings(
|
||||
const obj = new Inbound.VmessSettings(
|
||||
Protocols.VMESS,
|
||||
json.clients.map(client => Inbound.VmessSettings.VMESS.fromJson(client)),
|
||||
);
|
||||
obj.subHost = json.subHost || '';
|
||||
return obj;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
||||
subHost: this.subHost || undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -1901,6 +1920,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
|||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
subHost = '',
|
||||
comment = '',
|
||||
reset = 0,
|
||||
created_at = undefined,
|
||||
|
|
@ -1916,6 +1936,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
|||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.subHost = subHost;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
this.created_at = created_at;
|
||||
|
|
@ -1933,6 +1954,7 @@ Inbound.VmessSettings.VMESS = class extends XrayCommonClass {
|
|||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.subHost,
|
||||
json.comment,
|
||||
json.reset,
|
||||
json.created_at,
|
||||
|
|
@ -2009,6 +2031,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||
json.selectedAuth,
|
||||
testseed
|
||||
);
|
||||
obj.subHost = json.subHost || '';
|
||||
return obj;
|
||||
}
|
||||
|
||||
|
|
@ -2032,6 +2055,9 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||
if (this.selectedAuth) {
|
||||
json.selectedAuth = this.selectedAuth;
|
||||
}
|
||||
if (this.subHost) {
|
||||
json.subHost = this.subHost;
|
||||
}
|
||||
|
||||
// Only include testseed if at least one client has a flow set
|
||||
const hasFlow = this.vlesses && this.vlesses.some(vless => vless.flow && vless.flow !== '');
|
||||
|
|
@ -2056,6 +2082,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
subHost = '',
|
||||
comment = '',
|
||||
reset = 0,
|
||||
created_at = undefined,
|
||||
|
|
@ -2071,6 +2098,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.subHost = subHost;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
this.created_at = created_at;
|
||||
|
|
@ -2088,6 +2116,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
|||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.subHost,
|
||||
json.comment,
|
||||
json.reset,
|
||||
json.created_at,
|
||||
|
|
@ -2177,16 +2206,19 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.TrojanSettings(
|
||||
const obj = new Inbound.TrojanSettings(
|
||||
Protocols.TROJAN,
|
||||
json.clients.map(client => Inbound.TrojanSettings.Trojan.fromJson(client)),
|
||||
Inbound.TrojanSettings.Fallback.fromJson(json.fallbacks),);
|
||||
obj.subHost = json.subHost || '';
|
||||
return obj;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
clients: Inbound.TrojanSettings.toJsonArray(this.trojans),
|
||||
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks)
|
||||
fallbacks: Inbound.TrojanSettings.toJsonArray(this.fallbacks),
|
||||
subHost: this.subHost || undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -2201,6 +2233,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
subHost = '',
|
||||
comment = '',
|
||||
reset = 0,
|
||||
created_at = undefined,
|
||||
|
|
@ -2215,6 +2248,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.subHost = subHost;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
this.created_at = created_at;
|
||||
|
|
@ -2231,6 +2265,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||
enable: this.enable,
|
||||
tgId: this.tgId,
|
||||
subId: this.subId,
|
||||
subHost: this.subHost,
|
||||
comment: this.comment,
|
||||
reset: this.reset,
|
||||
created_at: this.created_at,
|
||||
|
|
@ -2248,6 +2283,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
|||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.subHost,
|
||||
json.comment,
|
||||
json.reset,
|
||||
json.created_at,
|
||||
|
|
@ -2338,7 +2374,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
return new Inbound.ShadowsocksSettings(
|
||||
const obj = new Inbound.ShadowsocksSettings(
|
||||
Protocols.SHADOWSOCKS,
|
||||
json.method,
|
||||
json.password,
|
||||
|
|
@ -2346,6 +2382,8 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||
json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)),
|
||||
json.ivCheck,
|
||||
);
|
||||
obj.subHost = json.subHost || '';
|
||||
return obj;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
|
|
@ -2355,6 +2393,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||
network: this.network,
|
||||
clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses),
|
||||
ivCheck: this.ivCheck,
|
||||
subHost: this.subHost || undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -2370,6 +2409,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||
enable = true,
|
||||
tgId = '',
|
||||
subId = RandomUtil.randomLowerAndNum(16),
|
||||
subHost = '',
|
||||
comment = '',
|
||||
reset = 0,
|
||||
created_at = undefined,
|
||||
|
|
@ -2385,6 +2425,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||
this.enable = enable;
|
||||
this.tgId = tgId;
|
||||
this.subId = subId;
|
||||
this.subHost = subHost;
|
||||
this.comment = comment;
|
||||
this.reset = reset;
|
||||
this.created_at = created_at;
|
||||
|
|
@ -2402,6 +2443,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||
enable: this.enable,
|
||||
tgId: this.tgId,
|
||||
subId: this.subId,
|
||||
subHost: this.subHost,
|
||||
comment: this.comment,
|
||||
reset: this.reset,
|
||||
created_at: this.created_at,
|
||||
|
|
@ -2420,6 +2462,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||
json.enable,
|
||||
json.tgId,
|
||||
json.subId,
|
||||
json.subHost,
|
||||
json.comment,
|
||||
json.reset,
|
||||
json.created_at,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class AllSetting {
|
|||
this.subPath = "/sub/";
|
||||
this.subJsonPath = "/json/";
|
||||
this.subDomain = "";
|
||||
this.subDefaultHost = "";
|
||||
this.externalTrafficInformEnable = false;
|
||||
this.externalTrafficInformURI = "";
|
||||
this.subCertFile = "";
|
||||
|
|
@ -86,4 +87,4 @@ class AllSetting {
|
|||
equals(other) {
|
||||
return ObjectUtil.equals(this, other);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
"text/template"
|
||||
"time"
|
||||
"fmt"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/logger"
|
||||
"github.com/mhsanaei/3x-ui/v2/web/service"
|
||||
|
|
@ -71,14 +72,22 @@ func (a *IndexController) login(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
user := a.userService.CheckUser(form.Username, form.Password, form.TwoFactorCode)
|
||||
user, checkErr := a.userService.CheckUser(form.Username, form.Password, form.TwoFactorCode)
|
||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||
safeUser := template.HTMLEscapeString(form.Username)
|
||||
safePass := template.HTMLEscapeString(form.Password)
|
||||
|
||||
if user == nil {
|
||||
logger.Warningf("wrong username: \"%s\", password: \"%s\", IP: \"%s\"", safeUser, safePass, getRemoteIp(c))
|
||||
a.tgbot.UserLoginNotify(safeUser, safePass, getRemoteIp(c), timeStr, 0)
|
||||
|
||||
notifyPass := safePass
|
||||
|
||||
if checkErr != nil && checkErr.Error() == "invalid 2fa code" {
|
||||
translatedError := a.tgbot.I18nBot("tgbot.messages.2faFailed")
|
||||
notifyPass = fmt.Sprintf("*** (%s)", translatedError)
|
||||
}
|
||||
|
||||
a.tgbot.UserLoginNotify(safeUser, notifyPass, getRemoteIp(c), timeStr, 0)
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ type AllSetting struct {
|
|||
SubPort int `json:"subPort" form:"subPort"` // Subscription server port
|
||||
SubPath string `json:"subPath" form:"subPath"` // Base path for subscription URLs
|
||||
SubDomain string `json:"subDomain" form:"subDomain"` // Domain for subscription server validation
|
||||
SubDefaultHost string `json:"subDefaultHost" form:"subDefaultHost"` // Default host/IP used in exported client links
|
||||
SubCertFile string `json:"subCertFile" form:"subCertFile"` // SSL certificate file for subscription server
|
||||
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` // SSL private key file for subscription server
|
||||
SubUpdates int `json:"subUpdates" form:"subUpdates"` // Subscription update interval in minutes
|
||||
|
|
|
|||
|
|
@ -56,6 +56,18 @@
|
|||
</template>
|
||||
<a-input v-model.trim="client.subId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email && app.subSettings?.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.settings.subClientHostDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.settings.subClientHost" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="client.subHost" placeholder="client.example.com"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
|
|
@ -169,4 +181,4 @@
|
|||
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,19 @@
|
|||
<a-input-number v-model.number="inbound.port" :min="1"
|
||||
:max="65535"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="[Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.settings.subInboundHostDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.settings.subInboundHost" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.settings.subHost" placeholder="cdn.example.com"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
|
|
@ -170,4 +183,4 @@
|
|||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -612,7 +612,7 @@
|
|||
</a-divider>
|
||||
<a-form-item label='Type'>
|
||||
<a-select v-model="mask.type"
|
||||
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
|
||||
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(outbound.stream.network === 'kcp') { outbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<!-- Salamander for Hysteria2 only -->
|
||||
<a-select-option v-if="outbound.protocol === Protocols.Hysteria"
|
||||
|
|
@ -643,9 +643,9 @@
|
|||
<a-select-option v-if="outbound.stream.network === 'kcp'"
|
||||
value="mkcp-original">
|
||||
mKCP Original</a-select-option>
|
||||
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP -->
|
||||
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP/KCP -->
|
||||
<a-select-option
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(outbound.stream.network)"
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(outbound.stream.network)"
|
||||
value="xdns">
|
||||
xDNS (Experimental)</a-select-option>
|
||||
</a-select>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
</a-divider>
|
||||
<a-form-item label='Type'>
|
||||
<a-select v-model="mask.type"
|
||||
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
|
||||
@change="(type) => { mask.settings = mask._getDefaultSettings(type, {}); if(inbound.stream.network === 'kcp') { inbound.stream.kcp.mtu = type === 'xdns' ? 900 : 1350; } }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<!-- mKCP-specific masks -->
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
|
|
@ -48,9 +48,9 @@
|
|||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="xicmp">
|
||||
xICMP (Experimental)</a-select-option>
|
||||
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP -->
|
||||
<!-- xDNS for TCP/WS/HTTPUpgrade/XHTTP/KCP -->
|
||||
<a-select-option
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(inbound.stream.network)"
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)"
|
||||
value="xdns">
|
||||
xDNS (Experimental)</a-select-option>
|
||||
</a-select>
|
||||
|
|
|
|||
|
|
@ -734,6 +734,7 @@
|
|||
subURI: '',
|
||||
subJsonURI: '',
|
||||
subJsonEnable: false,
|
||||
defaultHost: '',
|
||||
},
|
||||
remarkModel: '-ieo',
|
||||
datepicker: 'gregorian',
|
||||
|
|
@ -791,6 +792,7 @@
|
|||
subURI: subURI,
|
||||
subJsonURI: subJsonURI,
|
||||
subJsonEnable: subJsonEnable,
|
||||
defaultHost: subDefaultHost,
|
||||
};
|
||||
this.pageSize = pageSize;
|
||||
this.remarkModel = remarkModel;
|
||||
|
|
@ -1471,7 +1473,7 @@
|
|||
inboundLinks(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
newDbInbound = this.checkFallback(dbInbound);
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark);
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel, this.subSettings.defaultHost), newDbInbound.remark);
|
||||
},
|
||||
exportSubs(dbInboundId) {
|
||||
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
|
|
@ -1520,7 +1522,7 @@
|
|||
exportAllLinks() {
|
||||
let copyText = [];
|
||||
for (const dbInbound of this.dbInbounds) {
|
||||
copyText.push(dbInbound.genInboundLinks(this.remarkModel));
|
||||
copyText.push(dbInbound.genInboundLinks(this.remarkModel, this.subSettings.defaultHost));
|
||||
}
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', copyText.join('\r\n'), 'All-Inbounds');
|
||||
},
|
||||
|
|
@ -1697,4 +1699,4 @@
|
|||
},
|
||||
});
|
||||
</script>
|
||||
{{ template "page/body_end" .}}
|
||||
{{ template "page/body_end" .}}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@
|
|||
</template>
|
||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.subSettings?.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.settings.subClientHostDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.settings.subClientHost" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="clientsBulkModal.subHost" placeholder="client.example.com"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.tgBotEnable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
|
|
@ -144,6 +156,7 @@
|
|||
emailPrefix: "",
|
||||
emailPostfix: "",
|
||||
subId: "",
|
||||
subHost: "",
|
||||
tgId: '',
|
||||
security: "auto",
|
||||
flow: "",
|
||||
|
|
@ -167,6 +180,7 @@
|
|||
if (method == 4) newClient.email = "";
|
||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
|
||||
if (clientsBulkModal.subHost.length > 0) newClient.subHost = clientsBulkModal.subHost;
|
||||
newClient.tgId = clientsBulkModal.tgId;
|
||||
newClient.security = clientsBulkModal.security;
|
||||
newClient.limitIp = clientsBulkModal.limitIp;
|
||||
|
|
@ -200,6 +214,7 @@
|
|||
this.emailPrefix = "";
|
||||
this.emailPostfix = "";
|
||||
this.subId = "";
|
||||
this.subHost = "";
|
||||
this.tgId = '';
|
||||
this.security = "auto";
|
||||
this.flow = "";
|
||||
|
|
@ -247,4 +262,4 @@
|
|||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -668,9 +668,9 @@
|
|||
}
|
||||
}
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
||||
this.links = this.inbound.genInboundLinks(dbInbound.remark, app.remarkModel, app.subSettings.defaultHost).split('\r\n')
|
||||
} else {
|
||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings, app.subSettings.defaultHost);
|
||||
}
|
||||
if (this.clientSettings) {
|
||||
if (this.clientSettings.subId) {
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@
|
|||
// Reset the status fetched flag when showing the modal
|
||||
if (qrModalApp) qrModalApp.statusFetched = false;
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD) {
|
||||
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l, index) => {
|
||||
this.inbound.genInboundLinks(dbInbound.remark, app.remarkModel, app.subSettings.defaultHost).split('\r\n').forEach((l, index) => {
|
||||
this.qrcodes.push({
|
||||
remark: "Peer " + (index + 1),
|
||||
link: l,
|
||||
|
|
@ -124,7 +124,7 @@
|
|||
});
|
||||
});
|
||||
} else {
|
||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client, app.subSettings.defaultHost).forEach(l => {
|
||||
this.qrcodes.push({
|
||||
remark: l.remark,
|
||||
link: l.link,
|
||||
|
|
@ -303,4 +303,4 @@
|
|||
});
|
||||
}
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@
|
|||
<a-input type="text" v-model="allSetting.subDomain"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subDefaultHost"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subDefaultHostDesc"}}</template>
|
||||
<template #control>
|
||||
<a-input type="text" v-model.trim="allSetting.subDefaultHost"
|
||||
placeholder="public.example.com"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>{{ i18n "pages.settings.subPort"}}</template>
|
||||
<template #description>{{ i18n "pages.settings.subPortDesc"}}</template>
|
||||
|
|
@ -142,4 +150,4 @@
|
|||
</a-setting-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -271,10 +271,7 @@ func (j *LdapSyncJob) deleteClientsNotInLDAP(inboundTag string, ldapEmails map[s
|
|||
|
||||
// Delete in batches
|
||||
for i := 0; i < len(toDelete); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(toDelete) {
|
||||
end = len(toDelete)
|
||||
}
|
||||
end := min(i+batchSize, len(toDelete))
|
||||
batch := toDelete[i:end]
|
||||
|
||||
for _, c := range batch {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ type SettingService interface {
|
|||
|
||||
// InitLocalizer initializes the internationalization system with embedded translation files.
|
||||
func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||
// set default bundle to english
|
||||
// set default bundle to English
|
||||
i18nBundle = i18n.NewBundle(language.MustParse("en-US"))
|
||||
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ var defaultValueMap = map[string]string{
|
|||
"subPort": "2096",
|
||||
"subPath": "/sub/",
|
||||
"subDomain": "",
|
||||
"subDefaultHost": "",
|
||||
"subCertFile": "",
|
||||
"subKeyFile": "",
|
||||
"subUpdates": "12",
|
||||
|
|
@ -108,7 +109,7 @@ var defaultValueMap = map[string]string{
|
|||
// It handles configuration storage, retrieval, and validation for all system settings.
|
||||
type SettingService struct{}
|
||||
|
||||
func (s *SettingService) GetDefaultJsonConfig() (any, error) {
|
||||
func (s *SettingService) GetDefaultJSONConfig() (any, error) {
|
||||
var jsonData any
|
||||
err := json.Unmarshal([]byte(xrayTemplateConfig), &jsonData)
|
||||
if err != nil {
|
||||
|
|
@ -125,7 +126,7 @@ func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
|||
return nil, err
|
||||
}
|
||||
allSetting := &entity.AllSetting{}
|
||||
t := reflect.TypeOf(allSetting).Elem()
|
||||
t := reflect.TypeFor[entity.AllSetting]()
|
||||
v := reflect.ValueOf(allSetting).Elem()
|
||||
fields := reflect_util.GetFields(t)
|
||||
|
||||
|
|
@ -515,6 +516,10 @@ func (s *SettingService) GetSubDomain() (string, error) {
|
|||
return s.getString("subDomain")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubDefaultHost() (string, error) {
|
||||
return s.getString("subDefaultHost")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetSubCertFile(subCertFile string) error {
|
||||
return s.setString("subCertFile", subCertFile)
|
||||
}
|
||||
|
|
@ -607,7 +612,7 @@ func (s *SettingService) GetIpLimitEnable() (bool, error) {
|
|||
return (accessLogPath != "none" && accessLogPath != ""), nil
|
||||
}
|
||||
|
||||
// LDAP exported getters
|
||||
// GetLdapEnable returns whether LDAP is enabled.
|
||||
func (s *SettingService) GetLdapEnable() (bool, error) {
|
||||
return s.getBool("ldapEnable")
|
||||
}
|
||||
|
|
@ -694,7 +699,7 @@ func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
|||
}
|
||||
|
||||
v := reflect.ValueOf(allSetting).Elem()
|
||||
t := reflect.TypeOf(allSetting).Elem()
|
||||
t := reflect.TypeFor[entity.AllSetting]()
|
||||
fields := reflect_util.GetFields(t)
|
||||
errs := make([]error, 0)
|
||||
for _, field := range fields {
|
||||
|
|
@ -754,6 +759,9 @@ func (s *SettingService) GetDefaultSettings(host string) (any, error) {
|
|||
"subTitle": func() (any, error) { return s.GetSubTitle() },
|
||||
"subURI": func() (any, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (any, error) { return s.GetSubJsonURI() },
|
||||
"subDefaultHost": func() (any, error) {
|
||||
return s.GetSubDefaultHost()
|
||||
},
|
||||
"remarkModel": func() (any, error) { return s.GetRemarkModel() },
|
||||
"datepicker": func() (any, error) { return s.GetDatepicker() },
|
||||
"ipLimitEnable": func() (any, error) { return s.GetIpLimitEnable() },
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -2719,7 +2720,7 @@ func (t *Tgbot) prepareServerUsageInfo() string {
|
|||
info += t.I18nBot("tgbot.messages.ip", "IP=="+t.I18nBot("tgbot.unknown"))
|
||||
info += "\r\n"
|
||||
} else {
|
||||
for i := 0; i < len(netInterfaces); i++ {
|
||||
for i := range netInterfaces {
|
||||
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||
addrs, _ := netInterfaces[i].Addrs()
|
||||
|
||||
|
|
@ -2788,29 +2789,29 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
|
|||
|
||||
// getInboundUsages retrieves and formats inbound usage information.
|
||||
func (t *Tgbot) getInboundUsages() string {
|
||||
info := ""
|
||||
var info strings.Builder
|
||||
// get traffic
|
||||
inbounds, err := t.inboundService.GetAllInbounds()
|
||||
if err != nil {
|
||||
logger.Warning("GetAllInbounds run failed:", err)
|
||||
info += t.I18nBot("tgbot.answers.getInboundsFailed")
|
||||
info.WriteString(t.I18nBot("tgbot.answers.getInboundsFailed"))
|
||||
} else {
|
||||
// NOTE:If there no any sessions here,need to notify here
|
||||
// TODO:Sub-node push, automatic conversion format
|
||||
for _, inbound := range inbounds {
|
||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||
info.WriteString(t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark))
|
||||
info.WriteString(t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port)))
|
||||
info.WriteString(t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down)))
|
||||
|
||||
if inbound.ExpiryTime == 0 {
|
||||
info += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
||||
info.WriteString(t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited")))
|
||||
} else {
|
||||
info += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
||||
info.WriteString(t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05")))
|
||||
}
|
||||
info += "\r\n"
|
||||
info.WriteString("\r\n")
|
||||
}
|
||||
}
|
||||
return info
|
||||
return info.String()
|
||||
}
|
||||
|
||||
// getInbounds creates an inline keyboard with all inbounds.
|
||||
|
|
@ -3060,12 +3061,9 @@ func (t *Tgbot) clientInfoMsg(
|
|||
status := t.I18nBot("tgbot.offline")
|
||||
isOnline := false
|
||||
if p.IsRunning() {
|
||||
for _, online := range p.GetOnlineClients() {
|
||||
if online == traffic.Email {
|
||||
status = t.I18nBot("tgbot.online")
|
||||
isOnline = true
|
||||
break
|
||||
}
|
||||
if slices.Contains(p.GetOnlineClients(), traffic.Email) {
|
||||
status = t.I18nBot("tgbot.online")
|
||||
isOnline = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3430,11 +3428,11 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||
t.SendMsgToTgbot(chatId, info)
|
||||
|
||||
if len(inbound.ClientStats) > 0 {
|
||||
output := ""
|
||||
var output strings.Builder
|
||||
for _, traffic := range inbound.ClientStats {
|
||||
output += t.clientInfoMsg(&traffic, true, true, true, true, true, true)
|
||||
output.WriteString(t.clientInfoMsg(&traffic, true, true, true, true, true, true))
|
||||
}
|
||||
t.SendMsgToTgbot(chatId, output)
|
||||
t.SendMsgToTgbot(chatId, output.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func (s *UserService) GetFirstUser() (*model.User, error) {
|
|||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) CheckUser(username string, password string, twoFactorCode string) *model.User {
|
||||
func (s *UserService) CheckUser(username string, password string, twoFactorCode string) (*model.User, error) {
|
||||
db := database.GetDB()
|
||||
|
||||
user := &model.User{}
|
||||
|
|
@ -43,17 +43,16 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
|
|||
First(user).
|
||||
Error
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil
|
||||
return nil, errors.New("invalid credentials")
|
||||
} else if err != nil {
|
||||
logger.Warning("check user err:", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If LDAP enabled and local password check fails, attempt LDAP auth
|
||||
if !crypto.CheckPasswordHash(user.Password, password) {
|
||||
ldapEnabled, _ := s.settingService.GetLdapEnable()
|
||||
if !ldapEnabled {
|
||||
return nil
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
host, _ := s.settingService.GetLdapHost()
|
||||
|
|
@ -77,15 +76,14 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
|
|||
}
|
||||
ok, err := ldaputil.AuthenticateUser(cfg, username, password)
|
||||
if err != nil || !ok {
|
||||
return nil
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
// On successful LDAP auth, continue 2FA checks below
|
||||
}
|
||||
|
||||
twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
|
||||
if err != nil {
|
||||
logger.Warning("check two factor err:", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if twoFactorEnable {
|
||||
|
|
@ -93,15 +91,15 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
|
|||
|
||||
if err != nil {
|
||||
logger.Warning("check two factor token err:", err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if gotp.NewDefaultTOTP(twoFactorToken).Now() != twoFactorCode {
|
||||
return nil
|
||||
return nil, errors.New("invalid 2fa code")
|
||||
}
|
||||
}
|
||||
|
||||
return user
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *UserService) UpdateUser(id int, username string, password string) error {
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "مسار URI لخدمة الاشتراك. (يبدأ بـ '/' وبينتهي بـ '/')"
|
||||
"subDomain" = "دومين الاستماع"
|
||||
"subDomainDesc" = "اسم الدومين لخدمة الاشتراك. (سيبه فاضي عشان يستمع على كل الدومينات والـ IPs)"
|
||||
"subDefaultHost" = "مضيف العميل الافتراضي"
|
||||
"subDefaultHostDesc" = "المضيف أو عنوان IP الافتراضي المستخدم في روابط/إعدادات العميل المُصدَّرة."
|
||||
"subInboundHost" = "مضيف الـ Inbound"
|
||||
"subInboundHostDesc" = "تجاوز اختياري للمضيف/IP للروابط المُصدَّرة من هذا الـ Inbound."
|
||||
"subClientHost" = "مضيف العميل"
|
||||
"subClientHostDesc" = "تجاوز اختياري للمضيف/IP للروابط المُصدَّرة لهذا العميل."
|
||||
"subUpdates" = "فترات التحديث"
|
||||
"subUpdatesDesc" = "فترات تحديث رابط الاشتراك في تطبيقات العملاء. (الوحدة: ساعة)"
|
||||
"subEncrypt" = "تشفير"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ حفظت بيانات مستخدم Telegram."
|
||||
"loginSuccess" = "✅ تسجيل الدخول للبانل تم بنجاح.\r\n"
|
||||
"loginFailed" = "❗️فشل محاولة تسجيل الدخول للبانل.\r\n"
|
||||
"2faFailed" = "فشل 2FA"
|
||||
"report" = "🕰 التقارير المجدولة: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ التاريخ والوقت: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 السيرفر: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "The URI path for the subscription service. (begins with ‘/‘ and concludes with ‘/‘)"
|
||||
"subDomain" = "Listen Domain"
|
||||
"subDomainDesc" = "The domain name for the subscription service. (leave blank to listen on all domains and IPs)"
|
||||
"subDefaultHost" = "Default Client Host"
|
||||
"subDefaultHostDesc" = "Default host or IP used in exported client links/configs."
|
||||
"subInboundHost" = "Inbound Host"
|
||||
"subInboundHostDesc" = "Optional host/IP override for links exported from this inbound."
|
||||
"subClientHost" = "Client Host"
|
||||
"subClientHostDesc" = "Optional host/IP override for links exported for this client."
|
||||
"subUpdates" = "Update Intervals"
|
||||
"subUpdatesDesc" = "The update intervals of the subscription URL in the client apps. (unit: hour)"
|
||||
"subEncrypt" = "Encode"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Telegram User saved."
|
||||
"loginSuccess" = "✅ Logged in to the panel successfully.\r\n"
|
||||
"loginFailed" = "❗️Login attempt to the panel failed.\r\n"
|
||||
"2faFailed" = "2FA Failed"
|
||||
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Date&Time: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "Debe empezar con '/' y terminar con '/'"
|
||||
"subDomain" = "Dominio de Escucha"
|
||||
"subDomainDesc" = "Dejar en blanco por defecto para monitorear todos los dominios e IPs."
|
||||
"subDefaultHost" = "Host predeterminado del cliente"
|
||||
"subDefaultHostDesc" = "Host o IP predeterminado utilizado en enlaces/configuraciones de cliente exportados."
|
||||
"subInboundHost" = "Host del inbound"
|
||||
"subInboundHostDesc" = "Reemplazo opcional de host/IP para los enlaces exportados desde este inbound."
|
||||
"subClientHost" = "Host del cliente"
|
||||
"subClientHostDesc" = "Reemplazo opcional de host/IP para los enlaces exportados para este cliente."
|
||||
"subUpdates" = "Intervalos de Actualización de Suscripción"
|
||||
"subUpdatesDesc" = "Horas de intervalo entre actualizaciones en la aplicación del cliente."
|
||||
"subEncrypt" = "Encriptar configuraciones"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Usuario de Telegram guardado."
|
||||
"loginSuccess" = "✅ Has iniciado sesión en el panel con éxito.\r\n"
|
||||
"loginFailed" = "❗️ Falló el inicio de sesión en el panel.\r\n"
|
||||
"2faFailed" = "Error de 2FA"
|
||||
"report" = "🕰 Informes programados: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Fecha y Hora: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Nombre del Host: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "برای سرویس سابسکریپشن. با '/' شروع و با '/' خاتمه مییابد URI مسیر"
|
||||
"subDomain" = "نام دامنه"
|
||||
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آیپیها خالیبگذارید"
|
||||
"subDefaultHost" = "هاست پیشفرض کاربر"
|
||||
"subDefaultHostDesc" = "هاست یا IP پیشفرضی که در لینکها/پیکربندیهای خروجی کاربر استفاده میشود."
|
||||
"subInboundHost" = "هاست ورودی"
|
||||
"subInboundHostDesc" = "جایگزینی اختیاری هاست/IP برای لینکهای خروجی این ورودی."
|
||||
"subClientHost" = "هاست کاربر"
|
||||
"subClientHostDesc" = "جایگزینی اختیاری هاست/IP برای لینکهای خروجی این کاربر."
|
||||
"subUpdates" = "فاصله بروزرسانی سابسکریپشن"
|
||||
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامههای کاربری. (واحد: ساعت"
|
||||
"subEncrypt" = "کدگذاری"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ کاربر تلگرام ذخیره شد."
|
||||
"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n"
|
||||
"loginFailed" = "❗️ ورود به پنل ناموفقبود \r\n"
|
||||
"2faFailed" = "خطای 2FA"
|
||||
"report" = "🕰 گزارشاتزمانبندیشده: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ تاریخوزمان: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 ناممیزبان: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "URI path untuk layanan langganan. (dimulai dengan ‘/‘ dan diakhiri dengan ‘/‘)"
|
||||
"subDomain" = "Domain Pendengar"
|
||||
"subDomainDesc" = "Nama domain untuk layanan langganan. (biarkan kosong untuk mendengarkan semua domain dan IP)"
|
||||
"subDefaultHost" = "Host Klien Default"
|
||||
"subDefaultHostDesc" = "Host atau IP default yang digunakan pada tautan/konfigurasi klien yang diekspor."
|
||||
"subInboundHost" = "Host Inbound"
|
||||
"subInboundHostDesc" = "Override host/IP opsional untuk tautan yang diekspor dari inbound ini."
|
||||
"subClientHost" = "Host Klien"
|
||||
"subClientHostDesc" = "Override host/IP opsional untuk tautan yang diekspor untuk klien ini."
|
||||
"subUpdates" = "Interval Pembaruan"
|
||||
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
|
||||
"subEncrypt" = "Encode"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Pengguna Telegram tersimpan."
|
||||
"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n"
|
||||
"loginFailed" = "❗️ Gagal masuk ke panel.\r\n"
|
||||
"2faFailed" = "2FA Gagal"
|
||||
"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "サブスクリプションサービスで使用するURIパス('/'で始まり、'/'で終わる)"
|
||||
"subDomain" = "監視ドメイン"
|
||||
"subDomainDesc" = "サブスクリプションサービスが監視するドメイン(空白にするとすべてのドメインとIPを監視)"
|
||||
"subDefaultHost" = "クライアントのデフォルトホスト"
|
||||
"subDefaultHostDesc" = "エクスポートされるクライアントリンク/設定で使用するデフォルトのホストまたはIP。"
|
||||
"subInboundHost" = "インバウンドホスト"
|
||||
"subInboundHostDesc" = "このインバウンドからエクスポートされるリンクに対する任意のホスト/IP上書き。"
|
||||
"subClientHost" = "クライアントホスト"
|
||||
"subClientHostDesc" = "このクライアント向けにエクスポートされるリンクに対する任意のホスト/IP上書き。"
|
||||
"subUpdates" = "更新間隔"
|
||||
"subUpdatesDesc" = "クライアントアプリケーションでサブスクリプションURLの更新間隔(単位:時間)"
|
||||
"subEncrypt" = "エンコード"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Telegramユーザーが保存されました。"
|
||||
"loginSuccess" = "✅ パネルに正常にログインしました。\r\n"
|
||||
"loginFailed" = "❗️ パネルのログインに失敗しました。\r\n"
|
||||
"2faFailed" = "2FAエラー"
|
||||
"report" = "🕰 定期報告:{{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ 日時:{{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 ホスト名:{{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "O caminho URI para o serviço de assinatura. (começa com ‘/‘ e termina com ‘/‘)"
|
||||
"subDomain" = "Domínio de Escuta"
|
||||
"subDomainDesc" = "O nome de domínio para o serviço de assinatura. (deixe em branco para escutar em todos os domínios e IPs)"
|
||||
"subDefaultHost" = "Host padrão do cliente"
|
||||
"subDefaultHostDesc" = "Host ou IP padrão usado nos links/configurações de cliente exportados."
|
||||
"subInboundHost" = "Host do inbound"
|
||||
"subInboundHostDesc" = "Substituição opcional de host/IP para links exportados deste inbound."
|
||||
"subClientHost" = "Host do cliente"
|
||||
"subClientHostDesc" = "Substituição opcional de host/IP para links exportados para este cliente."
|
||||
"subUpdates" = "Intervalos de Atualização"
|
||||
"subUpdatesDesc" = "Os intervalos de atualização da URL de assinatura nos aplicativos de cliente. (unidade: hora)"
|
||||
"subEncrypt" = "Codificar"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Usuário do Telegram salvo."
|
||||
"loginSuccess" = "✅ Conectado ao painel com sucesso.\r\n"
|
||||
"loginFailed" = "❗️Tentativa de login no painel falhou.\r\n"
|
||||
"2faFailed" = "Falha no 2FA"
|
||||
"report" = "🕰 Relatórios agendados: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Data&Hora: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "Должен начинаться с '/' и заканчиваться на '/'"
|
||||
"subDomain" = "Домен прослушивания"
|
||||
"subDomainDesc" = "Оставьте пустым по умолчанию, чтобы слушать все домены и IP-адреса"
|
||||
"subDefaultHost" = "Хост клиента по умолчанию"
|
||||
"subDefaultHostDesc" = "Хост или IP по умолчанию, используемый в экспортируемых ссылках/конфигах клиента."
|
||||
"subInboundHost" = "Хост входящего подключения"
|
||||
"subInboundHostDesc" = "Необязательный хост/IP для переопределения в ссылках, экспортируемых из этого входящего подключения."
|
||||
"subClientHost" = "Хост клиента"
|
||||
"subClientHostDesc" = "Необязательный хост/IP для переопределения в ссылках, экспортируемых для этого клиента."
|
||||
"subUpdates" = "Интервалы обновления подписки"
|
||||
"subUpdatesDesc" = "Интервал между обновлениями в клиентском приложении (в часах)"
|
||||
"subEncrypt" = "Шифровать конфиги"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Пользователь Telegram сохранен."
|
||||
"loginSuccess" = "✅ Успешный вход в панель.\r\n"
|
||||
"loginFailed" = "❗️ Ошибка входа в панель.\r\n"
|
||||
"2faFailed" = "Ошибка 2FA"
|
||||
"report" = "🕰 Запланированные отчеты: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Дата и время: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Имя хоста: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "Abonelik hizmeti için URI yolu. ('/' ile başlar ve '/' ile biter)"
|
||||
"subDomain" = "Dinleme Alan Adı"
|
||||
"subDomainDesc" = "Abonelik hizmeti için alan adı. (tüm alan adlarını ve IP'leri dinlemek için boş bırakın)"
|
||||
"subDefaultHost" = "Varsayılan İstemci Ana Bilgisayarı"
|
||||
"subDefaultHostDesc" = "Dışa aktarılan istemci bağlantıları/yapılandırmalarında kullanılan varsayılan ana bilgisayar veya IP."
|
||||
"subInboundHost" = "Gelen Bağlantı Ana Bilgisayarı"
|
||||
"subInboundHostDesc" = "Bu gelen bağlantıdan dışa aktarılan bağlantılar için isteğe bağlı ana bilgisayar/IP geçersiz kılması."
|
||||
"subClientHost" = "İstemci Ana Bilgisayarı"
|
||||
"subClientHostDesc" = "Bu istemci için dışa aktarılan bağlantılar için isteğe bağlı ana bilgisayar/IP geçersiz kılması."
|
||||
"subUpdates" = "Güncelleme Aralıkları"
|
||||
"subUpdatesDesc" = "Müşteri uygulamalarındaki abonelik URL'sinin güncelleme aralıkları. (birim: saat)"
|
||||
"subEncrypt" = "Şifrele"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Telegram Kullanıcısı kaydedildi."
|
||||
"loginSuccess" = "✅ Panele başarıyla giriş yapıldı.\r\n"
|
||||
"loginFailed" = "❗️Panele giriş denemesi başarısız oldu.\r\n"
|
||||
"2faFailed" = "2FA Hatası"
|
||||
"report" = "🕰 Planlanmış Raporlar: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Tarih&Zaman: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Sunucu: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "Шлях URI для служби підписки. (починається з ‘/‘ і закінчується ‘/‘)"
|
||||
"subDomain" = "Домен прослуховування"
|
||||
"subDomainDesc" = "Ім'я домену для служби підписки. (залиште порожнім, щоб слухати всі домени та IP-адреси)"
|
||||
"subDefaultHost" = "Хост клієнта за замовчуванням"
|
||||
"subDefaultHostDesc" = "Хост або IP за замовчуванням, що використовується в експортованих посиланнях/конфігах клієнта."
|
||||
"subInboundHost" = "Хост вхідного підключення"
|
||||
"subInboundHostDesc" = "Необов'язкове перевизначення host/IP для посилань, експортованих із цього вхідного підключення."
|
||||
"subClientHost" = "Хост клієнта"
|
||||
"subClientHostDesc" = "Необов'язкове перевизначення host/IP для посилань, експортованих для цього клієнта."
|
||||
"subUpdates" = "Інтервали оновлення"
|
||||
"subUpdatesDesc" = "Інтервали оновлення URL-адреси підписки в клієнтських програмах. (одиниця: година)"
|
||||
"subEncrypt" = "Закодувати"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Користувача Telegram збережено."
|
||||
"loginSuccess" = "✅ Успішно ввійшли в панель\r\n"
|
||||
"loginFailed" = "❗️ Помилка входу в панель.\r\n"
|
||||
"2faFailed" = "Помилка 2FA"
|
||||
"report" = "🕰 Заплановані звіти: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Дата й час: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Хост: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "Phải bắt đầu và kết thúc bằng '/'"
|
||||
"subDomain" = "Tên miền con"
|
||||
"subDomainDesc" = "Mặc định để trống để nghe tất cả các tên miền và IP"
|
||||
"subDefaultHost" = "Host mặc định của khách hàng"
|
||||
"subDefaultHostDesc" = "Host hoặc IP mặc định được dùng trong liên kết/cấu hình khách hàng đã xuất."
|
||||
"subInboundHost" = "Host inbound"
|
||||
"subInboundHostDesc" = "Ghi đè host/IP tùy chọn cho các liên kết được xuất từ inbound này."
|
||||
"subClientHost" = "Host khách hàng"
|
||||
"subClientHostDesc" = "Ghi đè host/IP tùy chọn cho các liên kết được xuất cho khách hàng này."
|
||||
"subUpdates" = "Khoảng thời gian cập nhật gói đăng ký"
|
||||
"subUpdatesDesc" = "Số giờ giữa các cập nhật trong ứng dụng khách"
|
||||
"subEncrypt" = "Mã hóa cấu hình"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ Người dùng Telegram đã được lưu."
|
||||
"loginSuccess" = "✅ Đăng nhập thành công vào bảng điều khiển.\r\n"
|
||||
"loginFailed" = "❗️ Đăng nhập vào bảng điều khiển thất bại.\r\n"
|
||||
"2faFailed" = "Lỗi 2FA"
|
||||
"report" = "🕰 Báo cáo định kỳ: {{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ Ngày-Giờ: {{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 Tên máy chủ: {{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)"
|
||||
"subDomain" = "监听域名"
|
||||
"subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP)"
|
||||
"subDefaultHost" = "默认客户端主机"
|
||||
"subDefaultHostDesc" = "用于导出客户端链接/配置的默认主机或 IP。"
|
||||
"subInboundHost" = "入站主机"
|
||||
"subInboundHostDesc" = "为从该入站导出的链接提供可选的主机/IP 覆盖。"
|
||||
"subClientHost" = "客户端主机"
|
||||
"subClientHostDesc" = "为该客户端导出的链接提供可选的主机/IP 覆盖。"
|
||||
"subUpdates" = "更新间隔"
|
||||
"subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)"
|
||||
"subEncrypt" = "编码"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ 电报用户已保存。"
|
||||
"loginSuccess" = "✅ 成功登录到面板。\r\n"
|
||||
"loginFailed" = "❗️ 面板登录失败。\r\n"
|
||||
"2faFailed" = "2FA 失败"
|
||||
"report" = "🕰 定时报告:{{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ 日期时间:{{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 主机名:{{ .Hostname }}\r\n"
|
||||
|
|
|
|||
|
|
@ -396,6 +396,12 @@
|
|||
"subPathDesc" = "訂閱服務使用的 URI 路徑(以 '/' 開頭,以 '/' 結尾)"
|
||||
"subDomain" = "監聽域名"
|
||||
"subDomainDesc" = "訂閱服務監聽的域名(留空表示監聽所有域名和 IP)"
|
||||
"subDefaultHost" = "預設客戶端主機"
|
||||
"subDefaultHostDesc" = "用於匯出客戶端連結/設定的預設主機或 IP。"
|
||||
"subInboundHost" = "入站主機"
|
||||
"subInboundHostDesc" = "可選的主機/IP 覆寫,用於從此入站匯出的連結。"
|
||||
"subClientHost" = "客戶端主機"
|
||||
"subClientHostDesc" = "可選的主機/IP 覆寫,用於為此客戶端匯出的連結。"
|
||||
"subUpdates" = "更新間隔"
|
||||
"subUpdatesDesc" = "客戶端應用中訂閱 URL 的更新間隔(單位:小時)"
|
||||
"subEncrypt" = "編碼"
|
||||
|
|
@ -663,6 +669,7 @@
|
|||
"userSaved" = "✅ 電報使用者已儲存。"
|
||||
"loginSuccess" = "✅ 成功登入到面板。\r\n"
|
||||
"loginFailed" = "❗️ 面板登入失敗。\r\n"
|
||||
"2faFailed" = "2FA 失敗"
|
||||
"report" = "🕰 定時報告:{{ .RunTime }}\r\n"
|
||||
"datetime" = "⏰ 日期時間:{{ .DateTime }}\r\n"
|
||||
"hostname" = "💻 主機名:{{ .Hostname }}\r\n"
|
||||
|
|
|
|||
2
x-ui.sh
2
x-ui.sh
|
|
@ -431,7 +431,7 @@ status() {
|
|||
|
||||
enable() {
|
||||
if [[ $release == "alpine" ]]; then
|
||||
rc-update add x-ui
|
||||
rc-update add x-ui default
|
||||
else
|
||||
systemctl enable x-ui
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue