mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-08-23 03:16:52 +00:00
Merge branch 'main' into feature/multi-server-support
This commit is contained in:
commit
3299d15f28
65 changed files with 710 additions and 74153 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,6 +1,6 @@
|
||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: MHSanaei
|
||||||
patreon: # Replace with a single Patreon username
|
patreon: # Replace with a single Patreon username
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
71
.github/workflows/release.yml
vendored
71
.github/workflows/release.yml
vendored
|
@ -8,6 +8,7 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
|
- '.github/workflows/release.yml'
|
||||||
- '**.js'
|
- '**.js'
|
||||||
- '**.css'
|
- '**.css'
|
||||||
- '**.html'
|
- '**.html'
|
||||||
|
@ -31,7 +32,7 @@ jobs:
|
||||||
- 386
|
- 386
|
||||||
- armv5
|
- armv5
|
||||||
- s390x
|
- s390x
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -42,51 +43,37 @@ jobs:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Build 3X-UI
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabihf
|
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
|
||||||
sudo apt install gcc-i686-linux-gnu
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
|
||||||
sudo apt install gcc-arm-linux-gnueabi
|
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
|
||||||
sudo apt install gcc-s390x-linux-gnu
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build 3x-ui
|
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
export GOARCH=${{ matrix.platform }}
|
export GOARCH=${{ matrix.platform }}
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
|
||||||
export GOARCH=arm64
|
case "${{ matrix.platform }}" in
|
||||||
export CC=aarch64-linux-gnu-gcc
|
amd64) BOOTLIN_ARCH="x86-64" ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
arm64) BOOTLIN_ARCH="aarch64" ;;
|
||||||
export GOARCH=arm
|
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
|
||||||
export GOARM=7
|
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
386) BOOTLIN_ARCH="x86-i686" ;;
|
||||||
export GOARCH=arm
|
s390x) BOOTLIN_ARCH="s390x-z13" ;;
|
||||||
export GOARM=6
|
esac
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
|
||||||
export GOARCH=386
|
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
|
||||||
export CC=i686-linux-gnu-gcc
|
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
echo "Downloading: $TARBALL_URL"
|
||||||
export GOARCH=arm
|
cd /tmp
|
||||||
export GOARM=5
|
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
|
||||||
export CC=arm-linux-gnueabi-gcc
|
tar -xf "$(basename "$TARBALL_URL")"
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
|
||||||
export GOARCH=s390x
|
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
|
||||||
export CC=s390x-linux-gnu-gcc
|
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
|
||||||
fi
|
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
|
||||||
go build -ldflags "-w -s" -o xui-release -v main.go
|
cd -
|
||||||
|
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
|
||||||
|
file xui-release
|
||||||
|
ldd xui-release || echo "Static binary confirmed"
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/
|
cp xui-release x-ui/
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -29,9 +29,9 @@ main
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Ignore Go specific files
|
# Ignore Go build files
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
x-ui.db
|
||||||
|
|
||||||
# Ignore Docker specific files
|
# Ignore Docker specific files
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## النجوم عبر الزمن
|
## النجوم عبر الزمن
|
||||||
|
|
|
@ -48,7 +48,7 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## Estrellas a lo Largo del Tiempo
|
## Estrellas a lo Largo del Tiempo
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## ستارهها در طول زمان
|
## ستارهها در طول زمان
|
||||||
|
|
|
@ -48,7 +48,7 @@ For full documentation, please visit the [project Wiki](https://github.com/MHSan
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## Stargazers over Time
|
## Stargazers over Time
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## Звезды с течением времени
|
## Звезды с течением времени
|
||||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||||
|
|
||||||
## 随时间变化的星标数
|
## 随时间变化的星标数
|
||||||
|
|
|
@ -3,7 +3,10 @@ package config
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,12 +57,32 @@ func GetBinFolderPath() string {
|
||||||
return binFolderPath
|
return binFolderPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBaseDir() string {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
exeDir := filepath.Dir(exePath)
|
||||||
|
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
|
||||||
|
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return wd
|
||||||
|
}
|
||||||
|
return exeDir
|
||||||
|
}
|
||||||
|
|
||||||
func GetDBFolderPath() string {
|
func GetDBFolderPath() string {
|
||||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||||
if dbFolderPath == "" {
|
if dbFolderPath != "" {
|
||||||
dbFolderPath = "/etc/x-ui"
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
return dbFolderPath
|
if runtime.GOOS == "windows" {
|
||||||
|
return getBaseDir()
|
||||||
|
}
|
||||||
|
return "/etc/x-ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
|
@ -68,8 +91,54 @@ func GetDBPath() string {
|
||||||
|
|
||||||
func GetLogFolder() string {
|
func GetLogFolder() string {
|
||||||
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
||||||
if logFolderPath == "" {
|
if logFolderPath != "" {
|
||||||
logFolderPath = "/var/log"
|
return logFolderPath
|
||||||
}
|
}
|
||||||
return logFolderPath
|
if runtime.GOOS == "windows" {
|
||||||
|
return getBaseDir()
|
||||||
|
}
|
||||||
|
return "/var/log"
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if os.Getenv("XUI_DB_FOLDER") != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
oldDBFolder := "/etc/x-ui"
|
||||||
|
oldDBPath := fmt.Sprintf("%s/%s.db", oldDBFolder, GetName())
|
||||||
|
newDBFolder := GetDBFolderPath()
|
||||||
|
newDBPath := fmt.Sprintf("%s/%s.db", newDBFolder, GetName())
|
||||||
|
_, err := os.Stat(newDBPath)
|
||||||
|
if err == nil {
|
||||||
|
return // new exists
|
||||||
|
}
|
||||||
|
_, err = os.Stat(oldDBPath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return // old does not exist
|
||||||
|
}
|
||||||
|
_ = copyFile(oldDBPath, newDBPath) // ignore error
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.6.3
|
2.6.6
|
33
go.mod
33
go.mod
|
@ -9,7 +9,7 @@ require (
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/mymmrac/telego v0.32.0
|
github.com/mymmrac/telego v1.2.0
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
|
@ -19,8 +19,8 @@ require (
|
||||||
github.com/xlzd/gotp v0.1.0
|
github.com/xlzd/gotp v0.1.0
|
||||||
github.com/xtls/xray-core v1.250803.0
|
github.com/xtls/xray-core v1.250803.0
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/crypto v0.40.0
|
golang.org/x/crypto v0.41.0
|
||||||
golang.org/x/text v0.27.0
|
golang.org/x/text v0.28.0
|
||||||
google.golang.org/grpc v1.74.2
|
google.golang.org/grpc v1.74.2
|
||||||
gorm.io/driver/sqlite v1.6.0
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.30.1
|
gorm.io/gorm v1.30.1
|
||||||
|
@ -31,11 +31,9 @@ require (
|
||||||
github.com/bytedance/sonic v1.14.0 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/cloudflare/circl v1.6.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
github.com/ebitengine/purego v0.8.4 // indirect
|
||||||
github.com/fasthttp/router v1.5.4 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
@ -70,9 +68,8 @@ require (
|
||||||
github.com/refraction-networking/utls v1.8.0 // indirect
|
github.com/refraction-networking/utls v1.8.0 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagernet/sing v0.6.6 // indirect
|
github.com/sagernet/sing v0.7.5 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
|
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||||
|
@ -87,18 +84,18 @@ require (
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/mock v0.5.2 // indirect
|
go.uber.org/mock v0.5.2 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.19.0 // indirect
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
golang.org/x/mod v0.26.0 // indirect
|
golang.org/x/mod v0.27.0 // indirect
|
||||||
golang.org/x/net v0.42.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.34.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.12.0 // indirect
|
||||||
golang.org/x/tools v0.35.0 // indirect
|
golang.org/x/tools v0.36.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-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.7 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 // indirect
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
70
go.sum
70
go.sum
|
@ -4,14 +4,12 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
@ -21,8 +19,6 @@ github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mT
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8=
|
|
||||||
github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
|
@ -87,10 +83,8 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
@ -110,8 +104,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v0.32.0 h1:4X8C1l3k+opkk86r95+eQE8DxiS2LYlR61L/G7yreDY=
|
github.com/mymmrac/telego v1.2.0 h1:CHmR9eiugpTiF/ttppmK89E6mcu9d4DmNQS0tMpUs6M=
|
||||||
github.com/mymmrac/telego v0.32.0/go.mod h1:qS6NaRhJgcuEEBEMVCV79S2xCAuHq9O+ixwfLuRW31M=
|
github.com/mymmrac/telego v1.2.0/go.mod h1:OiCm4QjqB/ZY2E4VAmkVH8EeLLhM4QuFuO1KOCuvGoM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
|
@ -138,12 +132,10 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE=
|
github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc=
|
||||||
github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||||
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
|
|
||||||
github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
|
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||||
|
@ -153,7 +145,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
@ -207,14 +198,14 @@ go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -223,24 +214,24 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
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=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||||
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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
@ -254,8 +245,7 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5 h1:sfK5nHuG7lRFZ2FdTT3RimOqWBg8IrVm+/Vko1FVOsk=
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
||||||
gvisor.dev/gvisor v0.0.0-20250428193742-2d800c3129d5/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
||||||
|
|
13
install.sh
13
install.sh
|
@ -40,19 +40,6 @@ arch() {
|
||||||
|
|
||||||
echo "Arch: $(arch)"
|
echo "Arch: $(arch)"
|
||||||
|
|
||||||
check_glibc_version() {
|
|
||||||
glibc_version=$(ldd --version | head -n1 | awk '{print $NF}')
|
|
||||||
|
|
||||||
required_version="2.32"
|
|
||||||
if [[ "$(printf '%s\n' "$required_version" "$glibc_version" | sort -V | head -n1)" != "$required_version" ]]; then
|
|
||||||
echo -e "${red}GLIBC version $glibc_version is too old! Required: 2.32 or higher${plain}"
|
|
||||||
echo "Please upgrade to a newer version of your operating system to get a higher GLIBC version."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "GLIBC version: $glibc_version (meets requirement of 2.32+)"
|
|
||||||
}
|
|
||||||
check_glibc_version
|
|
||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +0,0 @@
|
||||||
@import "../lib/style/index.less";
|
|
||||||
@import "../lib/style/components.less";
|
|
||||||
|
|
||||||
@green-6: #008771;
|
|
||||||
@primary-color: @green-6;
|
|
||||||
@border-radius-base: 1rem;
|
|
||||||
@progress-remaining-color: #EDEDED;
|
|
File diff suppressed because one or more lines are too long
4
web/assets/axios/axios.min.js
vendored
4
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -560,6 +560,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
enableSessionResumption = false,
|
enableSessionResumption = false,
|
||||||
certificates = [new TlsStreamSettings.Cert()],
|
certificates = [new TlsStreamSettings.Cert()],
|
||||||
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
alpn = [ALPN_OPTION.H2, ALPN_OPTION.HTTP1],
|
||||||
|
echServerKeys = '',
|
||||||
|
echForceQuery = 'none',
|
||||||
settings = new TlsStreamSettings.Settings()
|
settings = new TlsStreamSettings.Settings()
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
@ -573,6 +575,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
this.enableSessionResumption = enableSessionResumption;
|
this.enableSessionResumption = enableSessionResumption;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
|
this.echServerKeys = echServerKeys;
|
||||||
|
this.echForceQuery = echForceQuery;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,7 +596,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.serverName, json.settings.domains);
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure, json.settings.fingerprint, json.settings.echConfigList);
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
|
@ -605,6 +609,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
json.enableSessionResumption,
|
json.enableSessionResumption,
|
||||||
certs,
|
certs,
|
||||||
json.alpn,
|
json.alpn,
|
||||||
|
json.echServerKeys,
|
||||||
|
json.echForceQuery,
|
||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -621,6 +627,8 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
enableSessionResumption: this.enableSessionResumption,
|
enableSessionResumption: this.enableSessionResumption,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
|
echServerKeys: this.echServerKeys,
|
||||||
|
echForceQuery: this.echForceQuery,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -633,7 +641,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
keyFile = '',
|
keyFile = '',
|
||||||
certificate = '',
|
certificate = '',
|
||||||
key = '',
|
key = '',
|
||||||
ocspStapling = 0,
|
|
||||||
oneTimeLoading = false,
|
oneTimeLoading = false,
|
||||||
usage = USAGE_OPTION.ENCIPHERMENT,
|
usage = USAGE_OPTION.ENCIPHERMENT,
|
||||||
buildChain = false,
|
buildChain = false,
|
||||||
|
@ -644,7 +651,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
this.keyFile = keyFile;
|
this.keyFile = keyFile;
|
||||||
this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate;
|
this.cert = Array.isArray(certificate) ? certificate.join('\n') : certificate;
|
||||||
this.key = Array.isArray(key) ? key.join('\n') : key;
|
this.key = Array.isArray(key) ? key.join('\n') : key;
|
||||||
this.ocspStapling = ocspStapling;
|
|
||||||
this.oneTimeLoading = oneTimeLoading;
|
this.oneTimeLoading = oneTimeLoading;
|
||||||
this.usage = usage;
|
this.usage = usage;
|
||||||
this.buildChain = buildChain
|
this.buildChain = buildChain
|
||||||
|
@ -656,7 +662,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
true,
|
true,
|
||||||
json.certificateFile,
|
json.certificateFile,
|
||||||
json.keyFile, '', '',
|
json.keyFile, '', '',
|
||||||
json.ocspStapling,
|
|
||||||
json.oneTimeLoading,
|
json.oneTimeLoading,
|
||||||
json.usage,
|
json.usage,
|
||||||
json.buildChain,
|
json.buildChain,
|
||||||
|
@ -666,7 +671,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
false, '', '',
|
false, '', '',
|
||||||
json.certificate.join('\n'),
|
json.certificate.join('\n'),
|
||||||
json.key.join('\n'),
|
json.key.join('\n'),
|
||||||
json.ocspStapling,
|
|
||||||
json.oneTimeLoading,
|
json.oneTimeLoading,
|
||||||
json.usage,
|
json.usage,
|
||||||
json.buildChain,
|
json.buildChain,
|
||||||
|
@ -679,7 +683,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
return {
|
return {
|
||||||
certificateFile: this.certFile,
|
certificateFile: this.certFile,
|
||||||
keyFile: this.keyFile,
|
keyFile: this.keyFile,
|
||||||
ocspStapling: this.ocspStapling,
|
|
||||||
oneTimeLoading: this.oneTimeLoading,
|
oneTimeLoading: this.oneTimeLoading,
|
||||||
usage: this.usage,
|
usage: this.usage,
|
||||||
buildChain: this.buildChain,
|
buildChain: this.buildChain,
|
||||||
|
@ -688,7 +691,6 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
return {
|
return {
|
||||||
certificate: this.cert.split('\n'),
|
certificate: this.cert.split('\n'),
|
||||||
key: this.key.split('\n'),
|
key: this.key.split('\n'),
|
||||||
ocspStapling: this.ocspStapling,
|
|
||||||
oneTimeLoading: this.oneTimeLoading,
|
oneTimeLoading: this.oneTimeLoading,
|
||||||
usage: this.usage,
|
usage: this.usage,
|
||||||
buildChain: this.buildChain,
|
buildChain: this.buildChain,
|
||||||
|
@ -701,21 +703,25 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
allowInsecure = false,
|
allowInsecure = false,
|
||||||
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
fingerprint = UTLS_FINGERPRINT.UTLS_CHROME,
|
||||||
|
echConfigList = '',
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
|
this.echConfigList = echConfigList;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
|
json.echConfigList,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
|
echConfigList: this.echConfigList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1375,6 +1381,9 @@ class Inbound extends XrayCommonClass {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||||
|
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||||
|
}
|
||||||
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
if (type == "tcp" && !ObjectUtil.isEmpty(flow)) {
|
||||||
params.set("flow", flow);
|
params.set("flow", flow);
|
||||||
}
|
}
|
||||||
|
@ -1474,6 +1483,9 @@ class Inbound extends XrayCommonClass {
|
||||||
if (this.stream.tls.settings.allowInsecure) {
|
if (this.stream.tls.settings.allowInsecure) {
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||||
|
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||||
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
|
@ -1552,6 +1564,9 @@ class Inbound extends XrayCommonClass {
|
||||||
if (this.stream.tls.settings.allowInsecure) {
|
if (this.stream.tls.settings.allowInsecure) {
|
||||||
params.set("allowInsecure", "1");
|
params.set("allowInsecure", "1");
|
||||||
}
|
}
|
||||||
|
if (this.stream.tls.settings.echConfigList?.length > 0) {
|
||||||
|
params.set("ech", this.stream.tls.settings.echConfigList);
|
||||||
|
}
|
||||||
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
if (!ObjectUtil.isEmpty(this.stream.tls.sni)) {
|
||||||
params.set("sni", this.stream.tls.sni);
|
params.set("sni", this.stream.tls.sni);
|
||||||
}
|
}
|
||||||
|
@ -1695,7 +1710,7 @@ class Inbound extends XrayCommonClass {
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
let streamSettings;
|
let streamSettings;
|
||||||
if (this.canEnableStream()) {
|
if (this.canEnableStream() || this.stream?.sockopt) {
|
||||||
streamSettings = this.stream.toJson();
|
streamSettings = this.stream.toJson();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
@ -2291,12 +2306,14 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
protocol,
|
protocol,
|
||||||
address,
|
address,
|
||||||
port,
|
port,
|
||||||
|
portMap = [],
|
||||||
network = 'tcp,udp',
|
network = 'tcp,udp',
|
||||||
followRedirect = false
|
followRedirect = false
|
||||||
) {
|
) {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
this.portMap = portMap;
|
||||||
this.network = network;
|
this.network = network;
|
||||||
this.followRedirect = followRedirect;
|
this.followRedirect = followRedirect;
|
||||||
}
|
}
|
||||||
|
@ -2306,6 +2323,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
Protocols.DOKODEMO,
|
Protocols.DOKODEMO,
|
||||||
json.address,
|
json.address,
|
||||||
json.port,
|
json.port,
|
||||||
|
XrayCommonClass.toHeaders(json.portMap),
|
||||||
json.network,
|
json.network,
|
||||||
json.followRedirect,
|
json.followRedirect,
|
||||||
);
|
);
|
||||||
|
@ -2315,6 +2333,7 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
||||||
return {
|
return {
|
||||||
address: this.address,
|
address: this.address,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
|
portMap: XrayCommonClass.toV2Headers(this.portMap, false),
|
||||||
network: this.network,
|
network: this.network,
|
||||||
followRedirect: this.followRedirect,
|
followRedirect: this.followRedirect,
|
||||||
};
|
};
|
||||||
|
|
|
@ -354,13 +354,15 @@ class TlsStreamSettings extends CommonClass {
|
||||||
serverName = '',
|
serverName = '',
|
||||||
alpn = [],
|
alpn = [],
|
||||||
fingerprint = '',
|
fingerprint = '',
|
||||||
allowInsecure = false
|
allowInsecure = false,
|
||||||
|
echConfigList = '',
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
|
this.echConfigList = echConfigList;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
@ -369,6 +371,7 @@ class TlsStreamSettings extends CommonClass {
|
||||||
json.alpn,
|
json.alpn,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
|
json.echConfigList,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,6 +381,7 @@ class TlsStreamSettings extends CommonClass {
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
|
echConfigList: this.echConfigList
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -782,7 +786,8 @@ class Outbound extends CommonClass {
|
||||||
let alpn = url.searchParams.get('alpn');
|
let alpn = url.searchParams.get('alpn');
|
||||||
let allowInsecure = url.searchParams.get('allowInsecure');
|
let allowInsecure = url.searchParams.get('allowInsecure');
|
||||||
let sni = url.searchParams.get('sni') ?? '';
|
let sni = url.searchParams.get('sni') ?? '';
|
||||||
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1);
|
let ech = url.searchParams.get('ech') ?? '';
|
||||||
|
stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, allowInsecure == 1, ech);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (security == 'reality') {
|
if (security == 'reality') {
|
||||||
|
|
|
@ -7,7 +7,7 @@ class AllSetting {
|
||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
this.webBasePath = "/";
|
this.webBasePath = "/";
|
||||||
this.sessionMaxAge = 60;
|
this.sessionMaxAge = 360;
|
||||||
this.pageSize = 50;
|
this.pageSize = 50;
|
||||||
this.expireDiff = 0;
|
this.expireDiff = 0;
|
||||||
this.trafficDiff = 0;
|
this.trafficDiff = 0;
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
module.exports = require('./vue.common.prod.js')
|
|
||||||
} else {
|
|
||||||
module.exports = require('./vue.common.dev.js')
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
6
web/assets/vue/vue.esm.browser.min.js
vendored
6
web/assets/vue/vue.esm.browser.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
11932
web/assets/vue/vue.js
11932
web/assets/vue/vue.js
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
module.exports = require('./vue.runtime.common.prod.js')
|
|
||||||
} else {
|
|
||||||
module.exports = require('./vue.runtime.common.dev.js')
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
6
web/assets/vue/vue.runtime.min.js
vendored
6
web/assets/vue/vue.runtime.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,76 +0,0 @@
|
||||||
import Vue from './vue.runtime.common.js'
|
|
||||||
export default Vue
|
|
||||||
|
|
||||||
// this should be kept in sync with src/v3/index.ts
|
|
||||||
export const {
|
|
||||||
version,
|
|
||||||
|
|
||||||
// refs
|
|
||||||
ref,
|
|
||||||
shallowRef,
|
|
||||||
isRef,
|
|
||||||
toRef,
|
|
||||||
toRefs,
|
|
||||||
unref,
|
|
||||||
proxyRefs,
|
|
||||||
customRef,
|
|
||||||
triggerRef,
|
|
||||||
computed,
|
|
||||||
|
|
||||||
// reactive
|
|
||||||
reactive,
|
|
||||||
isReactive,
|
|
||||||
isReadonly,
|
|
||||||
isShallow,
|
|
||||||
isProxy,
|
|
||||||
shallowReactive,
|
|
||||||
markRaw,
|
|
||||||
toRaw,
|
|
||||||
readonly,
|
|
||||||
shallowReadonly,
|
|
||||||
|
|
||||||
// watch
|
|
||||||
watch,
|
|
||||||
watchEffect,
|
|
||||||
watchPostEffect,
|
|
||||||
watchSyncEffect,
|
|
||||||
|
|
||||||
// effectScope
|
|
||||||
effectScope,
|
|
||||||
onScopeDispose,
|
|
||||||
getCurrentScope,
|
|
||||||
|
|
||||||
// provide / inject
|
|
||||||
provide,
|
|
||||||
inject,
|
|
||||||
|
|
||||||
// lifecycle
|
|
||||||
onBeforeMount,
|
|
||||||
onMounted,
|
|
||||||
onBeforeUpdate,
|
|
||||||
onUpdated,
|
|
||||||
onBeforeUnmount,
|
|
||||||
onUnmounted,
|
|
||||||
onErrorCaptured,
|
|
||||||
onActivated,
|
|
||||||
onDeactivated,
|
|
||||||
onServerPrefetch,
|
|
||||||
onRenderTracked,
|
|
||||||
onRenderTriggered,
|
|
||||||
|
|
||||||
// v2 only
|
|
||||||
set,
|
|
||||||
del,
|
|
||||||
|
|
||||||
// v3 compat
|
|
||||||
h,
|
|
||||||
getCurrentInstance,
|
|
||||||
useSlots,
|
|
||||||
useAttrs,
|
|
||||||
mergeDefaults,
|
|
||||||
nextTick,
|
|
||||||
useCssModule,
|
|
||||||
useCssVars,
|
|
||||||
defineComponent,
|
|
||||||
defineAsyncComponent
|
|
||||||
} = Vue
|
|
|
@ -120,8 +120,8 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundCreateSuccess"), inbound, nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,8 +138,8 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
||||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundDeleteSuccess"), id, nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,8 +164,8 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, err)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), inbound, nil)
|
||||||
if err == nil && needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,25 +354,25 @@ func (a *InboundController) onlines(c *gin.Context) {
|
||||||
|
|
||||||
func (a *InboundController) updateClientTraffic(c *gin.Context) {
|
func (a *InboundController) updateClientTraffic(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
// Define the request structure for traffic update
|
// Define the request structure for traffic update
|
||||||
type TrafficUpdateRequest struct {
|
type TrafficUpdateRequest struct {
|
||||||
Upload int64 `json:"upload"`
|
Upload int64 `json:"upload"`
|
||||||
Download int64 `json:"download"`
|
Download int64 `json:"download"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var request TrafficUpdateRequest
|
var request TrafficUpdateRequest
|
||||||
err := c.ShouldBindJSON(&request)
|
err := c.ShouldBindJSON(&request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundUpdateSuccess"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.UpdateClientTrafficByEmail(email, request.Upload, request.Download)
|
err = a.inboundService.UpdateClientTrafficByEmail(email, request.Upload, request.Download)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.inboundClientUpdateSuccess"), nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
|
||||||
type ServerController struct {
|
type ServerController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
serverService service.ServerService
|
serverService service.ServerService
|
||||||
|
settingService service.SettingService
|
||||||
|
|
||||||
lastStatus *service.Status
|
lastStatus *service.Status
|
||||||
lastGetStatusTime time.Time
|
lastGetStatusTime time.Time
|
||||||
|
@ -44,13 +45,16 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g.POST("/stopXrayService", a.stopXrayService)
|
g.POST("/stopXrayService", a.stopXrayService)
|
||||||
g.POST("/restartXrayService", a.restartXrayService)
|
g.POST("/restartXrayService", a.restartXrayService)
|
||||||
g.POST("/installXray/:version", a.installXray)
|
g.POST("/installXray/:version", a.installXray)
|
||||||
|
g.POST("/updateGeofile", a.updateGeofile)
|
||||||
g.POST("/updateGeofile/:fileName", a.updateGeofile)
|
g.POST("/updateGeofile/:fileName", a.updateGeofile)
|
||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
|
g.POST("/xraylogs/:count", a.getXrayLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
g.POST("/getConfigJson", a.getConfigJson)
|
||||||
g.GET("/getDb", a.getDb)
|
g.GET("/getDb", a.getDb)
|
||||||
g.POST("/importDB", a.importDB)
|
g.POST("/importDB", a.importDB)
|
||||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||||
g.POST("/getNewmldsa65", a.getNewmldsa65)
|
g.POST("/getNewmldsa65", a.getNewmldsa65)
|
||||||
|
g.POST("/getNewEchCert", a.getNewEchCert)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
|
@ -133,6 +137,50 @@ func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
jsonObj(c, logs, nil)
|
jsonObj(c, logs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getXrayLogs(c *gin.Context) {
|
||||||
|
count := c.Param("count")
|
||||||
|
filter := c.PostForm("filter")
|
||||||
|
showDirect := c.PostForm("showDirect")
|
||||||
|
showBlocked := c.PostForm("showBlocked")
|
||||||
|
showProxy := c.PostForm("showProxy")
|
||||||
|
|
||||||
|
var freedoms []string
|
||||||
|
var blackholes []string
|
||||||
|
|
||||||
|
//getting tags for freedom and blackhole outbounds
|
||||||
|
config, err := a.settingService.GetDefaultXrayConfig()
|
||||||
|
if err == nil && config != nil {
|
||||||
|
if cfgMap, ok := config.(map[string]interface{}); ok {
|
||||||
|
if outbounds, ok := cfgMap["outbounds"].([]interface{}); ok {
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
if obMap, ok := outbound.(map[string]interface{}); ok {
|
||||||
|
switch obMap["protocol"] {
|
||||||
|
case "freedom":
|
||||||
|
if tag, ok := obMap["tag"].(string); ok {
|
||||||
|
freedoms = append(freedoms, tag)
|
||||||
|
}
|
||||||
|
case "blackhole":
|
||||||
|
if tag, ok := obMap["tag"].(string); ok {
|
||||||
|
blackholes = append(blackholes, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(freedoms) == 0 {
|
||||||
|
freedoms = []string{"direct"}
|
||||||
|
}
|
||||||
|
if len(blackholes) == 0 {
|
||||||
|
blackholes = []string{"blocked"}
|
||||||
|
}
|
||||||
|
|
||||||
|
logs := a.serverService.GetXrayLogs(count, filter, showDirect, showBlocked, showProxy, freedoms, blackholes)
|
||||||
|
jsonObj(c, logs, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ServerController) getConfigJson(c *gin.Context) {
|
func (a *ServerController) getConfigJson(c *gin.Context) {
|
||||||
configJson, err := a.serverService.GetConfigJson()
|
configJson, err := a.serverService.GetConfigJson()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -208,3 +256,13 @@ func (a *ServerController) getNewmldsa65(c *gin.Context) {
|
||||||
}
|
}
|
||||||
jsonObj(c, cert, nil)
|
jsonObj(c, cert, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerController) getNewEchCert(c *gin.Context) {
|
||||||
|
sni := c.PostForm("sni")
|
||||||
|
cert, err := a.serverService.GetNewEchCert(sni)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "get ech certificate", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, cert, nil)
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
<a-form-item v-if="client.email && app.subSettings?.enable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
|
|
@ -6,6 +6,19 @@
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.portMap"}}'>
|
||||||
|
<a-button size="small" @click="inbound.settings.portMap.push({name: '', value: ''})">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
|
<a-input-group compact v-for="(pm, index) in inbound.settings.portMap">
|
||||||
|
<a-input style="width: 50%" v-model.trim="pm.name" placeholder='{{ i18n "pages.inbounds.port"}}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="pm.value" placeholder='{{ i18n "pages.inbounds.targetAddress" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="inbound.settings.portMap.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP,UDP</a-select-option>
|
||||||
|
@ -17,4 +30,8 @@
|
||||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<!-- sockopt -->
|
||||||
|
<template>
|
||||||
|
{{template "form/streamSockopt"}}
|
||||||
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -91,9 +91,6 @@
|
||||||
<a-textarea v-model="cert.key"></a-textarea>
|
<a-textarea v-model="cert.key"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<a-form-item label='OCSP stapling'>
|
|
||||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="One Time Loading">
|
<a-form-item label="One Time Loading">
|
||||||
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
<a-switch v-model="cert.oneTimeLoading"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -106,6 +103,21 @@
|
||||||
<a-switch v-model="cert.buildChain"></a-switch>
|
<a-switch v-model="cert.buildChain"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<a-form-item label='ECH key'>
|
||||||
|
<a-input v-model="inbound.stream.tls.echServerKeys"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='ECH config'>
|
||||||
|
<a-input v-model="inbound.stream.tls.settings.echConfigList"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='ECH force query'>
|
||||||
|
<a-select v-model="inbound.stream.tls.echForceQuery"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="key in ['none', 'half', 'full']" :value="key">[[ key ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label=" ">
|
||||||
|
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
|
||||||
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
|
|
|
@ -405,9 +405,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -419,9 +419,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -433,9 +433,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -447,9 +447,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -534,9 +534,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -548,9 +548,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -562,9 +562,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -576,9 +576,9 @@
|
||||||
<span>[[ clientEmail ]]</span>
|
<span>[[ clientEmail ]]</span>
|
||||||
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
<a-tooltip :overlay-class-name="themeSwitcher.currentTheme">
|
||||||
<template #title>
|
<template #title>
|
||||||
[[ getClientWithComment(clientEmail, dbInbound.id).comment ]]
|
[[ clientCount[dbInbound.id].comments.get(clientEmail) ]]
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="message" v-if="getClientWithComment(clientEmail, dbInbound.id).comment"></a-icon>
|
<a-icon type="message" v-if="clientCount[dbInbound.id].comments.get(clientEmail)"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -811,17 +811,6 @@
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
this.loadingStates.spinning = spinning;
|
this.loadingStates.spinning = spinning;
|
||||||
},
|
},
|
||||||
getClientWithComment(email, inboundId) {
|
|
||||||
const dbInbound = this.dbInbounds.find(inbound => inbound.id === inboundId);
|
|
||||||
if (!dbInbound) return { email, comment: '' };
|
|
||||||
|
|
||||||
const inboundSettings = JSON.parse(dbInbound.settings);
|
|
||||||
if (inboundSettings.clients) {
|
|
||||||
const client = inboundSettings.clients.find(c => c.email === email);
|
|
||||||
return client ? { email: client.email, comment: client.comment || '' } : { email, comment: '' };
|
|
||||||
}
|
|
||||||
return { email, comment: '' };
|
|
||||||
},
|
|
||||||
async getDBInbounds() {
|
async getDBInbounds() {
|
||||||
this.refreshing = true;
|
this.refreshing = true;
|
||||||
const msg = await HttpUtil.post('/panel/inbound/list');
|
const msg = await HttpUtil.post('/panel/inbound/list');
|
||||||
|
@ -893,7 +882,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [], online = [], comments = new Map();
|
||||||
clients = inbound.clients;
|
clients = inbound.clients;
|
||||||
clientStats = dbInbound.clientStats
|
clientStats = dbInbound.clientStats
|
||||||
now = new Date().getTime()
|
now = new Date().getTime()
|
||||||
|
@ -901,6 +890,9 @@
|
||||||
clientCount = clients.length;
|
clientCount = clients.length;
|
||||||
if (dbInbound.enable) {
|
if (dbInbound.enable) {
|
||||||
clients.forEach(client => {
|
clients.forEach(client => {
|
||||||
|
if (client.comment) {
|
||||||
|
comments.set(client.email, client.comment)
|
||||||
|
}
|
||||||
if (client.enable) {
|
if (client.enable) {
|
||||||
active.push(client.email);
|
active.push(client.email);
|
||||||
if (this.isClientOnline(client.email)) online.push(client.email);
|
if (this.isClientOnline(client.email)) online.push(client.email);
|
||||||
|
@ -929,6 +921,7 @@
|
||||||
depleted: depleted,
|
depleted: depleted,
|
||||||
expiring: expiring,
|
expiring: expiring,
|
||||||
online: online,
|
online: online,
|
||||||
|
comments: comments,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1126,7 +1119,11 @@
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()){
|
||||||
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
} else if (inbound.stream?.sockopt) {
|
||||||
|
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||||
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
data.allocate = inbound.allocate.toString();
|
data.allocate = inbound.allocate.toString();
|
||||||
|
|
||||||
|
@ -1146,7 +1143,11 @@
|
||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()){
|
||||||
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
} else if (inbound.stream?.sockopt) {
|
||||||
|
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||||
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
data.allocate = inbound.allocate.toString();
|
data.allocate = inbound.allocate.toString();
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,7 @@
|
||||||
<span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
<span>{{ i18n "pages.index.xrayErrorPopoverTitle" }}</span>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col>
|
<a-col>
|
||||||
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-tag>
|
<a-icon type="bars" :style="{ cursor: 'pointer', float: 'right' }" @click="openLogs()"></a-icon>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</span>
|
</span>
|
||||||
|
@ -179,6 +179,10 @@
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
|
<a-space v-if="app.ipLimitEnable" direction="horizontal" @click="openXrayLogs()" :style="{ justifyContent: 'center' }">
|
||||||
|
<a-icon type="bars"></a-icon>
|
||||||
|
<span v-if="!isMobile">{{ i18n "pages.index.logs" }}</span>
|
||||||
|
</a-space>
|
||||||
<a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
|
<a-space direction="horizontal" @click="stopXrayService" :style="{ justifyContent: 'center' }">
|
||||||
<a-icon type="poweroff"></a-icon>
|
<a-icon type="poweroff"></a-icon>
|
||||||
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
|
<span v-if="!isMobile">{{ i18n "pages.index.stopXray" }}</span>
|
||||||
|
@ -376,6 +380,9 @@
|
||||||
<a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/>
|
<a-icon type="reload" @click="updateGeofile(file)" :style="{ marginRight: '8px' }"/>
|
||||||
</a-list-item>
|
</a-list-item>
|
||||||
</a-list>
|
</a-list>
|
||||||
|
<div style="margin-top: 5px; display: flex; justify-content: flex-end;">
|
||||||
|
<a-button @click="updateGeofile('')">{{ i18n "pages.index.geofilesUpdateAll" }}</a-button>
|
||||||
|
</div>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
@ -422,6 +429,48 @@
|
||||||
</a-form>
|
</a-form>
|
||||||
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
|
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="logModal.formattedLogs"></div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
<a-modal id="xraylog-modal"
|
||||||
|
v-model="xraylogModal.visible"
|
||||||
|
:closable="true" @cancel="() => xraylogModal.visible = false"
|
||||||
|
:class="themeSwitcher.currentTheme"
|
||||||
|
width="80vw"
|
||||||
|
footer="">
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.logs" }}
|
||||||
|
<a-icon :spin="xraylogModal.loading"
|
||||||
|
type="sync"
|
||||||
|
:style="{ verticalAlign: 'middle', marginLeft: '10px' }"
|
||||||
|
:disabled="xraylogModal.loading"
|
||||||
|
@click="openXrayLogs()">
|
||||||
|
</a-icon>
|
||||||
|
</template>
|
||||||
|
<a-form layout="inline">
|
||||||
|
<a-form-item :style="{ marginRight: '0.5rem' }">
|
||||||
|
<a-input-group compact>
|
||||||
|
<a-select size="small" v-model="xraylogModal.rows" :style="{ width: '70px' }"
|
||||||
|
@change="openXrayLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option value="10">10</a-select-option>
|
||||||
|
<a-select-option value="20">20</a-select-option>
|
||||||
|
<a-select-option value="50">50</a-select-option>
|
||||||
|
<a-select-option value="100">100</a-select-option>
|
||||||
|
<a-select-option value="500">500</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="Filter:">
|
||||||
|
<a-input size="small" v-model="xraylogModal.filter" @keyup.enter="openXrayLogs()"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-checkbox v-model="xraylogModal.showDirect" @change="openXrayLogs()">Direct</a-checkbox>
|
||||||
|
<a-checkbox v-model="xraylogModal.showBlocked" @change="openXrayLogs()">Blocked</a-checkbox>
|
||||||
|
<a-checkbox v-model="xraylogModal.showProxy" @change="openXrayLogs()">Proxy</a-checkbox>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :style="{ float: 'right' }">
|
||||||
|
<a-button type="primary" icon="download" @click="FileManager.downloadTextFile(xraylogModal.logs?.join('\n'), 'x-ui.log')"></a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<div class="ant-input" :style="{ height: 'auto', maxHeight: '500px', overflow: 'auto', marginTop: '0.5rem' }" v-html="xraylogModal.formattedLogs"></div>
|
||||||
|
</a-modal>
|
||||||
<a-modal id="backup-modal"
|
<a-modal id="backup-modal"
|
||||||
v-model="backupModal.visible"
|
v-model="backupModal.visible"
|
||||||
title='{{ i18n "pages.index.backupTitle" }}'
|
title='{{ i18n "pages.index.backupTitle" }}'
|
||||||
|
@ -606,6 +655,60 @@
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const xraylogModal = {
|
||||||
|
visible: false,
|
||||||
|
logs: [],
|
||||||
|
rows: 20,
|
||||||
|
showDirect: true,
|
||||||
|
showBlocked: true,
|
||||||
|
showProxy: true,
|
||||||
|
loading: false,
|
||||||
|
show(logs) {
|
||||||
|
this.visible = true;
|
||||||
|
this.logs = logs;
|
||||||
|
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||||
|
},
|
||||||
|
formatLogs(logs) {
|
||||||
|
let formattedLogs = '';
|
||||||
|
|
||||||
|
logs.forEach((log, index) => {
|
||||||
|
if(index > 0) formattedLogs += '<br>';
|
||||||
|
|
||||||
|
const parts = log.split(' ');
|
||||||
|
|
||||||
|
if(parts.length === 10) {
|
||||||
|
const dateTime = `<b>${parts[0]} ${parts[1]}</b>`;
|
||||||
|
const from = `<b>${parts[3]}</b>`;
|
||||||
|
const to = `<b>${parts[5].replace(/^\/+/, "")}</b>`;
|
||||||
|
|
||||||
|
let outboundColor = '';
|
||||||
|
if (parts[9] === "b") {
|
||||||
|
outboundColor = ' style="color: #e04141;"'; //red for blocked
|
||||||
|
}
|
||||||
|
else if (parts[9] === "p") {
|
||||||
|
outboundColor = ' style="color: #3c89e8;"'; //blue for proxies
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedLogs += `<span${outboundColor}>
|
||||||
|
${dateTime}
|
||||||
|
${parts[2]}
|
||||||
|
${from}
|
||||||
|
${parts[4]}
|
||||||
|
${to}
|
||||||
|
${parts.slice(6, 9).join(' ')}
|
||||||
|
</span>`;
|
||||||
|
} else {
|
||||||
|
formattedLogs += `<span>${log}</span>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedLogs;
|
||||||
|
},
|
||||||
|
hide() {
|
||||||
|
this.visible = false;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const backupModal = {
|
const backupModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
show() {
|
show() {
|
||||||
|
@ -629,10 +732,12 @@
|
||||||
status: new Status(),
|
status: new Status(),
|
||||||
versionModal,
|
versionModal,
|
||||||
logModal,
|
logModal,
|
||||||
|
xraylogModal,
|
||||||
backupModal,
|
backupModal,
|
||||||
loadingTip: '{{ i18n "loading"}}',
|
loadingTip: '{{ i18n "loading"}}',
|
||||||
showAlert: false,
|
showAlert: false,
|
||||||
showIp: false
|
showIp: false,
|
||||||
|
ipLimitEnable: false,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning, tip = '{{ i18n "loading"}}') {
|
loading(spinning, tip = '{{ i18n "loading"}}') {
|
||||||
|
@ -681,16 +786,22 @@
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateGeofile(fileName) {
|
updateGeofile(fileName) {
|
||||||
|
const isSingleFile = !!fileName;
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
|
title: '{{ i18n "pages.index.geofileUpdateDialog" }}',
|
||||||
content: '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName),
|
content: isSingleFile
|
||||||
|
? '{{ i18n "pages.index.geofileUpdateDialogDesc" }}'.replace("#filename#", fileName)
|
||||||
|
: '{{ i18n "pages.index.geofilesUpdateDialogDesc" }}',
|
||||||
okText: '{{ i18n "confirm"}}',
|
okText: '{{ i18n "confirm"}}',
|
||||||
class: themeSwitcher.currentTheme,
|
class: themeSwitcher.currentTheme,
|
||||||
cancelText: '{{ i18n "cancel"}}',
|
cancelText: '{{ i18n "cancel"}}',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
versionModal.hide();
|
versionModal.hide();
|
||||||
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
this.loading(true, '{{ i18n "pages.index.dontRefresh"}}');
|
||||||
await HttpUtil.post(`/server/updateGeofile/${fileName}`);
|
const url = isSingleFile
|
||||||
|
? `/server/updateGeofile/${fileName}`
|
||||||
|
: `/server/updateGeofile`;
|
||||||
|
await HttpUtil.post(url);
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -721,6 +832,16 @@
|
||||||
await PromiseUtil.sleep(500);
|
await PromiseUtil.sleep(500);
|
||||||
logModal.loading = false;
|
logModal.loading = false;
|
||||||
},
|
},
|
||||||
|
async openXrayLogs(){
|
||||||
|
xraylogModal.loading = true;
|
||||||
|
const msg = await HttpUtil.post('server/xraylogs/'+xraylogModal.rows,{filter: xraylogModal.filter, showDirect: xraylogModal.showDirect, showBlocked: xraylogModal.showBlocked, showProxy: xraylogModal.showProxy});
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xraylogModal.show(msg.obj);
|
||||||
|
await PromiseUtil.sleep(500);
|
||||||
|
xraylogModal.loading = false;
|
||||||
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
const msg = await HttpUtil.post('server/getConfigJson');
|
||||||
|
@ -773,6 +894,12 @@
|
||||||
if (window.location.protocol !== "https:") {
|
if (window.location.protocol !== "https:") {
|
||||||
this.showAlert = true;
|
this.showAlert = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const msg = await HttpUtil.post('/panel/setting/defaultSettings');
|
||||||
|
if (msg.success) {
|
||||||
|
this.ipLimitEnable = msg.obj.ipLimitEnable;
|
||||||
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="app.subSettings.enable">
|
<a-form-item v-if="app.subSettings?.enable">
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
|
|
|
@ -199,7 +199,7 @@
|
||||||
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
|
<a-tag>[[ infoModal.clientSettings.limitIp ]]</a-tag>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="app.ipLimitEnable">
|
<tr v-if="app.ipLimitEnable && infoModal.clientSettings.limitIp > 0">
|
||||||
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
|
<td>{{ i18n "pages.inbounds.IPLimitlog" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag>[[ infoModal.clientIps ]]</a-tag>
|
<a-tag>[[ infoModal.clientIps ]]</a-tag>
|
||||||
|
|
|
@ -152,6 +152,16 @@
|
||||||
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
||||||
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
||||||
},
|
},
|
||||||
|
async getNewEchCert() {
|
||||||
|
inModal.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/server/getNewEchCert', {sni: inModal.inbound.stream.tls.sni});
|
||||||
|
inModal.loading(false);
|
||||||
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inModal.inbound.stream.tls.echServerKeys = msg.obj.echServerKeys;
|
||||||
|
inModal.inbound.stream.tls.settings.echConfigList = msg.obj.echConfigList;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
<tr-qr-modal class="qr-modal">
|
<tr-qr-modal class="qr-modal">
|
||||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
<template v-if="app.subSettings?.enable && qrModal.subId">
|
||||||
<tr-qr-box class="qr-box">
|
<tr-qr-box class="qr-box">
|
||||||
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
|
<a-tag color="purple" class="qr-tag"><span>{{ i18n "pages.settings.subSettings"}}</span></a-tag>
|
||||||
<tr-qr-bg class="qr-bg-sub">
|
<tr-qr-bg class="qr-bg-sub">
|
||||||
|
|
|
@ -16,7 +16,7 @@ func NewCheckXrayRunningJob() *CheckXrayRunningJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckXrayRunningJob) Run() {
|
func (j *CheckXrayRunningJob) Run() {
|
||||||
if j.xrayService.IsXrayRunning() {
|
if !j.xrayService.DidXrayCrash() {
|
||||||
j.checkTime = 0
|
j.checkTime = 0
|
||||||
} else {
|
} else {
|
||||||
j.checkTime++
|
j.checkTime++
|
||||||
|
|
|
@ -2,6 +2,7 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -329,7 +330,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if major > 25 || (major == 25 && minor > 7) || (major == 25 && minor == 7 && patch >= 26) {
|
if major > 25 || (major == 25 && minor > 8) || (major == 25 && minor == 8 && patch >= 3) {
|
||||||
versions = append(versions, release.TagName)
|
versions = append(versions, release.TagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,7 +347,6 @@ func (s *ServerService) StopXrayService() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) RestartXrayService() error {
|
func (s *ServerService) RestartXrayService() error {
|
||||||
s.xrayService.StopXray()
|
|
||||||
err := s.xrayService.RestartXray(true)
|
err := s.xrayService.RestartXray(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("start xray failed:", err)
|
logger.Error("start xray failed:", err)
|
||||||
|
@ -481,6 +481,81 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetXrayLogs(
|
||||||
|
count string,
|
||||||
|
filter string,
|
||||||
|
showDirect string,
|
||||||
|
showBlocked string,
|
||||||
|
showProxy string,
|
||||||
|
freedoms []string,
|
||||||
|
blackholes []string) []string {
|
||||||
|
|
||||||
|
countInt, _ := strconv.Atoi(count)
|
||||||
|
var lines []string
|
||||||
|
|
||||||
|
pathToAccessLog, err := xray.GetAccessLogPath()
|
||||||
|
if err != nil {
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(pathToAccessLog)
|
||||||
|
if err != nil {
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
if line == "" || strings.Contains(line, "api -> api") {
|
||||||
|
//skipping empty lines and api calls
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter != "" && !strings.Contains(line, filter) {
|
||||||
|
//applying filter if it's not empty
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//adding suffixes to further distinguish entries by outbound
|
||||||
|
if hasSuffix(line, freedoms) {
|
||||||
|
if showDirect == "false" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = line + " f"
|
||||||
|
} else if hasSuffix(line, blackholes) {
|
||||||
|
if showBlocked == "false" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = line + " b"
|
||||||
|
} else {
|
||||||
|
if showProxy == "false" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line = line + " p"
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lines) > countInt {
|
||||||
|
lines = lines[len(lines)-countInt:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasSuffix(line string, suffixes []string) bool {
|
||||||
|
for _, sfx := range suffixes {
|
||||||
|
if strings.HasSuffix(line, sfx+"]") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetConfigJson() (any, error) {
|
func (s *ServerService) GetConfigJson() (any, error) {
|
||||||
config, err := s.xrayService.GetXrayConfig()
|
config, err := s.xrayService.GetXrayConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -666,27 +741,43 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileURL string
|
var errorMessages []string
|
||||||
for _, file := range files {
|
|
||||||
if file.FileName == fileName {
|
if fileName == "" {
|
||||||
fileURL = file.URL
|
for _, file := range files {
|
||||||
break
|
destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), file.FileName)
|
||||||
|
|
||||||
|
if err := downloadFile(file.URL, destPath); err != nil {
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", file.FileName, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), fileName)
|
||||||
|
|
||||||
if fileURL == "" {
|
var fileURL string
|
||||||
return common.NewErrorf("File '%s' not found in the list of Geofiles", fileName)
|
for _, file := range files {
|
||||||
}
|
if file.FileName == fileName {
|
||||||
|
fileURL = file.URL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destPath := fmt.Sprintf("%s/%s", config.GetBinFolderPath(), fileName)
|
if fileURL == "" {
|
||||||
|
errorMessages = append(errorMessages, fmt.Sprintf("File '%s' not found in the list of Geofiles", fileName))
|
||||||
|
}
|
||||||
|
|
||||||
if err := downloadFile(fileURL, destPath); err != nil {
|
if err := downloadFile(fileURL, destPath); err != nil {
|
||||||
return common.NewErrorf("Error downloading Geofile '%s': %v", fileName, err)
|
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", fileName, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.RestartXrayService()
|
err := s.RestartXrayService()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.NewErrorf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err)
|
errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errorMessages) > 0 {
|
||||||
|
return common.NewErrorf("%s", strings.Join(errorMessages, "\r\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -743,3 +834,27 @@ func (s *ServerService) GetNewmldsa65() (any, error) {
|
||||||
|
|
||||||
return keyPair, nil
|
return keyPair, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServerService) GetNewEchCert(sni string) (interface{}, error) {
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(xray.GetBinaryPath(), "tls", "ech", "--serverName", sni)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(out.String(), "\n")
|
||||||
|
if len(lines) < 4 {
|
||||||
|
return nil, common.NewError("invalid ech cert")
|
||||||
|
}
|
||||||
|
|
||||||
|
configList := lines[1]
|
||||||
|
serverKeys := lines[3]
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"echServerKeys": serverKeys,
|
||||||
|
"echConfigList": configList,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ var defaultValueMap = map[string]string{
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
"secret": random.Seq(32),
|
"secret": random.Seq(32),
|
||||||
"webBasePath": "/",
|
"webBasePath": "/",
|
||||||
"sessionMaxAge": "60",
|
"sessionMaxAge": "360",
|
||||||
"pageSize": "50",
|
"pageSize": "50",
|
||||||
"expireDiff": "0",
|
"expireDiff": "0",
|
||||||
"trafficDiff": "0",
|
"trafficDiff": "0",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"embed"
|
"embed"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -148,7 +149,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// After bot initialization, set up bot commands with localized descriptions
|
// After bot initialization, set up bot commands with localized descriptions
|
||||||
err = bot.SetMyCommands(&telego.SetMyCommandsParams{
|
err = bot.SetMyCommands(context.Background(), &telego.SetMyCommandsParams{
|
||||||
Commands: []telego.BotCommand{
|
Commands: []telego.BotCommand{
|
||||||
{Command: "start", Description: t.I18nBot("tgbot.commands.startDesc")},
|
{Command: "start", Description: t.I18nBot("tgbot.commands.startDesc")},
|
||||||
{Command: "help", Description: t.I18nBot("tgbot.commands.helpDesc")},
|
{Command: "help", Description: t.I18nBot("tgbot.commands.helpDesc")},
|
||||||
|
@ -221,8 +222,9 @@ func (t *Tgbot) SetHostname() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) Stop() {
|
func (t *Tgbot) Stop() {
|
||||||
botHandler.Stop()
|
if botHandler != nil {
|
||||||
bot.StopLongPolling()
|
botHandler.Stop()
|
||||||
|
}
|
||||||
logger.Info("Stop Telegram receiver ...")
|
logger.Info("Stop Telegram receiver ...")
|
||||||
isRunning = false
|
isRunning = false
|
||||||
adminIds = nil
|
adminIds = nil
|
||||||
|
@ -255,26 +257,29 @@ func (t *Tgbot) OnReceive() {
|
||||||
Timeout: 10,
|
Timeout: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
updates, _ := bot.UpdatesViaLongPolling(¶ms)
|
updates, _ := bot.UpdatesViaLongPolling(context.Background(), ¶ms)
|
||||||
|
|
||||||
botHandler, _ = th.NewBotHandler(bot, updates)
|
botHandler, _ = th.NewBotHandler(bot, updates)
|
||||||
|
|
||||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
|
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
|
||||||
|
return nil
|
||||||
}, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
|
}, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
|
||||||
|
|
||||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
|
t.answerCommand(&message, message.Chat.ID, checkAdmin(message.From.ID))
|
||||||
|
return nil
|
||||||
}, th.AnyCommand())
|
}, th.AnyCommand())
|
||||||
|
|
||||||
botHandler.HandleCallbackQuery(func(_ *telego.Bot, query telego.CallbackQuery) {
|
botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
|
||||||
delete(userStates, query.Message.GetChat().ID)
|
delete(userStates, query.Message.GetChat().ID)
|
||||||
t.answerCallback(&query, checkAdmin(query.From.ID))
|
t.answerCallback(&query, checkAdmin(query.From.ID))
|
||||||
|
return nil
|
||||||
}, th.AnyCallbackQueryWithMessage())
|
}, th.AnyCallbackQueryWithMessage())
|
||||||
|
|
||||||
botHandler.HandleMessage(func(_ *telego.Bot, message telego.Message) {
|
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
|
||||||
if userState, exists := userStates[message.Chat.ID]; exists {
|
if userState, exists := userStates[message.Chat.ID]; exists {
|
||||||
switch userState {
|
switch userState {
|
||||||
case "awaiting_id":
|
case "awaiting_id":
|
||||||
|
@ -284,7 +289,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID)
|
inbound, _ := t.inboundService.GetInbound(receiver_inbound_ID)
|
||||||
message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, _ := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
t.addClient(message.Chat.ID, message_text)
|
t.addClient(message.Chat.ID, message_text)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client_Id = strings.TrimSpace(message.Text)
|
client_Id = strings.TrimSpace(message.Text)
|
||||||
|
@ -309,7 +314,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
if client_TrPassword == strings.TrimSpace(message.Text) {
|
if client_TrPassword == strings.TrimSpace(message.Text) {
|
||||||
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client_TrPassword = strings.TrimSpace(message.Text)
|
client_TrPassword = strings.TrimSpace(message.Text)
|
||||||
|
@ -334,7 +339,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
if client_ShPassword == strings.TrimSpace(message.Text) {
|
if client_ShPassword == strings.TrimSpace(message.Text) {
|
||||||
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client_ShPassword = strings.TrimSpace(message.Text)
|
client_ShPassword = strings.TrimSpace(message.Text)
|
||||||
|
@ -359,7 +364,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
if client_Email == strings.TrimSpace(message.Text) {
|
if client_Email == strings.TrimSpace(message.Text) {
|
||||||
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client_Email = strings.TrimSpace(message.Text)
|
client_Email = strings.TrimSpace(message.Text)
|
||||||
|
@ -384,7 +389,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
if client_Comment == strings.TrimSpace(message.Text) {
|
if client_Comment == strings.TrimSpace(message.Text) {
|
||||||
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
t.SendMsgToTgbotDeleteAfter(message.Chat.ID, t.I18nBot("tgbot.messages.using_default_value"), 3, tu.ReplyKeyboardRemove())
|
||||||
delete(userStates, message.Chat.ID)
|
delete(userStates, message.Chat.ID)
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
client_Comment = strings.TrimSpace(message.Text)
|
client_Comment = strings.TrimSpace(message.Text)
|
||||||
|
@ -417,6 +422,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}, th.AnyMessage())
|
}, th.AnyMessage())
|
||||||
|
|
||||||
botHandler.Start()
|
botHandler.Start()
|
||||||
|
@ -699,7 +705,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
|
||||||
t.addClient(chatId, message_text, messageId)
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||||
case "add_client_limit_traffic_in":
|
case "add_client_limit_traffic_in":
|
||||||
if len(dataArray) >= 2 {
|
if len(dataArray) >= 2 {
|
||||||
|
@ -914,7 +920,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
|
||||||
t.addClient(chatId, message_text, messageId)
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||||
case "add_client_reset_exp_in":
|
case "add_client_reset_exp_in":
|
||||||
if len(dataArray) >= 2 {
|
if len(dataArray) >= 2 {
|
||||||
|
@ -1096,7 +1102,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
|
||||||
t.addClient(chatId, message_text, messageId)
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||||
case "add_client_ip_limit_in":
|
case "add_client_ip_limit_in":
|
||||||
if len(dataArray) >= 2 {
|
if len(dataArray) >= 2 {
|
||||||
|
@ -1157,8 +1163,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
|
||||||
t.searchClient(chatId, email, callbackQuery.Message.GetMessageID())
|
|
||||||
case "clear_ips":
|
case "clear_ips":
|
||||||
inlineKeyboard := tu.InlineKeyboard(
|
inlineKeyboard := tu.InlineKeyboard(
|
||||||
tu.InlineKeyboardRow(
|
tu.InlineKeyboardRow(
|
||||||
|
@ -1285,7 +1289,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
|
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
|
||||||
t.addClient(chatId, message_text)
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
@ -1859,7 +1863,7 @@ func (t *Tgbot) SendMsgToTgbot(chatId int64, msg string, replyMarkup ...telego.R
|
||||||
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
|
if len(replyMarkup) > 0 && n == (len(allMessages)-1) {
|
||||||
params.ReplyMarkup = replyMarkup[0]
|
params.ReplyMarkup = replyMarkup[0]
|
||||||
}
|
}
|
||||||
_, err := bot.SendMessage(¶ms)
|
_, err := bot.SendMessage(context.Background(), ¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("Error sending telegram message :", err)
|
logger.Warning("Error sending telegram message :", err)
|
||||||
}
|
}
|
||||||
|
@ -2765,7 +2769,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(document)
|
_, err = bot.SendDocument(context.Background(), document)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading backup: ", err)
|
logger.Error("Error in uploading backup: ", err)
|
||||||
}
|
}
|
||||||
|
@ -2779,7 +2783,7 @@ func (t *Tgbot) sendBackup(chatId int64) {
|
||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(document)
|
_, err = bot.SendDocument(context.Background(), document)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading config.json: ", err)
|
logger.Error("Error in uploading config.json: ", err)
|
||||||
}
|
}
|
||||||
|
@ -2803,7 +2807,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(document)
|
_, err = bot.SendDocument(context.Background(), document)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
||||||
}
|
}
|
||||||
|
@ -2824,7 +2828,7 @@ func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
||||||
tu.ID(chatId),
|
tu.ID(chatId),
|
||||||
tu.File(file),
|
tu.File(file),
|
||||||
)
|
)
|
||||||
_, err = bot.SendDocument(document)
|
_, err = bot.SendDocument(context.Background(), document)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
||||||
}
|
}
|
||||||
|
@ -2842,7 +2846,7 @@ func (t *Tgbot) sendCallbackAnswerTgBot(id string, message string) {
|
||||||
CallbackQueryID: id,
|
CallbackQueryID: id,
|
||||||
Text: message,
|
Text: message,
|
||||||
}
|
}
|
||||||
if err := bot.AnswerCallbackQuery(¶ms); err != nil {
|
if err := bot.AnswerCallbackQuery(context.Background(), ¶ms); err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2853,7 +2857,7 @@ func (t *Tgbot) editMessageCallbackTgBot(chatId int64, messageID int, inlineKeyb
|
||||||
MessageID: messageID,
|
MessageID: messageID,
|
||||||
ReplyMarkup: inlineKeyboard,
|
ReplyMarkup: inlineKeyboard,
|
||||||
}
|
}
|
||||||
if _, err := bot.EditMessageReplyMarkup(¶ms); err != nil {
|
if _, err := bot.EditMessageReplyMarkup(context.Background(), ¶ms); err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2868,7 +2872,7 @@ func (t *Tgbot) editMessageTgBot(chatId int64, messageID int, text string, inlin
|
||||||
if len(inlineKeyboard) > 0 {
|
if len(inlineKeyboard) > 0 {
|
||||||
params.ReplyMarkup = inlineKeyboard[0]
|
params.ReplyMarkup = inlineKeyboard[0]
|
||||||
}
|
}
|
||||||
if _, err := bot.EditMessageText(¶ms); err != nil {
|
if _, err := bot.EditMessageText(context.Background(), ¶ms); err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2881,7 +2885,7 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message
|
// Send the message
|
||||||
sentMsg, err := bot.SendMessage(&telego.SendMessageParams{
|
sentMsg, err := bot.SendMessage(context.Background(), &telego.SendMessageParams{
|
||||||
ChatID: tu.ID(chatId),
|
ChatID: tu.ID(chatId),
|
||||||
Text: msg,
|
Text: msg,
|
||||||
ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
|
ReplyMarkup: replyMarkupParam, // Use the correct replyMarkup value
|
||||||
|
@ -2904,7 +2908,7 @@ func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) {
|
||||||
ChatID: tu.ID(chatId),
|
ChatID: tu.ID(chatId),
|
||||||
MessageID: messageID,
|
MessageID: messageID,
|
||||||
}
|
}
|
||||||
if err := bot.DeleteMessage(¶ms); err != nil {
|
if err := bot.DeleteMessage(context.Background(), ¶ms); err != nil {
|
||||||
logger.Warning("Failed to delete message:", err)
|
logger.Warning("Failed to delete message:", err)
|
||||||
} else {
|
} else {
|
||||||
logger.Info("Message deleted successfully")
|
logger.Info("Message deleted successfully")
|
||||||
|
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -14,7 +15,8 @@ import (
|
||||||
var (
|
var (
|
||||||
p *xray.Process
|
p *xray.Process
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
isNeedXrayRestart atomic.Bool
|
isNeedXrayRestart atomic.Bool // Indicates that restart was requested for Xray
|
||||||
|
isManuallyStopped atomic.Bool // Indicates that Xray was stopped manually from the panel
|
||||||
result string
|
result string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,7 +34,16 @@ func (s *XrayService) GetXrayErr() error {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return p.GetErr()
|
|
||||||
|
err := p.GetErr()
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
|
||||||
|
// exit status 1 on Windows means that Xray process was killed
|
||||||
|
// as we kill process to stop in on Windows, this is not an error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) GetXrayResult() string {
|
func (s *XrayService) GetXrayResult() string {
|
||||||
|
@ -45,7 +56,15 @@ func (s *XrayService) GetXrayResult() string {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
result = p.GetResult()
|
result = p.GetResult()
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" && result == "exit status 1" {
|
||||||
|
// exit status 1 on Windows means that Xray process was killed
|
||||||
|
// as we kill process to stop in on Windows, this is not an error
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +203,8 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
|
||||||
func (s *XrayService) RestartXray(isForce bool) error {
|
func (s *XrayService) RestartXray(isForce bool) error {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
logger.Debug("restart xray, force:", isForce)
|
logger.Debug("restart Xray, force:", isForce)
|
||||||
|
isManuallyStopped.Store(false)
|
||||||
|
|
||||||
xrayConfig, err := s.GetXrayConfig()
|
xrayConfig, err := s.GetXrayConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -192,8 +212,8 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.IsXrayRunning() {
|
if s.IsXrayRunning() {
|
||||||
if !isForce && p.GetConfig().Equals(xrayConfig) {
|
if !isForce && p.GetConfig().Equals(xrayConfig) && !isNeedXrayRestart.Load() {
|
||||||
logger.Debug("It does not need to restart xray")
|
logger.Debug("It does not need to restart Xray")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p.Stop()
|
p.Stop()
|
||||||
|
@ -205,12 +225,14 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) StopXray() error {
|
func (s *XrayService) StopXray() error {
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
isManuallyStopped.Store(true)
|
||||||
logger.Debug("Attempting to stop Xray...")
|
logger.Debug("Attempting to stop Xray...")
|
||||||
if s.IsXrayRunning() {
|
if s.IsXrayRunning() {
|
||||||
return p.Stop()
|
return p.Stop()
|
||||||
|
@ -225,3 +247,8 @@ func (s *XrayService) SetToNeedRestart() {
|
||||||
func (s *XrayService) IsNeedRestartAndSetFalse() bool {
|
func (s *XrayService) IsNeedRestartAndSetFalse() bool {
|
||||||
return isNeedXrayRestart.CompareAndSwap(true, false)
|
return isNeedXrayRestart.CompareAndSwap(true, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if Xray is not running and wasn't stopped manually, i.e. crashed
|
||||||
|
func (s *XrayService) DidXrayCrash() bool {
|
||||||
|
return !s.IsXrayRunning() && !isManuallyStopped.Load()
|
||||||
|
}
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "تم تحديث Xray بنجاح"
|
"xraySwitchVersionPopover" = "تم تحديث Xray بنجاح"
|
||||||
"geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟"
|
"geofileUpdateDialog" = "هل تريد حقًا تحديث ملف الجغرافيا؟"
|
||||||
"geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#."
|
"geofileUpdateDialogDesc" = "سيؤدي هذا إلى تحديث ملف #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "سيؤدي هذا إلى تحديث كافة الملفات."
|
||||||
|
"geofilesUpdateAll" = "تحديث الكل"
|
||||||
"geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح"
|
"geofileUpdatePopover" = "تم تحديث ملف الجغرافيا بنجاح"
|
||||||
"dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة"
|
"dontRefresh" = "التثبيت شغال، متعملش Refresh للصفحة"
|
||||||
"logs" = "السجلات"
|
"logs" = "السجلات"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "ملاحظة"
|
"remark" = "ملاحظة"
|
||||||
"protocol" = "بروتوكول"
|
"protocol" = "بروتوكول"
|
||||||
"port" = "بورت"
|
"port" = "بورت"
|
||||||
|
"portMap" = "خريطة البورت"
|
||||||
"traffic" = "الترافيك"
|
"traffic" = "الترافيك"
|
||||||
"details" = "تفاصيل"
|
"details" = "تفاصيل"
|
||||||
"transportConfig" = "نقل"
|
"transportConfig" = "نقل"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray updated successfully"
|
"xraySwitchVersionPopover" = "Xray updated successfully"
|
||||||
"geofileUpdateDialog" = "Do you really want to update the geofile?"
|
"geofileUpdateDialog" = "Do you really want to update the geofile?"
|
||||||
"geofileUpdateDialogDesc" = "This will update the #filename# file."
|
"geofileUpdateDialogDesc" = "This will update the #filename# file."
|
||||||
|
"geofilesUpdateDialogDesc" = "This will update all geofiles."
|
||||||
|
"geofilesUpdateAll" = "Update all"
|
||||||
"geofileUpdatePopover" = "Geofile updated successfully"
|
"geofileUpdatePopover" = "Geofile updated successfully"
|
||||||
"dontRefresh" = "Installation is in progress, please do not refresh this page"
|
"dontRefresh" = "Installation is in progress, please do not refresh this page"
|
||||||
"logs" = "Logs"
|
"logs" = "Logs"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Remark"
|
"remark" = "Remark"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"port" = "Port"
|
"port" = "Port"
|
||||||
|
"portMap" = "Port Mapping"
|
||||||
"traffic" = "Traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "Details"
|
"details" = "Details"
|
||||||
"transportConfig" = "Transport"
|
"transportConfig" = "Transport"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray se actualizó correctamente"
|
"xraySwitchVersionPopover" = "Xray se actualizó correctamente"
|
||||||
"geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?"
|
"geofileUpdateDialog" = "¿Realmente deseas actualizar el geofichero?"
|
||||||
"geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#."
|
"geofileUpdateDialogDesc" = "Esto actualizará el archivo #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Esto actualizará todos los archivos."
|
||||||
|
"geofilesUpdateAll" = "Actualizar todo"
|
||||||
"geofileUpdatePopover" = "Geofichero actualizado correctamente"
|
"geofileUpdatePopover" = "Geofichero actualizado correctamente"
|
||||||
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
|
"dontRefresh" = "La instalación está en progreso, por favor no actualices esta página."
|
||||||
"logs" = "Registros"
|
"logs" = "Registros"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Notas"
|
"remark" = "Notas"
|
||||||
"protocol" = "Protocolo"
|
"protocol" = "Protocolo"
|
||||||
"port" = "Puerto"
|
"port" = "Puerto"
|
||||||
|
"portMap" = "Puertos de Destino"
|
||||||
"traffic" = "Tráfico"
|
"traffic" = "Tráfico"
|
||||||
"details" = "Detalles"
|
"details" = "Detalles"
|
||||||
"transportConfig" = "Transporte"
|
"transportConfig" = "Transporte"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray با موفقیت بهروز شد"
|
"xraySwitchVersionPopover" = "Xray با موفقیت بهروز شد"
|
||||||
"geofileUpdateDialog" = "آیا واقعاً میخواهید فایل جغرافیایی را بهروز کنید؟"
|
"geofileUpdateDialog" = "آیا واقعاً میخواهید فایل جغرافیایی را بهروز کنید؟"
|
||||||
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را بهروز میکند."
|
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را بهروز میکند."
|
||||||
|
"geofilesUpdateDialogDesc" = "با این کار همه فایلها بهروزرسانی میشوند."
|
||||||
|
"geofilesUpdateAll" = "همه را بهروزرسانی کنید"
|
||||||
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت بهروز شد"
|
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت بهروز شد"
|
||||||
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
|
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
|
||||||
"logs" = "گزارشها"
|
"logs" = "گزارشها"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "نام"
|
"remark" = "نام"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"port" = "پورت"
|
"port" = "پورت"
|
||||||
|
"portMap" = "پورتهای نظیر"
|
||||||
"traffic" = "ترافیک"
|
"traffic" = "ترافیک"
|
||||||
"details" = "توضیحات"
|
"details" = "توضیحات"
|
||||||
"transportConfig" = "نحوه اتصال"
|
"transportConfig" = "نحوه اتصال"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray berhasil diperbarui"
|
"xraySwitchVersionPopover" = "Xray berhasil diperbarui"
|
||||||
"geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?"
|
"geofileUpdateDialog" = "Apakah Anda yakin ingin memperbarui geofile?"
|
||||||
"geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#."
|
"geofileUpdateDialogDesc" = "Ini akan memperbarui file #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Ini akan memperbarui semua berkas."
|
||||||
|
"geofilesUpdateAll" = "Perbarui semua"
|
||||||
"geofileUpdatePopover" = "Geofile berhasil diperbarui"
|
"geofileUpdatePopover" = "Geofile berhasil diperbarui"
|
||||||
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
|
"dontRefresh" = "Instalasi sedang berlangsung, harap jangan menyegarkan halaman ini"
|
||||||
"logs" = "Log"
|
"logs" = "Log"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Catatan"
|
"remark" = "Catatan"
|
||||||
"protocol" = "Protokol"
|
"protocol" = "Protokol"
|
||||||
"port" = "Port"
|
"port" = "Port"
|
||||||
|
"portMap" = "Port Mapping"
|
||||||
"traffic" = "Traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "Rincian"
|
"details" = "Rincian"
|
||||||
"transportConfig" = "Transport"
|
"transportConfig" = "Transport"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xrayの更新が成功しました"
|
"xraySwitchVersionPopover" = "Xrayの更新が成功しました"
|
||||||
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
|
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
|
||||||
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
|
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
|
||||||
|
"geofilesUpdateDialogDesc" = "これにより、すべてのファイルが更新されます。"
|
||||||
|
"geofilesUpdateAll" = "すべて更新"
|
||||||
"geofileUpdatePopover" = "ジオファイルの更新が成功しました"
|
"geofileUpdatePopover" = "ジオファイルの更新が成功しました"
|
||||||
"dontRefresh" = "インストール中、このページをリロードしないでください"
|
"dontRefresh" = "インストール中、このページをリロードしないでください"
|
||||||
"logs" = "ログ"
|
"logs" = "ログ"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "備考"
|
"remark" = "備考"
|
||||||
"protocol" = "プロトコル"
|
"protocol" = "プロトコル"
|
||||||
"port" = "ポート"
|
"port" = "ポート"
|
||||||
|
"portMap" = "ポートマッピング"
|
||||||
"traffic" = "トラフィック"
|
"traffic" = "トラフィック"
|
||||||
"details" = "詳細情報"
|
"details" = "詳細情報"
|
||||||
"transportConfig" = "トランスポート設定"
|
"transportConfig" = "トランスポート設定"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray atualizado com sucesso"
|
"xraySwitchVersionPopover" = "Xray atualizado com sucesso"
|
||||||
"geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?"
|
"geofileUpdateDialog" = "Você realmente deseja atualizar o geofile?"
|
||||||
"geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#."
|
"geofileUpdateDialogDesc" = "Isso atualizará o arquivo #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Isso atualizará todos os arquivos."
|
||||||
|
"geofilesUpdateAll" = "Atualizar tudo"
|
||||||
"geofileUpdatePopover" = "Geofile atualizado com sucesso"
|
"geofileUpdatePopover" = "Geofile atualizado com sucesso"
|
||||||
"dontRefresh" = "Instalação em andamento, por favor não atualize a página"
|
"dontRefresh" = "Instalação em andamento, por favor não atualize a página"
|
||||||
"logs" = "Logs"
|
"logs" = "Logs"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Observação"
|
"remark" = "Observação"
|
||||||
"protocol" = "Protocolo"
|
"protocol" = "Protocolo"
|
||||||
"port" = "Porta"
|
"port" = "Porta"
|
||||||
|
"portMap" = "Porta Mapeada"
|
||||||
"traffic" = "Tráfego"
|
"traffic" = "Tráfego"
|
||||||
"details" = "Detalhes"
|
"details" = "Detalhes"
|
||||||
"transportConfig" = "Transporte"
|
"transportConfig" = "Transporte"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray успешно обновлён"
|
"xraySwitchVersionPopover" = "Xray успешно обновлён"
|
||||||
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
|
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
|
||||||
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
|
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Это обновит все геофайлы."
|
||||||
|
"geofilesUpdateAll" = "Обновить все"
|
||||||
"geofileUpdatePopover" = "Геофайл успешно обновлён"
|
"geofileUpdatePopover" = "Геофайл успешно обновлён"
|
||||||
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
||||||
"logs" = "Журнал"
|
"logs" = "Журнал"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Примечание"
|
"remark" = "Примечание"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
|
"portMap" = "Порт-маппинг"
|
||||||
"traffic" = "Трафик"
|
"traffic" = "Трафик"
|
||||||
"details" = "Подробнее"
|
"details" = "Подробнее"
|
||||||
"transportConfig" = "Транспорт"
|
"transportConfig" = "Транспорт"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray başarıyla güncellendi"
|
"xraySwitchVersionPopover" = "Xray başarıyla güncellendi"
|
||||||
"geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?"
|
"geofileUpdateDialog" = "Geofile'ı gerçekten güncellemek istiyor musunuz?"
|
||||||
"geofileUpdateDialogDesc" = "Bu işlem #filename# dosyasını güncelleyecektir."
|
"geofileUpdateDialogDesc" = "Bu işlem #filename# dosyasını güncelleyecektir."
|
||||||
|
"geofilesUpdateDialogDesc" = "Bu, tüm dosyaları güncelleyecektir."
|
||||||
|
"geofilesUpdateAll" = "Tümünü güncelle"
|
||||||
"geofileUpdatePopover" = "Geofile başarıyla güncellendi"
|
"geofileUpdatePopover" = "Geofile başarıyla güncellendi"
|
||||||
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
|
"dontRefresh" = "Kurulum devam ediyor, lütfen bu sayfayı yenilemeyin"
|
||||||
"logs" = "Günlükler"
|
"logs" = "Günlükler"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Açıklama"
|
"remark" = "Açıklama"
|
||||||
"protocol" = "Protokol"
|
"protocol" = "Protokol"
|
||||||
"port" = "Port"
|
"port" = "Port"
|
||||||
|
"portMap" = "Port Atama"
|
||||||
"traffic" = "Trafik"
|
"traffic" = "Trafik"
|
||||||
"details" = "Detaylar"
|
"details" = "Detaylar"
|
||||||
"transportConfig" = "Taşıma"
|
"transportConfig" = "Taşıma"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray успішно оновлено"
|
"xraySwitchVersionPopover" = "Xray успішно оновлено"
|
||||||
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
|
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
|
||||||
"geofileUpdateDialogDesc" = "Це оновить файл #filename#."
|
"geofileUpdateDialogDesc" = "Це оновить файл #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Це оновить усі геофайли."
|
||||||
|
"geofilesUpdateAll" = "Оновити все"
|
||||||
"geofileUpdatePopover" = "Геофайл успішно оновлено"
|
"geofileUpdatePopover" = "Геофайл успішно оновлено"
|
||||||
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
|
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
|
||||||
"logs" = "Журнали"
|
"logs" = "Журнали"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Примітка"
|
"remark" = "Примітка"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
|
"portMap" = "Порт-перехід"
|
||||||
"traffic" = "Трафік"
|
"traffic" = "Трафік"
|
||||||
"details" = "Деталі"
|
"details" = "Деталі"
|
||||||
"transportConfig" = "Транспорт"
|
"transportConfig" = "Транспорт"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray đã được cập nhật thành công"
|
"xraySwitchVersionPopover" = "Xray đã được cập nhật thành công"
|
||||||
"geofileUpdateDialog" = "Bạn có chắc chắn muốn cập nhật geofile không?"
|
"geofileUpdateDialog" = "Bạn có chắc chắn muốn cập nhật geofile không?"
|
||||||
"geofileUpdateDialogDesc" = "Hành động này sẽ cập nhật tệp #filename#."
|
"geofileUpdateDialogDesc" = "Hành động này sẽ cập nhật tệp #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Thao tác này sẽ cập nhật tất cả các tập tin."
|
||||||
|
"geofilesUpdateAll" = "Cập nhật tất cả"
|
||||||
"geofileUpdatePopover" = "Geofile đã được cập nhật thành công"
|
"geofileUpdatePopover" = "Geofile đã được cập nhật thành công"
|
||||||
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
|
"dontRefresh" = "Đang tiến hành cài đặt, vui lòng không làm mới trang này."
|
||||||
"logs" = "Nhật ký"
|
"logs" = "Nhật ký"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "Chú thích"
|
"remark" = "Chú thích"
|
||||||
"protocol" = "Giao thức"
|
"protocol" = "Giao thức"
|
||||||
"port" = "Cổng"
|
"port" = "Cổng"
|
||||||
|
"portMap" = "Cổng tạo"
|
||||||
"traffic" = "Lưu lượng"
|
"traffic" = "Lưu lượng"
|
||||||
"details" = "Chi tiết"
|
"details" = "Chi tiết"
|
||||||
"transportConfig" = "Giao vận"
|
"transportConfig" = "Giao vận"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray 更新成功"
|
"xraySwitchVersionPopover" = "Xray 更新成功"
|
||||||
"geofileUpdateDialog" = "您确定要更新地理文件吗?"
|
"geofileUpdateDialog" = "您确定要更新地理文件吗?"
|
||||||
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
|
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
|
||||||
|
"geofilesUpdateDialogDesc" = "这将更新所有文件。"
|
||||||
|
"geofilesUpdateAll" = "全部更新"
|
||||||
"geofileUpdatePopover" = "地理文件更新成功"
|
"geofileUpdatePopover" = "地理文件更新成功"
|
||||||
"dontRefresh" = "安装中,请勿刷新此页面"
|
"dontRefresh" = "安装中,请勿刷新此页面"
|
||||||
"logs" = "日志"
|
"logs" = "日志"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "备注"
|
"remark" = "备注"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"port" = "端口"
|
"port" = "端口"
|
||||||
|
"portMap" = "端口映射"
|
||||||
"traffic" = "流量"
|
"traffic" = "流量"
|
||||||
"details" = "详细信息"
|
"details" = "详细信息"
|
||||||
"transportConfig" = "传输配置"
|
"transportConfig" = "传输配置"
|
||||||
|
|
|
@ -132,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray 更新成功"
|
"xraySwitchVersionPopover" = "Xray 更新成功"
|
||||||
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
|
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
|
||||||
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
|
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
|
||||||
|
"geofilesUpdateDialogDesc" = "這將更新所有文件。"
|
||||||
|
"geofilesUpdateAll" = "全部更新"
|
||||||
"geofileUpdatePopover" = "地理檔案更新成功"
|
"geofileUpdatePopover" = "地理檔案更新成功"
|
||||||
"dontRefresh" = "安裝中,請勿重新整理此頁面"
|
"dontRefresh" = "安裝中,請勿重新整理此頁面"
|
||||||
"logs" = "日誌"
|
"logs" = "日誌"
|
||||||
|
@ -158,6 +160,7 @@
|
||||||
"remark" = "備註"
|
"remark" = "備註"
|
||||||
"protocol" = "協議"
|
"protocol" = "協議"
|
||||||
"port" = "埠"
|
"port" = "埠"
|
||||||
|
"portMap" = "埠映射"
|
||||||
"traffic" = "流量"
|
"traffic" = "流量"
|
||||||
"details" = "詳細資訊"
|
"details" = "詳細資訊"
|
||||||
"transportConfig" = "傳輸配置"
|
"transportConfig" = "傳輸配置"
|
||||||
|
|
Loading…
Reference in a new issue