Merge branch 'MHSanaei:main' into main

This commit is contained in:
Alireza Ahmand 2024-02-18 09:33:29 +03:30 committed by GitHub
commit 32925736b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 2091 additions and 341 deletions

24
go.mod
View file

@ -7,23 +7,23 @@ require (
github.com/gin-contrib/gzip v0.0.6
github.com/gin-gonic/gin v1.9.1
github.com/goccy/go-json v0.10.2
github.com/mymmrac/telego v0.28.0
github.com/mymmrac/telego v0.29.1
github.com/nicksnyder/go-i18n/v2 v2.4.0
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml/v2 v2.1.1
github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil/v3 v3.24.1
github.com/valyala/fasthttp v1.51.0
github.com/valyala/fasthttp v1.52.0
github.com/xtls/xray-core v1.8.7
go.uber.org/atomic v1.11.0
golang.org/x/text v0.14.0
google.golang.org/grpc v1.61.0
gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.6
google.golang.org/grpc v1.61.1
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.7
)
require (
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
@ -45,10 +45,11 @@ require (
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/grbit/go-json v0.11.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
@ -75,6 +76,7 @@ require (
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fastjson v1.6.4 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
@ -82,16 +84,16 @@ require (
go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.1 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect

47
go.sum
View file

@ -12,8 +12,8 @@ github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
@ -124,6 +124,8 @@ github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTj
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
@ -136,8 +138,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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@ -175,8 +177,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE=
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
github.com/mymmrac/telego v0.29.1 h1:nsNnK0mS18OL+unoDjDI6BVfafJBbT8Wtj7rCzEWoM8=
github.com/mymmrac/telego v0.29.1/go.mod h1:ZLD1+L2TQRr97NPOCoN1V2w8y9kmFov33OfZ3qT8cF4=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
@ -288,8 +290,10 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
@ -319,8 +323,8 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
@ -338,8 +342,8 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -369,8 +373,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -406,14 +411,14 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@ -434,10 +439,10 @@ gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=

View file

@ -82,16 +82,16 @@ fi
install_base() {
case "${release}" in
centos | almalinux | rocky)
yum -y update && yum install -y -q wget curl tar
yum -y update && yum install -y -q wget curl tar tzdata
;;
fedora)
dnf -y update && dnf install -y -q wget curl tar
dnf -y update && dnf install -y -q wget curl tar tzdata
;;
arch | manjaro)
pacman -Syu && pacman -Syu --noconfirm wget curl tar
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
;;
*)
apt-get update && apt install -y -q wget curl tar
apt-get update && apt install -y -q wget curl tar tzdata
;;
esac
}

View file

@ -2,7 +2,6 @@ package random
import (
"math/rand"
"time"
)
var numSeq [10]rune
@ -13,8 +12,6 @@ var numUpperSeq [36]rune
var allSeq [62]rune
func init() {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 10; i++ {
numSeq[i] = rune('0' + i)
}
@ -41,3 +38,7 @@ func Seq(n int) string {
}
return string(runes)
}
func Num(n int) int {
return rand.Intn(n)
}

View file

@ -1050,14 +1050,19 @@ li.ant-select-dropdown-menu-item:empty:after {
color: rgba(255, 255, 255, 0.25);
}
.ant-input-group.ant-input-group-compact-addon:not(:first-child):not(
:last-child
),
.ant-input-group.ant-input-group-compact-wrap:not(:first-child):not(
:last-child
),
.ant-input-group.ant-input-group-compact
> .ant-input:not(:first-child):not(:last-child),
.ant-input-number-handler,
.ant-input-number-handler-wrap {
border-radius: 0;
}
.ant-input-number-handler {
border-radius: 0;
}
.ant-input-number {
overflow: clip;
}
@ -1089,7 +1094,8 @@ li.ant-select-dropdown-menu-item:empty:after {
> td,
.ant-table-thead
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
> td {
> td,
.ant-calendar-time-picker-select li:hover {
background-color: rgb(232 244 242);
}
@ -1121,3 +1127,11 @@ li.ant-select-dropdown-menu-item:empty:after {
.ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
border-radius: 0rem 1rem 1rem 0rem;
}
.ant-tag {
margin-right: 6px;
}
b, strong {
font-weight: 500;
}

View file

@ -29,6 +29,11 @@ const supportLangs = [
value: 'es-ES',
icon: '🇪🇸',
},
{
name: 'Indonesian',
value: 'id-ID',
icon: '🇮🇩',
},
];
function getLang() {

View file

@ -418,7 +418,7 @@ class Outbound extends CommonClass {
}
canEnableTls() {
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
}

View file

@ -1534,6 +1534,28 @@ class Inbound extends XrayCommonClass {
return url.toString();
}
getWireguardLink(address, port, remark, peerId) {
let txt = `[Interface]\n`
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
txt += `DNS = 1.1.1.1, 1.0.0.1\n`
if (this.settings.mtu) {
txt += `MTU = ${this.settings.mtu}\n`
}
txt += `\n# ${remark}\n`
txt += `[Peer]\n`
txt += `PublicKey = ${this.settings.pubKey}\n`
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
txt += `Endpoint = ${address}:${port}`
if (this.settings.peers[peerId].psk) {
txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
}
if (this.settings.peers[peerId].keepAlive) {
txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
}
return txt;
}
genLink(address='', port=this.port, forceTls='same', remark='', client) {
switch (this.protocol) {
case Protocols.VMESS:
@ -1557,7 +1579,7 @@ class Inbound extends XrayCommonClass {
const orderChars = remarkModel.slice(1);
let orders = {
'i': remark,
'e': client ? client.email : '',
'e': email,
'o': '',
};
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
@ -1580,6 +1602,7 @@ class Inbound extends XrayCommonClass {
}
genInboundLinks(remark = '', remarkModel = '-ieo') {
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
if(this.clients){
let links = [];
this.clients.forEach((client) => {
@ -1589,7 +1612,14 @@ class Inbound extends XrayCommonClass {
});
return links.join('\r\n');
} else {
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, 'same', remark);
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark);
if(this.protocol == Protocols.WIREGUARD) {
let links = [];
this.settings.peers.forEach((p,index) => {
links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
});
return links.join('\r\n');
}
return '';
}
}
@ -2297,9 +2327,13 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
};
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
constructor(publicKey=Wireguard.generateKeypair().publicKey, psk='', allowedIPs=['0.0.0.0/0','::/0'], keepAlive=0) {
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.0/24'], keepAlive=0) {
super();
this.privateKey = privateKey
this.publicKey = publicKey;
if (!this.publicKey){
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
}
this.psk = psk;
this.allowedIPs = allowedIPs;
this.keepAlive = keepAlive;
@ -2307,6 +2341,7 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
static fromJson(json={}){
return new Inbound.WireguardSettings.Peer(
json.privateKey,
json.publicKey,
json.preSharedKey,
json.allowedIPs,
@ -2316,6 +2351,7 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
toJson() {
return {
privateKey: this.privateKey,
publicKey: this.publicKey,
preSharedKey: this.psk.length>0 ? this.psk : undefined,
allowedIPs: this.allowedIPs,

File diff suppressed because one or more lines are too long

View file

@ -29,6 +29,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
g.POST("/warp/:action", a.warp)
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
}
func (a *XraySettingController) getXraySetting(c *gin.Context) {
@ -95,3 +96,13 @@ func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
}
jsonObj(c, outboundsTraffic, nil)
}
func (a *XraySettingController) resetOutboundsTraffic(c *gin.Context) {
tag := c.PostForm("tag")
err := a.OutboundService.ResetOutboundTraffic(tag)
if err != nil {
jsonMsg(c, "Error in reset outbound traffics", err)
return
}
jsonObj(c, "", nil)
}

View file

@ -35,12 +35,21 @@
this.client = client;
this.subId = '';
this.qrcodes = [];
if (this.inbound.protocol == Protocols.WIREGUARD){
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
this.qrcodes.push({
remark: "Peer " + (index+1),
link: l
});
});
} else {
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
this.qrcodes.push({
remark: l.remark,
link: l.link
});
});
}
this.visible = true;
},
close: function () {

View file

@ -31,7 +31,7 @@
}
.title {
font-size: 32px;
font-weight: bold;
font-weight: 600;
}
#app {
overflow: hidden;
@ -204,7 +204,7 @@
position: relative;
width: 100%;
height: 15vh;
margin-bottom: -5px; /*Fix for safari gap*/
margin-bottom: -8px; /*Fix for safari gap*/
min-height: 100px;
max-height: 150px;
}
@ -212,23 +212,27 @@
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
}
.dark .parallax > use {
fill: rgb(10 117 87 / 20%);
fill: #0f2d32;
}
.parallax > use:nth-child(1) {
animation-delay: -2s;
animation-duration: 7s;
animation-duration: 4s;
opacity: 0.2;
}
.parallax > use:nth-child(2) {
animation-delay: -3s;
animation-duration: 10s;
animation-duration: 7s;
opacity: 0.4;
}
.parallax > use:nth-child(3) {
animation-delay: -4s;
animation-duration: 13s;
animation-duration: 10s;
opacity: 0.6;
}
.parallax > use:nth-child(4) {
animation-delay: -5s;
animation-duration: 13s;
}
@keyframes move-forever {
0% {
transform: translate3d(-90px, 0, 0);
@ -255,9 +259,10 @@
<path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
</defs>
<g class="parallax">
<use xlink:href="#gentle-wave" x="48" y="0" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="3" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="5" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="rgba(0, 135, 113, 0.08)" />
<use xlink:href="#gentle-wave" x="48" y="7" fill="#c7ebe2" />
</g>
</svg>
</div>
@ -290,7 +295,7 @@
</a-form-item>
<a-form-item>
<a-row justify="center" class="centered">
<div class="wave-btn-bg wave-btn-bg-cl" :style="loading ? { width: '54px' } : { display: 'inline-block' }">
<div class="wave-btn-bg wave-btn-bg-cl" :style="loading ? { width: '52px' } : { display: 'inline-block' }">
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined">
[[ loading ? '' : '{{ i18n "login" }}' ]]
</a-button>

View file

@ -1,19 +1,19 @@
{{define "menuItems"}}
<a-menu-item key="{{ .base_path }}panel/">
<a-icon type="dashboard"></a-icon>
<span>{{ i18n "menu.dashboard"}}</span>
<span><b>{{ i18n "menu.dashboard"}}</b></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}panel/inbounds">
<a-icon type="user"></a-icon>
<span>{{ i18n "menu.inbounds"}}</span>
<span><b>{{ i18n "menu.inbounds"}}</b></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}panel/settings">
<a-icon type="setting"></a-icon>
<span>{{ i18n "menu.settings"}}</span>
<span><b>{{ i18n "menu.settings"}}</b></span>
</a-menu-item>
<a-menu-item key="{{ .base_path }}panel/xray">
<a-icon type="tool"></a-icon>
<span>{{ i18n "menu.xray"}}</span>
<span><b>{{ i18n "menu.xray"}}</b></span>
</a-menu-item>
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
<!-- <a-icon type="laptop"></a-icon>-->
@ -21,7 +21,7 @@
<!--</a-menu-item>-->
<a-menu-item key="{{ .base_path }}logout">
<a-icon type="logout"></a-icon>
<span>{{ i18n "menu.logout"}}</span>
<span><b>{{ i18n "menu.logout"}}</b></span>
</a-menu-item>
{{end}}

View file

@ -5,7 +5,7 @@
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
:placeholder="placeholder">
<template #addonAfter>
<a-icon type="calendar" style="font-size: 16px;"/>
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/>
</template>
</a-input>
</div>

View file

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

View file

@ -134,28 +134,10 @@
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
<a-input v-model.trim="peer.endpoint"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.publicKey" }}
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
<a-input v-model.trim="peer.psk"></a-input>
</a-form-item>
<a-form-item>
@ -189,7 +171,6 @@
<a-form-item label='ID'>
<a-input v-model.trim="outbound.settings.id"></a-input>
</a-form-item>
<!-- vless settings -->
<template v-if="outbound.canEnableTlsFlow()">
<a-form-item label='Flow'>
@ -212,12 +193,17 @@
<a-input v-model.trim="outbound.settings.pass"></a-input>
</a-form-item>
</template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<!-- trojan/shadowsocks -->
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
<a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.settings.password"></a-input>
</a-form-item>
</template>
<!-- shadowsocks -->
<template v-if="outbound.protocol === Protocols.Shadowsocks">
<a-form-item label='{{ i18n "encryption" }}'>
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
<a-select-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='UDP over TCP'>
@ -363,13 +349,15 @@
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="outbound.stream.tls.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select v-model="outbound.stream.tls.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value=''>None</a-select-option>
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="ALPN">
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
<a-select mode="multiple"
:dropdown-class-name="themeSwitcher.currentTheme"
v-model="outbound.stream.tls.alpn">
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
</a-select>
@ -381,11 +369,12 @@
<!-- reality settings -->
<template v-if="outbound.stream.isReality">
<a-form-item label='{{ i18n "domainName" }}'>
<a-form-item label="SNI">
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
</a-form-item>
<a-form-item label="uTLS">
<a-select v-model="outbound.stream.reality.fingerprint" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select v-model="outbound.stream.reality.fingerprint"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
</a-select>
</a-form-item>

View file

@ -38,10 +38,16 @@
<template slot="title">
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.publicKey" }}
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
{{ i18n "pages.xray.wireguard.secretKey" }}
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.privateKey"></a-input>
</a-form-item>
<a-form-item>
<template slot="label">
{{ i18n "pages.xray.wireguard.publicKey" }}
</template>
<a-input v-model.trim="peer.publicKey"></a-input>
</a-form-item>
<a-form-item>
@ -51,7 +57,7 @@
<span>{{ i18n "reset" }}</span>
</template>
{{ i18n "pages.xray.wireguard.psk" }}
<a-icon @click="peer.psk = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
</a-tooltip>
</template>
<a-input v-model.trim="peer.psk"></a-input>

View file

@ -20,7 +20,7 @@
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
</a-tooltip>
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
<a-button style="width: 10%; margin: 0px; border-radius: 0 1rem 1rem 0;" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
</a-input-group>
</a-form>
{{end}}

View file

@ -33,7 +33,7 @@
</template>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('host', '')">+</a-button>
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', '')">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">

View file

@ -7,7 +7,7 @@
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
<a-button size="small" @click="inbound.stream.ws.addHeader('host', '')">+</a-button>
<a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
</a-form-item>
<a-form-item :wrapper-col="{span:24}">
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">

View file

@ -179,10 +179,10 @@
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
<a-divider>Telegram ID</a-divider>
<a-row>
<a-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
<a-col :sx="24" :md="22">[[ infoModal.clientSettings.tgId ]]</a-col>
<a-col :sx="24" :md="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', infoModal.clientSettings.tgId)">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
@ -283,24 +283,50 @@
</tr>
<template v-for="(peer, index) in inbound.settings.peers">
<tr>
<td colspan="2"><a-tag>Peer [[ index + 1 ]]</a-tag></td>
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td>
</tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
<td>[[ peer.privateKey ]]</td>
</tr>
<tr>
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
<td>[[ peer.publicKey ]]</td>
</tr>
<tr>
<tr class="client-table-odd-row">
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
<td>[[ peer.psk ]]</td>
</tr>
<tr class="client-table-odd-row">
<tr>
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
<td>[[ peer.allowedIPs.join(",") ]]</td>
</tr>
<tr>
<tr class="client-table-odd-row">
<td>Keep Alive</td>
<td>[[ peer.keepAlive ]]</td>
</tr>
<tr>
<td colspan="2">
<a-row>
<a-col :span="22" style="overflow-wrap: anywhere;">
<a-tag color="blue">Config</a-tag>
<div
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
style="border-radius: 1rem; padding: 0.5rem;"
class="client-table-odd-row"></div>
</a-col>
<a-col :span="2" style="text-align: right;">
<a-tooltip title='{{ i18n "copy" }}'>
<button class="ant-btn ant-btn-primary"
:id="'copy-url-link-'+index"
@click="copyToClipboard('copy-url-link-'+index, infoModal.links[index])">
<a-icon type="snippets"></a-icon>
</button>
</a-tooltip>
</a-col>
</a-row>
</td>
</tr>
</table>
</template>
</template>
@ -319,7 +345,6 @@
index: null,
isExpired: false,
subLink: '',
tgLink: '',
show(dbInbound, index) {
this.index = index;
this.inbound = dbInbound.toInbound();
@ -327,14 +352,15 @@
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
if (this.inbound.protocol == Protocols.WIREGUARD){
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
} else {
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
}
if (this.clientSettings) {
if (this.clientSettings.subId) {
this.subLink = this.genSubLink(this.clientSettings.subId);
}
if (this.clientSettings.tgId) {
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
}
}
this.visible = true;
},

View file

@ -133,6 +133,10 @@
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }}
</a-menu-item>
<a-menu-item key="subs">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="resetInbounds">
<a-icon type="reload"></a-icon>
{{ i18n "pages.inbounds.resetAllTraffic" }}
@ -141,7 +145,7 @@
<a-icon type="file-done"></a-icon>
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
</a-menu-item>
<a-menu-item key="delDepletedClients">
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
@ -196,7 +200,7 @@
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser">
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
<a-icon type="qrcode"></a-icon>
{{ i18n "qrCode" }}
</a-menu-item>
@ -217,7 +221,11 @@
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}}
</a-menu-item>
<a-menu-item key="delDepletedClients">
<a-menu-item key="subs">
<a-icon type="export"></a-icon>
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
</a-menu-item>
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
<a-icon type="rest"></a-icon>
{{ i18n "pages.inbounds.delDepletedClients" }}
</a-menu-item>
@ -578,6 +586,7 @@
this.refreshing = false;
return;
}
await this.getOnlineUsers();
this.setInbounds(msg.obj);
setTimeout(() => {
@ -642,8 +651,12 @@
clientCount = clients.length;
if (dbInbound.enable) {
clients.forEach(client => {
client.enable ? active.push(client.email) : deactive.push(client.email);
if(this.isClientOnline(client.email)) online.push(client.email);
if (client.enable && this.isClientOnline(client.email)) {
active.push(client.email);
online.push(client.email);
} else {
deactive.push(client.email);
}
});
clientStats.forEach(client => {
if (!client.enable) {
@ -668,6 +681,7 @@
online: online,
};
},
searchInbounds(key) {
if (ObjectUtil.isEmpty(key)) {
this.searchedInbounds = this.dbInbounds.slice();
@ -731,6 +745,9 @@
case "export":
this.exportAllLinks();
break;
case "subs":
this.exportAllSubs();
break;
case "resetInbounds":
this.resetAllTraffic();
break;
@ -762,6 +779,9 @@
case "export":
this.inboundLinks(dbInbound.id);
break;
case "subs":
this.exportSubs(dbInbound.id);
break;
case "clipboard":
this.copyToClipboard(dbInbound.id);
break;
@ -1186,6 +1206,22 @@
newDbInbound = this.checkFallback(dbInbound);
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
},
exportSubs(dbInboundId) {
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
const clients = this.getInboundClients(dbInbound);
let subLinks = []
if (clients != null){
clients.forEach(c => {
if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
}
})
}
txtModal.show(
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
[...new Set(subLinks)].join('\n'),
dbInbound.remark + "-Subs");
},
importInbound() {
promptModal.open({
title: '{{ i18n "pages.inbounds.importInbound" }}',
@ -1198,6 +1234,23 @@
},
});
},
exportAllSubs() {
let subLinks = []
for (const dbInbound of this.dbInbounds) {
const clients = this.getInboundClients(dbInbound);
if (clients != null){
clients.forEach(c => {
if (c.subId && c.subId.length>0){
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
}
})
}
}
txtModal.show(
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
[...new Set(subLinks)].join('\r\n'),
'All-Inbounds-Subs');
},
exportAllLinks() {
let copyText = [];
for (const dbInbound of this.dbInbounds) {

View file

@ -18,6 +18,14 @@
.ant-card-dark h2 {
color: hsla(0, 0%, 100%, .65);
}
.ant-tag-df {
color: rgb(0 0 0 / 80%);
}
.dark .ant-tag-df {
color: rgb(255 255 255 / 80%);
}
</style>
<body>
@ -36,15 +44,15 @@
<a-progress type="dashboard" status="normal"
:stroke-color="status.cpu.color"
:percent="status.cpu.percent"></a-progress>
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
</a-col>
<a-col :span="12" style="text-align: center">
<a-progress type="dashboard" status="normal"
:stroke-color="status.mem.color"
:percent="status.mem.percent"></a-progress>
<div>
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
<b>{{ i18n "pages.index.memory"}}:</b> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
</div>
</a-col>
</a-row>
@ -56,7 +64,7 @@
:stroke-color="status.swap.color"
:percent="status.swap.percent"></a-progress>
<div>
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
</div>
</a-col>
<a-col :span="12" style="text-align: center">
@ -64,7 +72,7 @@
:stroke-color="status.disk.color"
:percent="status.disk.percent"></a-progress>
<div>
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
<b>{{ i18n "pages.index.hard"}}:</b> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
</div>
</a-col>
</a-row>
@ -75,25 +83,25 @@
</transition>
<transition name="list" appear>
<a-row>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
3X-UI <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
Xray <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
<b>3X-UI:</b>
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
<a rel="noopener" href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@Panel3xui</a-tag></a>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
{{ i18n "menu.link" }}:
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
<b>{{ i18n "pages.index.operationHours" }}:</b>
<a-tag class="ant-tag-df">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
<a-tag class="ant-tag-df">OS [[ formatSecond(status.uptime) ]]</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
{{ i18n "pages.index.xrayStatus" }}:
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]]
</a-tag>
<a-popover v-if="status.xray.state === State.Error"
:overlay-class-name="themeSwitcher.currentTheme">
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
@ -106,137 +114,143 @@
</a-popover>
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">{{ i18n "pages.index.xraySwitch" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
{{ i18n "pages.index.operationHours" }}:
Xray
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
OS
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
<b>{{ i18n "menu.link" }}:</b>
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
<b>{{ i18n "pages.index.systemLoad" }}:</b>
<a-tag class="ant-tag-df">
<a-tooltip>
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
<template slot="title">
{{ i18n "pages.index.systemLoadDesc" }}
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
{{ i18n "usage"}}:
RAM [[ sizeFormat(status.appStats.mem) ]] -
<b>{{ i18n "usage"}}:</b>
<a-tag class="ant-tag-df">
RAM [[ sizeFormat(status.appStats.mem) ]]
</a-tag>
<a-tag class="ant-tag-df">
Threads [[ status.appStats.threads ]]
</a-tooltip>
</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
<a-row>
<a-col :span="12">
<a-icon type="global"></a-icon>
IPv4:
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="global"></a-icon> IPv4
<template slot="title">
[[ status.publicIP.ipv4 ]]
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-col>
<a-col :span="12">
<a-icon type="global"></a-icon>
IPv6:
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="global"></a-icon> IPv6
<template slot="title">
[[ status.publicIP.ipv6 ]]
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-col>
</a-row>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
<a-row>
<a-col :span="12">
<a-icon type="swap"></a-icon>
TCP: [[ status.tcpCount ]]
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
<template slot="title">
{{ i18n "pages.index.connectionTcpCountDesc" }}
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-col>
<a-col :span="12">
<a-icon type="swap"></a-icon>
UDP: [[ status.udpCount ]]
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
<template slot="title">
{{ i18n "pages.index.connectionUdpCountDesc" }}
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-col>
</a-row>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
<a-row>
<a-col :span="12">
<a-icon type="arrow-up"></a-icon>
[[ sizeFormat(status.netIO.up) ]]/s
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="arrow-up"></a-icon>
Up: [[ sizeFormat(status.netIO.up) ]]/s
<template slot="title">
{{ i18n "pages.index.upSpeed" }}
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-col>
<a-col :span="12">
<a-icon type="arrow-down"></a-icon>
[[ sizeFormat(status.netIO.down) ]]/s
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="arrow-down"></a-icon>
Down: [[ sizeFormat(status.netIO.down) ]]/s
<template slot="title">
{{ i18n "pages.index.downSpeed" }}
</template>
<a-icon type="question-circle"></a-icon>
</a-tooltip>
</a-tag>
</a-col>
</a-row>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
<a-col :sm="24" :lg="12">
<a-card hoverable>
<a-row>
<a-col :span="12">
<a-icon type="cloud-upload"></a-icon>
[[ sizeFormat(status.netTraffic.sent) ]]
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="cloud-upload"></a-icon>
<template slot="title">
{{ i18n "pages.index.totalSent" }}
</template>
<a-icon type="question-circle"></a-icon>
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
</a-tooltip>
</a-tag>
</a-col>
<a-col :span="12">
<a-icon type="cloud-download"></a-icon>
[[ sizeFormat(status.netTraffic.recv) ]]
<a-tag class="ant-tag-df">
<a-tooltip>
<a-icon type="cloud-download"></a-icon>
<template slot="title">
{{ i18n "pages.index.totalReceive" }}
</template>
<a-icon type="question-circle"></a-icon>
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
</a-tooltip>
</a-tag>
</a-col>
</a-row>
</a-card>
@ -256,7 +270,7 @@
></a-alert>
<template v-for="version, index in versionModal.versions">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
style="margin: 10px" @click="switchV2rayVersion(version)">
style="margin-right: 10px" @click="switchV2rayVersion(version)">
[[ version ]]
</a-tag>
</template>
@ -440,8 +454,8 @@
loading: false,
show(logs) {
this.visible = true;
this.logs = logs;
this.formattedLogs = logs.length > 0 ? this.formatLogs(logs) : "No Record...";
this.logs = logs || [];
this.formattedLogs = this.logs.length > 0 ? this.formatLogs(this.logs) : "No Record...";
},
formatLogs(logs) {
let formattedLogs = '';

View file

@ -76,15 +76,15 @@
<a-layout-content>
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
<a-space direction="vertical">
<a-card hoverable style="margin-bottom: .5rem;">
<a-row>
<a-col :xs="24" :sm="8" style="padding: 4px;">
<a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
<a-col :xs="24" :sm="10" style="padding: 4px;">
<a-space direction="horizontal">
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
<a-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
</a-space>
</a-col>
<a-col :xs="24" :sm="16">
<a-col :xs="24" :sm="14">
<template>
<div>
<template>

View file

@ -114,15 +114,12 @@
<a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.FreedomStrategy" }}'
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
<a-list-item-meta title='{{ i18n "pages.xray.FreedomStrategy" }}'
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select
v-model="freedomStrategy"
:dropdown-class-name="themeSwitcher.currentTheme"
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%">
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
@ -132,33 +129,37 @@
</a-list-item>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.RoutingStrategy" }}'
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
<a-list-item-meta title='{{ i18n "pages.xray.RoutingStrategy" }}'
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select
v-model="routingStrategy"
:dropdown-class-name="themeSwitcher.currentTheme"
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%">
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.logConfigs" }}'>
<a-row :xs="24" :sm="24" :lg="12">
<a-alert type="warning" style="text-align: center;">
<template slot="message">
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
{{ i18n "pages.xray.logConfigsDesc" }}
</template>
</a-alert>
</a-row>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.logLevel" }}'
description='{{ i18n "pages.xray.logLevelDesc" }}'/>
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
description='{{ i18n "pages.xray.logLevelDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select
v-model="setLogLevel"
:dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%">
<a-select v-model="setLogLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option v-for="s in logLevel" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
@ -166,21 +167,30 @@
</a-row>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta
title='{{ i18n "pages.xray.accessLog" }}'
description='{{ i18n "pages.xray.accessLogDesc" }}'/>
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
description='{{ i18n "pages.xray.accessLogDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select
v-model="setAccessLog"
:dropdown-class-name="themeSwitcher.currentTheme"
style="width: 100%">
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
<a-row style="padding: 20px">
<a-col :lg="24" :xl="12">
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
description='{{ i18n "pages.xray.errorLogDesc" }}' />
</a-col>
<a-col :lg="24" :xl="12">
<template>
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option v-for="s in error" :value="s">[[ s ]]</a-select-option>
</a-select>
</template>
</a-col>
</a-row>
</a-list-item>
</a-collapse-panel>
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
@ -327,6 +337,14 @@
[[ rule.outboundTag ]]
</a-popover>
</template>
<template slot="balancer" slot-scope="text, rule, index">
<a-popover :overlay-class-name="themeSwitcher.currentTheme">
<template slot="content">
<p v-if="rule.balancerTag">Balancer Tag: [[ rule.balancerTag ]]</p>
</template>
[[ rule.balancerTag ]]
</a-popover>
</template>
<template slot="info" slot-scope="text, rule, index">
<a-popover placement="bottomRight"
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
@ -377,11 +395,13 @@
<a-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
<a-row>
<a-col :xs="12" :sm="12" :lg="12">
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
<a-button type="primary" icon="plus" @click="addOutbound()" style="margin-bottom: 10px;">{{ i18n
"pages.xray.outbound.addOutbound" }}</a-button>
<a-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
</a-col>
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"/>
<a-icon type="sync" :spin="refreshing" @click="refreshOutboundTraffic()" style="margin: 0 5px;"></a-icon>
<a-icon type="retweet" @click="resetOutboundTraffic(-1)"></a-icon>
</a-col>
</a-row>
<a-table :columns="outboundColumns" bordered
@ -396,10 +416,19 @@
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item v-if="index>0" @click="setFirstOutbound(index)">
<a-icon type="vertical-align-top"></a-icon>
{{ i18n "pages.xray.rules.first"}}
</a-menu-item>
<a-menu-item @click="editOutbound(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="resetOutboundTraffic(index)">
<span>
<a-icon type="retweet"></a-icon> {{ i18n "pages.inbounds.resetTraffic"}}
</span>
</a-menu-item>
<a-menu-item @click="deleteOutbound(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
@ -452,6 +481,41 @@
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="tpl-balancers" tab='{{ i18n "pages.xray.Balancers"}}' style="padding-top: 20px;" force-render="true">
<a-button type="primary" icon="plus" @click="addBalancer()" style="margin-bottom: 10px;">{{ i18n "pages.xray.balancer.addBalancer"}}</a-button>
<a-table :columns="balancerColumns" bordered
:row-key="r => r.key"
:data-source="balancersData"
:scroll="isMobile ? {} : { x: 200 }"
:pagination="false"
:indent-size="0"
:style="isMobile ? 'padding: 5px 0' : 'margin-left: 1px;'">
<template slot="action" slot-scope="text, balancer, index">
[[ index+1 ]]
<a-dropdown :trigger="['click']">
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
<a-menu slot="overlay" :theme="themeSwitcher.currentTheme">
<a-menu-item @click="editBalancer(index)">
<a-icon type="edit"></a-icon>
{{ i18n "edit" }}
</a-menu-item>
<a-menu-item @click="deleteBalancer(index)">
<span style="color: #FF4D4F">
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
</span>
</a-menu-item>
</a-menu>
</a-dropdown>
</template>
<template slot="strategy" slot-scope="text, balancer, index">
<a-tag style="margin:0;" v-if="balancer.strategy=='random'" color="purple">Random</a-tag>
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
</template>
<template slot="selector" slot-scope="text, balancer, index">
<a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="tpl-advanced" tab='{{ i18n "pages.xray.advancedTemplate"}}' style="padding-top: 20px;" force-render="true">
<a-list-item-meta title='{{ i18n "pages.xray.Template"}}' description='{{ i18n "pages.xray.TemplateDesc"}}'></a-list-item-meta>
<a-radio-group v-model="advSettings" @change="changeCode" button-style="solid" style="margin: 10px 0;" :size="isMobile ? 'small' : ''">
@ -474,6 +538,7 @@
{{template "ruleModal"}}
{{template "outModal"}}
{{template "reverseModal"}}
{{template "balancerModal"}}
{{template "warpModal"}}
<script>
const rulesColumns = [
@ -490,9 +555,10 @@
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 20, ellipsis: true },
{ title: 'Inbound Tag', dataIndex: 'inboundTag', align: 'center', width: 15, ellipsis: true },
{ title: 'Client Email', dataIndex: 'user', align: 'center', width: 20, ellipsis: true }]},
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 20 },
{ title: '{{ i18n "pages.xray.rules.outbound"}}', dataIndex: 'outboundTag', align: 'center', width: 15 },
{ title: '{{ i18n "pages.xray.rules.balancer"}}', dataIndex: 'balancerTag', align: 'center', width: 15 },
];
const rulesMobileColumns = [
@ -517,6 +583,13 @@
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
];
const balancerColumns = [
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
{ title: '{{ i18n "pages.xray.balancer.tag"}}', dataIndex: 'tag', align: 'center', width: 50 },
{ title: '{{ i18n "pages.xray.balancer.balancerStrategy"}}', align: 'center', width: 50, scopedSlots: { customRender: 'strategy' }},
{ title: '{{ i18n "pages.xray.balancer.balancerSelectors"}}', align: 'center', width: 100, scopedSlots: { customRender: 'selector' }},
];
const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
@ -570,6 +643,7 @@
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
logLevel: ["none" , "debug" , "info" , "warning", "error"],
access: ["none" , "./access.log" ],
error: ["none" , "./error.log" ],
settingsData: {
protocols: {
bittorrent: ["bittorrent"],
@ -879,6 +953,11 @@
outbounds.splice(index,1);
this.outboundSettings = JSON.stringify(outbounds);
},
setFirstOutbound(index){
outbounds = this.templateSettings.outbounds;
outbounds.splice(0, 0, outbounds.splice(index, 1)[0]);
this.outboundSettings = JSON.stringify(outbounds);
},
async refreshOutboundTraffic() {
if (!this.refreshing) {
this.refreshing = true;
@ -895,6 +974,105 @@
this.refreshing = false;
}
},
async resetOutboundTraffic(index) {
let tag = "-alltags-";
if (index >= 0) {
tag = this.outboundData[index].tag ? this.outboundData[index].tag : ""
}
const msg = await HttpUtil.post("/panel/xray/resetOutboundsTraffic", { tag: tag });
if (msg.success) {
await this.refreshOutboundTraffic();
}
},
addBalancer() {
balancerModal.show({
title: '{{ i18n "pages.xray.balancer.addBalancer"}}',
okText: '{{ i18n "pages.xray.balancer.addBalancer"}}',
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
balancer: {
tag: '',
strategy: 'random',
selector: []
},
confirm: (balancer) => {
balancerModal.loading();
newTemplateSettings = this.templateSettings;
if (newTemplateSettings.routing.balancers == undefined) {
newTemplateSettings.routing.balancers = [];
}
let tmpBalancer = {
'tag': balancer.tag,
'selector': balancer.selector
};
if (balancer.strategy == 'roundRobin') {
tmpBalancer.strategy = {
'type': balancer.strategy
};
}
newTemplateSettings.routing.balancers.push(tmpBalancer);
this.templateSettings = newTemplateSettings;
balancerModal.close();
},
isEdit: false
});
},
editBalancer(index) {
const oldTag = this.balancersData[index].tag;
balancerModal.show({
title: '{{ i18n "pages.xray.balancer.editBalancer"}}',
okText: '{{ i18n "sure" }}',
balancerTags: this.balancersData.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag),
balancer: this.balancersData[index],
confirm: (balancer) => {
balancerModal.loading();
newTemplateSettings = this.templateSettings;
let tmpBalancer = {
'tag': balancer.tag,
'selector': balancer.selector
};
if (balancer.strategy == 'roundRobin') {
tmpBalancer.strategy = {
'type': balancer.strategy
};
}
newTemplateSettings.routing.balancers[index] = tmpBalancer;
// change edited tag if used in rule section
if (oldTag != balancer.tag) {
newTemplateSettings.routing.rules.forEach((rule) => {
if (rule.balancerTag && rule.balancerTag == oldTag) {
rule.balancerTag = balancer.tag;
}
});
}
this.templateSettings = newTemplateSettings;
balancerModal.close();
},
isEdit: true
});
},
deleteBalancer(index) {
newTemplateSettings = this.templateSettings;
//remove from balancers
const oldTag = this.balancersData[index].tag;
this.balancersData.splice(index, 1);
// remove from settings
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag == oldTag);
newTemplateSettings.routing.balancers.splice(realIndex, 1);
// remove related routing rules
let rules = [];
newTemplateSettings.routing.rules.forEach((r) => {
if (!r.balancerTag || r.balancerTag != oldTag) {
rules.push(r);
}
});
newTemplateSettings.routing.rules = rules;
this.templateSettings = newTemplateSettings;
},
addReverse(){
reverseModal.show({
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
@ -1084,6 +1262,27 @@
return data;
},
},
balancersData: {
get: function () {
data = []
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
this.templateSettings.routing.balancers.forEach((o, index) => {
let strategy = "random"
if (o.strategy && o.strategy.type == "roundRobin") {
strategy = o.strategy.type
}
data.push({
'key': index,
'tag': o.tag ? o.tag : "",
'strategy': strategy,
'selector': o.selector ? o.selector : []
});
});
}
return data;
}
},
routingRuleSettings: {
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
set: function (newValue) {
@ -1156,9 +1355,9 @@
this.templateSettings = newTemplateSettings;
}
},
setAccessLog: {
accessLog: {
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "none";
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
return this.templateSettings.log.access;
},
set: function (newValue) {
@ -1167,6 +1366,17 @@
this.templateSettings = newTemplateSettings;
}
},
errorLog: {
get: function () {
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
return this.templateSettings.log.error;
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.log.error = newValue;
this.templateSettings = newTemplateSettings;
}
},
blockedIPs: {
get: function () {
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });

View file

