mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-09 11:46:18 +00:00
Merge branch 'MHSanaei:main' into main
This commit is contained in:
commit
32925736b6
42 changed files with 2091 additions and 341 deletions
24
go.mod
24
go.mod
|
@ -7,23 +7,23 @@ require (
|
||||||
github.com/gin-contrib/gzip v0.0.6
|
github.com/gin-contrib/gzip v0.0.6
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/goccy/go-json v0.10.2
|
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/nicksnyder/go-i18n/v2 v2.4.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.1.1
|
github.com/pelletier/go-toml/v2 v2.1.1
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.24.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
|
github.com/xtls/xray-core v1.8.7
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.14.0
|
||||||
google.golang.org/grpc v1.61.0
|
google.golang.org/grpc v1.61.1
|
||||||
gorm.io/driver/sqlite v1.5.4
|
gorm.io/driver/sqlite v1.5.5
|
||||||
gorm.io/gorm v1.25.6
|
gorm.io/gorm v1.25.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
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/bytedance/sonic v1.10.2 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
github.com/chenzhuoyu/iasm v0.9.1 // 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/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
github.com/gorilla/sessions v1.2.2 // indirect
|
||||||
github.com/gorilla/websocket v1.5.1 // 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/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/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/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // 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/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||||
|
@ -82,16 +84,16 @@ require (
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.6.0 // 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/exp v0.0.0-20240103183307-be819d1f06fc // indirect
|
||||||
golang.org/x/mod v0.14.0 // indirect
|
golang.org/x/mod v0.14.0 // indirect
|
||||||
golang.org/x/net v0.20.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.16.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.16.1 // indirect
|
golang.org/x/tools v0.16.1 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
||||||
|
|
47
go.sum
47
go.sum
|
@ -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/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 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
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.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
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/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/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=
|
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/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 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
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/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/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=
|
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/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/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/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.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
||||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
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.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 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.28.0 h1:DNXaYISeZw1J9oB81vCNdskLow8gCRRUJxufqLuH3XE=
|
github.com/mymmrac/telego v0.29.1 h1:nsNnK0mS18OL+unoDjDI6BVfafJBbT8Wtj7rCzEWoM8=
|
||||||
github.com/mymmrac/telego v0.28.0/go.mod h1:oRperySNzJq8dRTl24+uBF1Uy7tlQGIjid/JQtHDsZg=
|
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/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||||
|
@ -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/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
|
||||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
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/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
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=
|
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
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-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 h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
||||||
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
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-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
@ -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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.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.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.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/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -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-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 h1:gphdwh0npgs8elJ4T6J+DQJHPVF7RsuJHCfwztUb4J4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ=
|
||||||
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/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0=
|
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||||
google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||||
gorm.io/gorm v1.25.6 h1:V92+vVda1wEISSOMtodHVRcUIOPYa2tgQtyF+DfFx+A=
|
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
||||||
gorm.io/gorm v1.25.6/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
||||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||||
|
|
|
@ -82,16 +82,16 @@ fi
|
||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
centos | almalinux | rocky)
|
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)
|
fedora)
|
||||||
dnf -y update && dnf install -y -q wget curl tar
|
dnf -y update && dnf install -y -q wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
arch | manjaro)
|
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
|
esac
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package random
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var numSeq [10]rune
|
var numSeq [10]rune
|
||||||
|
@ -13,8 +12,6 @@ var numUpperSeq [36]rune
|
||||||
var allSeq [62]rune
|
var allSeq [62]rune
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
numSeq[i] = rune('0' + i)
|
numSeq[i] = rune('0' + i)
|
||||||
}
|
}
|
||||||
|
@ -41,3 +38,7 @@ func Seq(n int) string {
|
||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Num(n int) int {
|
||||||
|
return rand.Intn(n)
|
||||||
|
}
|
||||||
|
|
|
@ -1050,12 +1050,17 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
color: rgba(255, 255, 255, 0.25);
|
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 {
|
.ant-input-number-handler-wrap {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.ant-input-number-handler {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-input-number {
|
.ant-input-number {
|
||||||
|
@ -1089,7 +1094,8 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||||
> td,
|
> td,
|
||||||
.ant-table-thead
|
.ant-table-thead
|
||||||
> tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)
|
> 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);
|
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) {
|
.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;
|
border-radius: 0rem 1rem 1rem 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-tag {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
b, strong {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
|
@ -29,6 +29,11 @@ const supportLangs = [
|
||||||
value: 'es-ES',
|
value: 'es-ES',
|
||||||
icon: '🇪🇸',
|
icon: '🇪🇸',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Indonesian',
|
||||||
|
value: 'id-ID',
|
||||||
|
icon: '🇮🇩',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
|
|
|
@ -418,7 +418,7 @@ class Outbound extends CommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
canEnableTls() {
|
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);
|
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1534,6 +1534,28 @@ class Inbound extends XrayCommonClass {
|
||||||
return url.toString();
|
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) {
|
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
|
@ -1557,7 +1579,7 @@ class Inbound extends XrayCommonClass {
|
||||||
const orderChars = remarkModel.slice(1);
|
const orderChars = remarkModel.slice(1);
|
||||||
let orders = {
|
let orders = {
|
||||||
'i': remark,
|
'i': remark,
|
||||||
'e': client ? client.email : '',
|
'e': email,
|
||||||
'o': '',
|
'o': '',
|
||||||
};
|
};
|
||||||
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||||
|
@ -1580,6 +1602,7 @@ class Inbound extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||||
|
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||||
if(this.clients){
|
if(this.clients){
|
||||||
let links = [];
|
let links = [];
|
||||||
this.clients.forEach((client) => {
|
this.clients.forEach((client) => {
|
||||||
|
@ -1589,7 +1612,14 @@ class Inbound extends XrayCommonClass {
|
||||||
});
|
});
|
||||||
return links.join('\r\n');
|
return links.join('\r\n');
|
||||||
} else {
|
} 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 '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2297,9 +2327,13 @@ Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.WireguardSettings.Peer = 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();
|
super();
|
||||||
|
this.privateKey = privateKey
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
|
if (!this.publicKey){
|
||||||
|
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
||||||
|
}
|
||||||
this.psk = psk;
|
this.psk = psk;
|
||||||
this.allowedIPs = allowedIPs;
|
this.allowedIPs = allowedIPs;
|
||||||
this.keepAlive = keepAlive;
|
this.keepAlive = keepAlive;
|
||||||
|
@ -2307,6 +2341,7 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
|
|
||||||
static fromJson(json={}){
|
static fromJson(json={}){
|
||||||
return new Inbound.WireguardSettings.Peer(
|
return new Inbound.WireguardSettings.Peer(
|
||||||
|
json.privateKey,
|
||||||
json.publicKey,
|
json.publicKey,
|
||||||
json.preSharedKey,
|
json.preSharedKey,
|
||||||
json.allowedIPs,
|
json.allowedIPs,
|
||||||
|
@ -2316,6 +2351,7 @@ Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
|
privateKey: this.privateKey,
|
||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
||||||
allowedIPs: this.allowedIPs,
|
allowedIPs: this.allowedIPs,
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -29,6 +29,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
||||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||||
g.POST("/warp/:action", a.warp)
|
g.POST("/warp/:action", a.warp)
|
||||||
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
g.GET("/getOutboundsTraffic", a.getOutboundsTraffic)
|
||||||
|
g.POST("/resetOutboundsTraffic", a.resetOutboundsTraffic)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||||
|
@ -95,3 +96,13 @@ func (a *XraySettingController) getOutboundsTraffic(c *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonObj(c, outboundsTraffic, nil)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -35,12 +35,21 @@
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.subId = '';
|
this.subId = '';
|
||||||
this.qrcodes = [];
|
this.qrcodes = [];
|
||||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
if (this.inbound.protocol == Protocols.WIREGUARD){
|
||||||
this.qrcodes.push({
|
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
|
||||||
remark: l.remark,
|
this.qrcodes.push({
|
||||||
link: l.link
|
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;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
#app {
|
#app {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -204,7 +204,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 15vh;
|
height: 15vh;
|
||||||
margin-bottom: -5px; /*Fix for safari gap*/
|
margin-bottom: -8px; /*Fix for safari gap*/
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
}
|
}
|
||||||
|
@ -212,23 +212,27 @@
|
||||||
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
animation: move-forever 25s cubic-bezier(0.55, 0.5, 0.45, 0.5) infinite;
|
||||||
}
|
}
|
||||||
.dark .parallax > use {
|
.dark .parallax > use {
|
||||||
fill: rgb(10 117 87 / 20%);
|
fill: #0f2d32;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(1) {
|
.parallax > use:nth-child(1) {
|
||||||
animation-delay: -2s;
|
animation-delay: -2s;
|
||||||
animation-duration: 7s;
|
animation-duration: 4s;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(2) {
|
.parallax > use:nth-child(2) {
|
||||||
animation-delay: -3s;
|
animation-delay: -3s;
|
||||||
animation-duration: 10s;
|
animation-duration: 7s;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
.parallax > use:nth-child(3) {
|
.parallax > use:nth-child(3) {
|
||||||
animation-delay: -4s;
|
animation-delay: -4s;
|
||||||
animation-duration: 13s;
|
animation-duration: 10s;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
.parallax > use:nth-child(4) {
|
||||||
|
animation-delay: -5s;
|
||||||
|
animation-duration: 13s;
|
||||||
|
}
|
||||||
@keyframes move-forever {
|
@keyframes move-forever {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(-90px, 0, 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" />
|
<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>
|
</defs>
|
||||||
<g class="parallax">
|
<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="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="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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
@ -290,7 +295,7 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-row justify="center" class="centered">
|
<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">
|
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined">
|
||||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
{{define "menuItems"}}
|
{{define "menuItems"}}
|
||||||
<a-menu-item key="{{ .base_path }}panel/">
|
<a-menu-item key="{{ .base_path }}panel/">
|
||||||
<a-icon type="dashboard"></a-icon>
|
<a-icon type="dashboard"></a-icon>
|
||||||
<span>{{ i18n "menu.dashboard"}}</span>
|
<span><b>{{ i18n "menu.dashboard"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
<a-menu-item key="{{ .base_path }}panel/inbounds">
|
||||||
<a-icon type="user"></a-icon>
|
<a-icon type="user"></a-icon>
|
||||||
<span>{{ i18n "menu.inbounds"}}</span>
|
<span><b>{{ i18n "menu.inbounds"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/settings">
|
<a-menu-item key="{{ .base_path }}panel/settings">
|
||||||
<a-icon type="setting"></a-icon>
|
<a-icon type="setting"></a-icon>
|
||||||
<span>{{ i18n "menu.settings"}}</span>
|
<span><b>{{ i18n "menu.settings"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="{{ .base_path }}panel/xray">
|
<a-menu-item key="{{ .base_path }}panel/xray">
|
||||||
<a-icon type="tool"></a-icon>
|
<a-icon type="tool"></a-icon>
|
||||||
<span>{{ i18n "menu.xray"}}</span>
|
<span><b>{{ i18n "menu.xray"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
<!--<a-menu-item key="{{ .base_path }}panel/clients">-->
|
||||||
<!-- <a-icon type="laptop"></a-icon>-->
|
<!-- <a-icon type="laptop"></a-icon>-->
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<!--</a-menu-item>-->
|
<!--</a-menu-item>-->
|
||||||
<a-menu-item key="{{ .base_path }}logout">
|
<a-menu-item key="{{ .base_path }}logout">
|
||||||
<a-icon type="logout"></a-icon>
|
<a-icon type="logout"></a-icon>
|
||||||
<span>{{ i18n "menu.logout"}}</span>
|
<span><b>{{ i18n "menu.logout"}}</b></span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
@input="$emit('input', convertToGregorian($event.target.value)); jalaliDatepicker.hide();"
|
||||||
:placeholder="placeholder">
|
:placeholder="placeholder">
|
||||||
<template #addonAfter>
|
<template #addonAfter>
|
||||||
<a-icon type="calendar" style="font-size: 16px;"/>
|
<a-icon type="calendar" style="font-size: 14px; opacity: 0.5;"/>
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||||
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
<persian-datepicker v-else placeholder='{{ i18n "pages.settings.datepickerPlaceholder" }}'
|
||||||
|
|
|
@ -134,28 +134,10 @@
|
||||||
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
||||||
<a-input v-model.trim="peer.endpoint"></a-input>
|
<a-input v-model.trim="peer.endpoint"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
|
||||||
<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-input v-model.trim="peer.publicKey"></a-input>
|
<a-input v-model.trim="peer.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
|
||||||
<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-input v-model.trim="peer.psk"></a-input>
|
<a-input v-model.trim="peer.psk"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
@ -189,7 +171,6 @@
|
||||||
<a-form-item label='ID'>
|
<a-form-item label='ID'>
|
||||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- vless settings -->
|
<!-- vless settings -->
|
||||||
<template v-if="outbound.canEnableTlsFlow()">
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
<a-form-item label='Flow'>
|
<a-form-item label='Flow'>
|
||||||
|
@ -212,18 +193,23 @@
|
||||||
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- trojan/shadowsocks -->
|
||||||
<!-- shadowsocks -->
|
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||||
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
<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-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='UDP over TCP'>
|
<a-form-item label='UDP over TCP'>
|
||||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
|
@ -363,13 +349,15 @@
|
||||||
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<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 value=''>None</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ALPN">
|
<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">
|
v-model="outbound.stream.tls.alpn">
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
@ -381,11 +369,12 @@
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
<template v-if="outbound.stream.isReality">
|
<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-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="uTLS">
|
<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-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
|
@ -38,10 +38,16 @@
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
{{ i18n "pages.xray.wireguard.publicKey" }}
|
{{ i18n "pages.xray.wireguard.secretKey" }}
|
||||||
<a-icon @click="peer.publicKey = publicKey=Wireguard.generateKeypair().publicKey"type="sync"> </a-icon>
|
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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-input v-model.trim="peer.publicKey"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
|
@ -51,7 +57,7 @@
|
||||||
<span>{{ i18n "reset" }}</span>
|
<span>{{ i18n "reset" }}</span>
|
||||||
</template>
|
</template>
|
||||||
{{ i18n "pages.xray.wireguard.psk" }}
|
{{ 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>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="peer.psk"></a-input>
|
<a-input v-model.trim="peer.psk"></a-input>
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
<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-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
</template>
|
</template>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<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>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
<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>
|
||||||
<a-form-item :wrapper-col="{span:24}">
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
|
|
|
@ -179,10 +179,10 @@
|
||||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
<a-divider>Telegram ID</a-divider>
|
<a-divider>Telegram ID</a-divider>
|
||||||
<a-row>
|
<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-col :sx="24" :md="2" style="text-align: right;">
|
||||||
<a-tooltip title='{{ i18n "copy" }}'>
|
<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>
|
<a-icon type="snippets"></a-icon>
|
||||||
</button>
|
</button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
@ -283,24 +283,50 @@
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="(peer, index) in inbound.settings.peers">
|
<template v-for="(peer, index) in inbound.settings.peers">
|
||||||
<tr>
|
<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>
|
||||||
<tr class="client-table-odd-row">
|
<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>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
|
||||||
<td>[[ peer.publicKey ]]</td>
|
<td>[[ peer.publicKey ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="client-table-odd-row">
|
||||||
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
||||||
<td>[[ peer.psk ]]</td>
|
<td>[[ peer.psk ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="client-table-odd-row">
|
<tr>
|
||||||
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
||||||
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr class="client-table-odd-row">
|
||||||
<td>Keep Alive</td>
|
<td>Keep Alive</td>
|
||||||
<td>[[ peer.keepAlive ]]</td>
|
<td>[[ peer.keepAlive ]]</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
@ -319,7 +345,6 @@
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
subLink: '',
|
subLink: '',
|
||||||
tgLink: '',
|
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
|
@ -327,14 +352,15 @@
|
||||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
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) : [];
|
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
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) {
|
||||||
if (this.clientSettings.subId) {
|
if (this.clientSettings.subId) {
|
||||||
this.subLink = this.genSubLink(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;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -133,6 +133,10 @@
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export" }}
|
{{ i18n "pages.inbounds.export" }}
|
||||||
</a-menu-item>
|
</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-menu-item key="resetInbounds">
|
||||||
<a-icon type="reload"></a-icon>
|
<a-icon type="reload"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||||
|
@ -141,7 +145,7 @@
|
||||||
<a-icon type="file-done"></a-icon>
|
<a-icon type="file-done"></a-icon>
|
||||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<a-menu-item key="delDepletedClients">
|
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||||
<a-icon type="rest"></a-icon>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
@ -196,7 +200,7 @@
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</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>
|
<a-icon type="qrcode"></a-icon>
|
||||||
{{ i18n "qrCode" }}
|
{{ i18n "qrCode" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
@ -217,7 +221,11 @@
|
||||||
<a-icon type="export"></a-icon>
|
<a-icon type="export"></a-icon>
|
||||||
{{ i18n "pages.inbounds.export"}}
|
{{ i18n "pages.inbounds.export"}}
|
||||||
</a-menu-item>
|
</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>
|
<a-icon type="rest"></a-icon>
|
||||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
@ -578,6 +586,7 @@
|
||||||
this.refreshing = false;
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.getOnlineUsers();
|
await this.getOnlineUsers();
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -642,8 +651,12 @@
|
||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
if (client.enable && this.isClientOnline(client.email)) {
|
||||||
if(this.isClientOnline(client.email)) online.push(client.email);
|
active.push(client.email);
|
||||||
|
online.push(client.email);
|
||||||
|
} else {
|
||||||
|
deactive.push(client.email);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
clientStats.forEach(client => {
|
clientStats.forEach(client => {
|
||||||
if (!client.enable) {
|
if (!client.enable) {
|
||||||
|
@ -668,6 +681,7 @@
|
||||||
online: online,
|
online: online,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
searchInbounds(key) {
|
searchInbounds(key) {
|
||||||
if (ObjectUtil.isEmpty(key)) {
|
if (ObjectUtil.isEmpty(key)) {
|
||||||
this.searchedInbounds = this.dbInbounds.slice();
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
@ -731,6 +745,9 @@
|
||||||
case "export":
|
case "export":
|
||||||
this.exportAllLinks();
|
this.exportAllLinks();
|
||||||
break;
|
break;
|
||||||
|
case "subs":
|
||||||
|
this.exportAllSubs();
|
||||||
|
break;
|
||||||
case "resetInbounds":
|
case "resetInbounds":
|
||||||
this.resetAllTraffic();
|
this.resetAllTraffic();
|
||||||
break;
|
break;
|
||||||
|
@ -762,6 +779,9 @@
|
||||||
case "export":
|
case "export":
|
||||||
this.inboundLinks(dbInbound.id);
|
this.inboundLinks(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
case "subs":
|
||||||
|
this.exportSubs(dbInbound.id);
|
||||||
|
break;
|
||||||
case "clipboard":
|
case "clipboard":
|
||||||
this.copyToClipboard(dbInbound.id);
|
this.copyToClipboard(dbInbound.id);
|
||||||
break;
|
break;
|
||||||
|
@ -1186,6 +1206,22 @@
|
||||||
newDbInbound = this.checkFallback(dbInbound);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(), newDbInbound.remark);
|
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() {
|
importInbound() {
|
||||||
promptModal.open({
|
promptModal.open({
|
||||||
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
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() {
|
exportAllLinks() {
|
||||||
let copyText = [];
|
let copyText = [];
|
||||||
for (const dbInbound of this.dbInbounds) {
|
for (const dbInbound of this.dbInbounds) {
|
||||||
|
|
|
@ -18,6 +18,14 @@
|
||||||
.ant-card-dark h2 {
|
.ant-card-dark h2 {
|
||||||
color: hsla(0, 0%, 100%, .65);
|
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>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@ -36,15 +44,15 @@
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
<div><b>CPU:</b> [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
<div><b>Speed:</b> [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
:stroke-color="status.mem.color"
|
:stroke-color="status.mem.color"
|
||||||
:percent="status.mem.percent"></a-progress>
|
:percent="status.mem.percent"></a-progress>
|
||||||
<div>
|
<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>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -56,7 +64,7 @@
|
||||||
:stroke-color="status.swap.color"
|
:stroke-color="status.swap.color"
|
||||||
:percent="status.swap.percent"></a-progress>
|
:percent="status.swap.percent"></a-progress>
|
||||||
<div>
|
<div>
|
||||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
<b>Swap:</b> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
|
@ -64,7 +72,7 @@
|
||||||
:stroke-color="status.disk.color"
|
:stroke-color="status.disk.color"
|
||||||
:percent="status.disk.percent"></a-progress>
|
:percent="status.disk.percent"></a-progress>
|
||||||
<div>
|
<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>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
@ -75,25 +83,25 @@
|
||||||
</transition>
|
</transition>
|
||||||
<transition name="list" appear>
|
<transition name="list" appear>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<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>
|
<b>3X-UI:</b>
|
||||||
Xray <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
<a rel="noopener" href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
<a rel="noopener" href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@Panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "menu.link" }}:
|
<b>{{ i18n "pages.index.operationHours" }}:</b>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
<a-tag class="ant-tag-df">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag class="ant-tag-df">OS [[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.xrayStatus" }}:
|
<b>{{ i18n "pages.index.xrayStatus" }}:</b>
|
||||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
<a-tag style="text-transform: capitalize;" :color="status.xray.color">[[ status.xray.state ]]
|
||||||
|
</a-tag>
|
||||||
<a-popover v-if="status.xray.state === State.Error"
|
<a-popover v-if="status.xray.state === State.Error"
|
||||||
:overlay-class-name="themeSwitcher.currentTheme">
|
:overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||||
|
@ -106,137 +114,143 @@
|
||||||
</a-popover>
|
</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="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="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-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
<b>{{ i18n "menu.link" }}:</b>
|
||||||
Xray
|
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
OS
|
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<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>
|
<a-tooltip>
|
||||||
|
[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.systemLoadDesc" }}
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
{{ i18n "usage"}}:
|
<b>{{ i18n "usage"}}:</b>
|
||||||
RAM [[ sizeFormat(status.appStats.mem) ]] -
|
<a-tag class="ant-tag-df">
|
||||||
Threads [[ status.appStats.threads ]]
|
RAM [[ sizeFormat(status.appStats.mem) ]]
|
||||||
</a-tooltip>
|
</a-tag>
|
||||||
|
<a-tag class="ant-tag-df">
|
||||||
|
Threads [[ status.appStats.threads ]]
|
||||||
|
</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="global"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
IPv4:
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="global"></a-icon> IPv4
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv4 ]]
|
[[ status.publicIP.ipv4 ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-col>
|
</a-tag>
|
||||||
<a-col :span="12">
|
</a-col>
|
||||||
<a-icon type="global"></a-icon>
|
<a-col :span="12">
|
||||||
IPv6:
|
<a-tag class="ant-tag-df">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="global"></a-icon> IPv6
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
[[ status.publicIP.ipv6 ]]
|
[[ status.publicIP.ipv6 ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="swap"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
TCP: [[ status.tcpCount ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="swap"></a-icon> TCP: [[ status.tcpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="swap"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
UDP: [[ status.udpCount ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="swap"></a-icon> UDP: [[ status.udpCount ]]
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
[[ sizeFormat(status.netIO.up) ]]/s
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="arrow-up"></a-icon>
|
||||||
|
Up: [[ sizeFormat(status.netIO.up) ]]/s
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-down"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
[[ sizeFormat(status.netIO.down) ]]/s
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="arrow-down"></a-icon>
|
||||||
|
Down: [[ sizeFormat(status.netIO.down) ]]/s
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :lg="12">
|
||||||
<a-card hoverable>
|
<a-card hoverable>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-upload"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
[[ sizeFormat(status.netTraffic.sent) ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="cloud-upload"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalSent" }}
|
{{ i18n "pages.index.totalSent" }}
|
||||||
</template>
|
</template> Out: [[ sizeFormat(status.netTraffic.sent) ]]
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="cloud-download"></a-icon>
|
<a-tag class="ant-tag-df">
|
||||||
[[ sizeFormat(status.netTraffic.recv) ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
|
<a-icon type="cloud-download"></a-icon>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.totalReceive" }}
|
{{ i18n "pages.index.totalReceive" }}
|
||||||
</template>
|
</template> In: [[ sizeFormat(status.netTraffic.recv) ]]
|
||||||
<a-icon type="question-circle"></a-icon>
|
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
</a-tag>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
@ -256,7 +270,7 @@
|
||||||
></a-alert>
|
></a-alert>
|
||||||
<template v-for="version, index in versionModal.versions">
|
<template v-for="version, index in versionModal.versions">
|
||||||
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
|
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'"
|
||||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
style="margin-right: 10px" @click="switchV2rayVersion(version)">
|
||||||
[[ version ]]
|
[[ version ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
@ -440,8 +454,8 @@
|
||||||
loading: false,
|
loading: false,
|
||||||
show(logs) {
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.logs = logs;
|
this.logs = logs || [];
|
||||||
this.formattedLogs = logs.length > 0 ? this.formatLogs(logs) : "No Record...";
|
this.formattedLogs = this.logs.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||||
},
|
},
|
||||||
formatLogs(logs) {
|
formatLogs(logs) {
|
||||||
let formattedLogs = '';
|
let formattedLogs = '';
|
||||||
|
|
|
@ -76,15 +76,15 @@
|
||||||
<a-layout-content>
|
<a-layout-content>
|
||||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<a-space direction="vertical">
|
<a-space direction="vertical">
|
||||||
<a-card hoverable style="margin-bottom: .5rem;">
|
<a-card hoverable style="margin-bottom: .5rem; overflow-x: hidden;">
|
||||||
<a-row>
|
<a-row style="display: flex; flex-wrap: wrap; align-items: center;">
|
||||||
<a-col :xs="24" :sm="8" style="padding: 4px;">
|
<a-col :xs="24" :sm="10" style="padding: 4px;">
|
||||||
<a-space direction="horizontal">
|
<a-space direction="horizontal">
|
||||||
<a-button type="primary" :disabled="saveBtnDisable" @click="updateAllSetting">{{ i18n "pages.settings.save" }}</a-button>
|
<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-button type="danger" :disabled="!saveBtnDisable" @click="restartPanel">{{ i18n "pages.settings.restartPanel" }}</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="16">
|
<a-col :xs="24" :sm="14">
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -114,15 +114,12 @@
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta
|
<a-list-item-meta title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
||||||
title='{{ i18n "pages.xray.FreedomStrategy" }}'
|
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}' />
|
||||||
description='{{ i18n "pages.xray.FreedomStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="freedomStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="freedomStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
style="width: 100%">
|
||||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
@ -132,33 +129,37 @@
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta
|
<a-list-item-meta title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
||||||
title='{{ i18n "pages.xray.RoutingStrategy" }}'
|
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}' />
|
||||||
description='{{ i18n "pages.xray.RoutingStrategyDesc" }}'/>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="routingStrategy" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="routingStrategy"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
style="width: 100%">
|
||||||
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in routingDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta
|
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
|
||||||
title='{{ i18n "pages.xray.logLevel" }}'
|
description='{{ i18n "pages.xray.logLevelDesc" }}' />
|
||||||
description='{{ i18n "pages.xray.logLevelDesc" }}'/>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="setLogLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
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-option v-for="s in logLevel" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
|
@ -166,22 +167,31 @@
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta
|
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
|
||||||
title='{{ i18n "pages.xray.accessLog" }}'
|
description='{{ i18n "pages.xray.accessLogDesc" }}' />
|
||||||
description='{{ i18n "pages.xray.accessLogDesc" }}'/>
|
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
v-model="setAccessLog"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
|
||||||
style="width: 100%">
|
|
||||||
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-list-item>
|
<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>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
|
@ -327,6 +337,14 @@
|
||||||
[[ rule.outboundTag ]]
|
[[ rule.outboundTag ]]
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</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">
|
<template slot="info" slot-scope="text, rule, index">
|
||||||
<a-popover placement="bottomRight"
|
<a-popover placement="bottomRight"
|
||||||
v-if="(rule.source+rule.sourcePort+rule.network+rule.protocol+rule.attrs+rule.ip+rule.domain+rule.port).length>0"
|
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-tab-pane key="tpl-3" tab='{{ i18n "pages.xray.Outbounds"}}' style="padding-top: 20px;" force-render="true">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :xs="12" :sm="12" :lg="12">
|
<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-button type="primary" @click="showWarp()" style="margin-bottom: 10px;">WARP</a-button>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="12" :sm="12" :lg="12" style="text-align: right;">
|
<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-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-table :columns="outboundColumns" bordered
|
<a-table :columns="outboundColumns" bordered
|
||||||
|
@ -396,10 +416,19 @@
|
||||||
<a-dropdown :trigger="['click']">
|
<a-dropdown :trigger="['click']">
|
||||||
<a-icon @click="e => e.preventDefault()" type="more" style="font-size: 16px; text-decoration: bold;"></a-icon>
|
<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 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-menu-item @click="editOutbound(index)">
|
||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</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)">
|
<a-menu-item @click="deleteOutbound(index)">
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
<a-icon type="delete"></a-icon> {{ i18n "delete"}}
|
||||||
|
@ -452,6 +481,41 @@
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
</a-tab-pane>
|
</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-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-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' : ''">
|
<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 "ruleModal"}}
|
||||||
{{template "outModal"}}
|
{{template "outModal"}}
|
||||||
{{template "reverseModal"}}
|
{{template "reverseModal"}}
|
||||||
|
{{template "balancerModal"}}
|
||||||
{{template "warpModal"}}
|
{{template "warpModal"}}
|
||||||
<script>
|
<script>
|
||||||
const rulesColumns = [
|
const rulesColumns = [
|
||||||
|
@ -490,9 +555,10 @@
|
||||||
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
{ title: 'Domain', dataIndex: 'domain', align: 'center', width: 20, ellipsis: true },
|
||||||
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
{ title: 'Port', dataIndex: 'port', align: 'center', width: 10, ellipsis: true }]},
|
||||||
{ title: '{{ i18n "pages.xray.rules.inbound"}}', children: [
|
{ 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: '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 = [
|
const rulesMobileColumns = [
|
||||||
|
@ -517,6 +583,13 @@
|
||||||
{ title: '{{ i18n "pages.xray.outbound.domain"}}', dataIndex: 'domain', align: 'center', width: 50 },
|
{ 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({
|
const app = new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
@ -570,6 +643,7 @@
|
||||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||||
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
logLevel: ["none" , "debug" , "info" , "warning", "error"],
|
||||||
access: ["none" , "./access.log" ],
|
access: ["none" , "./access.log" ],
|
||||||
|
error: ["none" , "./error.log" ],
|
||||||
settingsData: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
|
@ -879,6 +953,11 @@
|
||||||
outbounds.splice(index,1);
|
outbounds.splice(index,1);
|
||||||
this.outboundSettings = JSON.stringify(outbounds);
|
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() {
|
async refreshOutboundTraffic() {
|
||||||
if (!this.refreshing) {
|
if (!this.refreshing) {
|
||||||
this.refreshing = true;
|
this.refreshing = true;
|
||||||
|
@ -895,6 +974,105 @@
|
||||||
this.refreshing = false;
|
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(){
|
addReverse(){
|
||||||
reverseModal.show({
|
reverseModal.show({
|
||||||
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
title: '{{ i18n "pages.xray.outbound.addReverse"}}',
|
||||||
|
@ -1084,6 +1262,27 @@
|
||||||
return data;
|
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: {
|
routingRuleSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.routing.rules, null, 2) : null; },
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
|
@ -1156,9 +1355,9 @@
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setAccessLog: {
|
accessLog: {
|
||||||
get: function () {
|
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;
|
return this.templateSettings.log.access;
|
||||||
},
|
},
|
||||||
set: function (newValue) {
|
set: function (newValue) {
|
||||||
|
@ -1167,6 +1366,17 @@
|
||||||
this.templateSettings = newTemplateSettings;
|
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: {
|
blockedIPs: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
return this.templateRuleGetter({ outboundTag: "blocked", property: "ip" });
|
||||||
|
|
111
web/html/xui/xray_balancer_modal.html
Normal file
111
web/html/xui/xray_balancer_modal.html
Normal 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}}
|
|
@ -107,6 +107,19 @@
|
||||||
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</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>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
@ -133,11 +146,12 @@
|
||||||
protocol: [],
|
protocol: [],
|
||||||
attrs: [],
|
attrs: [],
|
||||||
outboundTag: "",
|
outboundTag: "",
|
||||||
|
balancerTag: "",
|
||||||
},
|
},
|
||||||
inboundTags: [],
|
inboundTags: [],
|
||||||
outboundTags: [],
|
outboundTags: [],
|
||||||
users: [],
|
users: [],
|
||||||
balancerTag: [],
|
balancerTags: [],
|
||||||
ok() {
|
ok() {
|
||||||
newRule = ruleModal.getResult();
|
newRule = ruleModal.getResult();
|
||||||
ObjectUtil.execute(ruleModal.confirm, newRule);
|
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||||
|
@ -160,6 +174,7 @@
|
||||||
this.rule.protocol = rule.protocol;
|
this.rule.protocol = rule.protocol;
|
||||||
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||||
this.rule.outboundTag = rule.outboundTag;
|
this.rule.outboundTag = rule.outboundTag;
|
||||||
|
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : ""
|
||||||
} else {
|
} else {
|
||||||
this.rule = {
|
this.rule = {
|
||||||
domainMatcher: "",
|
domainMatcher: "",
|
||||||
|
@ -174,6 +189,7 @@
|
||||||
protocol: [],
|
protocol: [],
|
||||||
attrs: [],
|
attrs: [],
|
||||||
outboundTag: "",
|
outboundTag: "",
|
||||||
|
balancerTag: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.isEdit = isEdit;
|
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.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() {
|
close() {
|
||||||
ruleModal.visible = false;
|
ruleModal.visible = false;
|
||||||
|
@ -211,6 +231,7 @@
|
||||||
rule.protocol = value.protocol;
|
rule.protocol = value.protocol;
|
||||||
rule.attrs = Object.fromEntries(value.attrs);
|
rule.attrs = Object.fromEntries(value.attrs);
|
||||||
rule.outboundTag = value.outboundTag;
|
rule.outboundTag = value.outboundTag;
|
||||||
|
rule.balancerTag = value.balancerTag;
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(rule)) {
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -25,7 +25,6 @@ type CheckClientIpJob struct {
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var ipFiles = []string{
|
var ipFiles = []string{
|
||||||
xray.GetIPLimitLogPath(),
|
xray.GetIPLimitLogPath(),
|
||||||
xray.GetIPLimitPrevLogPath(),
|
|
||||||
xray.GetIPLimitBannedLogPath(),
|
xray.GetIPLimitBannedLogPath(),
|
||||||
xray.GetIPLimitBannedPrevLogPath(),
|
xray.GetIPLimitBannedPrevLogPath(),
|
||||||
xray.GetAccessPersistentLogPath(),
|
xray.GetAccessPersistentLogPath(),
|
||||||
|
@ -51,6 +50,37 @@ func (j *CheckClientIpJob) Run() {
|
||||||
j.checkFail2BanInstalled()
|
j.checkFail2BanInstalled()
|
||||||
j.processLogFile()
|
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 {
|
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
|
@ -121,7 +151,7 @@ func (j *CheckClientIpJob) processLogFile() {
|
||||||
matches := ipRegx.FindStringSubmatch(line)
|
matches := ipRegx.FindStringSubmatch(line)
|
||||||
if len(matches) > 1 {
|
if len(matches) > 1 {
|
||||||
ip := matches[1]
|
ip := matches[1]
|
||||||
if ip == "127.0.0.1" || ip == "[::1]" {
|
if ip == "127.0.0.1" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,24 +190,7 @@ func (j *CheckClientIpJob) processLogFile() {
|
||||||
time.Sleep(time.Second * 2)
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
if shouldCleanLog {
|
if shouldCleanLog {
|
||||||
// copy access log to persistent file
|
j.clearAccessLog()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ func NewClearLogsJob() *ClearLogsJob {
|
||||||
// Here Run is an interface method of the Job interface
|
// Here Run is an interface method of the Job interface
|
||||||
func (j *ClearLogsJob) Run() {
|
func (j *ClearLogsJob) Run() {
|
||||||
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
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
|
// clear old previous logs
|
||||||
for i := 0; i < len(logFilesPrev); i++ {
|
for i := 0; i < len(logFilesPrev); i++ {
|
||||||
|
@ -26,25 +26,26 @@ func (j *ClearLogsJob) Run() {
|
||||||
|
|
||||||
// clear log files and copy to previous logs
|
// clear log files and copy to previous logs
|
||||||
for i := 0; i < len(logFiles); i++ {
|
for i := 0; i < len(logFiles); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
// copy to previous logs
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// copy to previous logs
|
logFile, err := os.ReadFile(logFiles[i])
|
||||||
logFilePrev, err := os.OpenFile(logFilesPrev[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
if err != nil {
|
||||||
if err != nil {
|
logger.Warning("clear logs job err:", err)
|
||||||
logger.Warning("clear logs job err:", err)
|
}
|
||||||
|
|
||||||
|
_, err = logFilePrev.Write(logFile)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
defer logFilePrev.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
logFile, err := os.ReadFile(logFiles[i])
|
err := os.Truncate(logFiles[i], 0)
|
||||||
if err != nil {
|
|
||||||
logger.Warning("clear logs job err:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = logFilePrev.Write(logFile)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("clear logs job err:", err)
|
|
||||||
}
|
|
||||||
defer logFilePrev.Close()
|
|
||||||
|
|
||||||
err = os.Truncate(logFiles[i], 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("clear logs job err:", err)
|
logger.Warning("clear logs job err:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"log": {
|
"log": {
|
||||||
"access": "none",
|
"access": "none",
|
||||||
"dnsLog": false,
|
"dnsLog": false,
|
||||||
|
"error": "./error.log",
|
||||||
"loglevel": "warning"
|
"loglevel": "warning"
|
||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type OutboundService struct {
|
type OutboundService struct {
|
||||||
xrayApi xray.XrayAPI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
|
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
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -202,9 +202,13 @@ func (t *Tgbot) OnReceive() {
|
||||||
}, th.AnyCallbackQueryWithMessage())
|
}, th.AnyCallbackQueryWithMessage())
|
||||||
|
|
||||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
||||||
if message.UserShared != nil {
|
if message.UsersShared != nil {
|
||||||
if checkAdmin(message.From.ID) {
|
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 := ""
|
output := ""
|
||||||
if err != nil {
|
if err != nil {
|
||||||
output += t.I18nBot("tgbot.messages.selectUserFailed")
|
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) {
|
func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool) {
|
||||||
|
|
||||||
chatId := callbackQuery.Message.Chat.ID
|
chatId := callbackQuery.Message.GetChat().ID
|
||||||
|
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
// get query from hash storage
|
// get query from hash storage
|
||||||
|
@ -296,22 +300,22 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
t.searchClient(chatId, email)
|
t.searchClient(chatId, email)
|
||||||
case "client_refresh":
|
case "client_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clientRefreshSuccess", "Email=="+email))
|
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":
|
case "client_cancel":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
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":
|
case "ips_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.IpRefreshSuccess", "Email=="+email))
|
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":
|
case "ips_cancel":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
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":
|
case "tgid_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.TGIdRefreshSuccess", "Email=="+email))
|
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":
|
case "tgid_cancel":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+email))
|
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":
|
case "reset_traffic":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
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)),
|
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":
|
case "reset_traffic_c":
|
||||||
err := t.inboundService.ResetClientTrafficByEmail(email)
|
err := t.inboundService.ResetClientTrafficByEmail(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetTrafficSuccess", "Email=="+email))
|
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 {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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")),
|
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":
|
case "limit_traffic_c":
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
limitTraffic, err := strconv.Atoi(dataArray[2])
|
limitTraffic, err := strconv.Atoi(dataArray[2])
|
||||||
|
@ -370,13 +374,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.setTrafficLimitSuccess", "Email=="+email))
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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":
|
case "limit_traffic_in":
|
||||||
if len(dataArray) >= 3 {
|
if len(dataArray) >= 3 {
|
||||||
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
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")),
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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":
|
case "reset_exp":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
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")),
|
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":
|
case "reset_exp_c":
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
days, err := strconv.Atoi(dataArray[2])
|
days, err := strconv.Atoi(dataArray[2])
|
||||||
|
@ -499,13 +503,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.expireResetSuccess", "Email=="+email))
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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":
|
case "reset_exp_in":
|
||||||
if len(dataArray) >= 3 {
|
if len(dataArray) >= 3 {
|
||||||
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
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")),
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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":
|
case "ip_limit":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
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")),
|
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":
|
case "ip_limit_c":
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
count, err := strconv.Atoi(dataArray[2])
|
count, err := strconv.Atoi(dataArray[2])
|
||||||
|
@ -604,13 +608,13 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.xrayService.SetToNeedRestart()
|
t.xrayService.SetToNeedRestart()
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.resetIpSuccess", "Email=="+email, "Count=="+strconv.Itoa(count)))
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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":
|
case "ip_limit_in":
|
||||||
if len(dataArray) >= 3 {
|
if len(dataArray) >= 3 {
|
||||||
oldInputNumber, err := strconv.Atoi(dataArray[2])
|
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")),
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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":
|
case "clear_ips":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
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)),
|
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":
|
case "clear_ips_c":
|
||||||
err := t.inboundService.ClearClientIps(email)
|
err := t.inboundService.ClearClientIps(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.clearIpSuccess", "Email=="+email))
|
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 {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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)),
|
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":
|
case "tgid_remove_c":
|
||||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
if err != nil || traffic == nil {
|
if err != nil || traffic == nil {
|
||||||
|
@ -715,7 +719,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
err = t.inboundService.SetClientTelegramUserID(traffic.Id, "")
|
err = t.inboundService.SetClientTelegramUserID(traffic.Id, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.removedTGUserSuccess", "Email=="+email))
|
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 {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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)),
|
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":
|
case "toggle_enable_c":
|
||||||
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
|
enabled, err := t.inboundService.ToggleClientEnableByEmail(email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -738,7 +742,7 @@ func (t *Tgbot) asnwerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
} else {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.disableSuccess", "Email=="+email))
|
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 {
|
} else {
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
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)
|
t.onlineClients(chatId)
|
||||||
case "onlines_refresh":
|
case "onlines_refresh":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||||
t.onlineClients(chatId, callbackQuery.Message.MessageID)
|
t.onlineClients(chatId, callbackQuery.Message.GetMessageID())
|
||||||
case "commands":
|
case "commands":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
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)
|
t.editMessageTgBot(chatId, messageID[0], output, inlineKeyboard)
|
||||||
} else {
|
} else {
|
||||||
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
t.SendMsgToTgbot(chatId, output, inlineKeyboard)
|
||||||
requestUser := telego.KeyboardButtonRequestUser{
|
requestUser := telego.KeyboardButtonRequestUsers{
|
||||||
RequestID: int32(traffic.Id),
|
RequestID: int32(traffic.Id),
|
||||||
UserIsBot: new(bool),
|
UserIsBot: new(bool),
|
||||||
}
|
}
|
||||||
keyboard := tu.Keyboard(
|
keyboard := tu.Keyboard(
|
||||||
tu.KeyboardRow(
|
tu.KeyboardRow(
|
||||||
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUser(&requestUser),
|
tu.KeyboardButton(t.I18nBot("tgbot.buttons.selectTGUser")).WithRequestUsers(&requestUser),
|
||||||
),
|
),
|
||||||
tu.KeyboardRow(
|
tu.KeyboardRow(
|
||||||
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
|
tu.KeyboardButton(t.I18nBot("tgbot.buttons.closeKeyboard")),
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
"title" = "Overview"
|
"title" = "Overview"
|
||||||
"memory" = "RAM"
|
"memory" = "RAM"
|
||||||
"hard" = "Disk"
|
"hard" = "Disk"
|
||||||
"xrayStatus" = "Status"
|
"xrayStatus" = "Xray"
|
||||||
"stopXray" = "Stop"
|
"stopXray" = "Stop"
|
||||||
"restartXray" = "Restart"
|
"restartXray" = "Restart"
|
||||||
"xraySwitch" = "Version"
|
"xraySwitch" = "Version"
|
||||||
|
@ -311,6 +311,8 @@
|
||||||
"advancedTemplate" = "Advanced"
|
"advancedTemplate" = "Advanced"
|
||||||
"generalConfigs" = "General"
|
"generalConfigs" = "General"
|
||||||
"generalConfigsDesc" = "These options will determine general adjustments."
|
"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"
|
"blockConfigs" = "Protection Shield"
|
||||||
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
||||||
"blockCountryConfigs" = "Block Country"
|
"blockCountryConfigs" = "Block Country"
|
||||||
|
@ -388,6 +390,7 @@
|
||||||
"Inbounds" = "Inbounds"
|
"Inbounds" = "Inbounds"
|
||||||
"InboundsDesc" = "Accepting the specific clients."
|
"InboundsDesc" = "Accepting the specific clients."
|
||||||
"Outbounds" = "Outbounds"
|
"Outbounds" = "Outbounds"
|
||||||
|
"Balancers" = "Balancers"
|
||||||
"OutboundsDesc" = "Set the outgoing traffic pathway."
|
"OutboundsDesc" = "Set the outgoing traffic pathway."
|
||||||
"Routings" = "Routing Rules"
|
"Routings" = "Routing Rules"
|
||||||
"RoutingsDesc" = "The priority of each rule is important!"
|
"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."
|
"logLevelDesc" = "The log level for error logs, indicating the information that needs to be recorded."
|
||||||
"accessLog" = "Access Log"
|
"accessLog" = "Access Log"
|
||||||
"accessLogDesc" = "The file path for the access log. The special value 'none' disabled access logs"
|
"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]
|
[pages.xray.rules]
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
|
@ -406,6 +411,7 @@
|
||||||
"dest" = "Destination"
|
"dest" = "Destination"
|
||||||
"inbound" = "Inbound"
|
"inbound" = "Inbound"
|
||||||
"outbound" = "Outbound"
|
"outbound" = "Outbound"
|
||||||
|
"balancer" = "Balancer"
|
||||||
"info" = "Info"
|
"info" = "Info"
|
||||||
"add" = "Add Rule"
|
"add" = "Add Rule"
|
||||||
"edit" = "Edit Rule"
|
"edit" = "Edit Rule"
|
||||||
|
@ -426,6 +432,15 @@
|
||||||
"portal" = "Portal"
|
"portal" = "Portal"
|
||||||
"intercon" = "Interconnection"
|
"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]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Secret Key"
|
"secretKey" = "Secret Key"
|
||||||
"publicKey" = "Public Key"
|
"publicKey" = "Public Key"
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"check" = "Verificar"
|
"check" = "Verificar"
|
||||||
"indefinite" = "Indefinido"
|
"indefinite" = "Indefinido"
|
||||||
"unlimited" = "Ilimitado"
|
"unlimited" = "Ilimitado"
|
||||||
"none" = "Ninguno"
|
"none" = "None"
|
||||||
"qrCode" = "Código QR"
|
"qrCode" = "Código QR"
|
||||||
"info" = "Más Información"
|
"info" = "Más Información"
|
||||||
"edit" = "Editar"
|
"edit" = "Editar"
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"title" = "Estado del Sistema"
|
"title" = "Estado del Sistema"
|
||||||
"memory" = "Memoria"
|
"memory" = "Memoria"
|
||||||
"hard" = "Disco Duro"
|
"hard" = "Disco Duro"
|
||||||
"xrayStatus" = "Estado de"
|
"xrayStatus" = "Xray"
|
||||||
"stopXray" = "Detener"
|
"stopXray" = "Detener"
|
||||||
"restartXray" = "Reiniciar"
|
"restartXray" = "Reiniciar"
|
||||||
"xraySwitch" = "Versión"
|
"xraySwitch" = "Versión"
|
||||||
|
@ -311,6 +311,8 @@
|
||||||
"advancedTemplate" = "Plantilla Avanzada"
|
"advancedTemplate" = "Plantilla Avanzada"
|
||||||
"generalConfigs" = "Configuraciones Generales"
|
"generalConfigs" = "Configuraciones Generales"
|
||||||
"generalConfigsDesc" = "Estas opciones proporcionarán ajustes 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"
|
"blockConfigs" = "Configuraciones de Bloqueo"
|
||||||
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
|
"blockConfigsDesc" = "Estas opciones evitarán que los usuarios se conecten a protocolos y sitios web específicos."
|
||||||
"blockCountryConfigs" = "Configuraciones de Bloqueo por País"
|
"blockCountryConfigs" = "Configuraciones de Bloqueo por País"
|
||||||
|
@ -388,6 +390,7 @@
|
||||||
"Inbounds" = "Entrante"
|
"Inbounds" = "Entrante"
|
||||||
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
|
"InboundsDesc" = "Cambia la plantilla de configuración para aceptar clientes específicos."
|
||||||
"Outbounds" = "Salidas"
|
"Outbounds" = "Salidas"
|
||||||
|
"Balancers" = "Equilibradores"
|
||||||
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
|
"OutboundsDesc" = "Cambia la plantilla de configuración para definir formas de salida para este servidor."
|
||||||
"Routings" = "Reglas de enrutamiento"
|
"Routings" = "Reglas de enrutamiento"
|
||||||
"RoutingsDesc" = "¡La prioridad de cada regla es importante!"
|
"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."
|
"logLevelDesc" = "El nivel de registro para registros de errores, que indica la información que debe registrarse."
|
||||||
"accessLog" = "Registro de acceso"
|
"accessLog" = "Registro de acceso"
|
||||||
"accessLogDesc" = "La ruta del archivo para el registro de acceso. El valor especial 'ninguno' deshabilita los registros 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]
|
[pages.xray.rules]
|
||||||
"first" = "Primero"
|
"first" = "Primero"
|
||||||
|
@ -406,6 +411,7 @@
|
||||||
"dest" = "Destino"
|
"dest" = "Destino"
|
||||||
"inbound" = "Entrante"
|
"inbound" = "Entrante"
|
||||||
"outbound" = "saliente"
|
"outbound" = "saliente"
|
||||||
|
"balancer" = "Balancín"
|
||||||
"info" = "Información"
|
"info" = "Información"
|
||||||
"add" = "Agregar regla"
|
"add" = "Agregar regla"
|
||||||
"edit" = "Editar regla"
|
"edit" = "Editar regla"
|
||||||
|
@ -426,6 +432,15 @@
|
||||||
"portal" = "portal"
|
"portal" = "portal"
|
||||||
"intercon" = "Interconexión"
|
"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]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Llave secreta"
|
"secretKey" = "Llave secreta"
|
||||||
"publicKey" = "Llave pública"
|
"publicKey" = "Llave pública"
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
"title" = "نمای کلی"
|
"title" = "نمای کلی"
|
||||||
"memory" = "RAM"
|
"memory" = "RAM"
|
||||||
"hard" = "Disk"
|
"hard" = "Disk"
|
||||||
"xrayStatus" = "وضعیتایکسری"
|
"xrayStatus" = "ایکسری"
|
||||||
"stopXray" = "توقف"
|
"stopXray" = "توقف"
|
||||||
"restartXray" = "شروعمجدد"
|
"restartXray" = "شروعمجدد"
|
||||||
"xraySwitch" = "نسخه"
|
"xraySwitch" = "نسخه"
|
||||||
|
@ -311,6 +311,8 @@
|
||||||
"advancedTemplate" = "پیشرفته"
|
"advancedTemplate" = "پیشرفته"
|
||||||
"generalConfigs" = "استراتژی کلی"
|
"generalConfigs" = "استراتژی کلی"
|
||||||
"generalConfigsDesc" = "این گزینهها استراتژی کلی ترافیک را تعیین میکنند"
|
"generalConfigsDesc" = "این گزینهها استراتژی کلی ترافیک را تعیین میکنند"
|
||||||
|
"logConfigs" = "گزارش"
|
||||||
|
"logConfigsDesc" = "گزارشها ممکن است بر کارایی سرور شما تأثیر بگذارد. توصیه می شود فقط در صورت نیاز آن را عاقلانه فعال کنید"
|
||||||
"blockConfigs" = "سپر محافظ"
|
"blockConfigs" = "سپر محافظ"
|
||||||
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
||||||
"blockCountryConfigs" = "مسدودسازی کشور"
|
"blockCountryConfigs" = "مسدودسازی کشور"
|
||||||
|
@ -388,6 +390,7 @@
|
||||||
"Inbounds" = "ورودیها"
|
"Inbounds" = "ورودیها"
|
||||||
"InboundsDesc" = "پذیرش کلاینت خاص"
|
"InboundsDesc" = "پذیرش کلاینت خاص"
|
||||||
"Outbounds" = "خروجیها"
|
"Outbounds" = "خروجیها"
|
||||||
|
"Balancers" = "بالانسرها"
|
||||||
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
|
"OutboundsDesc" = "مسیر ترافیک خروجی را تنظیم کنید"
|
||||||
"Routings" = "قوانین مسیریابی"
|
"Routings" = "قوانین مسیریابی"
|
||||||
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
"RoutingsDesc" = "اولویت هر قانون مهم است"
|
||||||
|
@ -396,6 +399,8 @@
|
||||||
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
|
"logLevelDesc" = "سطح گزارش برای گزارش های خطا، نشان دهنده اطلاعاتی است که باید ثبت شوند."
|
||||||
"accessLog" = "مسیر گزارش"
|
"accessLog" = "مسیر گزارش"
|
||||||
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
|
"accessLogDesc" = "مسیر فایل برای گزارش دسترسی. مقدار ویژه «هیچ» گزارشهای دسترسی را غیرفعال میکند."
|
||||||
|
"errorLog" = "گزارش خطا"
|
||||||
|
"errorLogDesc" = "مسیر فایل برای ورود به سیستم خطا. مقدار ویژه «هیچ» گزارش های خطا را غیرفعال میکند"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "اولین"
|
"first" = "اولین"
|
||||||
|
@ -406,6 +411,7 @@
|
||||||
"dest" = "مقصد"
|
"dest" = "مقصد"
|
||||||
"inbound" = "ورودی"
|
"inbound" = "ورودی"
|
||||||
"outbound" = "خروجی"
|
"outbound" = "خروجی"
|
||||||
|
"balancer" = "بالانسر"
|
||||||
"info" = "اطلاعات"
|
"info" = "اطلاعات"
|
||||||
"add" = "افزودن قانون"
|
"add" = "افزودن قانون"
|
||||||
"edit" = "ویرایش قانون"
|
"edit" = "ویرایش قانون"
|
||||||
|
@ -426,6 +432,15 @@
|
||||||
"portal" = "پورتال"
|
"portal" = "پورتال"
|
||||||
"intercon" = "اتصال میانی"
|
"intercon" = "اتصال میانی"
|
||||||
|
|
||||||
|
[pages.xray.balancer]
|
||||||
|
"addBalancer" = "افزودن بالانسر"
|
||||||
|
"editBalancer" = "ویرایش بالانسر"
|
||||||
|
"balancerStrategy" = "استراتژی"
|
||||||
|
"balancerSelectors" = "انتخابگرها"
|
||||||
|
"tag" = "برچسب"
|
||||||
|
"tagDesc" = "برچسب یگانه"
|
||||||
|
"balancerDesc" = "امکان استفاده همزمان balancerTag و outboundTag باهم وجود ندارد. درصورت استفاده همزمان فقط outboundTag عمل خواهد کرد."
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "کلید شخصی"
|
"secretKey" = "کلید شخصی"
|
||||||
"publicKey" = "کلید عمومی"
|
"publicKey" = "کلید عمومی"
|
||||||
|
|
595
web/translation/translate.id_ID.toml
Normal file
595
web/translation/translate.id_ID.toml
Normal 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>"
|
|
@ -76,7 +76,7 @@
|
||||||
"title" = "Статус системы"
|
"title" = "Статус системы"
|
||||||
"memory" = "Память"
|
"memory" = "Память"
|
||||||
"hard" = "Жесткий диск"
|
"hard" = "Жесткий диск"
|
||||||
"xrayStatus" = "Статус"
|
"xrayStatus" = "Xray"
|
||||||
"stopXray" = "Остановить"
|
"stopXray" = "Остановить"
|
||||||
"restartXray" = "Перезапустить"
|
"restartXray" = "Перезапустить"
|
||||||
"xraySwitch" = "Версия"
|
"xraySwitch" = "Версия"
|
||||||
|
@ -311,6 +311,8 @@
|
||||||
"advancedTemplate" = "Расширенный шаблон"
|
"advancedTemplate" = "Расширенный шаблон"
|
||||||
"generalConfigs" = "Основные настройки"
|
"generalConfigs" = "Основные настройки"
|
||||||
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
|
"generalConfigsDesc" = "Эти параметры описывают общие настройки"
|
||||||
|
"logConfigs" = "Журнал"
|
||||||
|
"logConfigsDesc" = "Журналы могут повлиять на эффективность вашего сервера. Рекомендуется включать их с умом только в случае ваших нужд!"
|
||||||
"blockConfigs" = "Блокировка конфигураций"
|
"blockConfigs" = "Блокировка конфигураций"
|
||||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам"
|
||||||
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
"blockCountryConfigs" = "Конфигурации блокировки страны"
|
||||||
|
@ -388,6 +390,7 @@
|
||||||
"Inbounds" = "Входящие"
|
"Inbounds" = "Входящие"
|
||||||
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
|
"InboundsDesc" = "Изменение шаблона конфигурации для подключения определенных пользователей"
|
||||||
"Outbounds" = "Исходящие"
|
"Outbounds" = "Исходящие"
|
||||||
|
"Balancers" = "Балансиры"
|
||||||
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
|
"OutboundsDesc" = "Изменение шаблона конфигурации, чтобы определить исходящие пути для этого сервера"
|
||||||
"Routings" = "Правила маршрутизации"
|
"Routings" = "Правила маршрутизации"
|
||||||
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
"RoutingsDesc" = "Важен приоритет каждого правила!"
|
||||||
|
@ -396,6 +399,8 @@
|
||||||
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
|
"logLevelDesc" = "Уровень журнала для журналов ошибок, указывающий информацию, которую необходимо записать."
|
||||||
"accessLog" = "Журнал доступа"
|
"accessLog" = "Журнал доступа"
|
||||||
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
|
"accessLogDesc" = "Путь к файлу журнала доступа. Специальное значение «none» отключило журналы доступа."
|
||||||
|
"errorLog" = "Журнал ошибок"
|
||||||
|
"errorLogDesc" = "Путь к файлу журнала ошибок. Специальное значение «none» отключает журналы ошибок."
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Первый"
|
"first" = "Первый"
|
||||||
|
@ -406,6 +411,7 @@
|
||||||
"dest" = "Пункт назначения"
|
"dest" = "Пункт назначения"
|
||||||
"inbound" = "Входящий"
|
"inbound" = "Входящий"
|
||||||
"outbound" = "Исходящий"
|
"outbound" = "Исходящий"
|
||||||
|
"balancer" = "балансир"
|
||||||
"info" = "Информация"
|
"info" = "Информация"
|
||||||
"add" = "Добавить правило"
|
"add" = "Добавить правило"
|
||||||
"edit" = "Редактировать правило"
|
"edit" = "Редактировать правило"
|
||||||
|
@ -426,6 +432,15 @@
|
||||||
"portal" = "Портал"
|
"portal" = "Портал"
|
||||||
"intercon" = "Соединение"
|
"intercon" = "Соединение"
|
||||||
|
|
||||||
|
[pages.xray.balancer]
|
||||||
|
"addBalancer" = "Добавить балансир"
|
||||||
|
"editBalancer" = "Редактировать балансир"
|
||||||
|
"balancerStrategy" = "Стратегия"
|
||||||
|
"balancerSelectors" = "Селекторы"
|
||||||
|
"tag" = "Тег"
|
||||||
|
"tagDesc" = "уникальный тег"
|
||||||
|
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Секретный ключ"
|
"secretKey" = "Секретный ключ"
|
||||||
"publicKey" = "Открытый ключ"
|
"publicKey" = "Открытый ключ"
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"check" = "Kiểm tra"
|
"check" = "Kiểm tra"
|
||||||
"indefinite" = "Không xác định"
|
"indefinite" = "Không xác định"
|
||||||
"unlimited" = "Không giới hạn"
|
"unlimited" = "Không giới hạn"
|
||||||
"none" = "Không có"
|
"none" = "None"
|
||||||
"qrCode" = "Mã QR"
|
"qrCode" = "Mã QR"
|
||||||
"info" = "Thông tin thêm"
|
"info" = "Thông tin thêm"
|
||||||
"edit" = "Chỉnh sửa"
|
"edit" = "Chỉnh sửa"
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"title" = "Trạng thái hệ thống"
|
"title" = "Trạng thái hệ thống"
|
||||||
"memory" = "Ram"
|
"memory" = "Ram"
|
||||||
"hard" = "Dung lượng"
|
"hard" = "Dung lượng"
|
||||||
"xrayStatus" = "Trạng thái Xray"
|
"xrayStatus" = "Xray"
|
||||||
"stopXray" = "Dừng lại"
|
"stopXray" = "Dừng lại"
|
||||||
"restartXray" = "Khởi động lại"
|
"restartXray" = "Khởi động lại"
|
||||||
"xraySwitch" = "Phiên bản"
|
"xraySwitch" = "Phiên bản"
|
||||||
|
@ -311,6 +311,8 @@
|
||||||
"advancedTemplate" = "Mẫu Nâng cao"
|
"advancedTemplate" = "Mẫu Nâng cao"
|
||||||
"generalConfigs" = "Cấu hình Chung"
|
"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."
|
"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"
|
"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ể."
|
"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"
|
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
||||||
|
@ -388,6 +390,7 @@
|
||||||
"Inbounds" = "Đầu vào"
|
"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ể."
|
"InboundsDesc" = "Thay đổi mẫu cấu hình để chấp nhận các máy khách cụ thể."
|
||||||
"Outbounds" = "Đầu ra"
|
"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."
|
"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"
|
"Routings" = "Quy tắc định tuyến"
|
||||||
"RoutingsDesc" = "Mức độ ưu tiên của mỗi quy tắc đều quan trọng!"
|
"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."
|
"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"
|
"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'"
|
"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]
|
[pages.xray.rules]
|
||||||
"first" = "Đầu tiên"
|
"first" = "Đầu tiên"
|
||||||
|
@ -406,6 +411,7 @@
|
||||||
"dest" = "Đích"
|
"dest" = "Đích"
|
||||||
"inbound" = "Vào"
|
"inbound" = "Vào"
|
||||||
"outbound" = "Ra"
|
"outbound" = "Ra"
|
||||||
|
"balancer" = "Cân bằng"
|
||||||
"info" = "Thông tin"
|
"info" = "Thông tin"
|
||||||
"add" = "Thêm quy tắc"
|
"add" = "Thêm quy tắc"
|
||||||
"edit" = "Chỉnh sửa quy tắc"
|
"edit" = "Chỉnh sửa quy tắc"
|
||||||
|
@ -426,6 +432,15 @@
|
||||||
"portal" = "Cổng thông tin"
|
"portal" = "Cổng thông tin"
|
||||||
"intercon" = "Kết nối"
|
"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]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "Khoá bí mật"
|
"secretKey" = "Khoá bí mật"
|
||||||
"publicKey" = "Khóa công khai"
|
"publicKey" = "Khóa công khai"
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
"title" = "系统状态"
|
"title" = "系统状态"
|
||||||
"memory" = "内存"
|
"memory" = "内存"
|
||||||
"hard" = "硬盘"
|
"hard" = "硬盘"
|
||||||
"xrayStatus" = "状态"
|
"xrayStatus" = "Xray"
|
||||||
"stopXray" = "停止"
|
"stopXray" = "停止"
|
||||||
"restartXray" = "重启"
|
"restartXray" = "重启"
|
||||||
"xraySwitch" = "版本"
|
"xraySwitch" = "版本"
|
||||||
|
@ -311,6 +311,8 @@
|
||||||
"advancedTemplate" = "高级模板部件"
|
"advancedTemplate" = "高级模板部件"
|
||||||
"generalConfigs" = "通用配置"
|
"generalConfigs" = "通用配置"
|
||||||
"generalConfigsDesc" = "这些选项将提供一般调整"
|
"generalConfigsDesc" = "这些选项将提供一般调整"
|
||||||
|
"logConfigs"="日志"
|
||||||
|
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它"
|
||||||
"blockConfigs" = "阻塞配置"
|
"blockConfigs" = "阻塞配置"
|
||||||
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
|
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
|
||||||
"blockCountryConfigs" = "阻止国家配置"
|
"blockCountryConfigs" = "阻止国家配置"
|
||||||
|
@ -388,6 +390,7 @@
|
||||||
"Inbounds" = "入站"
|
"Inbounds" = "入站"
|
||||||
"InboundsDesc" = "更改配置模板接受特殊客户端"
|
"InboundsDesc" = "更改配置模板接受特殊客户端"
|
||||||
"Outbounds" = "出站"
|
"Outbounds" = "出站"
|
||||||
|
"Balancers" = "平衡器"
|
||||||
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式"
|
||||||
"Routings" = "路由规则"
|
"Routings" = "路由规则"
|
||||||
"RoutingsDesc" = "每条规则的优先级都很重要"
|
"RoutingsDesc" = "每条规则的优先级都很重要"
|
||||||
|
@ -396,6 +399,8 @@
|
||||||
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
|
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。"
|
||||||
"accessLog" = "访问日志"
|
"accessLog" = "访问日志"
|
||||||
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
|
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志"
|
||||||
|
"errorLog" = "错误日志"
|
||||||
|
"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "第一个"
|
"first" = "第一个"
|
||||||
|
@ -406,6 +411,7 @@
|
||||||
"dest" = "目的地"
|
"dest" = "目的地"
|
||||||
"inbound" = "入站"
|
"inbound" = "入站"
|
||||||
"outbound" = "出站"
|
"outbound" = "出站"
|
||||||
|
"balancer" = "平衡器"
|
||||||
"info" = "信息"
|
"info" = "信息"
|
||||||
"add" = "添加规则"
|
"add" = "添加规则"
|
||||||
"edit" = "编辑规则"
|
"edit" = "编辑规则"
|
||||||
|
@ -426,6 +432,15 @@
|
||||||
"portal" = "门户"
|
"portal" = "门户"
|
||||||
"intercon" = "互连"
|
"intercon" = "互连"
|
||||||
|
|
||||||
|
[pages.xray.balancer]
|
||||||
|
"addBalancer" = "添加平衡器"
|
||||||
|
"editBalancer" = "编辑平衡器"
|
||||||
|
"balancerStrategy" = "战略"
|
||||||
|
"balancerSelectors" = "选择器"
|
||||||
|
"tag" = "标签"
|
||||||
|
"tagDesc" = "唯一标记"
|
||||||
|
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用,则只有outboundTag起作用。"
|
||||||
|
|
||||||
[pages.xray.wireguard]
|
[pages.xray.wireguard]
|
||||||
"secretKey" = "密钥"
|
"secretKey" = "密钥"
|
||||||
"publicKey" = "公钥"
|
"publicKey" = "公钥"
|
||||||
|
|
87
x-ui.sh
87
x-ui.sh
|
@ -150,6 +150,12 @@ custom_version() {
|
||||||
eval $install_command
|
eval $install_command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Function to handle the deletion of the script file
|
||||||
|
delete_script() {
|
||||||
|
rm "$0" # Remove the script file itself
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
uninstall() {
|
uninstall() {
|
||||||
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
|
confirm "Are you sure you want to uninstall the panel? xray will also uninstalled!" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
|
@ -167,12 +173,13 @@ uninstall() {
|
||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
|
|
||||||
echo ""
|
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 ""
|
echo ""
|
||||||
|
# Trap the SIGTERM signal
|
||||||
if [[ $# == 0 ]]; then
|
trap delete_script SIGTERM
|
||||||
before_show_menu
|
delete_script
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_user() {
|
reset_user() {
|
||||||
|
@ -483,6 +490,33 @@ show_xray_status() {
|
||||||
fi
|
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() {
|
open_ports() {
|
||||||
if ! command -v ufw &>/dev/null; then
|
if ! command -v ufw &>/dev/null; then
|
||||||
echo "ufw firewall is not installed. Installing now..."
|
echo "ufw firewall is not installed. Installing now..."
|
||||||
|
@ -535,6 +569,37 @@ open_ports() {
|
||||||
ufw status | grep $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() {
|
update_geo() {
|
||||||
local defaultBinFolder="/usr/local/x-ui/bin"
|
local defaultBinFolder="/usr/local/x-ui/bin"
|
||||||
read -p "Please enter x-ui bin folder path. Leave blank for default. (Default: '${defaultBinFolder}')" binFolder
|
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}17.${plain} Cloudflare SSL Certificate
|
||||||
${green}18.${plain} IP Limit Management
|
${green}18.${plain} IP Limit Management
|
||||||
${green}19.${plain} WARP Management
|
${green}19.${plain} WARP Management
|
||||||
|
${green}20.${plain} Firewall Management
|
||||||
————————————————
|
————————————————
|
||||||
${green}20.${plain} Enable BBR
|
${green}21.${plain} Enable BBR
|
||||||
${green}21.${plain} Update Geo Files
|
${green}22.${plain} Update Geo Files
|
||||||
${green}22.${plain} Active Firewall and open ports
|
|
||||||
${green}23.${plain} Speedtest by Ookla
|
${green}23.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
|
@ -1195,13 +1260,13 @@ show_menu() {
|
||||||
warp_cloudflare
|
warp_cloudflare
|
||||||
;;
|
;;
|
||||||
20)
|
20)
|
||||||
enable_bbr
|
firewall_menu
|
||||||
;;
|
;;
|
||||||
21)
|
21)
|
||||||
update_geo
|
enable_bbr
|
||||||
;;
|
;;
|
||||||
22)
|
22)
|
||||||
open_ports
|
update_geo
|
||||||
;;
|
;;
|
||||||
23)
|
23)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package xray
|
package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
@ -14,26 +15,18 @@ type LogWriter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lw *LogWriter) Write(m []byte) (n int, err error) {
|
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
|
// Convert the data to a string
|
||||||
message := strings.TrimSpace(string(m))
|
message := strings.TrimSpace(string(m))
|
||||||
messages := strings.Split(message, "\n")
|
messages := strings.Split(message, "\n")
|
||||||
lw.lastLine = messages[len(messages)-1]
|
lw.lastLine = messages[len(messages)-1]
|
||||||
|
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
messageBody := msg
|
matches := regex.FindStringSubmatch(msg)
|
||||||
|
|
||||||
// Remove timestamp
|
if len(matches) > 3 {
|
||||||
splittedMsg := strings.SplitN(msg, " ", 3)
|
level := matches[2]
|
||||||
if len(splittedMsg) > 2 {
|
msgBody := matches[3]
|
||||||
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:])
|
|
||||||
|
|
||||||
// Map the level to the appropriate logger function
|
// Map the level to the appropriate logger function
|
||||||
switch level {
|
switch level {
|
||||||
|
|
|
@ -41,10 +41,6 @@ func GetIPLimitLogPath() string {
|
||||||
return config.GetLogFolder() + "/3xipl.log"
|
return config.GetLogFolder() + "/3xipl.log"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIPLimitPrevLogPath() string {
|
|
||||||
return config.GetLogFolder() + "/3xipl.prev.log"
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetIPLimitBannedLogPath() string {
|
func GetIPLimitBannedLogPath() string {
|
||||||
return config.GetLogFolder() + "/3xipl-banned.log"
|
return config.GetLogFolder() + "/3xipl-banned.log"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue