This commit is contained in:
somebodywashere 2024-03-12 12:37:24 +03:00
commit dbb737eb79
81 changed files with 1074 additions and 748 deletions

View file

@ -77,7 +77,7 @@ jobs:
cd x-ui/bin cd x-ui/bin
# Download dependencies # Download dependencies
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.8/" Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.9/"
if [ "${{ matrix.platform }}" == "amd64" ]; then if [ "${{ matrix.platform }}" == "amd64" ]; then
wget ${Xray_URL}Xray-linux-64.zip wget ${Xray_URL}Xray-linux-64.zip
unzip Xray-linux-64.zip unzip Xray-linux-64.zip

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
.cache .cache
.sync* .sync*
*.tar.gz *.tar.gz
*.log
access.log access.log
error.log error.log
tmp tmp

View file

@ -27,7 +27,7 @@ case $1 in
esac esac
mkdir -p build/bin mkdir -p build/bin
cd build/bin cd build/bin
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.8/Xray-linux-${ARCH}.zip" wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.9/Xray-linux-${ARCH}.zip"
unzip "Xray-linux-${ARCH}.zip" unzip "Xray-linux-${ARCH}.zip"
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
mv xray "xray-linux-${FNAME}" mv xray "xray-linux-${FNAME}"

View file

@ -26,10 +26,10 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
## Install Custom Version ## Install Custom Version
To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.1`: To install your desired version, add the version to the end of the installation command. e.g., ver `v2.2.5`:
``` ```
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.1 bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v2.2.5
``` ```
## SSL Certificate ## SSL Certificate
@ -311,9 +311,9 @@ If you want to use routing to WARP before v2.1.0 follow steps as below:
```sh ```sh
"log": { "log": {
"access": "./access.log", "access": "./access.log",
"dnsLog": false, "dnsLog": false,
"loglevel": "warning" "loglevel": "warning"
}, },
``` ```

View file

@ -1 +1 @@
2.2.1 2.2.5

View file

@ -2,6 +2,7 @@ package model
import ( import (
"fmt" "fmt"
"x-ui/util/json_util" "x-ui/util/json_util"
"x-ui/xray" "x-ui/xray"
) )

42
go.mod
View file

@ -12,35 +12,35 @@ require (
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.2
github.com/valyala/fasthttp v1.52.0 github.com/valyala/fasthttp v1.52.0
github.com/xtls/xray-core v1.8.8 github.com/xtls/xray-core v1.8.9
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.62.0 google.golang.org/grpc v1.62.1
gorm.io/driver/sqlite v1.5.5 gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.7 gorm.io/gorm v1.25.7
) )
require ( require (
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.10.2 // indirect github.com/bytedance/sonic v1.11.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
github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudflare/circl v1.3.7 // indirect
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
github.com/fasthttp/router v1.4.22 // indirect github.com/fasthttp/router v1.5.0 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.17.0 // indirect github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 // indirect github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/gorilla/context v1.1.2 // indirect github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect github.com/gorilla/sessions v1.2.2 // indirect
@ -51,22 +51,22 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.2.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo/v2 v2.15.0 // indirect github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/pires/go-proxyproto v0.7.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
github.com/quic-go/quic-go v0.41.0 // indirect github.com/quic-go/quic-go v0.41.0 // indirect
github.com/refraction-networking/utls v1.6.3 // indirect github.com/refraction-networking/utls v1.6.3 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/sagernet/sing v0.3.2 // indirect github.com/sagernet/sing v0.3.6 // indirect
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect
@ -79,21 +79,21 @@ require (
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
github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/arch v0.6.0 // indirect golang.org/x/arch v0.6.0 // indirect
golang.org/x/crypto v0.19.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.15.0 // indirect golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.21.0 // indirect golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.17.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.18.0 // indirect golang.org/x/tools v0.19.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
google.golang.org/protobuf v1.32.0 // indirect google.golang.org/protobuf v1.33.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
lukechampine.com/blake3 v1.2.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect

93
go.sum
View file

@ -20,8 +20,8 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@ -41,8 +41,8 @@ github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fp
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fasthttp/router v1.4.22 h1:qwWcYBbndVDwts4dKaz+A2ehsnbKilmiP6pUhXBfYKo= github.com/fasthttp/router v1.5.0 h1:3Qbbo27HAPzwbpRzgiV5V9+2faPkPt3eNuRaDV6LYDA=
github.com/fasthttp/router v1.4.22/go.mod h1:KeMvHLqhlB9vyDWD5TSvTccl9qeWrjSSiTJrJALHKV0= github.com/fasthttp/router v1.5.0/go.mod h1:FddcKNXFZg1imHcy+uKB0oo/o6yE9zD3wNguqlhWDak=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
@ -61,8 +61,8 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
@ -76,8 +76,8 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@ -93,8 +93,8 @@ github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
@ -111,8 +111,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20240225044709-fd706174c886 h1:JSJUTZTQT1Gzb2ROdAKOY3HwzBYcclS2GgumhMfHqjw= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
github.com/google/pprof v0.0.0-20240225044709-fd706174c886/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -155,8 +155,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0= github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
@ -165,8 +165,8 @@ github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
@ -183,8 +183,8 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE
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=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
@ -221,17 +221,17 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/sing v0.3.2 h1:CwWcxUBPkMvwgfe2/zUgY5oHG9qOL8Aob/evIFYK9jo= github.com/sagernet/sing v0.3.6 h1:dsEdYLKBQlrxUfw1a92x0VdPvR1/BOxQ+HIMyaoEJsQ=
github.com/sagernet/sing v0.3.2/go.mod h1:qHySJ7u8po9DABtMYEkNBcOumx7ZZJf/fbv2sfTkNHE= github.com/sagernet/sing v0.3.6/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s= github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM= github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg= github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI= github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y=
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU= github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@ -270,9 +270,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
@ -301,10 +301,10 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI= github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE= github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
github.com/xtls/xray-core v1.8.8 h1:6ApBa5pNkPZ+I7jyJDZod3v5sadizS/BZr0pW7zcs8o= github.com/xtls/xray-core v1.8.9 h1:wefcON0behu4DoQvCKJYZKsJlSvNhyq2I7vC2fxLFcY=
github.com/xtls/xray-core v1.8.8/go.mod h1:Zp33A8cxnhP5Kt6nguQrMgNH4A/tgq7LE8cBedeNje8= github.com/xtls/xray-core v1.8.9/go.mod h1:XDE4f422qJKAU3hNDSNZyWrOHvn9kF8UHVdyOzU38rc=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
@ -321,16 +321,16 @@ 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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
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-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -340,8 +340,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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
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=
@ -371,9 +371,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/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/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.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=
@ -390,8 +390,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
@ -409,19 +409,18 @@ 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-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 h1:em/y72n4XlYRtayY/cVj6pnVzHa//BDA1BdoO+z9mdE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
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.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View file

@ -8,12 +8,14 @@ import (
"github.com/op/go-logging" "github.com/op/go-logging"
) )
var logger *logging.Logger var (
var logBuffer []struct { logger *logging.Logger
time string logBuffer []struct {
level logging.Level time string
log string level logging.Level
} log string
}
)
func init() { func init() {
InitLogger(logging.INFO) InitLogger(logging.INFO)

View file

@ -8,6 +8,7 @@ import (
"os/signal" "os/signal"
"syscall" "syscall"
_ "unsafe" _ "unsafe"
"x-ui/config" "x-ui/config"
"x-ui/database" "x-ui/database"
"x-ui/logger" "x-ui/logger"
@ -342,7 +343,7 @@ func main() {
updateTgbotEnableSts(enabletgbot) updateTgbotEnableSts(enabletgbot)
} }
default: default:
fmt.Println("except 'run' or 'setting' subcommands") fmt.Println("Invalid subcommands")
fmt.Println() fmt.Println()
runCmd.Usage() runCmd.Usage()
fmt.Println() fmt.Println()

View file

@ -1,4 +1,5 @@
{ {
"remarks": "",
"dns": { "dns": {
"tag": "dns_out", "tag": "dns_out",
"queryStrategy": "UseIP", "queryStrategy": "UseIP",
@ -78,28 +79,9 @@
{ {
"type": "field", "type": "field",
"network": "tcp,udp", "network": "tcp,udp",
"balancerTag": "all" "outboundTag": "proxy"
}
],
"balancers": [
{
"tag": "all",
"selector": [
"proxy"
],
"strategy": {
"type": "leastPing"
}
} }
] ]
}, },
"observatory": {
"probeInterval": "5m",
"probeURL": "https://api.github.com/_private/browser/stats",
"subjectSelector": [
"proxy"
],
"EnableConcurrency": true
},
"stats": {} "stats": {}
} }

View file

@ -7,6 +7,7 @@ import (
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"x-ui/config" "x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
@ -99,7 +100,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
} }
func (s *Server) Start() (err error) { func (s *Server) Start() (err error) {
//This is an anonymous function, no function name // This is an anonymous function, no function name
defer func() { defer func() {
if err != nil { if err != nil {
s.Stop() s.Stop()
@ -144,21 +145,19 @@ func (s *Server) Start() (err error) {
if certFile != "" || keyFile != "" { if certFile != "" || keyFile != "" {
cert, err := tls.LoadX509KeyPair(certFile, keyFile) cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil { if err == nil {
listener.Close() c := &tls.Config{
return err Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
logger.Info("sub server run https on", listener.Addr())
} else {
logger.Error("error in loading certificates: ", err)
logger.Info("sub server run http on", listener.Addr())
} }
c := &tls.Config{
Certificates: []tls.Certificate{cert},
}
listener = network.NewAutoHttpsListener(listener)
listener = tls.NewListener(listener, c)
}
if certFile != "" || keyFile != "" {
logger.Info("Sub server run https on", listener.Addr())
} else { } else {
logger.Info("Sub server run http on", listener.Addr()) logger.Info("sub server run http on", listener.Addr())
} }
s.listener = listener s.listener = listener

View file

@ -25,16 +25,17 @@ func NewSUBController(
showInfo bool, showInfo bool,
rModel string, rModel string,
update string, update string,
jsonFragment string) *SUBController { jsonFragment string,
) *SUBController {
sub := NewSubService(showInfo, rModel)
a := &SUBController{ a := &SUBController{
subPath: subPath, subPath: subPath,
subJsonPath: jsonPath, subJsonPath: jsonPath,
subEncrypt: encrypt, subEncrypt: encrypt,
updateInterval: update, updateInterval: update,
subService: NewSubService(showInfo, rModel), subService: sub,
subJsonService: NewSubJsonService(jsonFragment), subJsonService: NewSubJsonService(jsonFragment, sub),
} }
a.initRouter(g) a.initRouter(g)
return a return a

View file

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
"x-ui/util/json_util" "x-ui/util/json_util"
@ -17,15 +18,34 @@ import (
var defaultJson string var defaultJson string
type SubJsonService struct { type SubJsonService struct {
fragmanet string configJson map[string]interface{}
defaultOutbounds []json_util.RawMessage
fragment string
inboundService service.InboundService inboundService service.InboundService
SubService SubService *SubService
} }
func NewSubJsonService(fragment string) *SubJsonService { func NewSubJsonService(fragment string, subService *SubService) *SubJsonService {
var configJson map[string]interface{}
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
for _, defaultOutbound := range outboundSlices {
jsonBytes, _ := json.Marshal(defaultOutbound)
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
}
if fragment != "" {
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
}
return &SubJsonService{ return &SubJsonService{
fragmanet: fragment, configJson: configJson,
defaultOutbounds: defaultOutbounds,
fragment: fragment,
SubService: subService,
} }
} }
@ -38,19 +58,8 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
var header string var header string
var traffic xray.ClientTraffic var traffic xray.ClientTraffic
var clientTraffics []xray.ClientTraffic var clientTraffics []xray.ClientTraffic
var configJson map[string]interface{} var configArray []json_util.RawMessage
var defaultOutbounds []json_util.RawMessage
json.Unmarshal([]byte(defaultJson), &configJson)
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
for _, defaultOutbound := range outboundSlices {
jsonBytes, _ := json.Marshal(defaultOutbound)
defaultOutbounds = append(defaultOutbounds, jsonBytes)
}
}
outbounds := []json_util.RawMessage{}
startIndex := 0
// Prepare Inbounds // Prepare Inbounds
for _, inbound := range inbounds { for _, inbound := range inbounds {
clients, err := s.inboundService.GetClients(inbound) clients, err := s.inboundService.GetClients(inbound)
@ -61,7 +70,7 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
continue continue
} }
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings) listen, port, streamSettings, err := s.SubService.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
if err == nil { if err == nil {
inbound.Listen = listen inbound.Listen = listen
inbound.Port = port inbound.Port = port
@ -69,22 +78,16 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
} }
} }
var subClients []model.Client
for _, client := range clients { for _, client := range clients {
if client.Enable && client.SubID == subId { if client.Enable && client.SubID == subId {
subClients = append(subClients, client)
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email)) clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
newConfigs := s.getConfig(inbound, client, host)
configArray = append(configArray, newConfigs...)
} }
} }
outbound := s.getOutbound(inbound, subClients, host, startIndex)
if outbound != nil {
outbounds = append(outbounds, outbound...)
startIndex += len(outbound)
}
} }
if len(outbounds) == 0 { if len(configArray) == 0 {
return "", "", nil return "", "", nil
} }
@ -111,21 +114,15 @@ func (s *SubJsonService) GetJson(subId string, host string) (string, string, err
} }
} }
if s.fragmanet != "" {
outbounds = append(outbounds, json_util.RawMessage(s.fragmanet))
}
// Combile outbounds // Combile outbounds
outbounds = append(outbounds, defaultOutbounds...) finalJson, _ := json.MarshalIndent(configArray, "", " ")
configJson["outbounds"] = outbounds
finalJson, _ := json.MarshalIndent(configJson, "", " ")
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
return string(finalJson), header, nil return string(finalJson), header, nil
} }
func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Client, host string, startIndex int) []json_util.RawMessage { func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, host string) []json_util.RawMessage {
var newOutbounds []json_util.RawMessage var newJsonArray []json_util.RawMessage
stream := s.streamData(inbound.StreamSettings) stream := s.streamData(inbound.StreamSettings)
externalProxies, ok := stream["externalProxy"].([]interface{}) externalProxies, ok := stream["externalProxy"].([]interface{})
@ -135,13 +132,13 @@ func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Cli
"forceTls": "same", "forceTls": "same",
"dest": host, "dest": host,
"port": float64(inbound.Port), "port": float64(inbound.Port),
"remark": "",
}, },
} }
} }
delete(stream, "externalProxy") delete(stream, "externalProxy")
config_index := startIndex
for _, ep := range externalProxies { for _, ep := range externalProxies {
extPrxy := ep.(map[string]interface{}) extPrxy := ep.(map[string]interface{})
inbound.Listen = extPrxy["dest"].(string) inbound.Listen = extPrxy["dest"].(string)
@ -160,21 +157,28 @@ func (s *SubJsonService) getOutbound(inbound *model.Inbound, clients []model.Cli
} }
} }
streamSettings, _ := json.MarshalIndent(newStream, "", " ") streamSettings, _ := json.MarshalIndent(newStream, "", " ")
inbound.StreamSettings = string(streamSettings)
for _, client := range clients { var newOutbounds []json_util.RawMessage
inbound.Tag = fmt.Sprintf("proxy_%d", config_index)
switch inbound.Protocol { switch inbound.Protocol {
case "vmess", "vless": case "vmess", "vless":
newOutbounds = append(newOutbounds, s.genVnext(inbound, client)) newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
case "trojan", "shadowsocks": case "trojan", "shadowsocks":
newOutbounds = append(newOutbounds, s.genServer(inbound, client)) newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
}
config_index += 1
} }
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
newConfigJson := make(map[string]interface{})
for key, value := range s.configJson {
newConfigJson[key] = value
}
newConfigJson["outbounds"] = newOutbounds
newConfigJson["remarks"] = s.SubService.genRemark(inbound, client.Email, extPrxy["remark"].(string))
newConfig, _ := json.MarshalIndent(newConfigJson, "", " ")
newJsonArray = append(newJsonArray, newConfig)
} }
return newOutbounds return newJsonArray
} }
func (s *SubJsonService) streamData(stream string) map[string]interface{} { func (s *SubJsonService) streamData(stream string) map[string]interface{} {
@ -188,7 +192,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
} }
delete(streamSettings, "sockopt") delete(streamSettings, "sockopt")
if s.fragmanet != "" { if s.fragment != "" {
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`) streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
} }
@ -214,7 +218,7 @@ func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]inter
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} { func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
tlsData := make(map[string]interface{}, 1) tlsData := make(map[string]interface{}, 1)
tlsClientSettings := tData["settings"].(map[string]interface{}) tlsClientSettings, _ := tData["settings"].(map[string]interface{})
tlsData["serverName"] = tData["serverName"] tlsData["serverName"] = tData["serverName"]
tlsData["alpn"] = tData["alpn"] tlsData["alpn"] = tData["alpn"]
@ -229,7 +233,7 @@ func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interf
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} { func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
rltyData := make(map[string]interface{}, 1) rltyData := make(map[string]interface{}, 1)
rltyClientSettings := rData["settings"].(map[string]interface{}) rltyClientSettings, _ := rData["settings"].(map[string]interface{})
rltyData["show"] = false rltyData["show"] = false
rltyData["publicKey"] = rltyClientSettings["publicKey"] rltyData["publicKey"] = rltyClientSettings["publicKey"]
@ -253,7 +257,7 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
return rltyData return rltyData
} }
func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) json_util.RawMessage { func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
outbound := Outbound{} outbound := Outbound{}
usersData := make([]UserVnext, 1) usersData := make([]UserVnext, 1)
@ -272,8 +276,8 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) j
} }
outbound.Protocol = string(inbound.Protocol) outbound.Protocol = string(inbound.Protocol)
outbound.Tag = inbound.Tag outbound.Tag = "proxy"
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings) outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{ outbound.Settings = OutboundSettings{
Vnext: vnextData, Vnext: vnextData,
} }
@ -282,7 +286,7 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, client model.Client) j
return result return result
} }
func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client) json_util.RawMessage { func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
outbound := Outbound{} outbound := Outbound{}
serverData := make([]ServerSetting, 1) serverData := make([]ServerSetting, 1)
@ -308,8 +312,8 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, client model.Client)
} }
outbound.Protocol = string(inbound.Protocol) outbound.Protocol = string(inbound.Protocol)
outbound.Tag = inbound.Tag outbound.Tag = "proxy"
outbound.StreamSettings = json_util.RawMessage(inbound.StreamSettings) outbound.StreamSettings = streamSettings
outbound.Settings = OutboundSettings{ outbound.Settings = OutboundSettings{
Servers: serverData, Servers: serverData,
} }

View file

@ -6,10 +6,12 @@ import (
"net/url" "net/url"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
"x-ui/util/random"
"x-ui/web/service" "x-ui/web/service"
"x-ui/xray" "x-ui/xray"
@ -212,9 +214,14 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
obj["path"] = grpc["serviceName"].(string) obj["path"] = grpc["serviceName"].(string)
obj["authority"] = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
obj["type"] = "multi" obj["type"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
obj["path"] = httpupgrade["path"].(string)
obj["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
@ -346,9 +353,14 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string) params["serviceName"] = grpc["serviceName"].(string)
params["authority"] = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
params["mode"] = "multi" params["mode"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
params["path"] = httpupgrade["path"].(string)
params["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
@ -391,25 +403,21 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
if realitySetting != nil { if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{}) sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string) params["sni"] = sNames[random.Num(len(sNames))].(string)
} }
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok { if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string) params["pbk"], _ = pbkValue.(string)
} }
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok { if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{}) shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string) params["sid"] = shortIds[random.Num(len(shortIds))].(string)
} }
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 { if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp params["fp"] = fp
} }
} }
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok { params["spx"] = "/" + random.Seq(15)
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
@ -562,9 +570,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string) params["serviceName"] = grpc["serviceName"].(string)
params["authority"] = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
params["mode"] = "multi" params["mode"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
params["path"] = httpupgrade["path"].(string)
params["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)
@ -603,25 +616,21 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
if realitySetting != nil { if realitySetting != nil {
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
sNames, _ := sniValue.([]interface{}) sNames, _ := sniValue.([]interface{})
params["sni"], _ = sNames[0].(string) params["sni"] = sNames[random.Num(len(sNames))].(string)
} }
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok { if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
params["pbk"], _ = pbkValue.(string) params["pbk"], _ = pbkValue.(string)
} }
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok { if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
shortIds, _ := sidValue.([]interface{}) shortIds, _ := sidValue.([]interface{})
params["sid"], _ = shortIds[0].(string) params["sid"] = shortIds[random.Num(len(shortIds))].(string)
} }
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
if fp, ok := fpValue.(string); ok && len(fp) > 0 { if fp, ok := fpValue.(string); ok && len(fp) > 0 {
params["fp"] = fp params["fp"] = fp
} }
} }
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok { params["spx"] = "/" + random.Seq(15)
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
params["spx"] = spx
}
}
} }
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
@ -779,9 +788,14 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
case "grpc": case "grpc":
grpc, _ := stream["grpcSettings"].(map[string]interface{}) grpc, _ := stream["grpcSettings"].(map[string]interface{})
params["serviceName"] = grpc["serviceName"].(string) params["serviceName"] = grpc["serviceName"].(string)
params["authority"] = grpc["authority"].(string)
if grpc["multiMode"].(bool) { if grpc["multiMode"].(bool) {
params["mode"] = "multi" params["mode"] = "multi"
} }
case "httpupgrade":
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
params["path"] = httpupgrade["path"].(string)
params["host"] = httpupgrade["host"].(string)
} }
security, _ := stream["security"].(string) security, _ := stream["security"].(string)

View file

@ -3,6 +3,7 @@ package common
import ( import (
"errors" "errors"
"fmt" "fmt"
"x-ui/logger" "x-ui/logger"
) )

View file

@ -4,12 +4,14 @@ import (
"math/rand" "math/rand"
) )
var numSeq [10]rune var (
var lowerSeq [26]rune numSeq [10]rune
var upperSeq [26]rune lowerSeq [26]rune
var numLowerSeq [36]rune upperSeq [26]rune
var numUpperSeq [36]rune numLowerSeq [36]rune
var allSeq [62]rune numUpperSeq [36]rune
allSeq [62]rune
)
func init() { func init() {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {

View file

@ -80,6 +80,11 @@ html[data-theme='ultra-dark'] {
.dark .waves-header { .dark .waves-header {
background-color: #0a2227; background-color: #0a2227;
} }
.dark .ant-calendar-year-panel-year:hover,
.dark .ant-calendar-month-panel-month:hover,
.dark .ant-calendar-decade-panel-decade:hover {
background-color: var(--dark-color-surface-600);
}
} }
html, html,
@ -175,7 +180,7 @@ style attribute {
position: relative; position: relative;
clear: both; clear: both;
} }
.ant-table-body { .ant-table-wrapper > div > div > div > div > div > div {
overflow-x: auto !important; overflow-x: auto !important;
} }
.ant-card-hoverable { .ant-card-hoverable {
@ -791,6 +796,15 @@ style attribute {
border-color: var(--dark-color-surface-500); border-color: var(--dark-color-surface-500);
} }
@media (max-width: 768px) {
.dark .ant-popover-inner {
background-color: var(--dark-color-surface-200);
}
.dark > .ant-popover-content > .ant-popover-arrow {
border-color: var(--dark-color-surface-200);
}
}
.ant-dropdown-menu-dark .ant-dropdown-menu-item:hover, .ant-dropdown-menu-dark .ant-dropdown-menu-item:hover,
.dark .ant-select-dropdown-menu-item-selected, .dark .ant-select-dropdown-menu-item-selected,
.dark .ant-calendar-time-picker-select-option-selected { .dark .ant-calendar-time-picker-select-option-selected {
@ -1257,3 +1271,8 @@ b, strong {
.ant-empty-small .ant-empty-image { .ant-empty-small .ant-empty-image {
height: 20px; height: 20px;
} }
.ant-menu-theme-switch:hover {
background-color: transparent !important;
cursor: default !important;
}

View file

@ -14,3 +14,17 @@ axios.interceptors.request.use(
}, },
(error) => Promise.reject(error), (error) => Promise.reject(error),
); );
axios.interceptors.response.use(
(response) => response,
(error) => {
if (error.response) {
const statusCode = error.response.status;
// Check the status code
if (statusCode === 401) { // Unauthorized
return window.location.reload();
}
}
return Promise.reject(error);
}
);

View file

@ -51,7 +51,14 @@ const OutboundDomainStrategies = [
"AsIs", "AsIs",
"UseIP", "UseIP",
"UseIPv4", "UseIPv4",
"UseIPv6" "UseIPv6",
"UseIPv6v4",
"UseIPv4v6",
"ForceIP",
"ForceIPv6v4",
"ForceIPv6",
"ForceIPv4v6",
"ForceIPv4"
]; ];
const WireguardDomainStrategy = [ const WireguardDomainStrategy = [
@ -268,6 +275,28 @@ class GrpcStreamSettings extends CommonClass {
} }
} }
class HttpUpgradeStreamSettings extends CommonClass {
constructor(path='/', host='') {
super();
this.path = path;
this.host = host;
}
static fromJson(json={}) {
return new HttpUpgradeStreamSettings(
json.path,
json.Host,
);
}
toJson() {
return {
path: this.path,
host: this.host,
};
}
}
class TlsStreamSettings extends CommonClass { class TlsStreamSettings extends CommonClass {
constructor(serverName='', constructor(serverName='',
alpn=[], alpn=[],
@ -327,6 +356,34 @@ class RealityStreamSettings extends CommonClass {
}; };
} }
}; };
class SockoptStreamSettings extends CommonClass {
constructor(dialerProxy = "", tcpFastOpen = false, tcpKeepAliveInterval = 0, tcpNoDelay = false) {
super();
this.dialerProxy = dialerProxy;
this.tcpFastOpen = tcpFastOpen;
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
this.tcpNoDelay = tcpNoDelay;
}
static fromJson(json = {}) {
if (Object.keys(json).length === 0) return undefined;
return new SockoptStreamSettings(
json.dialerProxy,
json.tcpFastOpen,
json.tcpKeepAliveInterval,
json.tcpNoDelay,
);
}
toJson() {
return {
dialerProxy: this.dialerProxy,
tcpFastOpen: this.tcpFastOpen,
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
tcpNoDelay: this.tcpNoDelay,
};
}
}
class StreamSettings extends CommonClass { class StreamSettings extends CommonClass {
constructor(network='tcp', constructor(network='tcp',
@ -339,6 +396,8 @@ class StreamSettings extends CommonClass {
httpSettings=new HttpStreamSettings(), httpSettings=new HttpStreamSettings(),
quicSettings=new QuicStreamSettings(), quicSettings=new QuicStreamSettings(),
grpcSettings=new GrpcStreamSettings(), grpcSettings=new GrpcStreamSettings(),
httpupgradeSettings=new HttpUpgradeStreamSettings(),
sockopt = undefined,
) { ) {
super(); super();
this.network = network; this.network = network;
@ -351,6 +410,8 @@ class StreamSettings extends CommonClass {
this.http = httpSettings; this.http = httpSettings;
this.quic = quicSettings; this.quic = quicSettings;
this.grpc = grpcSettings; this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.sockopt = sockopt;
} }
get isTls() { get isTls() {
@ -361,6 +422,14 @@ class StreamSettings extends CommonClass {
return this.security === "reality"; return this.security === "reality";
} }
get sockoptSwitch() {
return this.sockopt != undefined;
}
set sockoptSwitch(value) {
this.sockopt = value ? new SockoptStreamSettings() : undefined;
}
static fromJson(json={}) { static fromJson(json={}) {
return new StreamSettings( return new StreamSettings(
json.network, json.network,
@ -373,6 +442,8 @@ class StreamSettings extends CommonClass {
HttpStreamSettings.fromJson(json.httpSettings), HttpStreamSettings.fromJson(json.httpSettings),
QuicStreamSettings.fromJson(json.quicSettings), QuicStreamSettings.fromJson(json.quicSettings),
GrpcStreamSettings.fromJson(json.grpcSettings), GrpcStreamSettings.fromJson(json.grpcSettings),
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
SockoptStreamSettings.fromJson(json.sockopt),
); );
} }
@ -389,6 +460,37 @@ class StreamSettings extends CommonClass {
httpSettings: network === 'http' ? this.http.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined,
quicSettings: network === 'quic' ? this.quic.toJson() : undefined, quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
};
}
}
class Mux extends CommonClass {
constructor(enabled = false, concurrency = 8, xudpConcurrency = 16, xudpProxyUDP443 = "reject") {
super();
this.enabled = enabled;
this.concurrency = concurrency;
this.xudpConcurrency = xudpConcurrency;
this.xudpProxyUDP443 = xudpProxyUDP443;
}
static fromJson(json = {}) {
if (Object.keys(json).length === 0) return undefined;
return new Mux(
json.enabled,
json.concurrency,
json.xudpConcurrency,
json.xudpProxyUDP443,
);
}
toJson() {
return {
enabled: this.enabled,
concurrency: this.concurrency,
xudpConcurrency: this.xudpConcurrency,
xudpProxyUDP443: this.xudpProxyUDP443,
}; };
} }
} }
@ -399,12 +501,14 @@ class Outbound extends CommonClass {
protocol=Protocols.VMess, protocol=Protocols.VMess,
settings=null, settings=null,
streamSettings = new StreamSettings(), streamSettings = new StreamSettings(),
mux = new Mux(),
) { ) {
super(); super();
this.tag = tag; this.tag = tag;
this._protocol = protocol; this._protocol = protocol;
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings; this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
this.stream = streamSettings; this.stream = streamSettings;
this.mux = mux;
} }
get protocol() { get protocol() {
@ -419,7 +523,7 @@ class Outbound extends CommonClass {
canEnableTls() { canEnableTls() {
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].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", "httpupgrade"].includes(this.stream.network);
} }
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
@ -469,6 +573,7 @@ class Outbound extends CommonClass {
json.protocol, json.protocol,
Outbound.Settings.fromJson(json.protocol, json.settings), Outbound.Settings.fromJson(json.protocol, json.settings),
StreamSettings.fromJson(json.streamSettings), StreamSettings.fromJson(json.streamSettings),
Mux.fromJson(json.mux),
) )
} }
@ -478,6 +583,7 @@ class Outbound extends CommonClass {
protocol: this.protocol, protocol: this.protocol,
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings, settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined, streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
mux: this.mux?.enabled ? this.mux : undefined,
}; };
} }
@ -523,6 +629,8 @@ class Outbound extends CommonClass {
json.type ? json.type : 'none'); json.type ? json.type : 'none');
} else if (network === 'grpc') { } else if (network === 'grpc') {
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi'); stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
} else if (network === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path,json.host);
} }
if(json.tls && json.tls == 'tls'){ if(json.tls && json.tls == 'tls'){
@ -533,7 +641,6 @@ class Outbound extends CommonClass {
json.allowInsecure); json.allowInsecure);
} }
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream); return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
} }
@ -564,6 +671,8 @@ class Outbound extends CommonClass {
headerType ?? 'none'); headerType ?? 'none');
} else if (type === 'grpc') { } else if (type === 'grpc') {
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi'); stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
} else if (type === 'httpupgrade') {
stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
} }
if(security == 'tls'){ if(security == 'tls'){
@ -580,7 +689,7 @@ class Outbound extends CommonClass {
let sni=url.searchParams.get('sni') ?? ''; let sni=url.searchParams.get('sni') ?? '';
let sid=url.searchParams.get('sid') ?? ''; let sid=url.searchParams.get('sid') ?? '';
let spx=url.searchParams.get('spx') ?? ''; let spx=url.searchParams.get('spx') ?? '';
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx); stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
} }
let data = link.split('?'); let data = link.split('?');

View file

@ -446,16 +446,19 @@ class QuicStreamSettings extends XrayCommonClass {
class GrpcStreamSettings extends XrayCommonClass { class GrpcStreamSettings extends XrayCommonClass {
constructor( constructor(
serviceName="", serviceName="",
authority="",
multiMode=false, multiMode=false,
) { ) {
super(); super();
this.serviceName = serviceName; this.serviceName = serviceName;
this.authority = authority;
this.multiMode = multiMode; this.multiMode = multiMode;
} }
static fromJson(json={}) { static fromJson(json={}) {
return new GrpcStreamSettings( return new GrpcStreamSettings(
json.serviceName, json.serviceName,
json.authority,
json.multiMode json.multiMode
); );
} }
@ -463,11 +466,36 @@ class GrpcStreamSettings extends XrayCommonClass {
toJson() { toJson() {
return { return {
serviceName: this.serviceName, serviceName: this.serviceName,
authority: this.authority,
multiMode: this.multiMode, multiMode: this.multiMode,
} }
} }
} }
class HttpUpgradeStreamSettings extends XrayCommonClass {
constructor(acceptProxyProtocol=false, path='/', host='') {
super();
this.acceptProxyProtocol = acceptProxyProtocol;
this.path = path;
this.host = host;
}
static fromJson(json={}) {
return new HttpUpgradeStreamSettings(
json.acceptProxyProtocol,
json.path,
json.host,
);
}
toJson() {
return {
acceptProxyProtocol: this.acceptProxyProtocol,
path: this.path,
host: this.host,
};
}
}
class TlsStreamSettings extends XrayCommonClass { class TlsStreamSettings extends XrayCommonClass {
constructor(serverName='', constructor(serverName='',
@ -833,6 +861,7 @@ class StreamSettings extends XrayCommonClass {
httpSettings=new HttpStreamSettings(), httpSettings=new HttpStreamSettings(),
quicSettings=new QuicStreamSettings(), quicSettings=new QuicStreamSettings(),
grpcSettings=new GrpcStreamSettings(), grpcSettings=new GrpcStreamSettings(),
httpupgradeSettings=new HttpUpgradeStreamSettings(),
sockopt = undefined, sockopt = undefined,
) { ) {
super(); super();
@ -848,6 +877,7 @@ class StreamSettings extends XrayCommonClass {
this.http = httpSettings; this.http = httpSettings;
this.quic = quicSettings; this.quic = quicSettings;
this.grpc = grpcSettings; this.grpc = grpcSettings;
this.httpupgrade = httpupgradeSettings;
this.sockopt = sockopt; this.sockopt = sockopt;
} }
@ -910,6 +940,7 @@ class StreamSettings extends XrayCommonClass {
HttpStreamSettings.fromJson(json.httpSettings), HttpStreamSettings.fromJson(json.httpSettings),
QuicStreamSettings.fromJson(json.quicSettings), QuicStreamSettings.fromJson(json.quicSettings),
GrpcStreamSettings.fromJson(json.grpcSettings), GrpcStreamSettings.fromJson(json.grpcSettings),
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
SockoptStreamSettings.fromJson(json.sockopt), SockoptStreamSettings.fromJson(json.sockopt),
); );
} }
@ -929,6 +960,7 @@ class StreamSettings extends XrayCommonClass {
httpSettings: network === 'http' ? this.http.toJson() : undefined, httpSettings: network === 'http' ? this.http.toJson() : undefined,
quicSettings: network === 'quic' ? this.quic.toJson() : undefined, quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined, grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined, sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
}; };
} }
@ -1045,6 +1077,10 @@ class Inbound extends XrayCommonClass {
return this.network === "http"; return this.network === "http";
} }
get isHttpupgrade() {
return this.network === "httpupgrade";
}
// Shadowsocks // Shadowsocks
get method() { get method() {
switch (this.protocol) { switch (this.protocol) {
@ -1075,6 +1111,8 @@ class Inbound extends XrayCommonClass {
return this.stream.ws.getHeader("Host"); return this.stream.ws.getHeader("Host");
} else if (this.isH2) { } else if (this.isH2) {
return this.stream.http.host[0]; return this.stream.http.host[0];
} else if (this.isHttpupgrade) {
return this.stream.httpupgrade.host;
} }
return null; return null;
} }
@ -1086,6 +1124,8 @@ class Inbound extends XrayCommonClass {
return this.stream.ws.path; return this.stream.ws.path;
} else if (this.isH2) { } else if (this.isH2) {
return this.stream.http.path; return this.stream.http.path;
} else if (this.isHttpupgrade) {
return this.stream.httpupgrade.path;
} }
return null; return null;
} }
@ -1121,7 +1161,7 @@ class Inbound extends XrayCommonClass {
canEnableTls() { canEnableTls() {
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].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.network); return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.network);
} }
//this is used for xtls-rprx-vision //this is used for xtls-rprx-vision
@ -1204,9 +1244,14 @@ class Inbound extends XrayCommonClass {
obj.path = this.stream.quic.key; obj.path = this.stream.quic.key;
} else if (network === 'grpc') { } else if (network === 'grpc') {
obj.path = this.stream.grpc.serviceName; obj.path = this.stream.grpc.serviceName;
obj.authority = this.stream.grpc.authority;
if (this.stream.grpc.multiMode){ if (this.stream.grpc.multiMode){
obj.type = 'multi' obj.type = 'multi'
} }
} else if (network === 'httpupgrade') {
let httpupgrade = this.stream.httpupgrade;
obj.path = httpupgrade.path;
obj.host = httpupgrade.host;
} }
if (security === 'tls') { if (security === 'tls') {
@ -1275,10 +1320,16 @@ class Inbound extends XrayCommonClass {
case "grpc": case "grpc":
const grpc = this.stream.grpc; const grpc = this.stream.grpc;
params.set("serviceName", grpc.serviceName); params.set("serviceName", grpc.serviceName);
params.set("authority", grpc.authority);
if(grpc.multiMode){ if(grpc.multiMode){
params.set("mode", "multi"); params.set("mode", "multi");
} }
break; break;
case "httpupgrade":
const httpupgrade = this.stream.httpupgrade;
params.set("path", httpupgrade.path);
params.set("host", httpupgrade.host);
break;
} }
if (security === 'tls') { if (security === 'tls') {
@ -1389,10 +1440,16 @@ class Inbound extends XrayCommonClass {
case "grpc": case "grpc":
const grpc = this.stream.grpc; const grpc = this.stream.grpc;
params.set("serviceName", grpc.serviceName); params.set("serviceName", grpc.serviceName);
params.set("authority", grpc.authority);
if(grpc.multiMode){ if(grpc.multiMode){
params.set("mode", "multi"); params.set("mode", "multi");
} }
break; break;
case "httpupgrade":
const httpupgrade = this.stream.httpupgrade;
params.set("path", httpupgrade.path);
params.set("host", httpupgrade.host);
break;
} }
if (security === 'tls') { if (security === 'tls') {
@ -1470,10 +1527,16 @@ class Inbound extends XrayCommonClass {
case "grpc": case "grpc":
const grpc = this.stream.grpc; const grpc = this.stream.grpc;
params.set("serviceName", grpc.serviceName); params.set("serviceName", grpc.serviceName);
params.set("authority", grpc.authority);
if(grpc.multiMode){ if(grpc.multiMode){
params.set("mode", "multi"); params.set("mode", "multi");
} }
break; break;
case "httpupgrade":
const httpupgrade = this.stream.httpupgrade;
params.set("path", httpupgrade.path);
params.set("host", httpupgrade.host);
break;
} }
if (security === 'tls') { if (security === 'tls') {

View file

@ -131,11 +131,11 @@ class RandomUtil {
static randomUUID() { static randomUUID() {
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return template.replace(/[xy]/g, function (c) { return template.replace(/[xy]/g, function (c) {
const randomValues = new Uint8Array(1); const randomValues = new Uint8Array(1);
crypto.getRandomValues(randomValues); crypto.getRandomValues(randomValues);
let randomValue = randomValues[0] % 16; let randomValue = randomValues[0] % 16;
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8); let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
return calculatedValue.toString(16); return calculatedValue.toString(16);
}); });
} }

View file

@ -22,91 +22,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
g = g.Group("/panel/api/inbounds") g = g.Group("/panel/api/inbounds")
g.Use(a.checkLogin) g.Use(a.checkLogin)
g.GET("/list", a.getAllInbounds)
g.GET("/get/:id", a.getSingleInbound)
g.GET("/getClientTraffics/:email", a.getClientTraffics)
g.POST("/add", a.addInbound)
g.POST("/del/:id", a.delInbound)
g.POST("/update/:id", a.updateInbound)
g.POST("/clientIps/:email", a.getClientIps)
g.POST("/clearClientIps/:email", a.clearClientIps)
g.POST("/addClient", a.addInboundClient)
g.POST("/:id/delClient/:clientId", a.delInboundClient)
g.POST("/updateClient/:clientId", a.updateInboundClient)
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
g.POST("/resetAllTraffics", a.resetAllTraffics)
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
g.POST("/delDepletedClients/:id", a.delDepletedClients)
g.GET("/createbackup", a.createBackup)
g.POST("/onlines", a.onlines)
a.inboundController = NewInboundController(g) a.inboundController = NewInboundController(g)
}
func (a *APIController) getAllInbounds(c *gin.Context) { inboundRoutes := []struct {
a.inboundController.getInbounds(c) Method string
} Path string
Handler gin.HandlerFunc
}{
{"GET", "/createbackup", a.createBackup},
{"GET", "/list", a.inboundController.getInbounds},
{"GET", "/get/:id", a.inboundController.getInbound},
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
{"POST", "/add", a.inboundController.addInbound},
{"POST", "/del/:id", a.inboundController.delInbound},
{"POST", "/update/:id", a.inboundController.updateInbound},
{"POST", "/clientIps/:email", a.inboundController.getClientIps},
{"POST", "/clearClientIps/:email", a.inboundController.clearClientIps},
{"POST", "/addClient", a.inboundController.addInboundClient},
{"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient},
{"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient},
{"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic},
{"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics},
{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics},
{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients},
{"POST", "/onlines", a.inboundController.onlines},
}
func (a *APIController) getSingleInbound(c *gin.Context) { for _, route := range inboundRoutes {
a.inboundController.getInbound(c) g.Handle(route.Method, route.Path, route.Handler)
} }
func (a *APIController) getClientTraffics(c *gin.Context) {
a.inboundController.getClientTraffics(c)
}
func (a *APIController) addInbound(c *gin.Context) {
a.inboundController.addInbound(c)
}
func (a *APIController) delInbound(c *gin.Context) {
a.inboundController.delInbound(c)
}
func (a *APIController) updateInbound(c *gin.Context) {
a.inboundController.updateInbound(c)
}
func (a *APIController) getClientIps(c *gin.Context) {
a.inboundController.getClientIps(c)
}
func (a *APIController) clearClientIps(c *gin.Context) {
a.inboundController.clearClientIps(c)
}
func (a *APIController) addInboundClient(c *gin.Context) {
a.inboundController.addInboundClient(c)
}
func (a *APIController) delInboundClient(c *gin.Context) {
a.inboundController.delInboundClient(c)
}
func (a *APIController) updateInboundClient(c *gin.Context) {
a.inboundController.updateInboundClient(c)
}
func (a *APIController) resetClientTraffic(c *gin.Context) {
a.inboundController.resetClientTraffic(c)
}
func (a *APIController) resetAllTraffics(c *gin.Context) {
a.inboundController.resetAllTraffics(c)
}
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
a.inboundController.resetAllClientTraffics(c)
}
func (a *APIController) delDepletedClients(c *gin.Context) {
a.inboundController.delDepletedClients(c)
} }
func (a *APIController) createBackup(c *gin.Context) { func (a *APIController) createBackup(c *gin.Context) {
a.Tgbot.SendBackupToAdmins() a.Tgbot.SendBackupToAdmins()
} }
func (a *APIController) onlines(c *gin.Context) {
a.inboundController.onlines(c)
}

View file

@ -2,6 +2,7 @@ package controller
import ( import (
"net/http" "net/http"
"x-ui/logger" "x-ui/logger"
"x-ui/web/locale" "x-ui/web/locale"
"x-ui/web/session" "x-ui/web/session"
@ -9,13 +10,12 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
type BaseController struct { type BaseController struct{}
}
func (a *BaseController) checkLogin(c *gin.Context) { func (a *BaseController) checkLogin(c *gin.Context) {
if !session.IsLogin(c) { if !session.IsLogin(c) {
if isAjax(c) { if isAjax(c) {
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain")) pureJsonMsg(c, http.StatusUnauthorized, false, I18nWeb(c, "pages.login.loginAgain"))
} else { } else {
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path")) c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
} }

View file

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"x-ui/database/model" "x-ui/database/model"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"
@ -174,7 +175,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
return return
} }
jsonMsg(c, "Client(s) added", nil) jsonMsg(c, "Client(s) added", nil)
if err == nil && needRestart { if needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@ -195,7 +196,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
return return
} }
jsonMsg(c, "Client deleted", nil) jsonMsg(c, "Client deleted", nil)
if err == nil && needRestart { if needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@ -218,7 +219,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
return return
} }
jsonMsg(c, "Client updated", nil) jsonMsg(c, "Client updated", nil)
if err == nil && needRestart { if needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }
@ -239,7 +240,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
return return
} }
jsonMsg(c, "traffic reseted", nil) jsonMsg(c, "traffic reseted", nil)
if err == nil && needRestart { if needRestart {
a.xrayService.SetToNeedRestart() a.xrayService.SetToNeedRestart()
} }
} }

View file

@ -3,6 +3,7 @@ package controller
import ( import (
"net/http" "net/http"
"time" "time"
"x-ui/logger" "x-ui/logger"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"
@ -49,15 +50,15 @@ func (a *IndexController) login(c *gin.Context) {
var form LoginForm var form LoginForm
err := c.ShouldBind(&form) err := c.ShouldBind(&form)
if err != nil { if err != nil {
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
return return
} }
if form.Username == "" { if form.Username == "" {
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
return return
} }
if form.Password == "" { if form.Password == "" {
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
return return
} }
@ -66,7 +67,7 @@ func (a *IndexController) login(c *gin.Context) {
if user == nil { if user == nil {
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password) logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0) a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword")) pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
return return
} else { } else {
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c)) logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))

View file

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"regexp" "regexp"
"time" "time"
"x-ui/web/global" "x-ui/web/global"
"x-ui/web/service" "x-ui/web/service"

View file

@ -3,6 +3,7 @@ package controller
import ( import (
"errors" "errors"
"time" "time"
"x-ui/web/entity" "x-ui/web/entity"
"x-ui/web/service" "x-ui/web/service"
"x-ui/web/session" "x-ui/web/session"

View file

@ -4,6 +4,7 @@ import (
"net" "net"
"net/http" "net/http"
"strings" "strings"
"x-ui/config" "x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/web/entity" "x-ui/web/entity"
@ -48,18 +49,11 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
c.JSON(http.StatusOK, m) c.JSON(http.StatusOK, m)
} }
func pureJsonMsg(c *gin.Context, success bool, msg string) { func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) {
if success { c.JSON(statusCode, entity.Msg{
c.JSON(http.StatusOK, entity.Msg{ Success: success,
Success: true, Msg: msg,
Msg: msg, })
})
} else {
c.JSON(http.StatusOK, entity.Msg{
Success: false,
Msg: msg,
})
}
} }
func html(c *gin.Context, name string, title string, data gin.H) { func html(c *gin.Context, name string, title string, data gin.H) {

View file

@ -5,6 +5,7 @@ import (
"net" "net"
"strings" "strings"
"time" "time"
"x-ui/util/common" "x-ui/util/common"
) )

View file

@ -7,8 +7,10 @@ import (
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
) )
var webServer WebServer var (
var subServer SubServer webServer WebServer
subServer SubServer
)
type WebServer interface { type WebServer interface {
GetCron() *cron.Cron GetCron() *cron.Cron

View file

@ -1,9 +1,10 @@
{{define "qrcodeModal"}} {{define "qrcodeModal"}}
<a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title" <a-modal id="qrcode-modal" v-model="qrModal.visible" :title="qrModal.title"
:closable="true" :dialog-style="{ top: '20px' }"
:class="themeSwitcher.currentTheme" :closable="true"
:footer="null" :class="themeSwitcher.currentTheme"
width="300px"> :footer="null"
width="300px">
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;"> <a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
{{ i18n "pages.inbounds.clickOnQRcode" }} {{ i18n "pages.inbounds.clickOnQRcode" }}
</a-tag> </a-tag>

View file

@ -374,6 +374,12 @@
transform: translateZ(-100px); transform: translateZ(-100px);
} }
} }
.ant-menu-item .anticon {
margin-right: 4px;
}
.ant-menu-inline .ant-menu-item {
padding: 0 16px !important;
}
</style> </style>
<body> <body>
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme"> <a-layout id="app" v-cloak :class="themeSwitcher.currentTheme">
@ -410,19 +416,19 @@
<a-col span="24"> <a-col span="24">
<a-form> <a-form>
<a-form-item> <a-form-item>
<a-input v-model.trim="user.username" placeholder='{{ i18n "username" }}' <a-input autocomplete="username" v-model.trim="user.username" placeholder='{{ i18n "username" }}'
@keydown.enter.native="login" autofocus> @keydown.enter.native="login" autofocus>
<a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon> <a-icon slot="prefix" type="user" style="font-size: 16px;"></a-icon>
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>
<password-input icon="lock" v-model.trim="user.password" <password-input autocomplete="current-password" icon="lock" v-model.trim="user.password"
placeholder='{{ i18n "password" }}' placeholder='{{ i18n "password" }}'
@keydown.enter.native="login"> @keydown.enter.native="login">
</password-input> </password-input>
</a-form-item> </a-form-item>
<a-form-item v-if="secretEnable"> <a-form-item v-if="secretEnable">
<password-input icon="key" v-model.trim="user.loginSecret" <password-input autocomplete="secret" icon="key" v-model.trim="user.loginSecret"
placeholder='{{ i18n "secretToken" }}' placeholder='{{ i18n "secretToken" }}'
@keydown.enter.native="login"> @keydown.enter.native="login">
</password-input> </password-input>
@ -455,17 +461,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">
<a-col> <theme-switch></theme-switch>
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>&nbsp;
</a-col>
<a-col>
<theme-switch></theme-switch>
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
:checked="themeSwitcher.isUltra"
@click="themeSwitcher.toggleUltra()">
Ultra
</a-checkbox>
</a-col>
</a-row> </a-row>
</a-form-item> </a-form-item>
</a-form> </a-form>

View file

@ -24,18 +24,7 @@
{{define "commonSider"}} {{define "commonSider"}}
<a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0"> <a-layout-sider :theme="themeSwitcher.currentTheme" id="sider" collapsible breakpoint="md" collapsed-width="0">
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> <theme-switch></theme-switch>
<a-menu-item mode="inline">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<theme-switch>
</theme-switch>
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
:checked="themeSwitcher.isUltra"
@click="themeSwitcher.toggleUltra()">
Ultra
</a-checkbox>
</a-menu-item>
</a-menu>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}
@ -49,18 +38,7 @@
<div class="drawer-handle" @click="siderDrawer.change()" slot="handle"> <div class="drawer-handle" @click="siderDrawer.change()" slot="handle">
<a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon> <a-icon :type="siderDrawer.visible ? 'close' : 'menu-fold'"></a-icon>
</div> </div>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys=""> <theme-switch></theme-switch>
<a-menu-item mode="inline">
<a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<theme-switch>
</theme-switch>
<a-checkbox v-if="themeSwitcher.isDarkTheme" style="padding-left: 1rem; vertical-align: middle;"
:checked="themeSwitcher.isUltra"
@click="themeSwitcher.toggleUltra()">
Ultra
</a-checkbox>
</a-menu-item>
</a-menu>
<a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']" <a-menu :theme="themeSwitcher.currentTheme" mode="inline" :selected-keys="['{{ .request_uri }}']"
@click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key"> @click="({key}) => key.startsWith('http') ? window.open(key) : location.href = key">
{{template "menuItems" .}} {{template "menuItems" .}}

View file

@ -16,7 +16,7 @@
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input> <a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
</template> </template>
<template v-else-if="type === 'number'"> <template v-else-if="type === 'number'">
<a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" style="width: 100%;"></a-input-number> <a-input-number :value="value" :step="step" @change="value => $emit('input', value)" :min="min" :max="max" style="width: 100%;"></a-input-number>
</template> </template>
<template v-else-if="type === 'switch'"> <template v-else-if="type === 'switch'">
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch> <a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
@ -29,7 +29,7 @@
{{define "component/setting"}} {{define "component/setting"}}
<script> <script>
Vue.component('setting-list-item', { Vue.component('setting-list-item', {
props: ["type", "title", "desc", "value", "min", "step", "placeholder"], props: ["type", "title", "desc", "value", "min", "max" , "step", "placeholder"],
template: `{{template "component/settingListItem"}}`, template: `{{template "component/settingListItem"}}`,
}); });
</script> </script>

View file

@ -1,8 +1,14 @@
{{define "component/themeSwitchTemplate"}} {{define "component/themeSwitchTemplate"}}
<template> <template>
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" <a-menu :theme="themeSwitcher.currentTheme" mode="inline" selected-keys="">
@change="themeSwitcher.toggleTheme()"> <a-menu-item mode="inline" class="ant-menu-theme-switch">
</a-switch> <a-icon type="bulb" :theme="themeSwitcher.isDarkTheme ? 'filled' : 'outlined'"></a-icon>
<a-switch size="small" :default-checked="themeSwitcher.isDarkTheme" @change="themeSwitcher.toggleTheme()"></a-switch>
<template v-if="themeSwitcher.isDarkTheme">
<a-checkbox style="margin-left: 1rem; vertical-align: middle;" :checked="themeSwitcher.isUltra" @click="themeSwitcher.toggleUltra()">Ultra</a-checkbox>
</template>
</a-menu-item>
</a-menu>
</template> </template>
{{end}} {{end}}

View file

@ -28,7 +28,7 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.inbounds.port" }}'> <a-form-item label='{{ i18n "pages.inbounds.port" }}'>
<a-input-number v-model.number="inbound.port"></a-input-number> <a-input-number v-model.number="inbound.port" :min="1" :max="65531"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item> <a-form-item>

View file

@ -214,34 +214,34 @@
<!-- stream settings --> <!-- stream settings -->
<template v-if="outbound.canEnableStream()"> <template v-if="outbound.canEnableStream()">
<a-form-item label='{{ i18n "transmission" }}'> <a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="outbound.stream.network" @change="streamNetworkChange" <a-select v-model="outbound.stream.network" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option> <a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WS</a-select-option> <a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">H2</a-select-option> <a-select-option value="http">H2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
</a-select> <a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
</a-form-item> </a-select>
</a-form-item>
<template v-if="outbound.stream.network === 'tcp'"> <template v-if="outbound.stream.network === 'tcp'">
<a-form-item label='HTTP {{ i18n "camouflage" }}'> <a-form-item label='HTTP {{ i18n "camouflage" }}'>
<a-switch <a-switch :checked="outbound.stream.tcp.type === 'http'"
:checked="outbound.stream.tcp.type === 'http'"
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'"> @change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
</a-switch> </a-switch>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.tcp.type == 'http'"> <template v-if="outbound.stream.tcp.type == 'http'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="outbound.stream.tcp.host"></a-input> <a-input v-model.trim="outbound.stream.tcp.host"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="outbound.stream.tcp.path"></a-input> <a-input v-model.trim="outbound.stream.tcp.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- kcp --> <!-- kcp -->
<template v-if="outbound.stream.network === 'kcp'"> <template v-if="outbound.stream.network === 'kcp'">
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
@ -265,7 +265,7 @@
</a-form-item> </a-form-item>
<a-form-item label='Uplink (MB/s)'> <a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Downlink (MB/s)'> <a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
</a-form-item> </a-form-item>
@ -279,7 +279,7 @@
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number> <a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
</a-form-item> </a-form-item>
</template> </template>
<!-- ws --> <!-- ws -->
<template v-if="outbound.stream.network === 'ws'"> <template v-if="outbound.stream.network === 'ws'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
@ -287,9 +287,9 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "path" }}'> <a-form-item label='{{ i18n "path" }}'>
<a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input> <a-form-item><a-input v-model.trim="outbound.stream.ws.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- http --> <!-- http -->
<template v-if="outbound.stream.network === 'http'"> <template v-if="outbound.stream.network === 'http'">
<a-form-item label='{{ i18n "host" }}'> <a-form-item label='{{ i18n "host" }}'>
@ -299,7 +299,7 @@
<a-input v-model.trim="outbound.stream.http.path"></a-input> <a-input v-model.trim="outbound.stream.http.path"></a-input>
</a-form-item> </a-form-item>
</template> </template>
<!-- quic --> <!-- quic -->
<template v-if="outbound.stream.network === 'quic'"> <template v-if="outbound.stream.network === 'quic'">
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'> <a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
@ -311,7 +311,7 @@
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "password" }}'> <a-form-item label='{{ i18n "password" }}'>
<a-input v-model.trim="outbound.stream.quic.key"></a-input> <a-input v-model.trim="outbound.stream.quic.key"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "camouflage" }}'> <a-form-item label='{{ i18n "camouflage" }}'>
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme"> <a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="none">None</a-select-option> <a-select-option value="none">None</a-select-option>
@ -323,7 +323,7 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
</template> </template>
<!-- grpc --> <!-- grpc -->
<template v-if="outbound.stream.network === 'grpc'"> <template v-if="outbound.stream.network === 'grpc'">
<a-form-item label='Service Name'> <a-form-item label='Service Name'>
@ -333,6 +333,16 @@
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch> <a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
</a-form-item> </a-form-item>
</template> </template>
<!-- httpupgrade -->
<template v-if="outbound.stream.network === 'httpupgrade'">
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model="outbound.stream.httpupgrade.host"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-form-item><a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
</a-form-item>
</template>
</template> </template>
<!-- tls settings --> <!-- tls settings -->
@ -341,7 +351,7 @@
<a-radio-group v-model="outbound.stream.security" button-style="solid"> <a-radio-group v-model="outbound.stream.security" button-style="solid">
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button> <a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
<a-radio-button value="tls">TLS</a-radio-button> <a-radio-button value="tls">TLS</a-radio-button>
<a-radio-button v-if="outbound.canEnableReality()" value="reality">REALITY</a-radio-button> <a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
</a-radio-group> </a-radio-group>
</a-form-item> </a-form-item>
<template v-if="outbound.stream.isTls"> <template v-if="outbound.stream.isTls">
@ -389,6 +399,46 @@
</a-form-item> </a-form-item>
</template> </template>
</template> </template>
<!-- sockopt settings -->
<a-form-item label="Sockopts">
<a-switch v-model="outbound.stream.sockoptSwitch"></a-switch>
</a-form-item>
<template v-if="outbound.stream.sockoptSwitch">
<a-form-item label="Dialer Proxy">
<a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="TCP Fast Open">
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
</a-form-item>
<a-form-item label="Keep Alive Interval">
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
</a-form-item>
<a-form-item label="TCP No-Delay">
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
</a-form-item>
</template>
<!-- mux settings -->
<a-form-item label="Mux">
<a-switch v-model="outbound.mux.enabled"></a-switch>
</a-form-item>
<template v-if="outbound.mux.enabled">
<a-form-item label="Concurrency">
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item>
<a-form-item label="xudp Concurrency">
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
</a-form-item>
<a-form-item label="xudp UDP 443">
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option>
</a-select>
</a-form-item>
</template>
</a-form> </a-form>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="2" tab="JSON" force-render="true"> <a-tab-pane key="2" tab="JSON" force-render="true">