@ -0,0 +1,111 @@
{{define "balancerModal"}}
<a-modal
id="balancer-modal"
v-model="balancerModal.visible"
:title="balancerModal.title"
@ok="balancerModal.ok"
:confirm-loading="balancerModal.confirmLoading"
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
:closable="true"
:mask-closable="false"
:ok-text="balancerModal.okText"
cancel-text='{{ i18n "close" }}'
:class="themeSwitcher.currentTheme">
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
placeholder='{{ i18n "pages.xray.balancer.tagDesc" }}'></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerStrategy" }}'>
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="random">Random</a-select-option>
<a-select-option value="roundRobin">Round Robin</a-select-option>
</a-select>
</a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback :validate-status="balancerModal.emptySelector? 'warning' : 'success'">
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
<script>
const balancerModal = {
title: '',
visible: false,
confirmLoading: false,
okText: '{{ i18n "sure" }}',
isEdit: false,
confirm: null,
duplicateTag: false,
emptySelector: false,
balancer: {
tag: '',
strategy: 'random',
selector: []
},
outboundTags: [],
balancerTags:[],
ok() {
if (balancerModal.balancer.selector.length == 0) {
balancerModal.emptySelector = true;
return;
}
balancerModal.emptySelector = false;
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
},
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.visible = true;
if (isEdit) {
balancerModal.balancer = balancer;
} else {
balancerModal.balancer = {
tag: '',
strategy: 'random',
selector: []
};
}
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
this.isEdit = isEdit;
this.check()
},
close() {
balancerModal.visible = false;
balancerModal.loading(false);
},
loading(loading) {
balancerModal.confirmLoading = loading;
},
check() {
if (balancerModal.balancer.tag == '' || balancerModal.balancerTags.includes(balancerModal.balancer.tag)) {
this.duplicateTag = true;
this.isValid = false;
} else {
this.duplicateTag = false;
this.isValid = true;
}
},
checkSelector() {
balancerModal.emptySelector = balancerModal.balancer.selector.length == 0;
}
};
new Vue({
delimiters: ['[[', ']]'],
el: '#balancer-modal',
data: {
balancerModal: balancerModal
},
methods: {
}
});
</script>
{{end}}

View file

@ -107,6 +107,19 @@
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<template slot="label">
<a-tooltip>
<template slot="title">
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
</template>
Balancer Tag <a-icon type="question-circle"></a-icon>
</a-tooltip>
</template>
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
</table>
</a-form>
</a-modal>
@ -133,11 +146,12 @@
protocol: [],
attrs: [],
outboundTag: "",
balancerTag: "",
},
inboundTags: [],
outboundTags: [],
users: [],
balancerTag: [],
balancerTags: [],
ok() {
newRule = ruleModal.getResult();
ObjectUtil.execute(ruleModal.confirm, newRule);
@ -160,6 +174,7 @@
this.rule.protocol = rule.protocol;
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
this.rule.outboundTag = rule.outboundTag;
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""
} else {
this.rule = {
domainMatcher: "",
@ -174,6 +189,7 @@
protocol: [],
attrs: [],
outboundTag: "",
balancerTag: "",
}
}
this.isEdit = isEdit;
@ -186,6 +202,10 @@
}
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
}
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
this.balancerTags = app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)
}
},
close() {
ruleModal.visible = false;
@ -211,6 +231,7 @@
rule.protocol = value.protocol;
rule.attrs = Object.fromEntries(value.attrs);
rule.outboundTag = value.outboundTag;
rule.balancerTag = value.balancerTag;
for (const [key, value] of Object.entries(rule)) {
if (

View file

@ -25,7 +25,6 @@ type CheckClientIpJob struct {
var job *CheckClientIpJob
var ipFiles = []string{
xray.GetIPLimitLogPath(),
xray.GetIPLimitPrevLogPath(),
xray.GetIPLimitBannedLogPath(),
xray.GetIPLimitBannedPrevLogPath(),
xray.GetAccessPersistentLogPath(),
@ -51,6 +50,37 @@ func (j *CheckClientIpJob) Run() {
j.checkFail2BanInstalled()
j.processLogFile()
}
if !j.hasLimitIp() && xray.GetAccessLogPath() == "./access.log" {
go j.clearLogTime()
}
}
func (j *CheckClientIpJob) clearLogTime() {
for {
time.Sleep(time.Hour)
j.clearAccessLog()
}
}
func (j *CheckClientIpJob) clearAccessLog() {
accessLogPath := xray.GetAccessLogPath()
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
j.checkError(err)
defer logAccessP.Close()
// reopen the access log file for reading
file, err := os.Open(accessLogPath)
j.checkError(err)
defer file.Close()
// copy access log content to persistent file
_, err = io.Copy(logAccessP, file)
j.checkError(err)
// clean access log
err = os.Truncate(accessLogPath, 0)
j.checkError(err)
}
func (j *CheckClientIpJob) hasLimitIp() bool {
@ -121,7 +151,7 @@ func (j *CheckClientIpJob) processLogFile() {
matches := ipRegx.FindStringSubmatch(line)
if len(matches) > 1 {
ip := matches[1]
if ip == "127.0.0.1" || ip == "[::1]" {
if ip == "127.0.0.1" {
continue
}
@ -160,24 +190,7 @@ func (j *CheckClientIpJob) processLogFile() {
time.Sleep(time.Second * 2)
if shouldCleanLog {
// copy access log to persistent file
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
j.checkError(err)
defer logAccessP.Close()
// reopen the access log file for reading
file, err := os.Open(accessLogPath)
j.checkError(err)
defer file.Close()
// copy access log content to persistent file
_, err = io.Copy(logAccessP, file)
j.checkError(err)
// clean access log
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
j.checkError(err)
}
j.clearAccessLog()
}
}

View file

@ -15,7 +15,7 @@ func NewClearLogsJob() *ClearLogsJob {
// Here Run is an interface method of the Job interface
func (j *ClearLogsJob) Run() {
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
logFilesPrev := []string{xray.GetIPLimitPrevLogPath(), xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
logFilesPrev := []string{xray.GetIPLimitBannedPrevLogPath(), xray.GetAccessPersistentPrevLogPath()}
// clear old previous logs
for i := 0; i < len(logFilesPrev); i++ {
@ -26,9 +26,9 @@ func (j *ClearLogsJob) Run() {
// clear log files and copy to previous logs
for i := 0; i < len(logFiles); i++ {
if i > 0 {
// copy to previous logs
logFilePrev, err := os.OpenFile(logFilesPrev[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
logFilePrev, err := os.OpenFile(logFilesPrev[i-1], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
if err != nil {
logger.Warning("clear logs job err:", err)
}
@ -43,8 +43,9 @@ func (j *ClearLogsJob) Run() {
logger.Warning("clear logs job err:", err)
}
defer logFilePrev.Close()
}
err = os.Truncate(logFiles[i], 0)
err := os.Truncate(logFiles[i], 0)
if err != nil {
logger.Warning("clear logs job err:", err)
}

View file

@ -2,6 +2,7 @@
"log": {
"access": "none",
"dnsLog": false,
"error": "./error.log",
"loglevel": "warning"
},
"api": {

View file

@ -10,7 +10,6 @@ import (
)
type OutboundService struct {
xrayApi xray.XrayAPI
}
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
@ -78,3 +77,25 @@ func (s *OutboundService) GetOutboundsTraffic() ([]*model.OutboundTraffics, erro
return traffics, nil
}
func (s *OutboundService) ResetOutboundTraffic(tag string) error {
db := database.GetDB()
whereText := "tag "
if tag == "-alltags-" {
whereText += " <> ?"
} else {
whereText += " = ?"
}
result := db.Model(model.OutboundTraffics{}).
Where(whereText, tag).
Updates(map[string]interface{}{"up": 0, "down": 0, "total": 0})
err := result.Error
if err != nil {
return err
}
return nil
}

View file

@ -202,9 +202,13 @@ func (t *Tgbot) OnReceive() {
}, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
if message.UserShared != nil {
if message.UsersShared != nil {
if checkAdmin(message.From.ID) {
err := t.inboundService.SetClientTelegramUserID(message.UserShared.RequestID, strconv.FormatInt(message.UserShared.UserID, 10))
userIDsStr := ""
for _, userID := range message.UsersShared.UserIDs {
userIDsStr += strconv.FormatInt(userID, 10) + " "
}
err := t.inboundService.SetClientTelegramUserID(message.UsersShared.RequestID, userIDsStr)
output := ""
if err != nil {
output += t.I18nBot("tgbot.messages.selectUserFailed")
@ -277,7 +281,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
chatId := callbackQuery.Message.Chat.ID
chatId := callbackQuery.Message.GetChat().ID
if isAdmin {
// get query from hash storage
@ -296,22 +300,22 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
t.searchClient(chatId, email)
case "client_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "client_cancel":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "ips_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
case "ips_cancel":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
case "tgid_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
case "tgid_cancel":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
case "reset_traffic":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@ -321,13 +325,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmResetTraffic")).WithCallbackData(t.encodeQuery("reset_traffic_c "+email)),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "reset_traffic_c":
err := t.inboundService.ResetClientTrafficByEmail(email)
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@ -361,7 +365,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("200 GB").WithCallbackData(t.encodeQuery("limit_traffic_c "+email+" 200")),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "limit_traffic_c":
if len(dataArray) == 3 {
limitTraffic, err := strconv.Atoi(dataArray[2])
@ -370,13 +374,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
return
}
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "limit_traffic_in":
if len(dataArray) >= 3 {
oldInputNumber, err := strconv.Atoi(dataArray[2])
@ -432,12 +436,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("limit_traffic_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "reset_exp":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@ -464,7 +468,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.add")+" 12 "+t.I18nBot("tgbot.months")).WithCallbackData(t.encodeQuery("reset_exp_c "+email+" 365")),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "reset_exp_c":
if len(dataArray) == 3 {
days, err := strconv.Atoi(dataArray[2])
@ -499,13 +503,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
return
}
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "reset_exp_in":
if len(dataArray) >= 3 {
oldInputNumber, err := strconv.Atoi(dataArray[2])
@ -561,12 +565,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("reset_exp_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "ip_limit":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@ -595,7 +599,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("10").WithCallbackData(t.encodeQuery("ip_limit_c "+email+" 10")),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "ip_limit_c":
if len(dataArray) == 3 {
count, err := strconv.Atoi(dataArray[2])
@ -604,13 +608,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if err == nil {
t.xrayService.SetToNeedRestart()
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
return
}
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "ip_limit_in":
if len(dataArray) >= 3 {
oldInputNumber, err := strconv.Atoi(dataArray[2])
@ -666,12 +670,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton("⬅️").WithCallbackData(t.encodeQuery("ip_limit_in "+email+" "+strconv.Itoa(inputNumber)+" -1")),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
return
}
}
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
case "clear_ips":
inlineKeyboard := tu.InlineKeyboard(
tu.InlineKeyboardRow(
@ -681,12 +685,12 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmClearIps")).WithCallbackData(t.encodeQuery("clear_ips_c "+email)),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "clear_ips_c":
err := t.inboundService.ClearClientIps(email)
if err == nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
t.searchClientIps(chatId, email, callbackQuery.Message.MessageID)
t.searchClientIps(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@ -705,7 +709,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmRemoveTGUser")).WithCallbackData(t.encodeQuery("tgid_remove_c "+email)),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "tgid_remove_c":
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
if err != nil || traffic == nil {
@ -715,7 +719,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
err = t.inboundService.SetClientTelegramUserID(traffic.Id, "")
if err == nil {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.MessageID)
t.clientTelegramUserInfo(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@ -728,7 +732,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.confirmToggle")).WithCallbackData(t.encodeQuery("toggle_enable_c "+email)),
),
)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.MessageID, inlineKeyboard)
t.editMessageCallbackTgBot(chatId, callbackQuery.Message.GetMessageID(), inlineKeyboard)
case "toggle_enable_c":
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
if err == nil {
@ -738,7 +742,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
}
t.searchClient(chatId, email, callbackQuery.Message.MessageID)
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
} else {
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
}
@ -774,7 +778,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
t.onlineClients(chatId)
case "onlines_refresh":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
t.onlineClients(chatId, callbackQuery.Message.MessageID)
t.onlineClients(chatId, callbackQuery.Message.GetMessageID())
case "commands":
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
@ -1215,13 +1219,13 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
} else {
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
requestUser := telego.KeyboardButtonRequestUser{
requestUser := telego.KeyboardButtonRequestUsers{
RequestID: int32(traffic.Id),
UserIsBot: new(bool),
}
keyboard := tu.Keyboard(
tu.KeyboardRow(
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUser(&requestUser),
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser),
),
tu.KeyboardRow(
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),

View file

@ -76,7 +76,7 @@
"title" = "Overview"
"memory" = "RAM"
"hard" = "Disk"
"xrayStatus" = "Status"
"xrayStatus" = "Xray"
"stopXray" = "Stop"
"restartXray" = "Restart"
"xraySwitch" = "Version"
@ -311,6 +311,8 @@
"advancedTemplate" = "Advanced"
"generalConfigs" = "General"
"generalConfigsDesc" = "These options will determine general adjustments."
"logConfigs" = "Log"
"logConfigsDesc" = "Logs may affect your server's efficiency. It is recommended to enable it wisely only in case of your needs"
"blockConfigs" = "Protection Shield"
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
"blockCountryConfigs" = "Block Country"
@ -388,6 +390,7 @@
"Inbounds" = "Inbounds"
"InboundsDesc" = "Accepting the specific clients."
"Outbounds" = "Outbounds"
"Balancers" = "Balancers"
"OutboundsDesc" = "Set the outgoing traffic pathway."
"Routings" = "Routing Rules"
"RoutingsDesc" = "The priority of each rule is important!"
@ -396,6 +399,8 @@
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
"accessLog" = "Access Log"
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
"errorLog" = "Error Log"
"errorLogDesc" = "The file path for the error log. The special value 'none' disabled error logs"
[pages.xray.rules]
"first" = "First"
@ -406,6 +411,7 @@
"dest" = "Destination"
"inbound" = "Inbound"
"outbound" = "Outbound"
"balancer" = "Balancer"
"info" = "Info"
"add" = "Add Rule"
"edit" = "Edit Rule"
@ -426,6 +432,15 @@
"portal" = "Portal"
"intercon" = "Interconnection"
[pages.xray.balancer]
"addBalancer" = "Add Balancer"
"editBalancer" = "Edit Balancer"
"balancerStrategy" = "Strategy"
"balancerSelectors" = "Selectors"
"tag" = "Tag"
"tagDesc" = "Unique Tag"
"balancerDesc" = "It is not possible to use balancerTag and outboundTag at the same time. If used at the same time, only outboundTag will work."
[pages.xray.wireguard]
"secretKey" = "Secret Key"
"publicKey" = "Public Key"

View file

@ -20,7 +20,7 @@
"check" = "Verificar"
"indefinite" = "Indefinido"
"unlimited" = "Ilimitado"
"none" = "Ninguno"
"none" = "None"
"qrCode" = "Código QR"
"info" = "Más Información"
"edit" = "Editar"
@ -76,7 +76,7 @@
"title" = "Estado del Sistema"
"memory" = "Memoria"
"hard" = "Disco Duro"
"xrayStatus" = "Estado de"
"xrayStatus" = "Xray"
"stopXray" = "Detener"
"restartXray" = "Reiniciar"
"xraySwitch" = "Versión"
@ -311,6 +311,8 @@
"advancedTemplate" = "Plantilla Avanzada"
"generalConfigs" = "Configuraciones Generales"
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes generales."
"logConfigs" = "Registro"
"logConfigsDesc" = "Los registros pueden afectar la eficiencia de su servidor. Se recomienda habilitarlo sabiamente solo en caso de sus necesidades."
"blockConfigs" = "Configuraciones de Bloqueo"
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
"blockCountryConfigs" = "Configuraciones de Bloqueo por País"
@ -388,6 +390,7 @@
"Inbounds" = "Entrante"
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
"Outbounds" = "Salidas"
"Balancers" = "Equilibradores"
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
"Routings" = "Reglas de enrutamiento"
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
@ -396,6 +399,8 @@
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
"accessLog" = "Registro de acceso"
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros de acceso"
"errorLog" = "Registro de errores"
"errorLogDesc" = "La ruta del archivo para el registro de errores. El valor especial 'ninguno' deshabilitó los registros de errores"
[pages.xray.rules]
"first" = "Primero"
@ -406,6 +411,7 @@
"dest" = "Destino"
"inbound" = "Entrante"
"outbound" = "saliente"
"balancer" = "Balancín"
"info" = "Información"
"add" = "Agregar regla"
"edit" = "Editar regla"
@ -426,6 +432,15 @@
"portal" = "portal"
"intercon" = "Interconexión"
[pages.xray.balancer]
"addBalancer" = "Agregar equilibrador"
"editBalancer" = "Editar balanceador"
"balancerStrategy" = "Estrategia"
"balancerSelectors" = "Selectores"
"tag" = "Etiqueta"
"tagDesc" = "etiqueta única"
"balancerDesc" = "No es posible utilizar balancerTag y outboundTag al mismo tiempo. Si se utilizan al mismo tiempo, sólo funcionará outboundTag."
[pages.xray.wireguard]
"secretKey" = "Llave secreta"
"publicKey" = "Llave pública"

View file

@ -76,7 +76,7 @@
"title" = "نمای کلی"
"memory" = "RAM"
"hard" = "Disk"
"xrayStatus" = "وضعیت‌ایکس‌ری"
"xrayStatus" = "ایکس‌ری"
"stopXray" = "توقف"
"restartXray" = "شروع‌مجدد"
"xraySwitch" = "‌نسخه"
@ -311,6 +311,8 @@
"advancedTemplate" = "پیشرفته"
"generalConfigs" = "استراتژی‌ کلی"
"generalConfigsDesc" = "این گزینه‌ها استراتژی کلی ترافیک را تعیین می‌کنند"
"logConfigs" = "گزارش"
"logConfigsDesc" = "گزارش‌ها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید"
"blockConfigs" = "سپر محافظ"
"blockConfigsDesc" = "این گزینه‌ها ترافیک را بر اساس پروتکل‌های درخواستی خاص، و وب سایت‌ها مسدود می‌کند"
"blockCountryConfigs" = "مسدودسازی کشور"
@ -388,6 +390,7 @@
"Inbounds" = "ورودی‌ها"
"InboundsDesc" = "پذیرش کلاینت خاص"
"Outbounds" = "خروجی‌ها"
"Balancers" = "بالانسرها"
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
"Routings" = "قوانین مسیریابی"
"RoutingsDesc" = "اولویت هر قانون مهم است"
@ -396,6 +399,8 @@
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
"accessLog" = "مسیر گزارش"
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارش‌های دسترسی را غیرفعال میکند."
"errorLog" = "گزارش خطا"
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
[pages.xray.rules]
"first" = "اولین"
@ -406,6 +411,7 @@
"dest" = "مقصد"
"inbound" = "ورودی"
"outbound" = "خروجی"
"balancer" = "بالانسر"
"info" = "اطلاعات"
"add" = "افزودن قانون"
"edit" = "ویرایش قانون"
@ -426,6 +432,15 @@
"portal" = "پورتال"
"intercon" = "اتصال میانی"
[pages.xray.balancer]
"addBalancer" = "افزودن بالانسر"
"editBalancer" = "ویرایش بالانسر"
"balancerStrategy" = "استراتژی"
"balancerSelectors" = "انتخاب‌گرها"
"tag" = "برچسب"
"tagDesc" = "برچسب یگانه"
"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد."
[pages.xray.wireguard]
"secretKey" = "کلید شخصی"
"publicKey" = "کلید عمومی"

View file

@ -0,0 +1,595 @@
"username" = "Nama Pengguna"
"password" = "Kata Sandi"
"login" = "Masuk"
"confirm" = "Konfirmasi"
"cancel" = "Batal"
"close" = "Tutup"
"copy" = "Salin"
"copied" = "Tersalin"
"download" = "Unduh"
"remark" = "Catatan"
"enable" = "Aktifkan"
"protocol" = "Protokol"
"search" = "Cari"
"filter" = "Filter"
"loading" = "Memuat..."
"second" = "Detik"
"minute" = "Menit"
"hour" = "Jam"
"day" = "Hari"
"check" = "Centang"
"indefinite" = "Tak Terbatas"
"unlimited" = "Tanpa Batas"
"none" = "None"
"qrCode" = "Kode QR"
"info" = "Informasi Lebih Lanjut"
"edit" = "Edit"
"delete" = "Hapus"
"reset" = "Reset"
"copySuccess" = "Berhasil Disalin"
"sure" = "Yakin"
"encryption" = "Enkripsi"
"transmission" = "Transmisi"
"host" = "Host"
"path" = "Jalur"
"camouflage" = "Obfuscation"
"status" = "Status"
"enabled" = "Aktif"
"disabled" = "Nonaktif"
"depleted" = "Habis"
"depletingSoon" = "Akan Habis"
"offline" = "Offline"
"online" = "Online"
"domainName" = "Nama Domain"
"monitor" = "IP Pemantauan"
"certificate" = "Sertifikat"
"fail" = "Gagal"
"success" = "Berhasil"
"getVersion" = "Dapatkan Versi"
"install" = "Instal"
"clients" = "Klien"
"usage" = "Penggunaan"
"secretToken" = "Token Rahasia"
"remained" = "Tersisa"
"security" = "Keamanan"
[menu]
"dashboard" = "Ikhtisar"
"inbounds" = "Masuk"
"settings" = "Pengaturan Panel"
"xray" = "Konfigurasi Xray"
"logout" = "Keluar"
"link" = "Kelola"
[pages.login]
"title" = "Selamat Datang"
"loginAgain" = "Sesi Anda telah berakhir, harap masuk kembali"
[pages.login.toasts]
"invalidFormData" = "Format data input tidak valid."
"emptyUsername" = "Nama Pengguna diperlukan"
"emptyPassword" = "Kata Sandi diperlukan"
"wrongUsernameOrPassword" = "Nama pengguna atau kata sandi tidak valid."
"successLogin" = "Login berhasil"
[pages.index]
"title" = "Ikhtisar"
"memory" = "RAM"
"hard" = "Disk"
"xrayStatus" = "Xray"
"stopXray" = "Stop"
"restartXray" = "Restart"
"xraySwitch" = "Versi"
"xraySwitchClick" = "Pilih versi yang ingin Anda pindah."
"xraySwitchClickDesk" = "Pilih dengan hati-hati, karena versi yang lebih lama mungkin tidak kompatibel dengan konfigurasi saat ini."
"operationHours" = "Waktu Aktif"
"systemLoad" = "Beban Sistem"
"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir"
"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem"
"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem"
"connectionCount" = "Statistik Koneksi"
"upSpeed" = "Kecepatan unggah keseluruhan di seluruh sistem"
"downSpeed" = "Kecepatan unduh keseluruhan di seluruh sistem"
"totalSent" = "Total data terkirim di seluruh sistem sejak startup OS"
"totalReceive" = "Total data diterima di seluruh sistem sejak startup OS"
"xraySwitchVersionDialog" = "Ganti Versi Xray"
"xraySwitchVersionDialogDesc" = "Apakah Anda yakin ingin mengubah versi Xray menjadi"
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
"logs" = "Log"
"config" = "Konfigurasi"
"backup" = "Cadangan & Pulihkan"
"backupTitle" = "Cadangan & Pulihkan Database"
"backupDescription" = "Disarankan untuk membuat cadangan sebelum memulihkan database."
"exportDatabase" = "Cadangkan"
"importDatabase" = "Pulihkan"
[pages.inbounds]
"title" = "Masuk"
"totalDownUp" = "Total Terkirim/Diterima"
"totalUsage" = "Penggunaan Total"
"inboundCount" = "Total Masuk"
"operate" = "Menu"
"enable" = "Aktifkan"
"remark" = "Catatan"
"protocol" = "Protokol"
"port" = "Port"
"traffic" = "Traffic"
"details" = "Rincian"
"transportConfig" = "Transport"
"expireDate" = "Durasi"
"resetTraffic" = "Reset Traffic"
"addInbound" = "Tambahkan Masuk"
"generalActions" = "Tindakan Umum"
"create" = "Buat"
"update" = "Perbarui"
"modifyInbound" = "Ubah Masuk"
"deleteInbound" = "Hapus Masuk"
"deleteInboundContent" = "Apakah Anda yakin ingin menghapus masuk?"
"deleteClient" = "Hapus Klien"
"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?"
"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?"
"copyLink" = "Salin URL"
"address" = "Alamat"
"network" = "Jaringan"
"destinationPort" = "Port Tujuan"
"targetAddress" = "Alamat Target"
"monitorDesc" = "Biarkan kosong untuk mendengarkan semua IP"
"meansNoLimit" = " = Unlimited. (unit: GB)"
"totalFlow" = "Total Aliran"
"leaveBlankToNeverExpire" = "Biarkan kosong untuk tidak pernah kedaluwarsa"
"noRecommendKeepDefault" = "Disarankan untuk tetap menggunakan pengaturan default"
"certificatePath" = "Path Berkas"
"certificateContent" = "Konten Berkas"
"publicKeyPath" = "Path Kunci Publik"
"publicKeyContent" = "Konten Kunci Publik"
"keyPath" = "Path Kunci Privat"
"keyContent" = "Konten Kunci Privat"
"clickOnQRcode" = "Klik pada Kode QR untuk Menyalin"
"client" = "Klien"
"export" = "Ekspor Semua URL"
"clone" = "Duplikat"
"cloneInbound" = "Duplikat"
"cloneInboundContent" = "Semua pengaturan masuk ini, kecuali Port, Listening IP, dan Klien, akan diterapkan pada duplikat."
"cloneInboundOk" = "Duplikat"
"resetAllTraffic" = "Reset Semua Traffic Masuk"
"resetAllTrafficTitle" = "Reset Semua Traffic Masuk"
"resetAllTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua masuk?"
"resetInboundClientTraffics" = "Reset Traffic Klien Masuk"
"resetInboundClientTrafficTitle" = "Reset Traffic Klien Masuk"
"resetInboundClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic klien masuk ini?"
"resetAllClientTraffics" = "Reset Traffic Semua Klien"
"resetAllClientTrafficTitle" = "Reset Traffic Semua Klien"
"resetAllClientTrafficContent" = "Apakah Anda yakin ingin mereset traffic semua klien?"
"delDepletedClients" = "Hapus Klien Habis"
"delDepletedClientsTitle" = "Hapus Klien Habis"
"delDepletedClientsContent" = "Apakah Anda yakin ingin menghapus semua klien yang habis?"
"email" = "Email"
"emailDesc" = "Harap berikan alamat email yang unik."
"IPLimit" = "Batas IP"
"IPLimitDesc" = "Menonaktifkan masuk jika jumlah melebihi nilai yang ditetapkan. (0 = nonaktif)"
"IPLimitlog" = "Log IP"
"IPLimitlogDesc" = "Log histori IP. (untuk mengaktifkan masuk setelah menonaktifkan, hapus log)"
"IPLimitlogclear" = "Hapus Log"
"setDefaultCert" = "Atur Sertifikat dari Panel"
"xtlsDesc" = "Xray harus versi 1.7.5"
"realityDesc" = "Xray harus versi 1.8.0+"
"telegramDesc" = "Harap berikan ID Telegram atau obrolan tanpa menggunakan '@'. (dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
"subscriptionDesc" = "Untuk menemukan URL langganan Anda, buka 'Rincian'. Selain itu, Anda dapat menggunakan nama yang sama untuk beberapa klien."
"info" = "Info"
"same" = "Sama"
"inboundData" = "Data Masuk"
"exportInbound" = "Ekspor Masuk"
"import" = "Impor"
"importInbound" = "Impor Masuk"
[pages.client]
"add" = "Tambah Klien"
"edit" = "Edit Klien"
"submitAdd" = "Tambah Klien"
"submitEdit" = "Simpan Perubahan"
"clientCount" = "Jumlah Klien"
"bulk" = "Tambahkan Massal"
"method" = "Metode"
"first" = "Pertama"
"last" = "Terakhir"
"prefix" = "Awalan"
"postfix" = "Akhiran"
"delayedStart" = "Mulai saat Penggunaan Awal"
"expireDays" = "Durasi"
"days" = "Hari"
"renew" = "Perpanjang Otomatis"
"renewDesc" = "Perpanjangan otomatis setelah kedaluwarsa. (0 = nonaktif)(unit: hari)"
[pages.inbounds.toasts]
"obtain" = "Dapatkan"
[pages.inbounds.stream.general]
"request" = "Permintaan"
"response" = "Respons"
"name" = "Nama"
"value" = "Nilai"
[pages.inbounds.stream.tcp]
"version" = "Versi"
"method" = "Metode"
"path" = "Path"
"status" = "Status"
"statusDescription" = "Deskripsi Status"
"requestHeader" = "Header Permintaan"
"responseHeader" = "Header Respons"
[pages.inbounds.stream.quic]
"encryption" = "Enkripsi"
[pages.settings]
"title" = "Pengaturan Panel"
"save" = "Simpan"
"infoDesc" = "Setiap perubahan yang dibuat di sini perlu disimpan. Harap restart panel untuk menerapkan perubahan."
"restartPanel" = "Restart Panel"
"restartPanelDesc" = "Apakah Anda yakin ingin merestart panel? Jika Anda tidak dapat mengakses panel setelah merestart, lihat info log panel di server."
"actions" = "Tindakan"
"resetDefaultConfig" = "Reset ke Default"
"panelSettings" = "Umum"
"securitySettings" = "Otentikasi"
"TGBotSettings" = "Bot Telegram"
"panelListeningIP" = "IP Pendengar"
"panelListeningIPDesc" = "Alamat IP untuk panel web. (biarkan kosong untuk mendengarkan semua IP)"
"panelListeningDomain" = "Domain Pendengar"
"panelListeningDomainDesc" = "Nama domain untuk panel web. (biarkan kosong untuk mendengarkan semua domain dan IP)"
"panelPort" = "Port Pendengar"
"panelPortDesc" = "Nomor port untuk panel web. (harus menjadi port yang tidak digunakan)"
"publicKeyPath" = "Path Kunci Publik"
"publicKeyPathDesc" = "Path berkas kunci publik untuk panel web. (dimulai dengan /)"
"privateKeyPath" = "Path Kunci Privat"
"privateKeyPathDesc" = "Path berkas kunci privat untuk panel web. (dimulai dengan /)"
"panelUrlPath" = "URI Path"
"panelUrlPathDesc" = "URI path untuk panel web. (dimulai dengan / dan diakhiri dengan /)"
"pageSize" = "Ukuran Halaman"
"pageSizeDesc" = "Tentukan ukuran halaman untuk tabel masuk. (0 = nonaktif)"
"remarkModel" = "Model Catatan & Karakter Pemisah"
"datepicker" = "Jenis Kalender"
"datepickerPlaceholder" = "Pilih tanggal"
"datepickerDescription" = "Tugas terjadwal akan berjalan berdasarkan kalender ini."
"sampleRemark" = "Contoh Catatan"
"oldUsername" = "Username Saat Ini"
"currentPassword" = "Kata Sandi Saat Ini"
"newUsername" = "Username Baru"
"newPassword" = "Kata Sandi Baru"
"telegramBotEnable" = "Aktifkan Bot Telegram"
"telegramBotEnableDesc" = "Mengaktifkan bot Telegram."
"telegramToken" = "Token Telegram"
"telegramTokenDesc" = "Token bot Telegram yang diperoleh dari '@BotFather'."
"telegramProxy" = "Proxy SOCKS"
"telegramProxyDesc" = "Mengaktifkan proxy SOCKS5 untuk terhubung ke Telegram. (sesuaikan pengaturan sesuai panduan)"
"telegramChatId" = "ID Obrolan Admin"
"telegramChatIdDesc" = "ID Obrolan Admin Telegram. (dipisahkan koma)(dapatkan di sini @userinfobot) atau (gunakan perintah '/id' di bot)"
"telegramNotifyTime" = "Waktu Notifikasi"
"telegramNotifyTimeDesc" = "Waktu notifikasi bot Telegram yang diatur untuk laporan berkala. (gunakan format waktu crontab)"
"tgNotifyBackup" = "Cadangan Database"
"tgNotifyBackupDesc" = "Kirim berkas cadangan database dengan laporan."
"tgNotifyLogin" = "Notifikasi Login"
"tgNotifyLoginDesc" = "Dapatkan notifikasi tentang username, alamat IP, dan waktu setiap kali seseorang mencoba masuk ke panel web Anda."
"sessionMaxAge" = "Durasi Sesi"
"sessionMaxAgeDesc" = "Durasi di mana Anda dapat tetap masuk. (unit: menit)"
"expireTimeDiff" = "Notifikasi Tanggal Kedaluwarsa"
"expireTimeDiffDesc" = "Dapatkan notifikasi tentang tanggal kedaluwarsa saat mencapai ambang batas ini. (unit: hari)"
"trafficDiff" = "Notifikasi Batas Traffic"
"trafficDiffDesc" = "Dapatkan notifikasi tentang batas traffic saat mencapai ambang batas ini. (unit: GB)"
"tgNotifyCpu" = "Notifikasi Beban CPU"
"tgNotifyCpuDesc" = "Dapatkan notifikasi jika beban CPU melebihi ambang batas ini. (unit: %)"
"timeZone" = "Zone Waktu"
"timeZoneDesc" = "Tugas terjadwal akan berjalan berdasarkan zona waktu ini."
"subSettings" = "Langganan"
"subEnable" = "Aktifkan Layanan Langganan"
"subEnableDesc" = "Mengaktifkan layanan langganan."
"subListen" = "IP Pendengar"
"subListenDesc" = "Alamat IP untuk layanan langganan. (biarkan kosong untuk mendengarkan semua IP)"
"subPort" = "Port Pendengar"
"subPortDesc" = "Nomor port untuk layanan langganan. (harus menjadi port yang tidak digunakan)"
"subCertPath" = "Path Kunci Publik"
"subCertPathDesc" = "Path berkas kunci publik untuk layanan langganan. (dimulai dengan /)"
"subKeyPath" = "Path Kunci Privat"
"subKeyPathDesc" = "Path berkas kunci privat untuk layanan langganan. (dimulai dengan /)"
"subPath" = "URI Path"
"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)"
"subUpdates" = "Interval Pembaruan"
"subUpdatesDesc" = "Interval pembaruan URL langganan dalam aplikasi klien. (unit: jam)"
"subEncrypt" = "Encode"
"subEncryptDesc" = "Konten yang dikembalikan dari layanan langganan akan dienkripsi Base64."
"subShowInfo" = "Tampilkan Info Penggunaan"
"subShowInfoDesc" = "Sisa traffic dan tanggal akan ditampilkan di aplikasi klien."
"subURI" = "URI Proxy Terbalik"
"subURIDesc" = "URI path URL langganan untuk penggunaan di belakang proxy."
[pages.xray]
"title" = "Konfigurasi Xray"
"save" = "Simpan"
"restart" = "Restart Xray"
"basicTemplate" = "Dasar"
"advancedTemplate" = "Lanjutan"
"generalConfigs" = "Strategi Umum"
"generalConfigsDesc" = "Opsi ini akan menentukan penyesuaian strategi umum."
"logConfigs" = "Catatan"
"logConfigsDesc" = "Log dapat mempengaruhi efisiensi server Anda. Disarankan untuk mengaktifkannya dengan bijak hanya jika diperlukan"
"blockConfigs" = "Pelindung"
"blockConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan protokol dan situs web yang diminta."
"blockCountryConfigs" = "Blokir Negara"
"blockCountryConfigsDesc" = "Opsi ini akan memblokir lalu lintas berdasarkan negara yang diminta."
"directCountryConfigs" = "Langsung ke Negara"
"directCountryConfigsDesc" = "Opsi ini akan langsung meneruskan lalu lintas berdasarkan negara yang diminta."
"ipv4Configs" = "Pengalihan IPv4"
"ipv4ConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui IPv4."
"warpConfigs" = "Pengalihan WARP"
"warpConfigsDesc" = "Opsi ini akan mengalihkan lalu lintas berdasarkan tujuan tertentu melalui WARP."
"Template" = "Template Konfigurasi Xray Lanjutan"
"TemplateDesc" = "File konfigurasi Xray akhir akan dibuat berdasarkan template ini."
"FreedomStrategy" = "Strategi Protokol Freedom"
"FreedomStrategyDesc" = "Atur strategi output untuk jaringan dalam Protokol Freedom."
"RoutingStrategy" = "Strategi Pengalihan Keseluruhan"
"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan."
"Torrent" = "Blokir Protokol BitTorrent"
"TorrentDesc" = "Memblokir protokol BitTorrent."
"PrivateIp" = "Blokir Koneksi ke IP Pribadi"
"PrivateIpDesc" = "Memblokir pembentukan koneksi ke rentang IP pribadi."
"Ads" = "Blokir Iklan"
"AdsDesc" = "Memblokir situs web periklanan."
"Family" = "Proteksi Keluarga"
"FamilyDesc" = "Memblokir konten dewasa dan situs web berbahaya."
"Security" = "Pelindung Keamanan"
"SecurityDesc" = "Memblokir situs web malware, phishing, dan penambang kripto."
"Speedtest" = "Blokir Speedtest"
"SpeedtestDesc" = "Memblokir pembentukan koneksi ke situs web speedtest."
"IRIp" = "Blokir Koneksi ke IP Iran"
"IRIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Iran."
"IRDomain" = "Blokir Koneksi ke Domain Iran"
"IRDomainDesc" = "Memblokir pembentukan koneksi ke domain Iran."
"ChinaIp" = "Blokir Koneksi ke IP China"
"ChinaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP China."
"ChinaDomain" = "Blokir Koneksi ke Domain China"
"ChinaDomainDesc" = "Memblokir pembentukan koneksi ke domain China."
"RussiaIp" = "Blokir Koneksi ke IP Rusia"
"RussiaIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Rusia."
"RussiaDomain" = "Blokir Koneksi ke Domain Rusia"
"RussiaDomainDesc" = "Memblokir pembentukan koneksi ke domain Rusia."
"VNIp" = "Blokir Koneksi ke IP Vietnam"
"VNIpDesc" = "Memblokir pembentukan koneksi ke rentang IP Vietnam."
"VNDomain" = "Blokir Koneksi ke Domain Vietnam"
"VNDomainDesc" = "Memblokir pembentukan koneksi ke domain Vietnam."
"DirectIRIp" = "Koneksi Langsung ke IP Iran"
"DirectIRIpDesc" = "Membentuk koneksi langsung ke rentang IP Iran."
"DirectIRDomain" = "Koneksi Langsung ke Domain Iran"
"DirectIRDomainDesc" = "Membentuk koneksi langsung ke domain Iran."
"DirectChinaIp" = "Koneksi Langsung ke IP China"
"DirectChinaIpDesc" = "Membentuk koneksi langsung ke rentang IP China."
"DirectChinaDomain" = "Koneksi Langsung ke Domain China"
"DirectChinaDomainDesc" = "Membentuk koneksi langsung ke domain China."
"DirectRussiaIp" = "Koneksi Langsung ke IP Rusia"
"DirectRussiaIpDesc" = "Membentuk koneksi langsung ke rentang IP Rusia."
"DirectRussiaDomain" = "Koneksi Langsung ke Domain Rusia"
"DirectRussiaDomainDesc" = "Membentuk koneksi langsung ke domain Rusia."
"DirectVNIp" = "Koneksi Langsung ke IP Vietnam"
"DirectVNIpDesc" = "Membentuk koneksi langsung ke rentang IP Vietnam."
"DirectVNDomain" = "Koneksi Langsung ke Domain Vietnam"
"DirectVNDomainDesc" = "Membentuk koneksi langsung ke domain Vietnam."
"GoogleIPv4" = "Google"
"GoogleIPv4Desc" = "Rute lalu lintas ke Google melalui IPv4."
"NetflixIPv4" = "Netflix"
"NetflixIPv4Desc" = "Rute lalu lintas ke Netflix melalui IPv4."
"GoogleWARP" = "Google"
"GoogleWARPDesc" = "Tambahkan pengalihan untuk Google melalui WARP."
"OpenAIWARP" = "ChatGPT"
"OpenAIWARPDesc" = "Rute lalu lintas ke ChatGPT melalui WARP."
"NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "Rute lalu lintas ke Netflix melalui WARP."
"SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "Rute lalu lintas ke Spotify melalui WARP."
"IRWARP" = "Domain Iran"
"IRWARPDesc" = "Rute lalu lintas ke domain Iran melalui WARP."
"Inbounds" = "Masuk"
"InboundsDesc" = "Menerima klien tertentu."
"Outbounds" = "Keluar"
"Balancers" = "Penyeimbang"
"OutboundsDesc" = "Atur jalur lalu lintas keluar."
"Routings" = "Aturan Pengalihan"
"RoutingsDesc" = "Prioritas setiap aturan penting!"
"completeTemplate" = "Semua"
"logLevel" = "Tingkat Log"
"logLevelDesc" = "Tingkat log untuk log kesalahan, menunjukkan informasi yang perlu dicatat."
"accessLog" = "Log Akses"
"accessLogDesc" = "Jalur file untuk log akses. Nilai khusus 'tidak ada' menonaktifkan log akses"
"errorLog" = "Catatan eror"
"errorLogDesc" = "Jalur file untuk log kesalahan. Nilai khusus 'tidak ada' menonaktifkan log kesalahan"
[pages.xray.rules]
"first" = "Pertama"
"last" = "Terakhir"
"up" = "Naik"
"down" = "Turun"
"source" = "Sumber"
"dest" = "Tujuan"
"inbound" = "Masuk"
"outbound" = "Keluar"
"balancer" = "Pengimbang"
"info" = "Info"
"add" = "Tambahkan Aturan"
"edit" = "Edit Aturan"
"useComma" = "Item yang dipisahkan koma"
[pages.xray.outbound]
"addOutbound" = "Tambahkan Keluar"
"addReverse" = "Tambahkan Revers"
"editOutbound" = "Edit Keluar"
"editReverse" = "Edit Revers"
"tag" = "Tag"
"tagDesc" = "Tag Unik"
"address" = "Alamat"
"reverse" = "Revers"
"domain" = "Domain"
"type" = "Tipe"
"bridge" = "Jembatan"
"portal" = "Portal"
"intercon" = "Interkoneksi"
[pages.xray.balancer]
"addBalancer" = "Tambahkan Penyeimbang"
"editBalancer" = "Sunting Penyeimbang"
"balancerStrategy" = "Strategi"
"balancerSelectors" = "Penyeleksi"
"tag" = "Menandai"
"tagDesc" = "Label Unik"
"balancerDesc" = "BalancerTag dan outboundTag tidak dapat digunakan secara bersamaan. Jika digunakan secara bersamaan, hanya outboundTag yang akan berfungsi."
[pages.xray.wireguard]
"secretKey" = "Kunci Rahasia"
"publicKey" = "Kunci Publik"
"allowedIPs" = "IP yang Diizinkan"
"endpoint" = "Titik Akhir"
"psk" = "Kunci Pra-Bagi"
"domainStrategy" = "Strategi Domain"
[pages.settings.security]
"admin" = "Admin"
"secret" = "Token Rahasia"
"loginSecurity" = "Login Aman"
"loginSecurityDesc" = "Menambahkan lapisan otentikasi tambahan untuk memberikan keamanan lebih."
"secretToken" = "Token Rahasia"
"secretTokenDesc" = "Simpan token ini dengan aman di tempat yang aman. Token ini diperlukan untuk login dan tidak dapat dipulihkan."
[pages.settings.toasts]
"modifySettings" = "Ubah Pengaturan"
"getSettings" = "Dapatkan Pengaturan"
"modifyUser" = "Ubah Admin"
"originalUserPassIncorrect" = "Username atau password saat ini tidak valid"
"userPassMustBeNotEmpty" = "Username dan password baru tidak boleh kosong"
[tgbot]
"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
"noResult" = "❗ Tidak ada hasil!"
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
"wentWrong" = "❌ Ada yang salah!"
"noIpRecord" = "❗ Tidak ada Catatan IP!"
"noInbounds" = "❗ Tidak ada masuk ditemukan!"
"unlimited" = "♾ Tak terbatas"
"add" = "Tambah"
"month" = "Bulan"
"months" = "Bulan"
"day" = "Hari"
"days" = "Hari"
"hours" = "Jam"
"unknown" = "Tidak diketahui"
"inbounds" = "Masuk"
"clients" = "Klien"
"offline" = "🔴 Offline"
"online" = "🟢 Online"
[tgbot.commands]
"unknown" = "❗ Perintah tidak dikenal."
"pleaseChoose" = "👇 Harap pilih:\r\n"
"help" = "🤖 Selamat datang di bot ini! Ini dirancang untuk menyediakan data tertentu dari panel web dan memungkinkan Anda melakukan modifikasi sesuai kebutuhan.\r\n\r\n"
"start" = "👋 Halo <i>{{ .Firstname }}</i>.\r\n"
"welcome" = "🤖 Selamat datang di <b>{{.Hostname }}</b> bot managemen.\r\n"
"status" = "✅ Bot dalam keadaan baik!"
"usage" = "❗ Harap berikan teks untuk mencari!"
"getID" = "🆔 ID Anda:<code>{{.ID }}</code>"
"helpAdminCommands" = "Untuk mencari email klien:\r\n<code>/usage [Email]</code>\r\n\r\nUntuk mencari masuk (dengan statistik klien):\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "Untuk mencari statistik, gunakan perintah berikut:\r\n\r\n<code>/usage [Email]</code>"
[tgbot.messages]
"cpuThreshold" = "🔴 Beban CPU {{ .Percent }}% melebihi batas {{ .Threshold }}%"
"selectUserFailed" = "❌ Kesalahan dalam pemilihan pengguna!"
"userSaved" = "✅ Pengguna Telegram tersimpan."
"loginSuccess" = "✅ Berhasil masuk ke panel.\r\n"
"loginFailed" = "❗️ Gagal masuk ke panel.\r\n"
"report" = "🕰 Laporan Terjadwal: {{ .RunTime }}\r\n"
"datetime" = "⏰ Tanggal & Waktu: {{ .DateTime }}\r\n"
"hostname" = "💻 Host: {{ .Hostname }}\r\n"
"version" = "🚀 Versi 3X-UI: {{ .Version }}\r\n"
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
"ip" = "🌐 IP: {{ .IP }}\r\n"
"ips" = "🔢 IP:\r\n{{ .IPs }}\r\n"
"serverUpTime" = "⏳ Waktu Aktif: {{ .UpTime }} {{ .Unit }}\r\n"
"serverLoad" = "📈 Beban Sistem: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
"serverMemory" = "📋 RAM: {{ .Current }}/{{ .Total }}\r\n"
"tcpCount" = "🔹 TCP: {{ .Count }}\r\n"
"udpCount" = "🔸 UDP: {{ .Count }}\r\n"
"traffic" = "🚦 Lalu Lintas: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
"xrayStatus" = " Status: {{ .State }}\r\n"
"username" = "👤 Nama Pengguna: {{ .Username }}\r\n"
"time" = "⏰ Waktu: {{ .Time }}\r\n"
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
"port" = "🔌 Port: {{ .Port }}\r\n"
"expire" = "📅 Tanggal Kadaluarsa: {{ .Time }}\r\n"
"expireIn" = "📅 Kadaluarsa Dalam: {{ .Time }}\r\n"
"active" = "💡 Aktif: {{ .Enable }}\r\n"
"enabled" = "🚨 Diaktifkan: {{ .Enable }}\r\n"
"online" = "🌐 Status Koneksi: {{ .Status }}\r\n"
"email" = "📧 Email: {{ .Email }}\r\n"
"upload" = "🔼 Unggah: ↑{{ .Upload }}\r\n"
"download" = "🔽 Unduh: ↓{{ .Download }}\r\n"
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 Pengguna Telegram: {{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 Habis {{ .Type }}:\r\n"
"exhaustedCount" = "🚨 Jumlah Habis {{ .Type }}:\r\n"
"onlinesCount" = "🌐 Klien Online: {{ .Count }}\r\n"
"disabled" = "🛑 Dinonaktifkan: {{ .Disabled }}\r\n"
"depleteSoon" = "🔜 Habis Sebentar: {{ .Deplete }}\r\n\r\n"
"backupTime" = "🗄 Waktu Backup: {{ .Time }}\r\n"
"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n"
"yes" = "✅ Ya"
"no" = "❌ Tidak"
[tgbot.buttons]
"closeKeyboard" = "❌ Tutup Papan Ketik"
"cancel" = "❌ Batal"
"cancelReset" = "❌ Batal Reset"
"cancelIpLimit" = "❌ Batal Batas IP"
"confirmResetTraffic" = "✅ Konfirmasi Reset Lalu Lintas?"
"confirmClearIps" = "✅ Konfirmasi Hapus IPs?"
"confirmRemoveTGUser" = "✅ Konfirmasi Hapus Pengguna Telegram?"
"confirmToggle" = "✅ Konfirmasi Aktifkan/Nonaktifkan Pengguna?"
"dbBackup" = "Dapatkan Cadangan DB"
"serverUsage" = "Penggunaan Server"
"getInbounds" = "Dapatkan Inbounds"
"depleteSoon" = "Habis Sebentar"
"clientUsage" = "Dapatkan Penggunaan"
"onlines" = "Klien Online"
"commands" = "Perintah"
"refresh" = "🔄 Perbarui"
"clearIPs" = "❌ Hapus IPs"
"removeTGUser" = "❌ Hapus Pengguna Telegram"
"selectTGUser" = "👤 Pilih Pengguna Telegram"
"selectOneTGUser" = "👤 Pilih Pengguna Telegram:"
"resetTraffic" = "📈 Reset Lalu Lintas"
"resetExpire" = "📅 Ubah Tanggal Kadaluarsa"
"ipLog" = "🔢 Log IP"
"ipLimit" = "🔢 Batas IP"
"setTGUser" = "👤 Set Pengguna Telegram"
"toggle" = "🔘 Aktifkan / Nonaktifkan"
"custom" = "🔢 Kustom"
"confirmNumber" = "✅ Konfirmasi: {{ .Num }}"
"confirmNumberAdd" = "✅ Konfirmasi menambahkan: {{ .Num }}"
"limitTraffic" = "🚧 Batas Lalu Lintas"
"getBanLogs" = "Dapatkan Log Pemblokiran"
[tgbot.answers]
"successfulOperation" = "✅ Operasi berhasil!"
"errorOperation" = "❗ Kesalahan dalam operasi."
"getInboundsFailed" = "❌ Gagal mendapatkan inbounds."
"canceled" = "❌ {{ .Email }}: Operasi dibatalkan."
"clientRefreshSuccess" = "✅ {{ .Email }}: Klien diperbarui dengan berhasil."
"IpRefreshSuccess" = "✅ {{ .Email }}: IP diperbarui dengan berhasil."
"TGIdRefreshSuccess" = "✅ {{ .Email }}: Pengguna Telegram Klien diperbarui dengan berhasil."
"resetTrafficSuccess" = "✅ {{ .Email }}: Lalu lintas direset dengan berhasil."
"setTrafficLimitSuccess" = "✅ {{ .Email }}: Batas lalu lintas disimpan dengan berhasil."
"expireResetSuccess" = "✅ {{ .Email }}: Hari kadaluarsa direset dengan berhasil."
"resetIpSuccess" = "✅ {{ .Email }}: Batas IP {{ .Count }} disimpan dengan berhasil."
"clearIpSuccess" = "✅ {{ .Email }}: IP dihapus dengan berhasil."
"getIpLog" = "✅ {{ .Email }}: Dapatkan Log IP."
"getUserInfo" = "✅ {{ .Email }}: Dapatkan Info Pengguna Telegram."
"removedTGUserSuccess" = "✅ {{ .Email }}: Pengguna Telegram dihapus dengan berhasil."
"enableSuccess" = "✅ {{ .Email }}: Diaktifkan dengan berhasil."
"disableSuccess" = "✅ {{ .Email }}: Dinonaktifkan dengan berhasil."
"askToAddUserId" = "Konfigurasi Anda tidak ditemukan!\r\nSilakan minta admin Anda untuk menggunakan ID Telegram Anda dalam konfigurasi Anda.\r\n\r\nID Pengguna Anda: <code>{{ .TgUserID }}</code>"

View file

@ -76,7 +76,7 @@
"title" = "Статус системы"
"memory" = "Память"
"hard" = "Жесткий диск"
"xrayStatus" = "Статус"
"xrayStatus" = "Xray"
"stopXray" = "Остановить"
"restartXray" = "Перезапустить"
"xraySwitch" = "Версия"
@ -311,6 +311,8 @@
"advancedTemplate" = "Расширенный шаблон"
"generalConfigs" = "Основные настройки"
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
"logConfigs" = "Журнал"
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
"blockConfigs" = "Блокировка конфигураций"
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
"blockCountryConfigs" = "Конфигурации блокировки страны"
@ -388,6 +390,7 @@
"Inbounds" = "Входящие"
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
"Outbounds" = "Исходящие"
"Balancers" = "Балансиры"
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
"Routings" = "Правила маршрутизации"
"RoutingsDesc" = "Важен приоритет каждого правила!"
@ -396,6 +399,8 @@
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
"accessLog" = "Журнал доступа"
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
"errorLog" = "Журнал ошибок"
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
[pages.xray.rules]
"first" = "Первый"
@ -406,6 +411,7 @@
"dest" = "Пункт назначения"
"inbound" = "Входящий"
"outbound" = "Исходящий"
"balancer" = "балансир"
"info" = "Информация"
"add" = "Добавить правило"
"edit" = "Редактировать правило"
@ -426,6 +432,15 @@
"portal" = "Портал"
"intercon" = "Соединение"
[pages.xray.balancer]
"addBalancer" = "Добавить балансир"
"editBalancer" = "Редактировать балансир"
"balancerStrategy" = "Стратегия"
"balancerSelectors" = "Селекторы"
"tag" = "Тег"
"tagDesc" = "уникальный тег"
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
[pages.xray.wireguard]
"secretKey" = "Секретный ключ"
"publicKey" = "Открытый ключ"

View file

@ -20,7 +20,7 @@
"check" = "Kiểm tra"
"indefinite" = "Không xác định"
"unlimited" = "Không giới hạn"
"none" = "Không có"
"none" = "None"
"qrCode" = "Mã QR"
"info" = "Thông tin thêm"
"edit" = "Chỉnh sửa"
@ -76,7 +76,7 @@
"title" = "Trạng thái hệ thống"
"memory" = "Ram"
"hard" = "Dung lượng"
"xrayStatus" = "Trạng thái Xray"
"xrayStatus" = "Xray"
"stopXray" = "Dừng lại"
"restartXray" = "Khởi động lại"
"xraySwitch" = "Phiên bản"
@ -311,6 +311,8 @@
"advancedTemplate" = "Mẫu Nâng cao"
"generalConfigs" = "Cấu hình Chung"
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
"logConfigs" = "Nhật ký"
"logConfigsDesc" = "Nhật ký có thể ảnh hưởng đến hiệu suất máy chủ của bạn. Bạn chỉ nên kích hoạt nó một cách khôn ngoan trong trường hợp bạn cần"
"blockConfigs" = "Cấu hình Chặn"
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
@ -388,6 +390,7 @@
"Inbounds" = "Đầu vào"
"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
"Outbounds" = "Đầu ra"
"Balancers" = "Cân bằng"
"OutboundsDesc" = "Thay đổi mẫu cấu hình để xác định các cách ra đi cho máy chủ này."
"Routings" = "Quy tắc định tuyến"
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
@ -396,6 +399,8 @@
"logLevelDesc" = "Cấp độ nhật ký cho nhật ký lỗi, cho biết thông tin cần được ghi lại."
"accessLog" = "Nhật ký truy cập"
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập. Nhật ký truy cập bị vô hiệu hóa có giá trị đặc biệt 'không'"
"errorLog" = "Nhật ký lỗi"
"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi. Nhật ký lỗi bị vô hiệu hóa có giá trị đặc biệt 'không'"
[pages.xray.rules]
"first" = "Đầu tiên"
@ -406,6 +411,7 @@
"dest" = "Đích"
"inbound" = "Vào"
"outbound" = "Ra"
"balancer" = "Cân bằng"
"info" = "Thông tin"
"add" = "Thêm quy tắc"
"edit" = "Chỉnh sửa quy tắc"
@ -426,6 +432,15 @@
"portal" = "Cổng thông tin"
"intercon" = "Kết nối"
[pages.xray.balancer]
"addBalancer" = "Thêm cân bằng"
"editBalancer" = "Chỉnh sửa cân bằng"
"balancerStrategy" = "Chiến lược"
"balancerSelectors" = "Bộ chọn"
"tag" = "Thẻ"
"tagDesc" = "thẻ duy nhất"
"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động."
[pages.xray.wireguard]
"secretKey" = "Khoá bí mật"
"publicKey" = "Khóa công khai"

View file

@ -76,7 +76,7 @@
"title" = "系统状态"
"memory" = "内存"
"hard" = "硬盘"
"xrayStatus" = "状态"
"xrayStatus" = "Xray"
"stopXray" = "停止"
"restartXray" = "重启"
"xraySwitch" = "版本"
@ -311,6 +311,8 @@
"advancedTemplate" = "高级模板部件"
"generalConfigs" = "通用配置"
"generalConfigsDesc" = "这些选项将提供一般调整"
"logConfigs"="日志"
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
"blockConfigs" = "阻塞配置"
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
"blockCountryConfigs" = "阻止国家配置"
@ -388,6 +390,7 @@
"Inbounds" = "入站"
"InboundsDesc" = "更改配置模板接受特殊客户端"
"Outbounds" = "出站"
"Balancers" = "平衡器"
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
"Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要"
@ -396,6 +399,8 @@
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
"accessLog" = "访问日志"
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
"errorLog" = "错误日志"
"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志"
[pages.xray.rules]
"first" = "第一个"
@ -406,6 +411,7 @@
"dest" = "目的地"
"inbound" = "入站"
"outbound" = "出站"
"balancer" = "平衡器"
"info" = "信息"
"add" = "添加规则"
"edit" = "编辑规则"
@ -426,6 +432,15 @@
"portal" = "门户"
"intercon" = "互连"
[pages.xray.balancer]
"addBalancer" = "添加平衡器"
"editBalancer" = "编辑平衡器"
"balancerStrategy" = "战略"
"balancerSelectors" = "选择器"
"tag" = "标签"
"tagDesc" = "唯一标记"
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用则只有outboundTag起作用。"
[pages.xray.wireguard]
"secretKey" = "密钥"
"publicKey" = "公钥"

87
x-ui.sh
View file

@ -150,6 +150,12 @@ custom_version() {
eval $install_command
}
# Function to handle the deletion of the script file
delete_script() {
rm "$0" # Remove the script file itself
exit 1
}
uninstall() {
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
if [[ $? != 0 ]]; then
@ -167,12 +173,13 @@ uninstall() {
rm /usr/local/x-ui/ -rf
echo ""
echo -e "Uninstalled Successfully, If you want to remove this script, then after exiting the script run ${green}rm /usr/bin/x-ui -f${plain} to delete it."
echo -e "Uninstalled Successfully.\n"
echo "If you need to install this panel again, you can use below command:"
echo -e "${green}bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)${plain}"
echo ""
if [[ $# == 0 ]]; then
before_show_menu
fi
# Trap the SIGTERM signal
trap delete_script SIGTERM
delete_script
}
reset_user() {
@ -483,6 +490,33 @@ show_xray_status() {
fi
}
firewall_menu() {
echo -e "${green}\t1.${plain} Install Firewall & open ports"
echo -e "${green}\t2.${plain} Allowed List"
echo -e "${green}\t3.${plain} Delete Ports from List"
echo -e "${green}\t4.${plain} Disable Firewall"
echo -e "${green}\t0.${plain} Back to Main Menu"
read -p "Choose an option: " choice
case "$choice" in
0)
show_menu
;;
1)
open_ports
;;
2)
sudo ufw status
;;
3)
delete_ports
;;
4)
sudo ufw disable
;;
*) echo "Invalid choice" ;;
esac
}
open_ports() {
if ! command -v ufw &>/dev/null; then
echo "ufw firewall is not installed. Installing now..."
@ -535,6 +569,37 @@ open_ports() {
ufw status | grep $ports
}
delete_ports() {
# Prompt the user to enter the ports they want to delete
read -p "Enter the ports you want to delete (e.g. 80,443,2053 or range 400-500): " ports
# Check if the input is valid
if ! [[ $ports =~ ^([0-9]+|[0-9]+-[0-9]+)(,([0-9]+|[0-9]+-[0-9]+))*$ ]]; then
echo "Error: Invalid input. Please enter a comma-separated list of ports or a range of ports (e.g. 80,443,2053 or 400-500)." >&2
exit 1
fi
# Delete the specified ports using ufw
IFS=',' read -ra PORT_LIST <<<"$ports"
for port in "${PORT_LIST[@]}"; do
if [[ $port == *-* ]]; then
# Split the range into start and end ports
start_port=$(echo $port | cut -d'-' -f1)
end_port=$(echo $port | cut -d'-' -f2)
# Loop through the range and delete each port
for ((i = start_port; i <= end_port; i++)); do
ufw delete allow $i
done
else
ufw delete allow "$port"
fi
done
# Confirm that the ports are deleted
echo "Deleted the specified ports:"
ufw status | grep $ports
}
update_geo() {
local defaultBinFolder="/usr/local/x-ui/bin"
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
@ -1124,10 +1189,10 @@ show_menu() {
${green}17.${plain} Cloudflare SSL Certificate
${green}18.${plain} IP Limit Management
${green}19.${plain} WARP Management
${green}20.${plain} Firewall Management
————————————————
${green}20.${plain} Enable BBR
${green}21.${plain} Update Geo Files
${green}22.${plain} Active Firewall and open ports
${green}21.${plain} Enable BBR
${green}22.${plain} Update Geo Files
${green}23.${plain} Speedtest by Ookla
"
show_status
@ -1195,13 +1260,13 @@ show_menu() {
warp_cloudflare
;;
20)
enable_bbr
firewall_menu
;;
21)
update_geo
enable_bbr
;;
22)
open_ports
update_geo
;;
23)
run_speedtest

View file

@ -1,6 +1,7 @@
package xray
import (
"regexp"
"strings"
"x-ui/logger"
)
@ -14,26 +15,18 @@ type LogWriter struct {
}
func (lw *LogWriter) Write(m []byte) (n int, err error) {
regex := regexp.MustCompile(`^(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[([^\]]+)\] (.+)$`)
// Convert the data to a string
message := strings.TrimSpace(string(m))
messages := strings.Split(message, "\n")
lw.lastLine = messages[len(messages)-1]
for _, msg := range messages {
messageBody := msg
matches := regex.FindStringSubmatch(msg)
// Remove timestamp
splittedMsg := strings.SplitN(msg, " ", 3)
if len(splittedMsg) > 2 {
messageBody = strings.TrimSpace(strings.SplitN(msg, " ", 3)[2])
}
// Find level in []
startIndex := strings.Index(messageBody, "[")
endIndex := strings.Index(messageBody, "]")
if startIndex != -1 && endIndex != -1 && startIndex < endIndex {
level := strings.TrimSpace(messageBody[startIndex+1 : endIndex])
msgBody := "XRAY: " + strings.TrimSpace(messageBody[endIndex+1:])
if len(matches) > 3 {
level := matches[2]
msgBody := matches[3]
// Map the level to the appropriate logger function
switch level {

View file

@ -41,10 +41,6 @@ func GetIPLimitLogPath() string {
return config.GetLogFolder() + "/3xipl.log"
}
func GetIPLimitPrevLogPath() string {
return config.GetLogFolder() + "/3xipl.prev.log"
}
func GetIPLimitBannedLogPath() string {
return config.GetLogFolder() + "/3xipl-banned.log"
}