View file

@ -3,6 +3,9 @@
<a-form-item label="Service Name"> <a-form-item label="Service Name">
<a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input> <a-input v-model.trim="inbound.stream.grpc.serviceName"></a-input>
</a-form-item> </a-form-item>
<a-form-item label="Authority">
<a-input v-model.trim="inbound.stream.grpc.authority"></a-input>
</a-form-item>
<a-form-item label="Multi Mode"> <a-form-item label="Multi Mode">
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch> <a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
</a-form-item> </a-form-item>

View file

@ -0,0 +1,13 @@
{{define "form/streamHTTPUpgrade"}}
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label="PROXY Protocol">
<a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch>
</a-form-item>
<a-form-item label='{{ i18n "path" }}'>
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "host" }}'>
<a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
</a-form-item>
</a-form>
{{end}}

View file

@ -23,25 +23,25 @@
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input> <a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='MTU'> <a-form-item label='MTU'>
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576" :max="1460"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='TTI (ms)'> <a-form-item label='TTI (ms)'>
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.tti" :min="10" :max="100"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Uplink (MB/s)'> <a-form-item label='Uplink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.upCap" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Downlink (MB/s)'> <a-form-item label='Downlink (MB/s)'>
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.downCap" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Congestion'> <a-form-item label='Congestion'>
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch> <a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
</a-form-item> </a-form-item>
<a-form-item label='Read Buffer (MB)'> <a-form-item label='Read Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.readBuffer" :min="0"></a-input-number>
</a-form-item> </a-form-item>
<a-form-item label='Write Buffer (MB)'> <a-form-item label='Write Buffer (MB)'>
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number> <a-input-number v-model.number="inbound.stream.kcp.writeBuffer" :min="0"></a-input-number>
</a-form-item> </a-form-item>
</a-form> </a-form>
{{end}} {{end}}

View file

@ -2,14 +2,15 @@
<!-- select stream network --> <!-- select stream network -->
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }"> <a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "transmission" }}'> <a-form-item label='{{ i18n "transmission" }}'>
<a-select v-model="inbound.stream.network" style="width: 50%" @change="streamNetworkChange" <a-select v-model="inbound.stream.network" style="width: 75%" @change="streamNetworkChange"
:dropdown-class-name="themeSwitcher.currentTheme"> :dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option value="tcp">TCP</a-select-option> <a-select-option value="tcp">TCP</a-select-option>
<a-select-option value="kcp">mKCP</a-select-option> <a-select-option value="kcp">mKCP</a-select-option>
<a-select-option value="ws">WS</a-select-option> <a-select-option value="ws">WebSocket</a-select-option>
<a-select-option value="http">H2</a-select-option> <a-select-option value="http">H2</a-select-option>
<a-select-option value="quic">QUIC</a-select-option> <a-select-option value="quic">QUIC</a-select-option>
<a-select-option value="grpc">gRPC</a-select-option> <a-select-option value="grpc">gRPC</a-select-option>
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -43,6 +44,12 @@
<template v-if="inbound.stream.network === 'grpc'"> <template v-if="inbound.stream.network === 'grpc'">
{{template "form/streamGRPC"}} {{template "form/streamGRPC"}}
</template> </template>
<!-- httpupgrade -->
<template v-if="inbound.stream.network === 'httpupgrade'">
{{template "form/streamHTTPUpgrade"}}
</template>
<!-- sockopt --> <!-- sockopt -->
<template> <template>
{{template "form/streamSockopt"}} {{template "form/streamSockopt"}}

View file

@ -25,7 +25,7 @@
<tr> <tr>
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td> <td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
</tr> </tr>
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2"> <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
<tr> <tr>
<td>{{ i18n "host" }}</td> <td>{{ i18n "host" }}</td>
<td v-if="inbound.host"> <td v-if="inbound.host">

View file

@ -1,8 +1,9 @@
{{define "inboundModal"}} {{define "inboundModal"}}
<a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title" @ok="inModal.ok" <a-modal id="inbound-modal" v-model="inModal.visible" :title="inModal.title"
:confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false" :dialog-style="{ top: '20px' }" @ok="inModal.ok"
:class="themeSwitcher.currentTheme" :confirm-loading="inModal.confirmLoading" :closable="true" :mask-closable="false"
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'> :class="themeSwitcher.currentTheme"
:ok-text="inModal.okText" cancel-text='{{ i18n "close" }}'>
{{template "form/inbound"}} {{template "form/inbound"}}
</a-modal> </a-modal>
<script> <script>

View file

@ -537,7 +537,7 @@
{ title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } }, { title: '{{ i18n "online" }}', width: 30, scopedSlots: { customRender: 'online' } },
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } }, { title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } }, { title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } }, { title: '{{ i18n "pages.inbounds.expireDate" }}', width: 100, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
]; ];
const innerMobileColumns = [ const innerMobileColumns = [

View file

@ -259,17 +259,13 @@
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' <a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}' :closable="true"
:closable="true" @ok="() => versionModal.visible = false" @ok="() => versionModal.visible = false" :class="themeSwitcher.currentTheme" footer="">
:class="themeSwitcher.currentTheme"
footer="">
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content" <a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
message='{{ i18n "pages.index.xraySwitchClickDesk" }}' message='{{ i18n "pages.index.xraySwitchClickDesk" }}' show-icon></a-alert>
show-icon
></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-right: 12px; margin-bottom: 12px"
style="margin-right: 10px" @click="switchV2rayVersion(version)"> @click="switchV2rayVersion(version)">
[[ version ]] [[ version ]]
</a-tag> </a-tag>
</template> </template>

View file

@ -138,7 +138,7 @@
</a-list-item> </a-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningIP"}}' desc='{{ i18n "pages.settings.panelListeningIPDesc"}}' v-model="allSetting.webListen"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.panelListeningDomain"}}' desc='{{ i18n "pages.settings.panelListeningDomainDesc"}}' v-model="allSetting.webDomain"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="0"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.settings.panelPort"}}' desc='{{ i18n "pages.settings.panelPortDesc"}}' v-model="allSetting.webPort" :min="1" :max="65531"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.publicKeyPath"}}' desc='{{ i18n "pages.settings.publicKeyPathDesc"}}' v-model="allSetting.webCertFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.privateKeyPath"}}' desc='{{ i18n "pages.settings.privateKeyPathDesc"}}' v-model="allSetting.webKeyFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.panelUrlPath"}}' desc='{{ i18n "pages.settings.panelUrlPathDesc"}}' v-model="allSetting.webBasePath"></setting-list-item>
@ -197,16 +197,16 @@
<a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider> <a-divider>{{ i18n "pages.settings.security.admin"}}</a-divider>
<a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }"> <a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'> <a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
<a-input v-model="user.oldUsername"></a-input> <a-input autocomplete="username" v-model="user.oldUsername"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'> <a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
<password-input v-model="user.oldPassword"></password-input> <password-input autocomplete="current-password" v-model="user.oldPassword"></password-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'> <a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
<a-input v-model="user.newUsername"></a-input> <a-input v-model="user.newUsername"></a-input>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'> <a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
<password-input v-model="user.newPassword"></password-input> <password-input autocomplete="new-password" v-model="user.newPassword"></password-input>
</a-form-item> </a-form-item>
<a-form-item label=" "> <a-form-item label=" ">
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button> <a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
@ -282,12 +282,12 @@
<setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.settings.subShowInfo"}}' desc='{{ i18n "pages.settings.subShowInfoDesc"}}' v-model="allSetting.subShowInfo"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subListen"}}' desc='{{ i18n "pages.settings.subListenDesc"}}' v-model="allSetting.subListen"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subDomain"}}' desc='{{ i18n "pages.settings.subDomainDesc"}}' v-model="allSetting.subDomain"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.settings.subPort"}}' desc='{{ i18n "pages.settings.subPortDesc"}}' v-model.number="allSetting.subPort" :min="1" :max="65531"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subPath"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subCertPath"}}' desc='{{ i18n "pages.settings.subCertPathDesc"}}' v-model="allSetting.subCertFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subKeyPath"}}' desc='{{ i18n "pages.settings.subKeyPathDesc"}}' v-model="allSetting.subKeyFile"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item> <setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates" :min="1"></setting-list-item>
</a-list> </a-list>
</a-tab-pane> </a-tab-pane>
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable"> <a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
@ -295,11 +295,30 @@
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item> <setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
<template v-if="fragment">
<setting-list-item type="text" title='length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
</template>
</a-list> </a-list>
<a-collapse v-if="fragment">
<a-collapse-panel header='{{ i18n "pages.settings.fragment"}}'>
<a-list-item style="padding: 20px">
<a-row>
<a-col :lg="24" :xl="12">
<a-list-item-meta title='Packets'/>
</a-col>
<a-col :lg="24" :xl="12">
<a-select
v-model="fragmentPackets"
style="width: 100%"
:dropdown-class-name="themeSwitcher.currentTheme">
<a-select-option :value="p" :label="p" v-for="p in ['1-1', '1-3', 'tlshello']">
[[ p ]]
</a-select-option>
</a-select>
</a-col>
</a-row>
</a-list-item>
<setting-list-item type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
</a-collapse-panel>
</a-collapse>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-space> </a-space>
@ -483,6 +502,16 @@
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : ""; this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
} }
}, },
fragmentPackets: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
set: function(v) {
if (v != ""){
newFragment = JSON.parse(this.allSetting.subJsonFragment);
newFragment.settings.fragment.packets = v;
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
}
}
},
fragmentLength: { fragmentLength: {
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; }, get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
set: function(v) { set: function(v) {
@ -531,4 +560,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

View file

@ -180,7 +180,7 @@
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%"> <a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
<a-select-option v-for="s in access" :value="s">[[ s ]]</a-select-option> <a-select-option v-for="s in access" :key="s" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</template> </template>
</a-col> </a-col>
@ -193,7 +193,7 @@
<a-col :lg="24" :xl="12"> <a-col :lg="24" :xl="12">
<template> <template>
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%"> <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-option v-for="s in error" :key="s" :value="s">[[ s ]]</a-select-option>
</a-select> </a-select>
</template> </template>
</a-col> </a-col>
@ -537,6 +537,8 @@
<template slot="strategy" slot-scope="text, balancer, index"> <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=='random'" color="purple">Random</a-tag>
<a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag> <a-tag style="margin:0;" v-if="balancer.strategy=='roundRobin'" color="green">Round Robin</a-tag>
<a-tag style="margin:0;" v-if="balancer.strategy=='leastload'" color="green">Least Load</a-tag>
<a-tag style="margin:0;" v-if="balancer.strategy=='leastping'" color="green">Least Ping</a-tag>
</template> </template>
<template slot="selector" slot-scope="text, balancer, index"> <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> <a-tag class="info-large-tag" style="margin:1;" v-for="sel in balancer.selector">[[ sel ]]</a-tag>
@ -546,6 +548,7 @@
<a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true"> <a-tab-pane key="tpl-6" tab='DNS' style="padding-top: 20px;" force-render="true">
<setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item> <setting-list-item type="switch" title='{{ i18n "pages.xray.dns.enable" }}' desc='{{ i18n "pages.xray.dns.enableDesc" }}' v-model="enableDNS"></setting-list-item>
<template v-if="enableDNS"> <template v-if="enableDNS">
<setting-list-item type="text" title='{{ i18n "pages.xray.dns.tag" }}' desc='{{ i18n "pages.xray.dns.tagDesc" }}' v-model="dnsTag"></setting-list-item>
<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">
@ -765,8 +768,8 @@
}, },
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: [],
error: ["none" , "./error.log" ], error: [],
settingsData: { settingsData: {
protocols: { protocols: {
bittorrent: ["bittorrent"], bittorrent: ["bittorrent"],
@ -869,10 +872,10 @@
}, },
async getXrayResult() { async getXrayResult() {
const msg = await HttpUtil.get("/panel/xray/getXrayResult"); const msg = await HttpUtil.get("/panel/xray/getXrayResult");
if(msg.success){ if (msg.success) {
this.restartResult=msg.obj; this.restartResult=msg.obj;
if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj); if(msg.obj.length > 1) Vue.prototype.$message.error(msg.obj);
} }
}, },
async fetchUserSecret() { async fetchUserSecret() {
this.loading(true); this.loading(true);
@ -910,9 +913,9 @@
}, },
async toggleToken(value) { async toggleToken(value) {
if (value) { if (value) {
await this.getNewSecret(); await this.getNewSecret();
} else { } else {
this.user.loginSecret = ""; this.user.loginSecret = "";
} }
}, },
async resetXrayConfigToDefault() { async resetXrayConfigToDefault() {
@ -1001,7 +1004,7 @@
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions); this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
this.cm.on('change',editor => { this.cm.on('change',editor => {
value = editor.getValue(); value = editor.getValue();
if(this.isJsonString(value)){ if (this.isJsonString(value)) {
this[this.advSettings] = value; this[this.advSettings] = value;
} }
}); });
@ -1130,7 +1133,7 @@
'tag': balancer.tag, 'tag': balancer.tag,
'selector': balancer.selector 'selector': balancer.selector
}; };
if (balancer.strategy == 'roundRobin') { if (balancer.strategy === 'roundRobin' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
tmpBalancer.strategy = { tmpBalancer.strategy = {
'type': balancer.strategy 'type': balancer.strategy
}; };
@ -1157,7 +1160,7 @@
'tag': balancer.tag, 'tag': balancer.tag,
'selector': balancer.selector 'selector': balancer.selector
}; };
if (balancer.strategy == 'roundRobin') { if (balancer.strategy === 'roundRobin' || balancer.strategy === 'leastload' || balancer.strategy === 'leastping') {
tmpBalancer.strategy = { tmpBalancer.strategy = {
'type': balancer.strategy 'type': balancer.strategy
}; };
@ -1181,22 +1184,21 @@
deleteBalancer(index) { deleteBalancer(index) {
newTemplateSettings = this.templateSettings; newTemplateSettings = this.templateSettings;
//remove from balancers // Remove from balancers
const oldTag = this.balancersData[index].tag; const removedBalancer = this.balancersData.splice(index, 1)[0];
this.balancersData.splice(index, 1);
// remove from settings // Remove from settings
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag == oldTag); let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
newTemplateSettings.routing.balancers.splice(realIndex, 1); newTemplateSettings.routing.balancers.splice(realIndex, 1);
// remove related routing rules // Remove related routing rules
let rules = []; let rules = newTemplateSettings.routing.rules.filter((r) => !r.balancerTag || r.balancerTag !== removedBalancer.tag);
newTemplateSettings.routing.rules.forEach((r) => {
if (!r.balancerTag || r.balancerTag != oldTag) {
rules.push(r);
}
});
newTemplateSettings.routing.rules = rules; newTemplateSettings.routing.rules = rules;
// Update balancers property to an empty array if there are no more balancers
if (newTemplateSettings.routing.balancers.length === 0) {
delete newTemplateSettings.routing.balancers;
}
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
}, },
addReverse(){ addReverse(){
@ -1403,8 +1405,19 @@
}, },
computed: { computed: {
templateSettings: { templateSettings: {
get: function () { return this.xraySetting ? JSON.parse(this.xraySetting) : null; }, get: function () {
set: function (newValue) { this.xraySetting = JSON.stringify(newValue, null, 2); }, const parsedSettings = this.xraySetting ? JSON.parse(this.xraySetting) : null;
const setLogPath = (type, defaultValue) => parsedSettings?.log?.[type] !== "none" ? parsedSettings.log[type] : defaultValue;
this.access = ["none", setLogPath("access", "./access.log")];
this.error = ["none", setLogPath("error", "./error.log")];
return parsedSettings;
},
set: function (newValue) {
this.xraySetting = JSON.stringify(newValue, null, 2);
const setLogPath = (type, defaultValue) => newValue.log?.[type] ? newValue.log[type] : defaultValue;
this.access = ["none", setLogPath("access", "./access.log")];
this.error = ["none", setLogPath("error", "./error.log")];
},
}, },
inboundSettings: { inboundSettings: {
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; }, get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
@ -1457,11 +1470,11 @@
if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) { if (this.templateSettings != null && this.templateSettings.routing != null && this.templateSettings.routing.balancers != null) {
this.templateSettings.routing.balancers.forEach((o, index) => { this.templateSettings.routing.balancers.forEach((o, index) => {
let strategy = "random" let strategy = "random"
if (o.strategy && o.strategy.type == "roundRobin") { if (o.strategy && (o.strategy.type == "roundRobin" || o.strategy.type == "leastload" || o.strategy.type == "leastping")) {
strategy = o.strategy.type strategy = o.strategy.type;
} }
data.push({ data.push({
'key': index, 'key': index,
'tag': o.tag ? o.tag : "", 'tag': o.tag ? o.tag : "",
'strategy': strategy, 'strategy': strategy,
@ -2011,7 +2024,23 @@
}, },
set: function (newValue) { set: function (newValue) {
newTemplateSettings = this.templateSettings; newTemplateSettings = this.templateSettings;
newTemplateSettings.dns = newValue ? { servers: [], queryStrategy: "UseIP" } : null; if (newValue) {
newTemplateSettings.dns = { servers: [], queryStrategy: "UseIP", tag: "dns_inbound" };
newTemplateSettings.fakedns = null;
} else {
delete newTemplateSettings.dns;
delete newTemplateSettings.fakedns;
}
this.templateSettings = newTemplateSettings;
}
},
dnsTag: {
get: function () {
return this.enableDNS ? this.templateSettings.dns.tag : "";
},
set: function (newValue) {
newTemplateSettings = this.templateSettings;
newTemplateSettings.dns.tag = newValue;
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
} }
}, },
@ -2037,7 +2066,11 @@
get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; }, get: function () { return this.templateSettings && this.templateSettings.fakedns ? this.templateSettings.fakedns : []; },
set: function (newValue) { set: function (newValue) {
newTemplateSettings = this.templateSettings; newTemplateSettings = this.templateSettings;
newTemplateSettings.fakedns = newValue.length >0 ? newValue : null; if (this.enableDNS) {
newTemplateSettings.fakedns = newValue.length > 0 ? newValue : null;
} else {
delete newTemplateSettings.fakedns;
}
this.templateSettings = newTemplateSettings; this.templateSettings = newTemplateSettings;
} }
} }

View file

@ -21,6 +21,8 @@
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme"> <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="random">Random</a-select-option>
<a-select-option value="roundRobin">Round Robin</a-select-option> <a-select-option value="roundRobin">Round Robin</a-select-option>
<a-select-option value="leastload">Least Load</a-select-option>
<a-select-option value="leastping">Least Ping</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback <a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback

View file

@ -38,7 +38,6 @@
:options="reverseModal.inboundTags"></a-checkbox-group> :options="reverseModal.inboundTags"></a-checkbox-group>
</a-form-item> </a-form-item>
</template> </template>
</table>
</a-form> </a-form>
</a-modal> </a-modal>
<script> <script>
@ -114,6 +113,7 @@
this.isEdit = isEdit; this.isEdit = isEdit;
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag); this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
this.inboundTags.push(...app.inboundTags); this.inboundTags.push(...app.inboundTags);
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag); this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
}, },
close() { close() {

View file

@ -195,6 +195,7 @@
this.isEdit = isEdit; this.isEdit = isEdit;
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag); this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
this.inboundTags.push(...app.inboundTags); this.inboundTags.push(...app.inboundTags);
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)]; this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
if(app.templateSettings.reverse){ if(app.templateSettings.reverse){
if(app.templateSettings.reverse.bridges) { if(app.templateSettings.reverse.bridges) {

View file

@ -35,35 +35,27 @@ func (j *CheckClientIpJob) Run() {
j.lastClear = time.Now().Unix() j.lastClear = time.Now().Unix()
} }
shouldClearAccessLog := false
f2bInstalled := j.checkFail2BanInstalled() f2bInstalled := j.checkFail2BanInstalled()
accessLogPath := xray.GetAccessLogPath() isAccessLogAvailable := j.checkAccessLogAvailable(f2bInstalled)
clearAccessLog := false
if j.hasLimitIp() { if j.hasLimitIp() {
if f2bInstalled && accessLogPath == "./access.log" { if f2bInstalled && isAccessLogAvailable {
clearAccessLog = j.processLogFile() shouldClearAccessLog = j.processLogFile()
} else { } else {
if !f2bInstalled { if !f2bInstalled {
logger.Warning("fail2ban is not installed. IP limiting may not work properly.") logger.Warning("fail2ban is not installed. IP limiting may not work properly.")
} }
switch accessLogPath {
case "none":
logger.Warning("Access log is set to 'none', check your Xray Configs")
case "":
logger.Warning("Access log doesn't exist in your Xray Configs")
default:
logger.Warning("Current access.log path is not compatible with IP Limit")
}
} }
} }
if clearAccessLog || accessLogPath == "./access.log" && time.Now().Unix() - j.lastClear > 3600 { if shouldClearAccessLog || isAccessLogAvailable && time.Now().Unix()-j.lastClear > 3600 {
j.clearAccessLog() j.clearAccessLog()
} }
} }
func (j *CheckClientIpJob) clearAccessLog() { func (j *CheckClientIpJob) clearAccessLog() {
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
j.checkError(err) j.checkError(err)
// reopen the access log file for reading // reopen the access log file for reading
@ -178,6 +170,25 @@ func (j *CheckClientIpJob) processLogFile() bool {
return shouldCleanLog return shouldCleanLog
} }
func (j *CheckClientIpJob) checkAccessLogAvailable(doWarning bool) bool {
accessLogPath := xray.GetAccessLogPath()
isAvailable := true
warningMsg := ""
// access log is not available if it is set to 'none' or an empty string
switch accessLogPath {
case "none":
warningMsg = "Access log is set to 'none', check your Xray Configs"
isAvailable = false
case "":
warningMsg = "Access log doesn't exist in your Xray Configs"
isAvailable = false
}
if doWarning && warningMsg != "" {
logger.Warning(warningMsg)
}
return isAvailable
}
func (j *CheckClientIpJob) checkError(e error) { func (j *CheckClientIpJob) checkError(e error) {
if e != nil { if e != nil {
logger.Warning("client ip job err:", e) logger.Warning("client ip job err:", e)
@ -253,7 +264,7 @@ func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.Inboun
j.disAllowedIps = []string{} j.disAllowedIps = []string{}
// create iplimit log file channel // create iplimit log file channel
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
if err != nil { if err != nil {
logger.Errorf("failed to create or open ip limit log file: %s", err) logger.Errorf("failed to create or open ip limit log file: %s", err)
} }

View file

@ -3,6 +3,7 @@ package job
import ( import (
"strconv" "strconv"
"time" "time"
"x-ui/web/service" "x-ui/web/service"
"github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/cpu"

View file

@ -20,7 +20,7 @@ func (j *CheckXrayRunningJob) Run() {
j.checkTime = 0 j.checkTime = 0
} else { } else {
j.checkTime++ j.checkTime++
//only restart if it's down 2 times in a row // only restart if it's down 2 times in a row
if j.checkTime > 1 { if j.checkTime > 1 {
err := j.xrayService.RestartXray(false) err := j.xrayService.RestartXray(false)
j.checkTime = 0 j.checkTime = 0

View file

@ -3,6 +3,7 @@ package job
import ( import (
"io" "io"
"os" "os"
"x-ui/logger" "x-ui/logger"
"x-ui/xray" "x-ui/xray"
) )
@ -17,7 +18,7 @@ func NewClearLogsJob() *ClearLogsJob {
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.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++ {
if err := os.Truncate(logFilesPrev[i], 0); err != nil { if err := os.Truncate(logFilesPrev[i], 0); err != nil {
@ -43,7 +44,7 @@ func (j *ClearLogsJob) Run() {
if err != nil { if err != nil {
logger.Warning("clear logs job err:", err) logger.Warning("clear logs job err:", err)
} }
logFile.Close() logFile.Close()
logFilePrev.Close() logFilePrev.Close()
} }

View file

@ -36,5 +36,4 @@ func (j *XrayTrafficJob) Run() {
if needRestart0 || needRestart1 { if needRestart0 || needRestart1 {
j.xrayService.SetToNeedRestart() j.xrayService.SetToNeedRestart()
} }
} }

View file

@ -4,6 +4,7 @@ import (
"embed" "embed"
"io/fs" "io/fs"
"strings" "strings"
"x-ui/logger" "x-ui/logger"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -12,9 +13,11 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
) )
var i18nBundle *i18n.Bundle var (
var LocalizerWeb *i18n.Localizer i18nBundle *i18n.Bundle
var LocalizerBot *i18n.Localizer LocalizerWeb *i18n.Localizer
LocalizerBot *i18n.Localizer
)
type I18nType string type I18nType string
@ -79,7 +82,6 @@ func I18n(i18nType I18nType, key string, params ...string) string {
MessageID: key, MessageID: key,
TemplateData: templateData, TemplateData: templateData,
}) })
if err != nil { if err != nil {
logger.Errorf("Failed to localize message: %v", err) logger.Errorf("Failed to localize message: %v", err)
return "" return ""
@ -135,7 +137,6 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
_, err = i18nBundle.ParseMessageFileBytes(data, path) _, err = i18nBundle.ParseMessageFileBytes(data, path)
return err return err
}) })
if err != nil { if err != nil {
return err return err
} }

View file

@ -28,7 +28,9 @@
{ {
"tag": "direct", "tag": "direct",
"protocol": "freedom", "protocol": "freedom",
"settings": {} "settings": {
"domainStrategy": "UseIP"
}
}, },
{ {
"tag": "blocked", "tag": "blocked",
@ -51,7 +53,7 @@
} }
}, },
"routing": { "routing": {
"domainStrategy": "IPIfNonMatch", "domainStrategy": "AsIs",
"rules": [ "rules": [
{ {
"type": "field", "type": "field",

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@ -90,7 +91,6 @@ func (s *InboundService) getAllEmails() ([]string, error) {
FROM inbounds, FROM inbounds,
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
`).Scan(&emails).Error `).Scan(&emails).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -573,15 +573,19 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
} }
oldEmail := "" oldEmail := ""
newClientId := ""
clientIndex := 0 clientIndex := 0
for index, oldClient := range oldClients { for index, oldClient := range oldClients {
oldClientId := "" oldClientId := ""
if oldInbound.Protocol == "trojan" { if oldInbound.Protocol == "trojan" {
oldClientId = oldClient.Password oldClientId = oldClient.Password
newClientId = clients[0].Password
} else if oldInbound.Protocol == "shadowsocks" { } else if oldInbound.Protocol == "shadowsocks" {
oldClientId = oldClient.Email oldClientId = oldClient.Email
newClientId = clients[0].Email
} else { } else {
oldClientId = oldClient.ID oldClientId = oldClient.ID
newClientId = clients[0].ID
} }
if clientId == oldClientId { if clientId == oldClientId {
oldEmail = oldClient.Email oldEmail = oldClient.Email
@ -590,6 +594,11 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
} }
} }
// Validate new client ID
if newClientId == "" {
return false, common.NewError("empty client ID")
}
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail { if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
existEmail, err := s.checkEmailsExistForClients(clients) existEmail, err := s.checkEmailsExistForClients(clients)
if err != nil { if err != nil {
@ -682,7 +691,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
return needRestart, tx.Save(oldInbound).Error return needRestart, tx.Save(oldInbound).Error
} }
func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { func (s *InboundService) AddTraffic(inboundTraffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
var err error var err error
db := database.GetDB() db := database.GetDB()
tx := db.Begin() tx := db.Begin()
@ -694,7 +703,7 @@ func (s *InboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*
tx.Commit() tx.Commit()
} }
}() }()
err = s.addInboundTraffic(tx, traffics) err = s.addInboundTraffic(tx, inboundTraffics)
if err != nil { if err != nil {
return err, false return err, false
} }
@ -1071,7 +1080,9 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
"email": client.Email, "email": client.Email,
"total": client.TotalGB, "total": client.TotalGB,
"expiry_time": client.ExpiryTime, "expiry_time": client.ExpiryTime,
"reset": client.Reset}) "reset": client.Reset,
})
err := result.Error err := result.Error
return err return err
} }

View file

@ -9,8 +9,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type OutboundService struct { type OutboundService struct{}
}
func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) { func (s *OutboundService) AddTraffic(traffics []*xray.Traffic, clientTraffics []*xray.ClientTraffic) (error, bool) {
var err error var err error

View file

@ -4,11 +4,11 @@ import (
"os" "os"
"syscall" "syscall"
"time" "time"
"x-ui/logger" "x-ui/logger"
) )
type PanelService struct { type PanelService struct{}
}
func (s *PanelService) RestartPanel(delay time.Duration) error { func (s *PanelService) RestartPanel(delay time.Duration) error {
p, err := os.FindProcess(syscall.Getpid()) p, err := os.FindProcess(syscall.Getpid())

View file

@ -382,7 +382,6 @@ func (s *ServerService) UpdateXray(version string) error {
} }
return nil return nil
} }
func (s *ServerService) GetLogs(count string, level string, syslog string) []string { func (s *ServerService) GetLogs(count string, level string, syslog string) []string {

View file

@ -9,6 +9,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@ -64,8 +65,7 @@ var defaultValueMap = map[string]string{
"warp": "", "warp": "",
} }
type SettingService struct { type SettingService struct{}
}
func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) { func (s *SettingService) GetDefaultJsonConfig() (interface{}, error) {
var jsonData interface{} var jsonData interface{}
@ -444,6 +444,7 @@ func (s *SettingService) GetDatepicker() (string, error) {
func (s *SettingService) GetWarp() (string, error) { func (s *SettingService) GetWarp() (string, error) {
return s.getString("warp") return s.getString("warp")
} }
func (s *SettingService) SetWarp(data string) error { func (s *SettingService) SetWarp(data string) error {
return s.setString("warp", data) return s.setString("warp", data)
} }

View file

@ -10,6 +10,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"x-ui/config" "x-ui/config"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
@ -26,12 +27,14 @@ import (
"github.com/valyala/fasthttp/fasthttpproxy" "github.com/valyala/fasthttp/fasthttpproxy"
) )
var bot *telego.Bot var (
var botHandler *th.BotHandler bot *telego.Bot
var adminIds []int64 botHandler *th.BotHandler
var isRunning bool adminIds []int64
var hostname string isRunning bool
var hashStorage *global.HashStorage hostname string
hashStorage *global.HashStorage
)
type LoginStatus byte type LoginStatus byte
@ -280,7 +283,6 @@ 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.GetChat().ID chatId := callbackQuery.Message.GetChat().ID
if isAdmin { if isAdmin {
@ -866,7 +868,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
Text: message, Text: message,
ParseMode: "HTML", ParseMode: "HTML",
} }
//only add replyMarkup to last message // only add replyMarkup to last message
if len(replyMarkup) > 0 && n == (len(allMessages)-1) { if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
params.ReplyMarkup = replyMarkup[0] params.ReplyMarkup = replyMarkup[0]
} }
@ -1030,9 +1032,15 @@ func (t *Tgbot) getInboundUsages() string {
return info return info
} }
func (t *Tgbot) clientInfoMsg(traffic *xray.ClientTraffic, printEnabled bool, printOnline bool, printActive bool, func (t *Tgbot) clientInfoMsg(
printDate bool, printTraffic bool, printRefreshed bool) string { traffic *xray.ClientTraffic,
printEnabled bool,
printOnline bool,
printActive bool,
printDate bool,
printTraffic bool,
printRefreshed bool,
) string {
now := time.Now().Unix() now := time.Now().Unix()
expiryTime := "" expiryTime := ""
flag := false flag := false
@ -1544,7 +1552,6 @@ func (t *Tgbot) sendBackup(chatId int64) {
} }
} else { } else {
logger.Error("Error in opening db file for backup: ", err) logger.Error("Error in opening db file for backup: ", err)
} }
file, err = os.Open(xray.GetConfigPath()) file, err = os.Open(xray.GetConfigPath())

View file

@ -2,6 +2,7 @@ package service
import ( import (
"errors" "errors"
"x-ui/database" "x-ui/database"
"x-ui/database/model" "x-ui/database/model"
"x-ui/logger" "x-ui/logger"
@ -9,8 +10,7 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type UserService struct { type UserService struct{}
}
func (s *UserService) GetFirstUser() (*model.User, error) { func (s *UserService) GetFirstUser() (*model.User, error) {
db := database.GetDB() db := database.GetDB()

View file

@ -4,16 +4,19 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"sync" "sync"
"x-ui/logger" "x-ui/logger"
"x-ui/xray" "x-ui/xray"
"go.uber.org/atomic" "go.uber.org/atomic"
) )
var p *xray.Process var (
var lock sync.Mutex p *xray.Process
var isNeedXrayRestart atomic.Bool lock sync.Mutex
var result string isNeedXrayRestart atomic.Bool
result string
)
type XrayService struct { type XrayService struct {
inboundService InboundService inboundService InboundService
@ -87,7 +90,6 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
// check users active or not // check users active or not
clientStats := inbound.ClientStats clientStats := inbound.ClientStats
for _, clientTraffic := range clientStats { for _, clientTraffic := range clientStats {
indexDecrease := 0 indexDecrease := 0
for index, client := range clients { for index, client := range clients {
c := client.(map[string]interface{}) c := client.(map[string]interface{})
@ -96,20 +98,15 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
clients = RemoveIndex(clients, index-indexDecrease) clients = RemoveIndex(clients, index-indexDecrease)
indexDecrease++ indexDecrease++
logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit") logger.Info("Remove Inbound User ", c["email"], " due the expire or traffic limit")
} }
} }
} }
} }
// clear client config for additional parameters // clear client config for additional parameters
var final_clients []interface{} var final_clients []interface{}
for _, client := range clients { for _, client := range clients {
c := client.(map[string]interface{}) c := client.(map[string]interface{})
if c["enable"] != nil { if c["enable"] != nil {
if enable, ok := c["enable"].(bool); ok && !enable { if enable, ok := c["enable"].(bool); ok && !enable {
continue continue

View file

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"os" "os"
"time" "time"
"x-ui/util/common" "x-ui/util/common"
"x-ui/xray" "x-ui/xray"
) )

View file

@ -2,6 +2,7 @@ package session
import ( import (
"encoding/gob" "encoding/gob"
"x-ui/database/model" "x-ui/database/model"
sessions "github.com/Calidity/gin-sessions" sessions "github.com/Calidity/gin-sessions"

View file

@ -467,6 +467,8 @@
[pages.xray.dns] [pages.xray.dns]
"enable" = "Enable DNS" "enable" = "Enable DNS"
"enableDesc" = "Enable built-in DNS server" "enableDesc" = "Enable built-in DNS server"
"tag" = "DNS Inbound Tag"
"tagDesc" = "This tag will be available as an Inbound tag in routing rules."
"strategy" = "Query Strategy" "strategy" = "Query Strategy"
"strategyDesc" = "Overall strategy to resolve domain names" "strategyDesc" = "Overall strategy to resolve domain names"
"add" = "Add Server" "add" = "Add Server"

View file

@ -467,6 +467,8 @@
[pages.xray.dns] [pages.xray.dns]
"enable" = "فعال کردن حل دامنه" "enable" = "فعال کردن حل دامنه"
"enableDesc" = "سرور حل دامنه داخلی را فعال کنید" "enableDesc" = "سرور حل دامنه داخلی را فعال کنید"
"tag" = "برچسب"
"tagDesc" = "این برچسب در قوانین مسیریابی به عنوان یک برچسب ورودی قابل استفاده خواهد بود"
"strategy" = "استراتژی پرس‌وجو" "strategy" = "استراتژی پرس‌وجو"
"strategyDesc" = "استراتژی کلی برای حل نام دامنه" "strategyDesc" = "استراتژی کلی برای حل نام دامنه"
"add" = "افزودن سرور" "add" = "افزودن سرور"

View file

@ -467,6 +467,8 @@
[pages.xray.dns] [pages.xray.dns]
"enable" = "Aktifkan DNS" "enable" = "Aktifkan DNS"
"enableDesc" = "Aktifkan server DNS bawaan" "enableDesc" = "Aktifkan server DNS bawaan"
"tag" = "Tanda DNS Masuk"
"tagDesc" = "Tanda ini akan tersedia sebagai tanda masuk dalam aturan penataan."
"strategy" = "Strategi Kueri" "strategy" = "Strategi Kueri"
"strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain" "strategyDesc" = "Strategi keseluruhan untuk menyelesaikan nama domain"
"add" = "Tambahkan Server" "add" = "Tambahkan Server"

View file

@ -467,6 +467,8 @@
[pages.xray.dns] [pages.xray.dns]
"enable" = "Включить DNS" "enable" = "Включить DNS"
"enableDesc" = "Включить встроенный DNS-сервер" "enableDesc" = "Включить встроенный DNS-сервер"
"tag" = "Входящий тег DNS"
"tagDesc" = "Этот тег будет доступен как входящий тег в правилах маршрутизации."
"strategy" = "Стратегия запроса" "strategy" = "Стратегия запроса"
"strategyDesc" = "Общая стратегия разрешения доменных имен" "strategyDesc" = "Общая стратегия разрешения доменных имен"
"add" = "Добавить сервер" "add" = "Добавить сервер"

View file

@ -467,6 +467,8 @@
[pages.xray.dns] [pages.xray.dns]
"enable" = "Увімкнути DNS" "enable" = "Увімкнути DNS"
"enableDesc" = "Увімкнути вбудований DNS-сервер" "enableDesc" = "Увімкнути вбудований DNS-сервер"
"tag" = "Мітка вхідного DNS"
"tagDesc" = "Ця мітка буде доступна як вхідна мітка в правилах маршрутизації."
"strategy" = "Стратегія запиту" "strategy" = "Стратегія запиту"
"strategyDesc" = "Загальна стратегія вирішення доменних імен" "strategyDesc" = "Загальна стратегія вирішення доменних імен"
"add" = "Додати сервер" "add" = "Додати сервер"

View file

@ -467,6 +467,8 @@
[pages.xray.dns] [pages.xray.dns]
"enable" = "Kích hoạt DNS" "enable" = "Kích hoạt DNS"
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp" "enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
"tag" = "Thẻ gửi đến DNS"
"tagDesc" = "Thẻ này sẽ có sẵn dưới dạng thẻ Gửi đến trong quy tắc định tuyến."
"strategy" = "Chiến lược truy vấn" "strategy" = "Chiến lược truy vấn"
"strategyDesc" = "Chiến lược tổng thể để phân giải tên miền" "strategyDesc" = "Chiến lược tổng thể để phân giải tên miền"
"add" = "Thêm máy chủ" "add" = "Thêm máy chủ"

View file

@ -53,10 +53,10 @@
"remained" = "剩余" "remained" = "剩余"
"security" = "安全" "security" = "安全"
"secAlertTitle" = "安全警报" "secAlertTitle" = "安全警报"
"secAlertSsl" = "此连接不安全在激活 TLS 进行数据保护之前,请勿输入敏感信息" "secAlertSsl" = "此连接不安全在激活 TLS 进行数据保护之前,请勿输入敏感信息"
"secAlertConf" = "某些设置容易受到攻击。建议加强安全协议以防止潜在的违规行为。" "secAlertConf" = "某些设置易受攻击。建议加强安全协议以防止潜在漏洞。"
"secAlertSSL" = "面板缺安全连接。请安装 TLS 证书以保护数据。" "secAlertSSL" = "面板缺安全连接。请安装 TLS 证书以保护数据安全。"
"secAlertPanelPort" = "面板默认端口存在漏洞。请配置随机或特定端口。" "secAlertPanelPort" = "面板默认端口存在安全风险。请配置随机端口或特定端口。"
"secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertPanelURI" = "面板默认 URI 路径不安全。请配置复杂的 URI 路径。"
"secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertSubURI" = "订阅默认 URI 路径不安全。请配置复杂的 URI 路径。"
"secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。" "secAlertSubJsonURI" = "订阅 JSON 默认 URI 路径不安全。请配置复杂的 URI 路径。"
@ -84,33 +84,33 @@
[pages.index] [pages.index]
"title" = "系统状态" "title" = "系统状态"
"memory" = "内存" "memory" = "内存"
"hard" = "盘" "hard" = "盘"
"xrayStatus" = "Xray" "xrayStatus" = "Xray"
"stopXray" = "停止" "stopXray" = "停止"
"restartXray" = "重启" "restartXray" = "重启"
"xraySwitch" = "版本" "xraySwitch" = "版本"
"xraySwitchClick" = "点击你想切换的版本" "xraySwitchClick" = "选择你要切换到的版本"
"xraySwitchClickDesk" = "请谨慎选择,旧版本可能配置不兼容" "xraySwitchClickDesk" = "请谨慎选择,因为较旧版本可能与当前配置不兼容"
"operationHours" = "系统正常运行时间" "operationHours" = "系统正常运行时间"
"systemLoad" = "系统负载" "systemLoad" = "系统负载"
"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载" "systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载"
"connectionTcpCountDesc" = "所有网卡的总 TCP 连接数" "connectionTcpCountDesc" = "系统中所有 TCP 连接数"
"connectionUdpCountDesc" = "所有网卡的总 UDP 连接数" "connectionUdpCountDesc" = "系统中所有 UDP 连接数"
"connectionCount" = "连接数" "connectionCount" = "连接数"
"upSpeed" = "所有网卡的总上传速度" "upSpeed" = "总上传速度"
"downSpeed" = "所有网卡的总下载速度" "downSpeed" = "总下载速度"
"totalSent" = "系统启动以来所有网卡的总上传流量" "totalSent" = "系统启动以来发送的总数据量"
"totalReceive" = "系统启动以来所有网卡的总下载流量" "totalReceive" = "系统启动以来接收的总数据量"
"xraySwitchVersionDialog" = "切换 xray 版本" "xraySwitchVersionDialog" = "切换 Xray 版本"
"xraySwitchVersionDialogDesc" = "是否切换 xray 版本至" "xraySwitchVersionDialogDesc" = "是否切换 Xray 版本至"
"dontRefresh" = "安装中,请不要刷新此页面" "dontRefresh" = "安装中,请刷新此页面"
"logs" = "日志" "logs" = "日志"
"config" = "配置" "config" = "配置"
"backup" = "备份还原" "backup" = "备份和恢复"
"backupTitle" = "备份和恢复数据库" "backupTitle" = "备份和恢复数据库"
"backupDescription" = "请记住在导入新数据库之前进行备份。" "backupDescription" = "恢复数据库之前建议进行备份"
"exportDatabase" = "下载数据库" "exportDatabase" = "备份"
"importDatabase" = "上传数据库" "importDatabase" = "恢复"
[pages.inbounds] [pages.inbounds]
"title" = "入站列表" "title" = "入站列表"
@ -133,20 +133,20 @@
"update" = "修改" "update" = "修改"
"modifyInbound" = "修改入站" "modifyInbound" = "修改入站"
"deleteInbound" = "删除入站" "deleteInbound" = "删除入站"
"deleteInboundContent" = "确定要删除入站吗?" "deleteInboundContent" = "确定要删除入站吗"
"deleteClient" = "删除客户端" "deleteClient" = "删除客户端"
"deleteClientContent" = "您确定要删除客户端吗?" "deleteClientContent" = "确定要删除客户端吗?"
"resetTrafficContent" = "确定要重置流量吗?" "resetTrafficContent" = "确定要重置流量吗"
"copyLink" = "复制链接" "copyLink" = "复制链接"
"address" = "地址" "address" = "地址"
"network" = "网络" "network" = "网络"
"destinationPort" = "目标端口" "destinationPort" = "目标端口"
"targetAddress" = "目标地址" "targetAddress" = "目标地址"
"monitorDesc" = "默认留空即可" "monitorDesc" = "留空表示监听所有 IP"
"meansNoLimit" = " = 无限制单位GB)" "meansNoLimit" = " = 无限制单位GB)"
"totalFlow" = "总流量" "totalFlow" = "总流量"
"leaveBlankToNeverExpire" = "留空则永不到期" "leaveBlankToNeverExpire" = "留空表示永不过期"
"noRecommendKeepDefault" = "没有特殊需求保持默认即可" "noRecommendKeepDefault" = "建议保留默认值"
"certificatePath" = "文件路径" "certificatePath" = "文件路径"
"certificateContent" = "文件内容" "certificateContent" = "文件内容"
"publicKey" = "公钥" "publicKey" = "公钥"
@ -156,71 +156,71 @@
"export" = "导出链接" "export" = "导出链接"
"clone" = "克隆" "clone" = "克隆"
"cloneInbound" = "克隆" "cloneInbound" = "克隆"
"cloneInboundContent" = "此入站的所有项目除 Port、Listening IP、Clients 将应用于克隆" "cloneInboundContent" = "此入站规则除端口Port、监听 IPListening IP和客户端Clients以外的所有配置都将应用于克隆"
"cloneInboundOk" = "创建克隆" "cloneInboundOk" = "创建克隆"
"resetAllTraffic" = "重置所有入站流量" "resetAllTraffic" = "重置所有入站流量"
"resetAllTrafficTitle" = "重置所有入站流量" "resetAllTrafficTitle" = "重置所有入站流量"
"resetAllTrafficContent" = "确定要重置所有入站流量吗?" "resetAllTrafficContent" = "确定要重置所有入站流量吗?"
"resetInboundClientTraffics" = "重置客户端流量" "resetInboundClientTraffics" = "重置客户端流量"
"resetInboundClientTrafficTitle" = "重置所有客户端流量" "resetInboundClientTrafficTitle" = "重置所有客户端流量"
"resetInboundClientTrafficContent" = "确定要重置此入站客户端的所有流量吗?" "resetInboundClientTrafficContent" = "确定要重置此入站客户端的所有流量吗?"
"resetAllClientTraffics" = "重置所有客户端流量" "resetAllClientTraffics" = "重置所有客户端流量"
"resetAllClientTrafficTitle" = "重置所有客户端流量" "resetAllClientTrafficTitle" = "重置所有客户端流量"
"resetAllClientTrafficContent" = "确定要重置所有客户端的所有流量吗?" "resetAllClientTrafficContent" = "确定要重置所有客户端的所有流量吗?"
"delDepletedClients" = "删除耗尽的客户端" "delDepletedClients" = "删除流量耗尽的客户端"
"delDepletedClientsTitle" = "删除耗尽的客户端" "delDepletedClientsTitle" = "删除流量耗尽的客户端"
"delDepletedClientsContent" = "确定要删除所有耗尽的客户端吗?" "delDepletedClientsContent" = "确定要删除所有流量耗尽的客户端吗?"
"email" = "电子邮件" "email" = "电子邮件"
"emailDesc" = "电子邮件必须完全唯一" "emailDesc" = "电子邮件必须完全唯一"
"IPLimit" = "IP限制" "IPLimit" = "IP 限制"
"IPLimitDesc" = "如果超过输入的计数则禁用入站0 表示禁用限制 ip" "IPLimitDesc" = "如果数量超过设置值则禁用入站流量。0 = 禁用"
"IPLimitlog" = "IP日志" "IPLimitlog" = "IP 日志"
"IPLimitlogDesc" = "IP 历史日志 通过IP限制禁用inbound之前需要清空日志)" "IPLimitlogDesc" = "IP 历史日志(要启用被禁用的入站流量,请清除日志)"
"IPLimitlogclear" = "清除日志" "IPLimitlogclear" = "清除日志"
"setDefaultCert" = "从面板设置证书" "setDefaultCert" = "从面板设置证书"
"xtlsDesc" = "Xray核心需要1.7.5" "xtlsDesc" = "Xray 核心需要 1.7.5"
"realityDesc" = "Xray核心需要1.8.0及以上版本" "realityDesc" = "Xray 核心需要 1.8.0 及以上版本"
"telegramDesc" = "仅使用聊天 ID可以在 @userinfobot 处获取,或在机器人中使用'/id'命令)" "telegramDesc" = "请输入电报 (Telegram) 或聊天 ID无需添加 '@' 符号。(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令)"
"subscriptionDesc" = "您可以在详细信息上找到您的子链接,也可以对多个配置使用相同的名称" "subscriptionDesc" = "要找到你的订阅 URL请导航到“详细信息”。此外你可以为多个客户端使用相同的名称。"
"info" = "信息" "info" = "信息"
"same" = "相同" "same" = "相同"
"inboundData" = "入站数据" "inboundData" = "入站数据"
"exportInbound" = "出口 入境" "exportInbound" = "导出入站规则"
"import"="导入" "import"="导入"
"importInbound" = "导入入站" "importInbound" = "导入入站规则"
[pages.client] [pages.client]
"add" = "添加客户端" "add" = "添加客户端"
"edit" = "编辑客户端" "edit" = "编辑客户端"
"submitAdd" = "添加客户端" "submitAdd" = "添加客户端"
"submitEdit" = "保存修改" "submitEdit" = "保存修改"
"clientCount" = "客户数量" "clientCount" = "客户数量"
"bulk" = "批量创建" "bulk" = "批量创建"
"method" = "方法" "method" = "方法"
"first" = "第一" "first" = "置顶"
"last" = "最后" "last" = "置底"
"prefix" = "前缀" "prefix" = "前缀"
"postfix" = "后缀" "postfix" = "后缀"
"delayedStart" = "首次使用后开始" "delayedStart" = "首次使用后开始"
"expireDays" = "期间" "expireDays" = "期间"
"days" = "天" "days" = "天"
"renew" = "自动续订" "renew" = "自动续订"
"renewDesc" = "到期后自动续订。(0 = 禁用)(单: 天)" "renewDesc" = "到期后自动续订。(0 = 禁用)(单: 天)"
[pages.inbounds.toasts] [pages.inbounds.toasts]
"obtain" = "获取" "obtain" = "获取"
[pages.inbounds.stream.general] [pages.inbounds.stream.general]
"request" = "求" "request" = "求"
"response" = "回复" "response" = "响应"
"name" = "名" "name" = ""
"value" = "值" "value" = "值"
[pages.inbounds.stream.tcp] [pages.inbounds.stream.tcp]
"version" = "版本" "version" = "版本"
"method" = "方法" "method" = "方法"
"path" = "路" "path" = ""
"status" = "地位" "status" = "状态"
"statusDescription" = "状态说明" "statusDescription" = "状态说明"
"requestHeader" = "请求头" "requestHeader" = "请求头"
"responseHeader" = "响应头" "responseHeader" = "响应头"
@ -229,16 +229,16 @@
"encryption" = "加密" "encryption" = "加密"
[pages.settings] [pages.settings]
"title" = "设置" "title" = "面板设置"
"save" = "保存配置" "save" = "保存"
"infoDesc" = "此处的所有更改都需要保存并重启面板才能生效" "infoDesc" = "此处的所有更改都需要保存并重启面板才能生效"
"restartPanel" = "重启面板" "restartPanel" = "重启面板"
"restartPanelDesc" = "确定要重启面板吗?点击确定将于 3 秒后重启,若重启后无法访问面板,请前往服务器查看面板日志信息" "restartPanelDesc" = "确定要重启面板吗?若重启后无法访问面板,请前往服务器查看面板日志信息"
"actions" = "作" "actions" = "作"
"resetDefaultConfig" = "重置为默认配置" "resetDefaultConfig" = "重置为默认配置"
"panelSettings" = "面板配置" "panelSettings" = "常规"
"securitySettings" = "安全设定" "securitySettings" = "安全设定"
"TGBotSettings" = "TG提醒相关设置" "TGBotSettings" = "Telegram 机器人配置"
"panelListeningIP" = "面板监听 IP" "panelListeningIP" = "面板监听 IP"
"panelListeningIPDesc" = "默认留空监听所有 IP" "panelListeningIPDesc" = "默认留空监听所有 IP"
"panelListeningDomain" = "面板监听域名" "panelListeningDomain" = "面板监听域名"
@ -262,171 +262,171 @@
"currentPassword" = "原密码" "currentPassword" = "原密码"
"newUsername" = "新用户名" "newUsername" = "新用户名"
"newPassword" = "新密码" "newPassword" = "新密码"
"telegramBotEnable" = "启用电报机器人" "telegramBotEnable" = "启用 Telegram 机器人"
"telegramBotEnableDesc" = "重启面板生效" "telegramBotEnableDesc" = "启用 Telegram 机器人功能"
"telegramToken" = "电报机器人TOKEN" "telegramToken" = "Telegram 机器人令牌token"
"telegramTokenDesc" = "重启面板生效" "telegramTokenDesc" = "从 '@BotFather' 获取的 Telegram 机器人令牌"
"telegramProxy" = "Socks5 代理" "telegramProxy" = "SOCKS5 Proxy"
"telegramProxyDesc" = "如果您需要 Socks5 代理来连接 Telegram。 根据指南调整其设置。" "telegramProxyDesc" = "启用 SOCKS5 代理连接到 Telegram根据指南调整设置"
"telegramChatId" = "以逗号分隔的多个 chatID 重启面板生效" "telegramChatId" = "管理员聊天 ID"
"telegramChatIdDesc" = "多个聊天 ID 用逗号分隔。使用 @userinfobot 或在机器人中使用'/id'命令获取您的聊天 ID。" "telegramChatIdDesc" = "Telegram 管理员聊天 ID (多个以逗号分隔)(可通过 @userinfobot 获取,或在机器人中使用 '/id' 命令获取)"
"telegramNotifyTime" = "电报机器人通知时间" "telegramNotifyTime" = "通知时间"
"telegramNotifyTimeDesc" = "采用Crontab定时格式,重启面板生效" "telegramNotifyTimeDesc" = "设置周期性的 Telegram 机器人通知时间(使用 crontab 时间格式)"
"tgNotifyBackup" = "数据库备份" "tgNotifyBackup" = "数据库备份"
"tgNotifyBackupDesc" = "正在发送数据库备份文件和报告通知" "tgNotifyBackupDesc" = "发送带有报告的数据库备份文件"
"tgNotifyLogin" = "登录通知" "tgNotifyLogin" = "登录通知"
"tgNotifyLoginDesc" = "当有人试图登录的面板时显示用户名、IP 地址和时间" "tgNotifyLoginDesc" = "当有人试图登录的面板时显示用户名、IP 地址和时间"
"sessionMaxAge" = "会话最大年龄" "sessionMaxAge" = "会话时长"
"sessionMaxAgeDesc" = "您可以保持登录状态的时间(单位:分钟)" "sessionMaxAgeDesc" = "保持登录状态的时长(单位:分钟)"
"expireTimeDiff" = "耗尽时间阈值" "expireTimeDiff" = "到期通知阈值"
"expireTimeDiffDesc" = "到期前检测耗尽(单位:天)" "expireTimeDiffDesc" = "达到此阈值时,将收到有关到期时间的通知(单位:天)"
"trafficDiff" = "耗尽流量阈值" "trafficDiff" = "流量耗尽阈值"
"trafficDiffDesc" = "完成流量前检测耗尽单位GB" "trafficDiffDesc" = "达到此阈值时,将收到有关流量耗尽的通知单位GB"
"tgNotifyCpu" = "CPU 百分比警报阈值" "tgNotifyCpu" = "CPU 负载通知阈值"
"tgNotifyCpuDesc" = "如果 CPU 使用率超过此百分比(单位:%),此 talegram bot 将向您发送通知" "tgNotifyCpuDesc" = "CPU 负载超过此阈值时,将收到通知(单位:%"
"timeZone" = "时区" "timeZone" = "时区"
"timeZoneDesc" = "定时任务按照该时区的时间运行" "timeZoneDesc" = "定时任务按照该时区的时间运行"
"subSettings" = "订阅" "subSettings" = "订阅设置"
"subEnable" = "启用服务" "subEnable" = "启用订阅服务"
"subEnableDesc" = "具有单独配置的订阅功能" "subEnableDesc" = "启用订阅服务功能"
"subListen" = "监听IP" "subListen" = "监听 IP"
"subListenDesc" = "留空默认监听所有IP" "subListenDesc" = "订阅服务监听的 IP 地址(留空表示监听所有 IP"
"subPort" = "订阅端口" "subPort" = "监听端口"
"subPortDesc" = "服务订阅服务的端口号必须在服务器中未使用" "subPortDesc" = "订阅服务监听的端口号(必须是未使用的端口)"
"subCertPath" = "订阅证书公钥文件路径" "subCertPath" = "公钥路径"
"subCertPathDesc" = "填写以'/'开头的绝对路径" "subCertPathDesc" = "订阅服务使用的公钥文件路径(以 '/' 开头)"
"subKeyPath" = "订阅证书私钥文件路径" "subKeyPath" = "私钥路径"
"subKeyPathDesc" = "填写以'/'开头的绝对路径" "subKeyPathDesc" = "订阅服务使用的私钥文件路径(以 '/' 开头)"
"subPath" = "订阅 URL 根路径" "subPath" = "URI 路径"
"subPathDesc" = "必须以'/'开始并以'/'结束" "subPathDesc" = "订阅服务使用的 URI 路径(以 '/' 开头,以 '/' 结尾)"
"subDomain" = "监听域名" "subDomain" = "监听域名"
"subDomainDesc" = "留空默认监控所有域名和IP" "subDomainDesc" = "订阅服务监听的域名(留空表示监听所有域名和 IP"
"subUpdates" = "订阅更新间隔" "subUpdates" = "更新间隔"
"subUpdatesDesc" = "客户端应用程序更新订阅的间隔时间" "subUpdatesDesc" = "客户端应用中订阅 URL 的更新间隔(单位:小时)"
"subEncrypt" = "加密配置" "subEncrypt" = "编码"
"subEncryptDesc" = "在订阅中加密返回的配置" "subEncryptDesc" = "订阅服务返回的内容将采用 Base64 编码"
"subShowInfo" = "显示使用信息" "subShowInfo" = "显示使用信息"
"subShowInfoDesc" = "在配置名称后显示剩余流量和日期" "subShowInfoDesc" = "客户端应用中将显示剩余流量和日期信息"
"subURI" = "反向代理 URI" "subURI" = "反向代理 URI"
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用" "subURIDesc" = "用于代理后面的订阅 URL 的 URI 路径"
"fragment" = "片" "fragment" = "片"
"fragmentDesc" = "启用 TLS hello 数据包分" "fragmentDesc" = "启用 TLS hello 数据包分"
[pages.xray] [pages.xray]
"title" = "Xray 置" "title" = "Xray 置"
"save" = "保存设置" "save" = "保存"
"restart" = "重新启动 Xray" "restart" = "重新启动 Xray"
"basicTemplate" = "基本模板" "basicTemplate" = "基础配置"
"advancedTemplate" = "高级模板部件" "advancedTemplate" = "高级配置"
"generalConfigs" = "通用配置" "generalConfigs" = "常规配置"
"generalConfigsDesc" = "这些选项将提供一般调整" "generalConfigsDesc" = "这些选项将决定常规配置"
"logConfigs"="日志" "logConfigs" = "日志"
"logConfigsDesc" = "日志可能会影响您服务器的效率。建议仅在您需要时明智地启用它" "logConfigsDesc" = "日志可能会影响服务器的效率,建议仅在需要时启用"
"blockConfigs" = "阻塞配置" "blockConfigs" = "防护屏蔽"
"blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站" "blockConfigsDesc" = "这些选项将阻止用户连接到特定协议和网站"
"blockCountryConfigs" = "阻止国家配置" "blockCountryConfigs" = "屏蔽国家/地区"
"blockCountryConfigsDesc" = "这些选项将阻止用户连接到特定国家/地区的域。" "blockCountryConfigsDesc" = "这些选项将阻止用户连接到特定国家/地区"
"directCountryConfigs" = "直接国家配置" "directCountryConfigs" = "直连国家/地区"
"directCountryConfigsDesc" = "直接连接确保特定流量不通过另一台服务器路由。" "directCountryConfigsDesc" = "直接连接可确保特定流量不会通过其他服务器路由"
"ipv4Configs" = "IPv4 配置" "ipv4Configs" = "IPv4 路由"
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域" "ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
"warpConfigs" = "WARP 配置" "warpConfigs" = "WARP 路由"
"warpConfigsDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在的服务器上以 socks5 代理模式安装 WARP。 WARP 将通过 Cloudflare 服务器将流量路由到网站。" "warpConfigsDesc" = "注意:在使用这些选项之前,请按照面板 GitHub 上的步骤在的服务器上以 socks5 代理模式安装 WARP。WARP 将通过 Cloudflare 服务器将流量路由到网站。"
"Template" = "Xray 配置模板" "Template" = "高级 Xray 配置模板"
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件重新启动面板生成效率" "TemplateDesc" = "最终的 Xray 配置文件将基于此模板生成"
"FreedomStrategy" = "配置自由协议的策略" "FreedomStrategy" = "Freedom 协议策略"
"FreedomStrategyDesc" = "在自由协议中设置网络输出策略" "FreedomStrategyDesc" = "设置 Freedom 协议中网络的输出策略"
"RoutingStrategy" = "配置路由域策略" "RoutingStrategy" = "配置路由域策略"
"RoutingStrategyDesc" = "设置DNS解析的整体路由策略" "RoutingStrategyDesc" = "设置 DNS 解析的整体路由策略"
"Torrent" = "禁止使用 bittorrent" "Torrent" = "屏蔽 BitTorrent 协议"
"TorrentDesc" = "更改配置模板避免用户使用bittorrent" "TorrentDesc" = "禁止使用 BitTorrent"
"PrivateIp" = "禁止私人 IP 范围连接" "PrivateIp" = "屏蔽私有 IP"
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围" "PrivateIpDesc" = "阻止连接到私有 IP"
"Ads" = "屏蔽广告" "Ads" = "屏蔽广告"
"AdsDesc" = "修改配置模板屏蔽广告" "AdsDesc" = "屏蔽广告网站"
"Family" = "阻止恶意软件和成人内容" "Family" = "家庭保护"
"FamilyDesc" = "Cloudflare DNS 解析器可阻止恶意软件和成人内容以保护家庭." "FamilyDesc" = "屏蔽成人内容和恶意网站"
"Security" = "阻止恶意软件、网络钓鱼和加密货币挖矿网站" "Security" = "安全防护"
"SecurityDesc" = "更改安全防护配置模板." "SecurityDesc" = "屏蔽恶意软件、网络钓鱼和挖矿网站"
"Speedtest" = "阻止测速网站" "Speedtest" = "屏蔽测速网站"
"SpeedtestDesc" = "更改配置模板以避免连接到速度测试网站。 重新启动面板以应用更改。" "SpeedtestDesc" = "阻止连接到测速网站"
"IRIp" = "禁止伊朗 IP 范围连接" "IRIp" = "屏蔽连接到伊朗 IP"
"IRIpDesc" = "修改配置模板避免连接伊朗IP段" "IRIpDesc" = "阻止建立到伊朗 IP 范围的连接"
"IRDomain" = "禁止伊朗域连接" "IRDomain" = "屏蔽连接到伊朗域名"
"IRDomainDesc" = "更改配置模板避免连接伊朗域名" "IRDomainDesc" = "阻止建立到伊朗域名的连接"
"ChinaIp" = "禁止中国 IP 范围连接" "ChinaIp" = "屏蔽连接到中国 IP"
"ChinaIpDesc" = "修改配置模板避免连接中国IP段" "ChinaIpDesc" = "阻止建立到中国 IP 范围的连接"
"ChinaDomain" = "禁止中国域名连接" "ChinaDomain" = "屏蔽连接到中国域名"
"ChinaDomainDesc" = "更改配置模板避免连接中国域" "ChinaDomainDesc" = "阻止建立到中国域名的连接"
"RussiaIp" = "禁止俄罗斯 IP 范围连接" "RussiaIp" = "屏蔽连接到俄罗斯 IP"
"RussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围" "RussiaIpDesc" = "阻止建立到俄罗斯 IP 范围的连接"
"RussiaDomain" = "禁止俄罗斯域连接" "RussiaDomain" = "屏蔽连接到俄罗斯域名"
"RussiaDomainDesc" = "更改配置模板避免连接俄罗斯域" "RussiaDomainDesc" = "阻止建立到俄罗斯域名的连接"
"VNIp" = "禁用与越南 IP 的连接" "VNIp" = "屏蔽连接到越南 IP"
"VNIpDesc" = "更改配置模板以避免连接到越南 IP 范围" "VNIpDesc" = "阻止建立到越南 IP 范围的连接"
"VNDomain" = "禁用与越南域的连接" "VNDomain" = "屏蔽连接到越南域名"
"VNDomainDesc" = "更改配置模板以避免连接到越南域" "VNDomainDesc" = "阻止建立到越南域名的连接"
"DirectIRIp" = "直接连接到伊朗 IP 范围" "DirectIRIp" = "直连伊朗 IP"
"DirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板" "DirectIRIpDesc" = "直接建立到伊朗 IP 范围的连接"
"DirectIRDomain" = "直接连接到伊朗域" "DirectIRDomain" = "直连伊朗域名"
"DirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板" "DirectIRDomainDesc" = "直接建立到伊朗域名的连接"
"DirectChinaIp" = "直连中国IP范围" "DirectChinaIp" = "直连中国 IP"
"DirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板" "DirectChinaIpDesc" = "直接建立到中国 IP 范围的连接"
"DirectChinaDomain" = "直连中国域名" "DirectChinaDomain" = "直连中国域名"
"DirectChinaDomainDesc" = "修改中国域名直连配置模板" "DirectChinaDomainDesc" = "直接建立到中国域名的连接"
"DirectRussiaIp" = "直接连接到俄罗斯 IP 范围" "DirectRussiaIp" = "直连俄罗斯 IP"
"DirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板" "DirectRussiaIpDesc" = "直接建立到俄罗斯 IP 范围的连接"
"DirectRussiaDomain" = "直接连接到俄罗斯域" "DirectRussiaDomain" = "直连俄罗斯域名"
"DirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板" "DirectRussiaDomainDesc" = "直接建立到俄罗斯域名的连接"
"DirectVNIp" = "直接连接越南IP" "DirectVNIp" = "直连越南 IP"
"DirectVNIpDesc" = "更改直接连接到越南 IP 范围的配置模板" "DirectVNIpDesc" = "直接建立到越南 IP 范围的连接"
"DirectVNDomain" = "直接连接至越南域名" "DirectVNDomain" = "直越南域名"
"DirectVNDomainDesc" = "更改直连越南域的配置模板。" "DirectVNDomainDesc" = "直接建立到越南域名的连接"
"GoogleIPv4" = "为谷歌使用 IPv4" "GoogleIPv4" = "Google"
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由" "GoogleIPv4Desc" = "通过 IPv4 将流量路由到谷歌"
"NetflixIPv4" = "Netflix 使用 IPv4" "NetflixIPv4" = "Netflix"
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由" "NetflixIPv4Desc" = "通过 IPv4 将流量路由到 Netflix"
"GoogleWARP" = "Google" "GoogleWARP" = "Google"
"GoogleWARPDesc" = "通过 WARP 将流量路由到 Google" "GoogleWARPDesc" = "通过 WARP 将流量路由到 Google"
"OpenAIWARP" = "OpenAI (ChatGPT)" "OpenAIWARP" = "OpenAI (ChatGPT)"
"OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)" "OpenAIWARPDesc" = "通过 WARP 将流量路由到 OpenAI (ChatGPT)"
"NetflixWARP" = "Netflix" "NetflixWARP" = "Netflix"
"NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix" "NetflixWARPDesc" = "通过 WARP 将流量路由到 Netflix"
"MetaWARP"="Meta" "MetaWARP"="Meta"
"MetaWARPDesc" = "通过 WARP 将流量路由到 MetaInstagram、Facebook、WhatsApp、Threads..." "MetaWARPDesc" = "通过 WARP 将流量路由到 MetaInstagram、Facebook、WhatsApp、Threads..."
"AppleWARP" = "Apple" "AppleWARP" = "Apple"
"AppleWARPDesc" = "通过 WARP 将流量路由到 Apple" "AppleWARPDesc" = "通过 WARP 将流量路由到 Apple"
"RedditWARP" = "Reddit" "RedditWARP" = "Reddit"
"RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit" "RedditWARPDesc" = "通过 WARP 将流量路由到 Reddit"
"SpotifyWARP" = "Spotify" "SpotifyWARP" = "Spotify"
"SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify" "SpotifyWARPDesc" = "通过 WARP 将流量路由到 Spotify"
"IRWARP" = "伊朗域名路由到 WARP" "IRWARP" = "伊朗域名"
"IRWARPDesc" = "将伊朗域的路由添加到 WARP。 重启面板生效" "IRWARPDesc" = "通过 WARP 将流量路由到伊朗域名"
"Inbounds" = "入站" "Inbounds" = "入站规则"
"InboundsDesc" = "更改配置模板接受特殊客户端" "InboundsDesc" = "接受来自特定客户端的流量"
"Outbounds" = "出站" "Outbounds" = "出站规则"
"Balancers" = "平衡器" "Balancers" = "负载均衡"
"OutboundsDesc" = "更改配置模板定义此服务器的传出方式" "OutboundsDesc" = "设置出站流量传出方式"
"Routings" = "路由规则" "Routings" = "路由规则"
"RoutingsDesc" = "每条规则的优先级都很重要" "RoutingsDesc" = "每条规则的优先级都很重要"
"completeTemplate" = "全部" "completeTemplate" = "全部"
"logLevel" = "日志级别" "logLevel" = "日志级别"
"logLevelDesc" = "错误日志的日志级别,表示需要记录的信息。" "logLevelDesc" = "错误日志的日志级别,用于指示需要记录的信息"
"accessLog" = "访问日志" "accessLog" = "访问日志"
"accessLogDesc" = "访问日志的文件路径。 特殊值“none”禁用访问日志" "accessLogDesc" = "访问日志的文件路径。特殊值 'none' 禁用访问日志"
"errorLog" = "错误日志" "errorLog" = "错误日志"
"errorLogDesc" = "错误日志的文件路径。 特殊值“none”禁用错误日志" "errorLogDesc" = "错误日志的文件路径。特殊值 'none' 禁用错误日志"
[pages.xray.rules] [pages.xray.rules]
"first" = "第一个" "first" = "置顶"
"last" = "最后" "last" = "置底"
"up" = "向上" "up" = "向上"
"down" = "向下" "down" = "向下"
"source" = "来源" "source" = "来源"
"dest" = "目的地" "dest" = "目的地"
"inbound" = "入站" "inbound" = "入站"
"outbound" = "出站" "outbound" = "出站"
"balancer" = "平衡器" "balancer" = "负载均衡"
"info" = "信息" "info" = "信息"
"add" = "添加规则" "add" = "添加规则"
"edit" = "编辑规则" "edit" = "编辑规则"
@ -438,35 +438,37 @@
"editOutbound" = "编辑出站" "editOutbound" = "编辑出站"
"editReverse" = "编辑反向" "editReverse" = "编辑反向"
"tag" = "标签" "tag" = "标签"
"tagDesc" = "唯一标" "tagDesc" = "唯一标"
"address" = "地址" "address" = "地址"
"reverse" = "反" "reverse" = "反"
"domain" = "域名" "domain" = "域名"
"type" = "类型" "type" = "类型"
"bridge" = "" "bridge" = "Bridge"
"portal" = "门户" "portal" = "Portal"
"intercon" = "互连" "intercon" = "互连"
[pages.xray.balancer] [pages.xray.balancer]
"addBalancer" = "添加平衡器" "addBalancer" = "添加负载均衡"
"editBalancer" = "编辑平衡器" "editBalancer" = "编辑负载均衡"
"balancerStrategy" = "略" "balancerStrategy" = "略"
"balancerSelectors" = "选择器" "balancerSelectors" = "选择器"
"tag" = "标签" "tag" = "标签"
"tagDesc" = "唯一标" "tagDesc" = "唯一标"
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用则只有outboundTag起作用。" "balancerDesc" = "无法同时使用 balancerTag 和 outboundTag。如果同时使用则只有 outboundTag 会生效。"
[pages.xray.wireguard] [pages.xray.wireguard]
"secretKey" = "密钥" "secretKey" = "密钥"
"publicKey" = "公钥" "publicKey" = "公钥"
"allowedIPs" = "允许的 IP" "allowedIPs" = "允许的 IP"
"endpoint" = "点" "endpoint" = "点"
"psk" = "共享密钥" "psk" = "共享密钥"
"domainStrategy" = "域策略" "domainStrategy" = "域策略"
[pages.xray.dns] [pages.xray.dns]
"enable" = "启用 DNS" "enable" = "启用 DNS"
"enableDesc" = "启用内置 DNS 服务器" "enableDesc" = "启用内置 DNS 服务器"
"tag" = "DNS 入站标签"
"tagDesc" = "此标签将在路由规则中可用作入站标签"
"strategy" = "查询策略" "strategy" = "查询策略"
"strategyDesc" = "解析域名的总体策略" "strategyDesc" = "解析域名的总体策略"
"add" = "添加服务器" "add" = "添加服务器"
@ -481,16 +483,16 @@
[pages.settings.security] [pages.settings.security]
"admin" = "管理员" "admin" = "管理员"
"secret" = "密钥" "secret" = "安全令牌"
"loginSecurity" = "登录安全" "loginSecurity" = "登录安全"
"loginSecurityDesc" = "在用户登录页面中切换附加步骤" "loginSecurityDesc" = "添加额外的身份验证以提高安全性"
"secretToken" = "密钥" "secretToken" = "安全令牌"
"secretTokenDesc" = "复制此密钥并将其保存在安全的地方;没有这个你将无法登录。这也无法从 x-ui 命令工具中恢复" "secretTokenDesc" = "请将此令牌存储在安全的地方。此令牌用于登录,丢失无法恢复。"
[pages.settings.toasts] [pages.settings.toasts]
"modifySettings" = "修改设置" "modifySettings" = "修改设置"
"getSettings" = "获取设置" "getSettings" = "获取设置"
"modifyUser" = "修改用户" "modifyUser" = "修改管理员"
"originalUserPassIncorrect" = "原用户名或原密码错误" "originalUserPassIncorrect" = "原用户名或原密码错误"
"userPassMustBeNotEmpty" = "新用户名和新密码不能为空" "userPassMustBeNotEmpty" = "新用户名和新密码不能为空"
@ -499,7 +501,7 @@
"noResult" = "❗ 没有结果!" "noResult" = "❗ 没有结果!"
"noQuery" = "❌ 未找到查询!请重新使用命令!" "noQuery" = "❌ 未找到查询!请重新使用命令!"
"wentWrong" = "❌ 出了点问题!" "wentWrong" = "❌ 出了点问题!"
"noIpRecord" = "❗ 没有IP记录" "noIpRecord" = "❗ 没有 IP 记录!"
"noInbounds" = "❗ 没有找到入站连接!" "noInbounds" = "❗ 没有找到入站连接!"
"unlimited" = "♾ 无限制" "unlimited" = "♾ 无限制"
"add" = "添加" "add" = "添加"
@ -512,17 +514,17 @@
"inbounds" = "入站连接" "inbounds" = "入站连接"
"clients" = "客户端" "clients" = "客户端"
"offline" = "🔴 离线" "offline" = "🔴 离线"
"online" = "🟢 在线" "online" = "🟢 在线"
[tgbot.commands] [tgbot.commands]
"unknown" = "❗ 未知命令" "unknown" = "❗ 未知命令"
"pleaseChoose" = "👇 请选择:\r\n" "pleaseChoose" = "👇 请选择:\r\n"
"help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n" "help" = "🤖 欢迎使用本机器人!它旨在为您提供来自服务器的特定数据,并允许您进行必要的修改。\r\n\r\n"
"start" = "👋 你好,<i>{{ .Firstname }}</i>。\r\n" "start" = "👋 你好,<i>{{ .Firstname }}</i>。\r\n"
"welcome" = "🤖 欢迎来到<b>{{ .Hostname }}</b>管理机器人。\r\n" "welcome" = "🤖 欢迎来到 <b>{{ .Hostname }}</b> 管理机器人。\r\n"
"status" = "✅ 机器人正常运行!" "status" = "✅ 机器人正常运行!"
"usage" = "❗ 请输入要搜索的文本!" "usage" = "❗ 请输入要搜索的文本!"
"getID" = "🆔 您的ID为<code>{{ .ID }}</code>" "getID" = "🆔 您的 ID 为:<code>{{ .ID }}</code>"
"helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n\r\n搜索入站连接包含客户端统计信息\r\n<code>/inbound [Remark]</code>" "helpAdminCommands" = "搜索客户端邮箱:\r\n<code>/usage [Email]</code>\r\n\r\n搜索入站连接包含客户端统计信息\r\n<code>/inbound [Remark]</code>"
"helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n\r\n<code>/usage [Email]</code>" "helpClientCommands" = "要搜索统计信息,请使用以下命令:\r\n\r\n<code>/usage [Email]</code>"
@ -561,8 +563,8 @@
"download" = "🔽 下载↓:{{ .Download }}\r\n" "download" = "🔽 下载↓:{{ .Download }}\r\n"
"total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n" "total" = "📊 总计:{{ .UpDown }} / {{ .Total }}\r\n"
"TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n" "TGUser" = "👤 电报用户:{{ .TelegramID }}\r\n"
"exhaustedMsg" = "🚨 耗尽的{{ .Type }}\r\n" "exhaustedMsg" = "🚨 耗尽的 {{ .Type }}\r\n"
"exhaustedCount" = "🚨 耗尽的{{ .Type }}数量:\r\n" "exhaustedCount" = "🚨 耗尽的 {{ .Type }} 数量:\r\n"
"onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n" "onlinesCount" = "🌐 在线客户:{{ .Count }}\r\n"
"disabled" = "🛑 禁用:{{ .Disabled }}\r\n" "disabled" = "🛑 禁用:{{ .Disabled }}\r\n"
"depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n" "depleteSoon" = "🔜 即将耗尽:{{ .Deplete }}\r\n\r\n"
@ -585,7 +587,7 @@
"getInbounds" = "获取入站信息" "getInbounds" = "获取入站信息"
"depleteSoon" = "即将耗尽" "depleteSoon" = "即将耗尽"
"clientUsage" = "获取使用情况" "clientUsage" = "获取使用情况"
"onlines" = "在线客户" "onlines" = "在线客户"
"commands" = "命令" "commands" = "命令"
"refresh" = "🔄 刷新" "refresh" = "🔄 刷新"
"clearIPs" = "❌ 清除 IP" "clearIPs" = "❌ 清除 IP"
@ -601,11 +603,11 @@
"custom" = "🔢 风俗" "custom" = "🔢 风俗"
"confirmNumber" = "✅ 确认: {{ .Num }}" "confirmNumber" = "✅ 确认: {{ .Num }}"
"confirmNumberAdd" = "✅ 确认添加:{{ .Num }}" "confirmNumberAdd" = "✅ 确认添加:{{ .Num }}"
"limitTraffic" = "🚧 交通限制" "limitTraffic" = "🚧 流量限制"
"getBanLogs" = "禁止日志" "getBanLogs" = "禁止日志"
[tgbot.answers] [tgbot.answers]
"successfulOperation" = "✅ 成功" "successfulOperation" = "✅ 成功"
"errorOperation" = "❗ 操作错误。" "errorOperation" = "❗ 操作错误。"
"getInboundsFailed" = "❌ 获取入站信息失败。" "getInboundsFailed" = "❌ 获取入站信息失败。"
"canceled" = "❌ {{ .Email }}:操作已取消。" "canceled" = "❌ {{ .Email }}:操作已取消。"
@ -613,7 +615,7 @@
"IpRefreshSuccess" = "✅ {{ .Email }}IP 刷新成功。" "IpRefreshSuccess" = "✅ {{ .Email }}IP 刷新成功。"
"TGIdRefreshSuccess" = "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。" "TGIdRefreshSuccess" = "✅ {{ .Email }}:客户端的 Telegram 用户刷新成功。"
"resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。" "resetTrafficSuccess" = "✅ {{ .Email }}:流量已重置成功。"
"setTrafficLimitSuccess" = "✅ {{ .Email }} : 流量限制保存成功。" "setTrafficLimitSuccess" = "✅ {{ .Email }}: 流量限制保存成功。"
"expireResetSuccess" = "✅ {{ .Email }}:过期天数已重置成功。" "expireResetSuccess" = "✅ {{ .Email }}:过期天数已重置成功。"
"resetIpSuccess" = "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。" "resetIpSuccess" = "✅ {{ .Email }}:成功保存 IP 限制数量为 {{ .Count }}。"
"clearIpSuccess" = "✅ {{ .Email }}IP 已成功清除。" "clearIpSuccess" = "✅ {{ .Email }}IP 已成功清除。"
@ -622,4 +624,4 @@
"removedTGUserSuccess" = "✅ {{ .Email }}Telegram 用户已成功移除。" "removedTGUserSuccess" = "✅ {{ .Email }}Telegram 用户已成功移除。"
"enableSuccess" = "✅ {{ .Email }}:已成功启用。" "enableSuccess" = "✅ {{ .Email }}:已成功启用。"
"disableSuccess" = "✅ {{ .Email }}:已成功禁用。" "disableSuccess" = "✅ {{ .Email }}:已成功禁用。"
"askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户ID。\r\n\r\n您的用户ID<code>{{ .TgUserID }}</code>" "askToAddUserId" = "未找到您的配置!\r\n请向管理员询问在您的配置中使用您的 Telegram 用户 ID。\r\n\r\n您的用户 ID<code>{{ .TgUserID }}</code>"

View file

@ -13,6 +13,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"x-ui/config" "x-ui/config"
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
@ -295,7 +296,7 @@ func (s *Server) startTask() {
} }
func (s *Server) Start() (err error) { func (s *Server) Start() (err error) {
//This is an anonymous function, no function name // This is an anonymous function, no function name
defer func() { defer func() {
if err != nil { if err != nil {
s.Stop() s.Stop()

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"time" "time"
"x-ui/logger" "x-ui/logger"
"x-ui/util/common" "x-ui/util/common"
@ -162,8 +163,8 @@ func (x *XrayAPI) GetTraffic(reset bool) ([]*Traffic, []*ClientTraffic, error) {
if x.grpcClient == nil { if x.grpcClient == nil {
return nil, nil, common.NewError("xray api is not initialized") return nil, nil, common.NewError("xray api is not initialized")
} }
var trafficRegex = regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)") trafficRegex := regexp.MustCompile("(inbound|outbound)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
var ClientTrafficRegex = regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)") ClientTrafficRegex := regexp.MustCompile("(user)>>>([^>]+)>>>traffic>>>(downlink|uplink)")
client := *x.StatsServiceClient client := *x.StatsServiceClient
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)

View file

@ -2,22 +2,24 @@ package xray
import ( import (
"bytes" "bytes"
"x-ui/util/json_util" "x-ui/util/json_util"
) )
type Config struct { type Config struct {
LogConfig json_util.RawMessage `json:"log"` LogConfig json_util.RawMessage `json:"log"`
RouterConfig json_util.RawMessage `json:"routing"` RouterConfig json_util.RawMessage `json:"routing"`
DNSConfig json_util.RawMessage `json:"dns"` DNSConfig json_util.RawMessage `json:"dns"`
InboundConfigs []InboundConfig `json:"inbounds"` InboundConfigs []InboundConfig `json:"inbounds"`
OutboundConfigs json_util.RawMessage `json:"outbounds"` OutboundConfigs json_util.RawMessage `json:"outbounds"`
Transport json_util.RawMessage `json:"transport"` Transport json_util.RawMessage `json:"transport"`
Policy json_util.RawMessage `json:"policy"` Policy json_util.RawMessage `json:"policy"`
API json_util.RawMessage `json:"api"` API json_util.RawMessage `json:"api"`
Stats json_util.RawMessage `json:"stats"` Stats json_util.RawMessage `json:"stats"`
Reverse json_util.RawMessage `json:"reverse"` Reverse json_util.RawMessage `json:"reverse"`
FakeDNS json_util.RawMessage `json:"fakedns"` FakeDNS json_util.RawMessage `json:"fakedns"`
Observatory json_util.RawMessage `json:"observatory"` Observatory json_util.RawMessage `json:"observatory"`
BurstObservatory json_util.RawMessage `json:"burstObservatory"`
} }
func (c *Config) Equals(other *Config) bool { func (c *Config) Equals(other *Config) bool {

View file

@ -2,6 +2,7 @@ package xray
import ( import (
"bytes" "bytes"
"x-ui/util/json_util" "x-ui/util/json_util"
) )

View file

@ -3,6 +3,7 @@ package xray
import ( import (
"regexp" "regexp"
"strings" "strings"
"x-ui/logger" "x-ui/logger"
) )

View file

@ -202,7 +202,7 @@ func (p *process) Start() (err error) {
if err != nil { if err != nil {
return common.NewErrorf("Failed to generate xray configuration file: %v", err) return common.NewErrorf("Failed to generate xray configuration file: %v", err)
} }
err = os.MkdirAll(config.GetLogFolder(), 0770) err = os.MkdirAll(config.GetLogFolder(), 0770)
if err != nil { if err != nil {
logger.Warningf("Something went wrong: %s", err) logger.Warningf("Something went wrong: %s", err)