mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-08-23 19:36:54 +00:00
Compare commits
46 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
24a3411465 | ||
![]() |
2198e7a28f | ||
![]() |
6b23b416a7 | ||
![]() |
16f53ce4c2 | ||
![]() |
27445b30e9 | ||
![]() |
3d0212c21d | ||
![]() |
978755960f | ||
![]() |
9b51e9a5c5 | ||
![]() |
6879a8fbcb | ||
![]() |
7258841491 | ||
![]() |
23dd80fbb0 | ||
![]() |
6556884c7f | ||
![]() |
d5c532c64f | ||
![]() |
ad5f774a1e | ||
![]() |
aa285914fa | ||
![]() |
4d02756e1e | ||
![]() |
825d93d95f | ||
![]() |
5ea6386815 | ||
![]() |
d064e85ecd | ||
![]() |
9fc03bd10a | ||
![]() |
ae08a29cde | ||
![]() |
4f25eb230e | ||
![]() |
ce72d53d1a | ||
![]() |
5e641ff9e8 | ||
![]() |
58898e5758 | ||
![]() |
569550d5f6 | ||
![]() |
419ea63dd0 | ||
![]() |
6a17285935 | ||
![]() |
4b03e9d919 | ||
![]() |
7e9c3bdbaf | ||
![]() |
957f3dbb54 | ||
![]() |
05e60af283 | ||
![]() |
5e40458116 | ||
![]() |
baf6fdd29d | ||
![]() |
45f78d3521 | ||
![]() |
01f984e054 | ||
![]() |
e4ba5ba53a | ||
![]() |
6ff555c8bb | ||
![]() |
3c1634ca7c | ||
![]() |
561c4810be | ||
![]() |
eb1b96643d | ||
![]() |
de5314c01f | ||
![]() |
1088d1faf3 | ||
![]() |
267024c43f | ||
![]() |
0d595f56e4 | ||
![]() |
a4c4f9efb3 |
78 changed files with 1201 additions and 74580 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
|
||||||
|
|
4
.github/workflows/docker.yml
vendored
4
.github/workflows/docker.yml
vendored
|
@ -7,10 +7,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|
75
.github/workflows/release.yml
vendored
75
.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,10 +32,10 @@ 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@v5
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
|
@ -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/
|
||||||
|
@ -97,7 +84,7 @@ jobs:
|
||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.7.26/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.8.3/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||||
unzip Xray-linux-64.zip
|
unzip Xray-linux-64.zip
|
||||||
|
|
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
|
||||||
|
|
|
@ -27,7 +27,7 @@ case $1 in
|
||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.7.26/Xray-linux-${ARCH}.zip"
|
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.8.3/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# ========================================================
|
# ========================================================
|
||||||
# Stage: Builder
|
# Stage: Builder
|
||||||
# ========================================================
|
# ========================================================
|
||||||
FROM golang:1.24-alpine AS builder
|
FROM golang:1.25-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
|
|
@ -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
|
54
go.mod
54
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.24.5
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.3
|
github.com/gin-contrib/gzip v1.2.3
|
||||||
|
@ -9,32 +9,31 @@ 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
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6
|
github.com/shirou/gopsutil/v4 v4.25.7
|
||||||
github.com/valyala/fasthttp v1.63.0
|
github.com/valyala/fasthttp v1.65.0
|
||||||
github.com/xlzd/gotp v0.1.0
|
github.com/xlzd/gotp v0.1.0
|
||||||
github.com/xtls/xray-core v1.250726.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.0
|
gorm.io/gorm v1.30.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/bytedance/sonic v1.13.3 // indirect
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // 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/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
|
||||||
|
@ -52,13 +51,13 @@ require (
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||||
github.com/miekg/dns v1.1.67 // indirect
|
github.com/miekg/dns v1.1.68 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||||
|
@ -68,9 +67,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
|
||||||
|
@ -81,22 +79,22 @@ require (
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
github.com/valyala/fastjson v1.6.4 // indirect
|
||||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 // indirect
|
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a // indirect
|
||||||
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-20250721164621-a45f3dfb1074 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // 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
|
||||||
)
|
)
|
||||||
|
|
110
go.sum
110
go.sum
|
@ -2,16 +2,14 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
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.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
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.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
github.com/bytedance/sonic/loader v0.2.4/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.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.11/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=
|
||||||
|
@ -101,17 +95,17 @@ github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr32
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
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,22 +132,19 @@ 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.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
||||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
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=
|
||||||
|
@ -171,8 +162,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns=
|
github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
||||||
github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM=
|
github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
|
@ -181,10 +172,10 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
||||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7 h1:Ript0vN+nSO33+Vj4n0mgNY5M+oOxFQJdrJ1VnwTBO0=
|
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a h1:Fs8Pc0JAc/LDOf9Q4DzKrk+Ujf4ILlyvfvDVZcmOZ2o=
|
||||||
github.com/xtls/reality v0.0.0-20250725142056-5b52a03d4fb7/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
||||||
github.com/xtls/xray-core v1.250726.0 h1:uTUHUt/CQ1JQLip1pLkiwoS0pMvl6oCHJgur4M4orWQ=
|
github.com/xtls/xray-core v1.250803.0 h1:sYdRC243UsujnePINH4IfM4MfHE4lj2p4wZFAfeE2GI=
|
||||||
github.com/xtls/xray-core v1.250726.0/go.mod h1:z2vn2o30flYEgpSz1iEhdZP1I46UZ3+gXINZyohH3yE=
|
github.com/xtls/xray-core v1.250803.0/go.mod h1:z2vn2o30flYEgpSz1iEhdZP1I46UZ3+gXINZyohH3yE=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
@ -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-20250721164621-a45f3dfb1074 h1:qJW29YvkiJmXOYMu5Tf8lyrTp3dOS+K4z6IixtLaCf8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||||
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=
|
||||||
|
@ -252,10 +243,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
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.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
||||||
gorm.io/gorm v1.30.0/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=
|
|
||||||
|
|
32
install.sh
32
install.sh
|
@ -7,7 +7,6 @@ yellow='\033[0;33m'
|
||||||
plain='\033[0m'
|
plain='\033[0m'
|
||||||
|
|
||||||
cur_dir=$(pwd)
|
cur_dir=$(pwd)
|
||||||
show_ip_service_lists=("https://api.ipify.org" "https://4.ident.me")
|
|
||||||
|
|
||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
||||||
|
@ -40,19 +39,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)
|
||||||
|
@ -71,7 +57,7 @@ install_base() {
|
||||||
zypper refresh && zypper -q install -y wget curl tar timezone
|
zypper refresh && zypper -q install -y wget curl tar timezone
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update && apt install -y -q wget curl tar tzdata
|
apt-get update && apt-get install -y -q wget curl tar tzdata
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
@ -86,10 +72,18 @@ config_after_install() {
|
||||||
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
|
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
|
||||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||||
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||||
|
local URL_lists=(
|
||||||
for ip_service_addr in "${show_ip_service_lists[@]}"; do
|
"https://api4.ipify.org"
|
||||||
local server_ip=$(curl -s --max-time 3 ${ip_service_addr} 2>/dev/null)
|
"https://ipv4.icanhazip.com"
|
||||||
if [ -n "${server_ip}" ]; then
|
"https://v4.api.ipinfo.io/ip"
|
||||||
|
"https://ipv4.myexternalip.com/raw"
|
||||||
|
"https://4.ident.me"
|
||||||
|
"https://check-host.net/ip"
|
||||||
|
)
|
||||||
|
local server_ip=""
|
||||||
|
for ip_address in "${URL_lists[@]}"; do
|
||||||
|
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
if [[ -n "${server_ip}" ]]; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
|
@ -209,9 +209,10 @@ func (s *SubJsonService) streamData(stream string) map[string]any {
|
||||||
var streamSettings map[string]any
|
var streamSettings map[string]any
|
||||||
json.Unmarshal([]byte(stream), &streamSettings)
|
json.Unmarshal([]byte(stream), &streamSettings)
|
||||||
security, _ := streamSettings["security"].(string)
|
security, _ := streamSettings["security"].(string)
|
||||||
if security == "tls" {
|
switch security {
|
||||||
|
case "tls":
|
||||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
||||||
} else if security == "reality" {
|
case "reality":
|
||||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
|
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
|
||||||
}
|
}
|
||||||
delete(streamSettings, "sockopt")
|
delete(streamSettings, "sockopt")
|
||||||
|
|
|
@ -437,6 +437,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
||||||
|
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
||||||
|
params["pqv"] = pqv
|
||||||
|
}
|
||||||
|
}
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -627,6 +632,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
||||||
|
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
||||||
|
params["pqv"] = pqv
|
||||||
|
}
|
||||||
|
}
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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
|
@ -559,7 +559,9 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||||
disableSystemRoot = false,
|
disableSystemRoot = false,
|
||||||
enableSessionResumption = false,
|
enableSessionResumption = false,
|
||||||
certificates = [new TlsStreamSettings.Cert()],
|
certificates = [new TlsStreamSettings.Cert()],
|
||||||
alpn = [ALPN_OPTION.H3, 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);
|
||||||
}
|
}
|
||||||
|
@ -1385,7 +1394,6 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pqv", this.stream.reality.settings.mldsa65Verify);
|
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
|
@ -1395,6 +1403,9 @@ class Inbound extends XrayCommonClass {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.mldsa65Verify)) {
|
||||||
|
params.set("pqv", this.stream.reality.settings.mldsa65Verify);
|
||||||
|
}
|
||||||
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
|
if (type == 'tcp' && !ObjectUtil.isEmpty(flow)) {
|
||||||
params.set("flow", flow);
|
params.set("flow", flow);
|
||||||
}
|
}
|
||||||
|
@ -1472,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);
|
||||||
}
|
}
|
||||||
|
@ -1550,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);
|
||||||
}
|
}
|
||||||
|
@ -1560,7 +1577,6 @@ class Inbound extends XrayCommonClass {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pqv", this.stream.reality.settings.mldsa65Verify);
|
|
||||||
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
if (!ObjectUtil.isArrEmpty(this.stream.reality.serverNames)) {
|
||||||
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
params.set("sni", this.stream.reality.serverNames.split(",")[0]);
|
||||||
}
|
}
|
||||||
|
@ -1570,6 +1586,9 @@ class Inbound extends XrayCommonClass {
|
||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
params.set("spx", this.stream.reality.settings.spiderX);
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.mldsa65Verify)) {
|
||||||
|
params.set("pqv", this.stream.reality.settings.mldsa65Verify);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
@ -1691,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 {
|
||||||
|
@ -2287,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;
|
||||||
}
|
}
|
||||||
|
@ -2302,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,
|
||||||
);
|
);
|
||||||
|
@ -2311,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') {
|
||||||
|
@ -914,12 +919,14 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
packets = '1-3',
|
packets = '1-3',
|
||||||
length = '',
|
length = '',
|
||||||
interval = ''
|
interval = '',
|
||||||
|
maxSplit = ''
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.packets = packets;
|
this.packets = packets;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
this.interval = interval;
|
this.interval = interval;
|
||||||
|
this.maxSplit = maxSplit;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
@ -927,6 +934,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||||
json.packets,
|
json.packets,
|
||||||
json.length,
|
json.length,
|
||||||
json.interval,
|
json.interval,
|
||||||
|
json.maxSplit
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -935,12 +943,14 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
type = 'rand',
|
type = 'rand',
|
||||||
packet = '10-20',
|
packet = '10-20',
|
||||||
delay = '10-16'
|
delay = '10-16',
|
||||||
|
applyTo = 'ip'
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.packet = packet;
|
this.packet = packet;
|
||||||
this.delay = delay;
|
this.delay = delay;
|
||||||
|
this.applyTo = applyTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
|
@ -948,6 +958,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
||||||
json.type,
|
json.type,
|
||||||
json.packet,
|
json.packet,
|
||||||
json.delay,
|
json.delay,
|
||||||
|
json.applyTo
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,6 +967,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
||||||
type: this.type,
|
type: this.type,
|
||||||
packet: this.packet,
|
packet: this.packet,
|
||||||
delay: this.delay,
|
delay: this.delay,
|
||||||
|
applyTo: this.applyTo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -983,7 +995,7 @@ Outbound.DNSSettings = class extends CommonClass {
|
||||||
network = 'udp',
|
network = 'udp',
|
||||||
address = '',
|
address = '',
|
||||||
port = 53,
|
port = 53,
|
||||||
nonIPQuery = 'drop',
|
nonIPQuery = 'reject',
|
||||||
blockTypes = []
|
blockTypes = []
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
|
@ -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
|
|
|
@ -108,8 +108,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,8 +126,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,8 +152,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"math"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"math"
|
|
||||||
|
|
||||||
"x-ui/util/common"
|
"x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
@ -39,8 +39,8 @@ type AllSetting struct {
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
TgLang string `json:"tgLang" form:"tgLang"`
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
|
TwoFactorEnable bool `json:"twoFactorEnable" form:"twoFactorEnable"`
|
||||||
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
|
TwoFactorToken string `json:"twoFactorToken" form:"twoFactorToken"`
|
||||||
SubEnable bool `json:"subEnable" form:"subEnable"`
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
SubTitle string `json:"subTitle" form:"subTitle"`
|
SubTitle string `json:"subTitle" form:"subTitle"`
|
||||||
SubListen string `json:"subListen" form:"subListen"`
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -42,6 +42,9 @@
|
||||||
<a-form-item label='Interval'>
|
<a-form-item label='Interval'>
|
||||||
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='Max Split'>
|
||||||
|
<a-input v-model.trim="outbound.settings.fragment.maxSplit"></a-input>
|
||||||
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Switch for Noises -->
|
<!-- Switch for Noises -->
|
||||||
|
@ -75,6 +78,11 @@
|
||||||
<a-form-item label='Delay'>
|
<a-form-item label='Delay'>
|
||||||
<a-input v-model.trim="noise.delay"></a-input>
|
<a-input v-model.trim="noise.delay"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='Apply To'>
|
||||||
|
<a-select v-model="noise.applyTo" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="s in ['ip','ipv4','ipv6']" :value="s">[[ s ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
@ -97,7 +105,7 @@
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='non-IP queries'>
|
<a-form-item label='non-IP queries'>
|
||||||
<a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option v-for="s in ['drop','skip']" :value="s">[[ s ]]</a-select-option>
|
<a-select-option v-for="s in ['reject','drop','skip']" :value="s">[[ s ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' >
|
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' >
|
||||||
|
@ -444,16 +452,16 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Short ID">
|
<a-form-item label="Short ID">
|
||||||
<a-input v-model.trim="outbound.stream.reality.shortId" :style="{ width: '250px' }"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.shortId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="SpiderX">
|
<a-form-item label="SpiderX">
|
||||||
<a-input v-model.trim="outbound.stream.reality.spiderX" :style="{ width: '250px' }"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.spiderX"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Public Key">
|
<a-form-item label="Public Key">
|
||||||
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
<a-textarea v-model.trim="outbound.stream.reality.publicKey"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="mldsa65 Verify">
|
<a-form-item label="mldsa65 Verify">
|
||||||
<a-input v-model.trim="outbound.stream.reality.mldsa65Verify"></a-input>
|
<a-textarea v-model.trim="outbound.stream.reality.mldsa65Verify"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -36,22 +36,22 @@
|
||||||
type="sync"></a-icon>
|
type="sync"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
<a-textarea v-model.trim="inbound.stream.reality.shortIds"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='SpiderX'>
|
<a-form-item label='SpiderX'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model="inbound.stream.reality.settings.publicKey"></a-input>
|
<a-textarea v-model="inbound.stream.reality.settings.publicKey"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model="inbound.stream.reality.privateKey"></a-input>
|
<a-textarea v-model="inbound.stream.reality.privateKey"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label=" ">
|
<a-form-item label=" ">
|
||||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="mldsa65 Seed">
|
<a-form-item label="mldsa65 Seed">
|
||||||
<a-input v-model="inbound.stream.reality.mldsa65Seed"></a-input>
|
<a-textarea v-model="inbound.stream.reality.mldsa65Seed"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="mldsa65 Verify">
|
<a-form-item label="mldsa65 Verify">
|
||||||
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
|
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
|
||||||
|
|
|
@ -85,15 +85,12 @@
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKey" }}'>
|
||||||
<a-input v-model="cert.cert"></a-input>
|
<a-textarea v-model="cert.cert"></a-textarea>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.privatekey" }}'>
|
||||||
<a-input v-model="cert.key"></a-input>
|
<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">
|
||||||
|
|
|
@ -207,7 +207,7 @@
|
||||||
settings: {
|
settings: {
|
||||||
domainStrategy: "AsIs",
|
domainStrategy: "AsIs",
|
||||||
noises: [
|
noises: [
|
||||||
{ type: "rand", packet: "10-20", delay: "10-16" },
|
{ type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -397,7 +397,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNoise() {
|
addNoise() {
|
||||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
|
const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
|
||||||
this.noisesArray = [...this.noisesArray, newNoise];
|
this.noisesArray = [...this.noisesArray, newNoise];
|
||||||
},
|
},
|
||||||
removeNoise(index) {
|
removeNoise(index) {
|
||||||
|
@ -420,6 +420,11 @@
|
||||||
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
||||||
this.noisesArray = updatedNoises;
|
this.noisesArray = updatedNoises;
|
||||||
},
|
},
|
||||||
|
updateNoiseApplyTo(index, value) {
|
||||||
|
const updatedNoises = [...this.noisesArray];
|
||||||
|
updatedNoises[index] = { ...updatedNoises[index], applyTo: value };
|
||||||
|
this.noisesArray = updatedNoises;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
fragment: {
|
fragment: {
|
||||||
|
|
|
@ -90,6 +90,18 @@
|
||||||
placeholder="10-20"></a-input>
|
placeholder="10-20"></a-input>
|
||||||
</template>
|
</template>
|
||||||
</a-setting-list-item>
|
</a-setting-list-item>
|
||||||
|
<a-setting-list-item paddings="small">
|
||||||
|
<template #title>ApplyTo</template>
|
||||||
|
<template #control>
|
||||||
|
<a-select :value="noise.applyTo" :style="{ width: '100%' }"
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
|
@change="(value) => updateNoiseApplyTo(index, value)">
|
||||||
|
<a-select-option :value="p" :label="p" v-for="p in ['ip', 'ipv4', 'ipv6']" :key="p">
|
||||||
|
<span>[[ p ]]</span>
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
</a-setting-list-item>
|
||||||
<a-space direction="horizontal" :style="{ padding: '10px 20px' }">
|
<a-space direction="horizontal" :style="{ padding: '10px 20px' }">
|
||||||
<a-button v-if="noisesArray.length > 1" type="danger"
|
<a-button v-if="noisesArray.length > 1" type="danger"
|
||||||
@click="removeNoise(index)">Remove</a-button>
|
@click="removeNoise(index)">Remove</a-button>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"slices"
|
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
@ -58,21 +57,21 @@ func (j *CheckClientIpJob) Run() {
|
||||||
func (j *CheckClientIpJob) clearAccessLog() {
|
func (j *CheckClientIpJob) clearAccessLog() {
|
||||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
defer logAccessP.Close()
|
||||||
|
|
||||||
accessLogPath, err := xray.GetAccessLogPath()
|
accessLogPath, err := xray.GetAccessLogPath()
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
file, err := os.Open(accessLogPath)
|
file, err := os.Open(accessLogPath)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
_, err = io.Copy(logAccessP, file)
|
_, err = io.Copy(logAccessP, file)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
logAccessP.Close()
|
|
||||||
file.Close()
|
|
||||||
|
|
||||||
err = os.Truncate(accessLogPath, 0)
|
err = os.Truncate(accessLogPath, 0)
|
||||||
j.checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
j.lastClear = time.Now().Unix()
|
j.lastClear = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,10 +192,6 @@ func (j *CheckClientIpJob) checkError(e error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
|
||||||
return slices.Contains(s, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
InboundClientIps := &model.InboundClientIps{}
|
InboundClientIps := &model.InboundClientIps{}
|
||||||
|
|
|
@ -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++
|
||||||
|
|
|
@ -177,15 +177,16 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||||
|
|
||||||
// Secure client ID
|
// Secure client ID
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if inbound.Protocol == "trojan" {
|
switch inbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
if client.Password == "" {
|
if client.Password == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
} else if inbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
if client.Email == "" {
|
if client.Email == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
if client.ID == "" {
|
if client.ID == "" {
|
||||||
return inbound, false, common.NewError("empty client ID")
|
return inbound, false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
|
@ -436,15 +437,16 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||||
|
|
||||||
// Secure client ID
|
// Secure client ID
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if oldInbound.Protocol == "trojan" {
|
switch oldInbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
if client.Password == "" {
|
if client.Password == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
} else if oldInbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
if client.Email == "" {
|
if client.Email == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
if client.ID == "" {
|
if client.ID == "" {
|
||||||
return false, common.NewError("empty client ID")
|
return false, common.NewError("empty client ID")
|
||||||
}
|
}
|
||||||
|
@ -631,13 +633,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for index, oldClient := range oldClients {
|
for index, oldClient := range oldClients {
|
||||||
oldClientId := ""
|
oldClientId := ""
|
||||||
if oldInbound.Protocol == "trojan" {
|
switch oldInbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
oldClientId = oldClient.Password
|
oldClientId = oldClient.Password
|
||||||
newClientId = clients[0].Password
|
newClientId = clients[0].Password
|
||||||
} else if oldInbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
oldClientId = oldClient.Email
|
oldClientId = oldClient.Email
|
||||||
newClientId = clients[0].Email
|
newClientId = clients[0].Email
|
||||||
} else {
|
default:
|
||||||
oldClientId = oldClient.ID
|
oldClientId = oldClient.ID
|
||||||
newClientId = clients[0].ID
|
newClientId = clients[0].ID
|
||||||
}
|
}
|
||||||
|
@ -1244,11 +1247,12 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
if inbound.Protocol == "trojan" {
|
switch inbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
} else if inbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
} else {
|
default:
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1328,11 +1332,12 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
if inbound.Protocol == "trojan" {
|
switch inbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
} else if inbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
} else {
|
default:
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
clientOldEnabled = oldClient.Enable
|
clientOldEnabled = oldClient.Enable
|
||||||
|
@ -1391,11 +1396,12 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
if inbound.Protocol == "trojan" {
|
switch inbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
} else if inbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
} else {
|
default:
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1448,11 +1454,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
if inbound.Protocol == "trojan" {
|
switch inbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
} else if inbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
} else {
|
default:
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1508,11 +1515,12 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
||||||
|
|
||||||
for _, oldClient := range oldClients {
|
for _, oldClient := range oldClients {
|
||||||
if oldClient.Email == clientEmail {
|
if oldClient.Email == clientEmail {
|
||||||
if inbound.Protocol == "trojan" {
|
switch inbound.Protocol {
|
||||||
|
case "trojan":
|
||||||
clientId = oldClient.Password
|
clientId = oldClient.Password
|
||||||
} else if inbound.Protocol == "shadowsocks" {
|
case "shadowsocks":
|
||||||
clientId = oldClient.Email
|
clientId = oldClient.Email
|
||||||
} else {
|
default:
|
||||||
clientId = oldClient.ID
|
clientId = oldClient.ID
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -2,6 +2,7 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -234,8 +235,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP fetching with caching
|
// IP fetching with caching
|
||||||
showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"}
|
showIp4ServiceLists := []string{
|
||||||
showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"}
|
"https://api4.ipify.org",
|
||||||
|
"https://ipv4.icanhazip.com",
|
||||||
|
"https://v4.api.ipinfo.io/ip",
|
||||||
|
"https://ipv4.myexternalip.com/raw",
|
||||||
|
"https://4.ident.me",
|
||||||
|
"https://check-host.net/ip",
|
||||||
|
}
|
||||||
|
showIp6ServiceLists := []string{
|
||||||
|
"https://api6.ipify.org",
|
||||||
|
"https://ipv6.icanhazip.com",
|
||||||
|
"https://v6.api.ipinfo.io/ip",
|
||||||
|
"https://ipv6.myexternalip.com/raw",
|
||||||
|
"https://6.ident.me",
|
||||||
|
}
|
||||||
|
|
||||||
if s.cachedIPv4 == "" {
|
if s.cachedIPv4 == "" {
|
||||||
for _, ip4Service := range showIp4ServiceLists {
|
for _, ip4Service := range showIp4ServiceLists {
|
||||||
|
@ -329,7 +343,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 +360,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 +494,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 +754,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 +847,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"
|
||||||
|
@ -39,7 +40,6 @@ var (
|
||||||
isRunning bool
|
isRunning bool
|
||||||
hostname string
|
hostname string
|
||||||
hashStorage *global.HashStorage
|
hashStorage *global.HashStorage
|
||||||
handler *th.Handler
|
|
||||||
|
|
||||||
// clients data to adding new client
|
// clients data to adding new client
|
||||||
receiver_inbound_ID int
|
receiver_inbound_ID int
|
||||||
|
@ -148,7 +148,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 +221,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 +256,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 +288,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 +313,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 +338,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 +363,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 +388,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 +421,7 @@ func (t *Tgbot) OnReceive() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}, th.AnyMessage())
|
}, th.AnyMessage())
|
||||||
|
|
||||||
botHandler.Start()
|
botHandler.Start()
|
||||||
|
@ -635,13 +640,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if len(dataArray) == 4 {
|
if len(dataArray) == 4 {
|
||||||
num, err := strconv.Atoi(dataArray[3])
|
num, err := strconv.Atoi(dataArray[3])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if num == -2 {
|
switch num {
|
||||||
|
case -2:
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
} else if num == -1 {
|
case -1:
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -698,8 +704,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -709,13 +719,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
num, err := strconv.Atoi(dataArray[2])
|
num, err := strconv.Atoi(dataArray[2])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if num == -2 {
|
switch num {
|
||||||
|
case -2:
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
} else if num == -1 {
|
case -1:
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -838,13 +849,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if len(dataArray) == 4 {
|
if len(dataArray) == 4 {
|
||||||
num, err := strconv.Atoi(dataArray[3])
|
num, err := strconv.Atoi(dataArray[3])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if num == -2 {
|
switch num {
|
||||||
|
case -2:
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
} else if num == -1 {
|
case -1:
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -913,8 +925,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -924,13 +940,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
num, err := strconv.Atoi(dataArray[2])
|
num, err := strconv.Atoi(dataArray[2])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if num == -2 {
|
switch num {
|
||||||
|
case -2:
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
} else if num == -1 {
|
case -1:
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1029,13 +1046,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if len(dataArray) == 4 {
|
if len(dataArray) == 4 {
|
||||||
num, err := strconv.Atoi(dataArray[3])
|
num, err := strconv.Atoi(dataArray[3])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if num == -2 {
|
switch num {
|
||||||
|
case -2:
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
} else if num == -1 {
|
case -1:
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1095,8 +1113,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -1106,13 +1128,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
if len(dataArray) == 3 {
|
if len(dataArray) == 3 {
|
||||||
num, err := strconv.Atoi(dataArray[2])
|
num, err := strconv.Atoi(dataArray[2])
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if num == -2 {
|
switch num {
|
||||||
|
case -2:
|
||||||
inputNumber = 0
|
inputNumber = 0
|
||||||
} else if num == -1 {
|
case -1:
|
||||||
if inputNumber > 0 {
|
if inputNumber > 0 {
|
||||||
inputNumber = (inputNumber / 10)
|
inputNumber = (inputNumber / 10)
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
inputNumber = (inputNumber * 10) + num
|
inputNumber = (inputNumber * 10) + num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1157,8 +1180,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(
|
||||||
|
@ -1284,8 +1305,12 @@ 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)
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
t.addClient(chatId, message_text)
|
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
@ -1520,6 +1545,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
t.addClient(chatId, message_text, messageId)
|
t.addClient(chatId, message_text, messageId)
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
|
||||||
case "add_client_default_ip_limit":
|
case "add_client_default_ip_limit":
|
||||||
|
@ -1530,6 +1559,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
t.addClient(chatId, message_text, messageId)
|
t.addClient(chatId, message_text, messageId)
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
|
||||||
case "add_client_submit_disable":
|
case "add_client_submit_disable":
|
||||||
|
@ -1594,6 +1627,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
|
valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
|
||||||
|
if err != nil {
|
||||||
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, valid_emails := range valid_emails {
|
for _, valid_emails := range valid_emails {
|
||||||
traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
|
||||||
|
@ -1756,6 +1793,10 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
|
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("BuildJSONForProtocol run failed:", err)
|
||||||
|
return false, errors.New("failed to build JSON for protocol")
|
||||||
|
}
|
||||||
|
|
||||||
newInbound := &model.Inbound{
|
newInbound := &model.Inbound{
|
||||||
Id: receiver_inbound_ID,
|
Id: receiver_inbound_ID,
|
||||||
|
@ -1859,7 +1900,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)
|
||||||
}
|
}
|
||||||
|
@ -2004,10 +2045,11 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := ""
|
msg := ""
|
||||||
if status == LoginSuccess {
|
switch status {
|
||||||
|
case LoginSuccess:
|
||||||
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
||||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||||
} else if status == LoginFail {
|
case LoginFail:
|
||||||
msg += t.I18nBot("tgbot.messages.loginFailed")
|
msg += t.I18nBot("tgbot.messages.loginFailed")
|
||||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||||
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
|
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
|
||||||
|
@ -2167,6 +2209,22 @@ func (t *Tgbot) clientInfoMsg(
|
||||||
expiryTime = t.I18nBot("tgbot.unlimited")
|
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||||
} else if diff > 172800 || !traffic.Enable {
|
} else if diff > 172800 || !traffic.Enable {
|
||||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||||
|
if diff > 0 {
|
||||||
|
days := diff / 86400
|
||||||
|
hours := (diff % 86400) / 3600
|
||||||
|
minutes := (diff % 3600) / 60
|
||||||
|
remainingTime := ""
|
||||||
|
if days > 0 {
|
||||||
|
remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
|
||||||
|
}
|
||||||
|
expiryTime += fmt.Sprintf(" (%s)", remainingTime)
|
||||||
|
}
|
||||||
} else if traffic.ExpiryTime < 0 {
|
} else if traffic.ExpiryTime < 0 {
|
||||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||||
flag = true
|
flag = true
|
||||||
|
@ -2765,7 +2823,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 +2837,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 +2861,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 +2882,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 +2900,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 +2911,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 +2926,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 +2939,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 +2962,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" = "نقل"
|
||||||
|
@ -173,8 +176,6 @@
|
||||||
"deleteClient" = "حذف العميل"
|
"deleteClient" = "حذف العميل"
|
||||||
"deleteClientContent" = "متأكد إنك عايز تحذف العميل؟"
|
"deleteClientContent" = "متأكد إنك عايز تحذف العميل؟"
|
||||||
"resetTrafficContent" = "متأكد إنك عايز تعيد ضبط الترافيك؟"
|
"resetTrafficContent" = "متأكد إنك عايز تعيد ضبط الترافيك؟"
|
||||||
"inboundUpdateSuccess" = "تم تحديث الوارد بنجاح."
|
|
||||||
"inboundCreateSuccess" = "تم إنشاء الوارد بنجاح."
|
|
||||||
"copyLink" = "انسخ الرابط"
|
"copyLink" = "انسخ الرابط"
|
||||||
"address" = "العنوان"
|
"address" = "العنوان"
|
||||||
"network" = "الشبكة"
|
"network" = "الشبكة"
|
||||||
|
@ -560,24 +561,25 @@
|
||||||
"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة"
|
"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ الكيبورد المخصص اتقفلت!"
|
"keyboardClosed" = "❌ لوحة المفاتيح مغلقة!"
|
||||||
"noResult" = "❗ مفيش نتيجة!"
|
"noResult" = "❗ لا يوجد نتائج!"
|
||||||
"noQuery" = "❌ مش لاقي السؤال! استخدم الأمر تاني!"
|
"noQuery" = "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!"
|
||||||
"wentWrong" = "❌ حصل خطأ!"
|
"wentWrong" = "❌ حدث خطأ ما!"
|
||||||
"noIpRecord" = "❗ مفيش سجل IP!"
|
"noIpRecord" = "❗ لا يوجد سجل IP!"
|
||||||
"noInbounds" = "❗ مفيش إدخال متواجد!"
|
"noInbounds" = "❗ لم يتم العثور على أي وارد!"
|
||||||
"unlimited" = "♾ غير محدود (إعادة ضبط)"
|
"unlimited" = "♾ غير محدود (إعادة تعيين)"
|
||||||
"add" = "أضف"
|
"add" = "إضافة"
|
||||||
"month" = "شهر"
|
"month" = "شهر"
|
||||||
"months" = "شهور"
|
"months" = "أشهر"
|
||||||
"day" = "يوم"
|
"day" = "يوم"
|
||||||
"days" = "أيام"
|
"days" = "أيام"
|
||||||
"hours" = "ساعات"
|
"hours" = "ساعات"
|
||||||
"unknown" = "مش معروف"
|
"minutes" = "دقائق"
|
||||||
"inbounds" = "الإدخالات"
|
"unknown" = "غير معروف"
|
||||||
|
"inbounds" = "الواردات"
|
||||||
"clients" = "العملاء"
|
"clients" = "العملاء"
|
||||||
"offline" = "🔴 أوفلاين"
|
"offline" = "🔴 غير متصل"
|
||||||
"online" = "🟢 أونلاين"
|
"online" = "🟢 متصل"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
"unknown" = "❗ أمر مش معروف."
|
"unknown" = "❗ أمر مش معروف."
|
||||||
|
@ -645,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 اتحدّث في: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 اتحدّث في: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ أيوه"
|
"yes" = "✅ أيوه"
|
||||||
"no" = "❌ لأ"
|
"no" = "❌ لأ"
|
||||||
|
|
||||||
"received_id" = "🔑📥 الـ ID اتحدث."
|
"received_id" = "🔑📥 الـ ID اتحدث."
|
||||||
"received_password" = "🔑📥 الباسورد اتحدث."
|
"received_password" = "🔑📥 الباسورد اتحدث."
|
||||||
"received_email" = "📧📥 الإيميل اتحدث."
|
"received_email" = "📧📥 الإيميل اتحدث."
|
||||||
|
@ -665,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ❌ فشل \n\n🛠️ الخطأ: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 البريد الإلكتروني: {{ .ClientEmail }}\n🏁 النتيجة: ❌ فشل \n\n🛠️ الخطأ: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 عملية إعادة ضبط الترافيك خلصت لكل العملاء."
|
"FinishProcess" = "🔚 عملية إعادة ضبط الترافيك خلصت لكل العملاء."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ اقفل الكيبورد"
|
"closeKeyboard" = "❌ اقفل الكيبورد"
|
||||||
"cancel" = "❌ إلغاء"
|
"cancel" = "❌ إلغاء"
|
||||||
|
@ -699,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 حد الترافيك"
|
"limitTraffic" = "🚧 حد الترافيك"
|
||||||
"getBanLogs" = "احصل على سجلات الحظر"
|
"getBanLogs" = "احصل على سجلات الحظر"
|
||||||
"allClients" = "كل العملاء"
|
"allClients" = "كل العملاء"
|
||||||
|
|
||||||
"addClient" = "إضافة عميل"
|
"addClient" = "إضافة عميل"
|
||||||
"submitDisable" = "إرسال كمعطّل ☑️"
|
"submitDisable" = "إرسال كمعطّل ☑️"
|
||||||
"submitEnable" = "إرسال كمفعّل ✅"
|
"submitEnable" = "إرسال كمفعّل ✅"
|
||||||
|
@ -711,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "إعادة ضبط جميع الترافيك"
|
"ResetAllTraffics" = "إعادة ضبط جميع الترافيك"
|
||||||
"SortedTrafficUsageReport" = "تقرير استخدام الترافيك المرتب"
|
"SortedTrafficUsageReport" = "تقرير استخدام الترافيك المرتب"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ العملية نجحت!"
|
"successfulOperation" = "✅ العملية نجحت!"
|
||||||
"errorOperation" = "❗ حصل خطأ في العملية."
|
"errorOperation" = "❗ حصل خطأ في العملية."
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -571,6 +574,7 @@
|
||||||
"day" = "Day"
|
"day" = "Day"
|
||||||
"days" = "Days"
|
"days" = "Days"
|
||||||
"hours" = "Hours"
|
"hours" = "Hours"
|
||||||
|
"minutes" = "Minutes"
|
||||||
"unknown" = "Unknown"
|
"unknown" = "Unknown"
|
||||||
"inbounds" = "Inbounds"
|
"inbounds" = "Inbounds"
|
||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
|
@ -643,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Yes"
|
"yes" = "✅ Yes"
|
||||||
"no" = "❌ No"
|
"no" = "❌ No"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID updated."
|
"received_id" = "🔑📥 ID updated."
|
||||||
"received_password" = "🔑📥 Password updated."
|
"received_password" = "🔑📥 Password updated."
|
||||||
"received_email" = "📧📥 Email updated."
|
"received_email" = "📧📥 Email updated."
|
||||||
|
@ -663,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Result: ❌ Failed \n\n🛠️ Error: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Result: ❌ Failed \n\n🛠️ Error: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Traffic reset process finished for all clients."
|
"FinishProcess" = "🔚 Traffic reset process finished for all clients."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Close Keyboard"
|
"closeKeyboard" = "❌ Close Keyboard"
|
||||||
"cancel" = "❌ Cancel"
|
"cancel" = "❌ Cancel"
|
||||||
|
@ -697,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Traffic Limit"
|
"limitTraffic" = "🚧 Traffic Limit"
|
||||||
"getBanLogs" = "Get Ban Logs"
|
"getBanLogs" = "Get Ban Logs"
|
||||||
"allClients" = "All Clients"
|
"allClients" = "All Clients"
|
||||||
|
|
||||||
"addClient" = "Add Client"
|
"addClient" = "Add Client"
|
||||||
"submitDisable" = "Submit As Disable ☑️"
|
"submitDisable" = "Submit As Disable ☑️"
|
||||||
"submitEnable" = "Submit As Enable ✅"
|
"submitEnable" = "Submit As Enable ✅"
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Tiempo de Funcionamiento"
|
"operationHours" = "Tiempo de Funcionamiento"
|
||||||
"systemLoad" = "Carga del Sistema"
|
"systemLoad" = "Carga del Sistema"
|
||||||
"systemLoadDesc" = "promedio de carga del sistema en los últimos 1, 5 y 15 minutos"
|
"systemLoadDesc" = "promedio de carga del sistema en los últimos 1, 5 y 15 minutos"
|
||||||
"connectionTcpCountDesc" = "Conexiones TCP totales en todas las tarjetas de red."
|
|
||||||
"connectionUdpCountDesc" = "Conexiones UDP totales en todas las tarjetas de red."
|
|
||||||
"connectionCount" = "Número de Conexiones"
|
"connectionCount" = "Número de Conexiones"
|
||||||
"ipAddresses" = "Direcciones IP"
|
"ipAddresses" = "Direcciones IP"
|
||||||
"toggleIpVisibility" = "Alternar visibilidad de la IP"
|
"toggleIpVisibility" = "Alternar visibilidad de la IP"
|
||||||
|
@ -134,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"
|
||||||
|
@ -160,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"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Eliminar cliente"
|
"deleteClient" = "Eliminar cliente"
|
||||||
"deleteClientContent" = "¿Está seguro de que desea eliminar el cliente?"
|
"deleteClientContent" = "¿Está seguro de que desea eliminar el cliente?"
|
||||||
"resetTrafficContent" = "¿Confirmar restablecimiento de tráfico?"
|
"resetTrafficContent" = "¿Confirmar restablecimiento de tráfico?"
|
||||||
"inboundUpdateSuccess" = "La entrada se ha actualizado correctamente."
|
|
||||||
"inboundCreateSuccess" = "La entrada se ha creado correctamente."
|
|
||||||
"copyLink" = "Copiar Enlace"
|
"copyLink" = "Copiar Enlace"
|
||||||
"address" = "Dirección"
|
"address" = "Dirección"
|
||||||
"network" = "Red"
|
"network" = "Red"
|
||||||
|
@ -562,23 +561,24 @@
|
||||||
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
|
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"
|
"keyboardClosed" = "❌ Teclado cerrado!"
|
||||||
"noResult" = "❗ ¡Sin resultados!"
|
"noResult" = "❗ ¡No hay resultados!"
|
||||||
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!"
|
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando de nuevo!"
|
||||||
"wentWrong" = "❌ ¡Algo salió mal!"
|
"wentWrong" = "❌ ¡Algo salió mal!"
|
||||||
"noIpRecord" = "❗ ¡Sin Registro de IP!"
|
"noIpRecord" = "❗ ¡No hay registro de IP!"
|
||||||
"noInbounds" = "❗ ¡No se encontraron entradas!"
|
"noInbounds" = "❗ ¡No se encontraron entradas!"
|
||||||
"unlimited" = "♾ Ilimitado"
|
"unlimited" = "♾ Ilimitado (Restablecer)"
|
||||||
"add" = "Agregar"
|
"add" = "Añadir"
|
||||||
"month" = "Mes"
|
"month" = "Mes"
|
||||||
"months" = "Meses"
|
"months" = "Meses"
|
||||||
"day" = "Día"
|
"day" = "Día"
|
||||||
"days" = "Días"
|
"days" = "Días"
|
||||||
"hours" = "Horas"
|
"hours" = "Horas"
|
||||||
|
"minutes" = "Minutos"
|
||||||
"unknown" = "Desconocido"
|
"unknown" = "Desconocido"
|
||||||
"inbounds" = "Entradas"
|
"inbounds" = "Entradas"
|
||||||
"clients" = "Clientes"
|
"clients" = "Clientes"
|
||||||
"offline" = "🔴 Sin conexión"
|
"offline" = "🔴 Desconectado"
|
||||||
"online" = "🟢 En línea"
|
"online" = "🟢 En línea"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Actualizado en: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Sí"
|
"yes" = "✅ Sí"
|
||||||
"no" = "❌ No"
|
"no" = "❌ No"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID actualizado."
|
"received_id" = "🔑📥 ID actualizado."
|
||||||
"received_password" = "🔑📥 Contraseña actualizada."
|
"received_password" = "🔑📥 Contraseña actualizada."
|
||||||
"received_email" = "📧📥 Correo electrónico actualizado."
|
"received_email" = "📧📥 Correo electrónico actualizado."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ❌ Fallido \n\n🛠️ Error: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Correo: {{ .ClientEmail }}\n🏁 Resultado: ❌ Fallido \n\n🛠️ Error: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Proceso de reinicio de tráfico finalizado para todos los clientes."
|
"FinishProcess" = "🔚 Proceso de reinicio de tráfico finalizado para todos los clientes."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Cerrar Teclado"
|
"closeKeyboard" = "❌ Cerrar Teclado"
|
||||||
"cancel" = "❌ Cancelar"
|
"cancel" = "❌ Cancelar"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Límite de tráfico"
|
"limitTraffic" = "🚧 Límite de tráfico"
|
||||||
"getBanLogs" = "Registros de prohibición"
|
"getBanLogs" = "Registros de prohibición"
|
||||||
"allClients" = "Todos los Clientes"
|
"allClients" = "Todos los Clientes"
|
||||||
|
|
||||||
"addClient" = "Añadir cliente"
|
"addClient" = "Añadir cliente"
|
||||||
"submitDisable" = "Enviar como deshabilitado ☑️"
|
"submitDisable" = "Enviar como deshabilitado ☑️"
|
||||||
"submitEnable" = "Enviar como habilitado ✅"
|
"submitEnable" = "Enviar como habilitado ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Reiniciar todo el tráfico"
|
"ResetAllTraffics" = "Reiniciar todo el tráfico"
|
||||||
"SortedTrafficUsageReport" = "Informe de uso de tráfico ordenado"
|
"SortedTrafficUsageReport" = "Informe de uso de tráfico ordenado"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ ¡Exitosa!"
|
"successfulOperation" = "✅ ¡Exitosa!"
|
||||||
"errorOperation" = "❗ Error en la Operación."
|
"errorOperation" = "❗ Error en la Operación."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "مدتکارکرد"
|
"operationHours" = "مدتکارکرد"
|
||||||
"systemLoad" = "بارسیستم"
|
"systemLoad" = "بارسیستم"
|
||||||
"systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته"
|
"systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته"
|
||||||
"connectionTcpCountDesc" = "در تمامشبکهها TCP مجموعاتصالات"
|
|
||||||
"connectionUdpCountDesc" = "در تمامشبکهها UDP مجموعاتصالات"
|
|
||||||
"connectionCount" = "تعداد کانکشن ها"
|
"connectionCount" = "تعداد کانکشن ها"
|
||||||
"ipAddresses" = "آدرسهای IP"
|
"ipAddresses" = "آدرسهای IP"
|
||||||
"toggleIpVisibility" = "تغییر وضعیت نمایش IP"
|
"toggleIpVisibility" = "تغییر وضعیت نمایش IP"
|
||||||
|
@ -134,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray با موفقیت بهروز شد"
|
"xraySwitchVersionPopover" = "Xray با موفقیت بهروز شد"
|
||||||
"geofileUpdateDialog" = "آیا واقعاً میخواهید فایل جغرافیایی را بهروز کنید؟"
|
"geofileUpdateDialog" = "آیا واقعاً میخواهید فایل جغرافیایی را بهروز کنید؟"
|
||||||
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را بهروز میکند."
|
"geofileUpdateDialogDesc" = "این عمل فایل #filename# را بهروز میکند."
|
||||||
|
"geofilesUpdateDialogDesc" = "با این کار همه فایلها بهروزرسانی میشوند."
|
||||||
|
"geofilesUpdateAll" = "همه را بهروزرسانی کنید"
|
||||||
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت بهروز شد"
|
"geofileUpdatePopover" = "فایل جغرافیایی با موفقیت بهروز شد"
|
||||||
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
|
"dontRefresh" = "در حال نصب، لطفا صفحه را رفرش نکنید"
|
||||||
"logs" = "گزارشها"
|
"logs" = "گزارشها"
|
||||||
|
@ -160,6 +160,7 @@
|
||||||
"remark" = "نام"
|
"remark" = "نام"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"port" = "پورت"
|
"port" = "پورت"
|
||||||
|
"portMap" = "پورتهای نظیر"
|
||||||
"traffic" = "ترافیک"
|
"traffic" = "ترافیک"
|
||||||
"details" = "توضیحات"
|
"details" = "توضیحات"
|
||||||
"transportConfig" = "نحوه اتصال"
|
"transportConfig" = "نحوه اتصال"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "حذف کاربر"
|
"deleteClient" = "حذف کاربر"
|
||||||
"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید؟"
|
"deleteClientContent" = "آیا مطمئن به حذف کاربر هستید؟"
|
||||||
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید؟"
|
"resetTrafficContent" = "آیا مطمئن به ریست ترافیک هستید؟"
|
||||||
"inboundUpdateSuccess" = "ورودی با موفقیت بهروزرسانی شد."
|
|
||||||
"inboundCreateSuccess" = "ورودی با موفقیت ایجاد شد."
|
|
||||||
"copyLink" = "کپی لینک"
|
"copyLink" = "کپی لینک"
|
||||||
"address" = "آدرس"
|
"address" = "آدرس"
|
||||||
"network" = "شبکه"
|
"network" = "شبکه"
|
||||||
|
@ -359,16 +358,16 @@
|
||||||
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آیپیها خالیبگذارید"
|
"subDomainDesc" = "آدرس دامنه برای سرویس سابسکریپشن. برای گوش دادن به تمام دامنهها و آیپیها خالیبگذارید"
|
||||||
"subUpdates" = "فاصله بروزرسانی سابسکریپشن"
|
"subUpdates" = "فاصله بروزرسانی سابسکریپشن"
|
||||||
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامههای کاربری. (واحد: ساعت"
|
"subUpdatesDesc" = "(فاصله مابین بروزرسانی در برنامههای کاربری. (واحد: ساعت"
|
||||||
"externalTrafficInformEnable" = "اطلاع رسانی خارجی مصرف ترافیک"
|
|
||||||
"externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود"
|
|
||||||
"externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک"
|
|
||||||
"externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود"
|
|
||||||
"subEncrypt" = "کدگذاری"
|
"subEncrypt" = "کدگذاری"
|
||||||
"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه"
|
"subEncryptDesc" = "کدگذاری خواهدشد Base64 محتوای برگشتی سرویس سابسکریپشن برپایه"
|
||||||
"subShowInfo" = "نمایش اطلاعات مصرف"
|
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||||
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در برنامههای کاربری نمایش میدهد"
|
||||||
"subURI" = "پروکسی معکوس URI مسیر"
|
"subURI" = "پروکسی معکوس URI مسیر"
|
||||||
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
"subURIDesc" = "سابسکریپشن را برای استفاده در پشت پراکسیها تغییر میدهد URI مسیر"
|
||||||
|
"externalTrafficInformEnable" = "اطلاع رسانی خارجی مصرف ترافیک"
|
||||||
|
"externalTrafficInformEnableDesc" = "مصرف ترافیک به سرویس خارجی ارسال می شود"
|
||||||
|
"externalTrafficInformURI" = "لینک اطلاع رسانی خارجی مصرف ترافیک"
|
||||||
|
"externalTrafficInformURIDesc" = "ترافیک های مصرفی به این لینک هم ارسال می شود"
|
||||||
"fragment" = "فرگمنت"
|
"fragment" = "فرگمنت"
|
||||||
"fragmentDesc" = "فعال کردن فرگمنت برای بستهی نخست تیالاس"
|
"fragmentDesc" = "فعال کردن فرگمنت برای بستهی نخست تیالاس"
|
||||||
"fragmentSett" = "تنظیمات فرگمنت"
|
"fragmentSett" = "تنظیمات فرگمنت"
|
||||||
|
@ -562,22 +561,23 @@
|
||||||
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
|
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
|
"keyboardClosed" = "❌ صفحه کلید بسته شد!"
|
||||||
"noResult" = "❗ نتیجهای یافت نشد!"
|
"noResult" = "❗ نتیجه ای یافت نشد!"
|
||||||
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
|
"noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!"
|
||||||
"wentWrong" = "❌ مشکلی رخ داده است!"
|
"wentWrong" = "❌ مشکلی پیش آمد!"
|
||||||
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
"noIpRecord" = "❗ رکورد آی پی وجود ندارد!"
|
||||||
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
"unlimited" = "♾ - نامحدود(ریست)"
|
"unlimited" = "♾ نامحدود(ریست)"
|
||||||
"add" = "اضافه کردن"
|
"add" = "افزودن"
|
||||||
"month" = "ماه"
|
"month" = "ماه"
|
||||||
"months" = "ماه"
|
"months" = "ماه"
|
||||||
"day" = "روز"
|
"day" = "روز"
|
||||||
"days" = "روز"
|
"days" = "روز"
|
||||||
"hours" = "ساعت"
|
"hours" = "ساعت"
|
||||||
|
"minutes" = "دقیقه"
|
||||||
"unknown" = "نامشخص"
|
"unknown" = "نامشخص"
|
||||||
"inbounds" = "ورودیها"
|
"inbounds" = "ورودی ها"
|
||||||
"clients" = "کلاینتها"
|
"clients" = "کاربران"
|
||||||
"offline" = "🔴 آفلاین"
|
"offline" = "🔴 آفلاین"
|
||||||
"online" = "🟢 آنلاین"
|
"online" = "🟢 آنلاین"
|
||||||
|
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 تازهسازی شده در: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 تازهسازی شده در: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ بله"
|
"yes" = "✅ بله"
|
||||||
"no" = "❌ خیر"
|
"no" = "❌ خیر"
|
||||||
|
|
||||||
"received_id" = "🔑📥 شناسه بهروزرسانی شد."
|
"received_id" = "🔑📥 شناسه بهروزرسانی شد."
|
||||||
"received_password" = "🔑📥 رمز عبور بهروزرسانی شد."
|
"received_password" = "🔑📥 رمز عبور بهروزرسانی شد."
|
||||||
"received_email" = "📧📥 ایمیل بهروزرسانی شد."
|
"received_email" = "📧📥 ایمیل بهروزرسانی شد."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ❌ ناموفق \n\n🛠️ خطا: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 ایمیل: {{ .ClientEmail }}\n🏁 نتیجه: ❌ ناموفق \n\n🛠️ خطا: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 فرآیند بازنشانی ترافیک برای همه مشتریان به پایان رسید."
|
"FinishProcess" = "🔚 فرآیند بازنشانی ترافیک برای همه مشتریان به پایان رسید."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ بستن کیبورد"
|
"closeKeyboard" = "❌ بستن کیبورد"
|
||||||
"cancel" = "❌ لغو"
|
"cancel" = "❌ لغو"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 محدودیت ترافیک"
|
"limitTraffic" = "🚧 محدودیت ترافیک"
|
||||||
"getBanLogs" = "گزارش های بلوک را دریافت کنید"
|
"getBanLogs" = "گزارش های بلوک را دریافت کنید"
|
||||||
"allClients" = "همه مشتریان"
|
"allClients" = "همه مشتریان"
|
||||||
|
|
||||||
"addClient" = "افزودن مشتری"
|
"addClient" = "افزودن مشتری"
|
||||||
"submitDisable" = "ارسال به عنوان غیرفعال ☑️"
|
"submitDisable" = "ارسال به عنوان غیرفعال ☑️"
|
||||||
"submitEnable" = "ارسال به عنوان فعال ✅"
|
"submitEnable" = "ارسال به عنوان فعال ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "بازنشانی همه ترافیکها"
|
"ResetAllTraffics" = "بازنشانی همه ترافیکها"
|
||||||
"SortedTrafficUsageReport" = "گزارش استفاده از ترافیک مرتبشده"
|
"SortedTrafficUsageReport" = "گزارش استفاده از ترافیک مرتبشده"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ انجام شد!"
|
"successfulOperation" = "✅ انجام شد!"
|
||||||
"errorOperation" = "❗ خطا در عملیات."
|
"errorOperation" = "❗ خطا در عملیات."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Waktu Aktif"
|
"operationHours" = "Waktu Aktif"
|
||||||
"systemLoad" = "Beban Sistem"
|
"systemLoad" = "Beban Sistem"
|
||||||
"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir"
|
"systemLoadDesc" = "Rata-rata beban sistem selama 1, 5, dan 15 menit terakhir"
|
||||||
"connectionTcpCountDesc" = "Total koneksi TCP di seluruh sistem"
|
|
||||||
"connectionUdpCountDesc" = "Total koneksi UDP di seluruh sistem"
|
|
||||||
"connectionCount" = "Statistik Koneksi"
|
"connectionCount" = "Statistik Koneksi"
|
||||||
"ipAddresses" = "Alamat IP"
|
"ipAddresses" = "Alamat IP"
|
||||||
"toggleIpVisibility" = "Alihkan visibilitas IP"
|
"toggleIpVisibility" = "Alihkan visibilitas IP"
|
||||||
|
@ -134,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"
|
||||||
|
@ -160,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"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Hapus Klien"
|
"deleteClient" = "Hapus Klien"
|
||||||
"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?"
|
"deleteClientContent" = "Apakah Anda yakin ingin menghapus klien?"
|
||||||
"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?"
|
"resetTrafficContent" = "Apakah Anda yakin ingin mereset traffic?"
|
||||||
"inboundUpdateSuccess" = "Inbound berhasil diperbarui."
|
|
||||||
"inboundCreateSuccess" = "Inbound berhasil dibuat."
|
|
||||||
"copyLink" = "Salin URL"
|
"copyLink" = "Salin URL"
|
||||||
"address" = "Alamat"
|
"address" = "Alamat"
|
||||||
"network" = "Jaringan"
|
"network" = "Jaringan"
|
||||||
|
@ -422,7 +421,6 @@
|
||||||
"RoutingStrategy" = "Strategi Pengalihan Keseluruhan"
|
"RoutingStrategy" = "Strategi Pengalihan Keseluruhan"
|
||||||
"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan."
|
"RoutingStrategyDesc" = "Atur strategi pengalihan lalu lintas keseluruhan untuk menyelesaikan semua permintaan."
|
||||||
"Torrent" = "Blokir Protokol BitTorrent"
|
"Torrent" = "Blokir Protokol BitTorrent"
|
||||||
"TorrentDesc" = "Memblokir protokol BitTorrent."
|
|
||||||
"Inbounds" = "Masuk"
|
"Inbounds" = "Masuk"
|
||||||
"InboundsDesc" = "Menerima klien tertentu."
|
"InboundsDesc" = "Menerima klien tertentu."
|
||||||
"Outbounds" = "Keluar"
|
"Outbounds" = "Keluar"
|
||||||
|
@ -563,21 +561,22 @@
|
||||||
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
|
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
|
"keyboardClosed" = "❌ Keyboard ditutup!"
|
||||||
"noResult" = "❗ Tidak ada hasil!"
|
"noResult" = "❗ Tidak ada hasil!"
|
||||||
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
|
"noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!"
|
||||||
"wentWrong" = "❌ Ada yang salah!"
|
"wentWrong" = "❌ Terjadi kesalahan!"
|
||||||
"noIpRecord" = "❗ Tidak ada Catatan IP!"
|
"noIpRecord" = "❗ Tidak ada Catatan IP!"
|
||||||
"noInbounds" = "❗ Tidak ada masuk ditemukan!"
|
"noInbounds" = "❗ Tidak ada inbound yang ditemukan!"
|
||||||
"unlimited" = "♾ Tak terbatas"
|
"unlimited" = "♾ Tidak terbatas (Reset)"
|
||||||
"add" = "Tambah"
|
"add" = "Tambah"
|
||||||
"month" = "Bulan"
|
"month" = "Bulan"
|
||||||
"months" = "Bulan"
|
"months" = "Bulan"
|
||||||
"day" = "Hari"
|
"day" = "Hari"
|
||||||
"days" = "Hari"
|
"days" = "Hari"
|
||||||
"hours" = "Jam"
|
"hours" = "Jam"
|
||||||
|
"minutes" = "Menit"
|
||||||
"unknown" = "Tidak diketahui"
|
"unknown" = "Tidak diketahui"
|
||||||
"inbounds" = "Masuk"
|
"inbounds" = "Inbound"
|
||||||
"clients" = "Klien"
|
"clients" = "Klien"
|
||||||
"offline" = "🔴 Offline"
|
"offline" = "🔴 Offline"
|
||||||
"online" = "🟢 Online"
|
"online" = "🟢 Online"
|
||||||
|
@ -648,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Diperbarui Pada: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Ya"
|
"yes" = "✅ Ya"
|
||||||
"no" = "❌ Tidak"
|
"no" = "❌ Tidak"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID diperbarui."
|
"received_id" = "🔑📥 ID diperbarui."
|
||||||
"received_password" = "🔑📥 Kata sandi diperbarui."
|
"received_password" = "🔑📥 Kata sandi diperbarui."
|
||||||
"received_email" = "📧📥 Email diperbarui."
|
"received_email" = "📧📥 Email diperbarui."
|
||||||
|
@ -668,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ❌ Gagal \n\n🛠️ Kesalahan: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Hasil: ❌ Gagal \n\n🛠️ Kesalahan: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Proses reset traffic selesai untuk semua klien."
|
"FinishProcess" = "🔚 Proses reset traffic selesai untuk semua klien."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Tutup Papan Ketik"
|
"closeKeyboard" = "❌ Tutup Papan Ketik"
|
||||||
"cancel" = "❌ Batal"
|
"cancel" = "❌ Batal"
|
||||||
|
@ -702,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Batas Lalu Lintas"
|
"limitTraffic" = "🚧 Batas Lalu Lintas"
|
||||||
"getBanLogs" = "Dapatkan Log Pemblokiran"
|
"getBanLogs" = "Dapatkan Log Pemblokiran"
|
||||||
"allClients" = "Semua Klien"
|
"allClients" = "Semua Klien"
|
||||||
|
|
||||||
"addClient" = "Tambah Klien"
|
"addClient" = "Tambah Klien"
|
||||||
"submitDisable" = "Kirim Sebagai Nonaktif ☑️"
|
"submitDisable" = "Kirim Sebagai Nonaktif ☑️"
|
||||||
"submitEnable" = "Kirim Sebagai Aktif ✅"
|
"submitEnable" = "Kirim Sebagai Aktif ✅"
|
||||||
|
@ -714,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Reset Semua Lalu Lintas"
|
"ResetAllTraffics" = "Reset Semua Lalu Lintas"
|
||||||
"SortedTrafficUsageReport" = "Laporan Penggunaan Lalu Lintas yang Terurut"
|
"SortedTrafficUsageReport" = "Laporan Penggunaan Lalu Lintas yang Terurut"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ Operasi berhasil!"
|
"successfulOperation" = "✅ Operasi berhasil!"
|
||||||
"errorOperation" = "❗ Kesalahan dalam operasi."
|
"errorOperation" = "❗ Kesalahan dalam operasi."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "システム稼働時間"
|
"operationHours" = "システム稼働時間"
|
||||||
"systemLoad" = "システム負荷"
|
"systemLoad" = "システム負荷"
|
||||||
"systemLoadDesc" = "過去1、5、15分間のシステム平均負荷"
|
"systemLoadDesc" = "過去1、5、15分間のシステム平均負荷"
|
||||||
"connectionTcpCountDesc" = "システム内のすべてのTCP接続数"
|
|
||||||
"connectionUdpCountDesc" = "システム内のすべてのUDP接続数"
|
|
||||||
"connectionCount" = "接続数"
|
"connectionCount" = "接続数"
|
||||||
"ipAddresses" = "IPアドレス"
|
"ipAddresses" = "IPアドレス"
|
||||||
"toggleIpVisibility" = "IPの表示を切り替える"
|
"toggleIpVisibility" = "IPの表示を切り替える"
|
||||||
|
@ -134,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xrayの更新が成功しました"
|
"xraySwitchVersionPopover" = "Xrayの更新が成功しました"
|
||||||
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
|
"geofileUpdateDialog" = "ジオファイルを本当に更新しますか?"
|
||||||
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
|
"geofileUpdateDialogDesc" = "これにより#filename#ファイルが更新されます。"
|
||||||
|
"geofilesUpdateDialogDesc" = "これにより、すべてのファイルが更新されます。"
|
||||||
|
"geofilesUpdateAll" = "すべて更新"
|
||||||
"geofileUpdatePopover" = "ジオファイルの更新が成功しました"
|
"geofileUpdatePopover" = "ジオファイルの更新が成功しました"
|
||||||
"dontRefresh" = "インストール中、このページをリロードしないでください"
|
"dontRefresh" = "インストール中、このページをリロードしないでください"
|
||||||
"logs" = "ログ"
|
"logs" = "ログ"
|
||||||
|
@ -160,6 +160,7 @@
|
||||||
"remark" = "備考"
|
"remark" = "備考"
|
||||||
"protocol" = "プロトコル"
|
"protocol" = "プロトコル"
|
||||||
"port" = "ポート"
|
"port" = "ポート"
|
||||||
|
"portMap" = "ポートマッピング"
|
||||||
"traffic" = "トラフィック"
|
"traffic" = "トラフィック"
|
||||||
"details" = "詳細情報"
|
"details" = "詳細情報"
|
||||||
"transportConfig" = "トランスポート設定"
|
"transportConfig" = "トランスポート設定"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "クライアント削除"
|
"deleteClient" = "クライアント削除"
|
||||||
"deleteClientContent" = "クライアントを削除してもよろしいですか?"
|
"deleteClientContent" = "クライアントを削除してもよろしいですか?"
|
||||||
"resetTrafficContent" = "トラフィックをリセットしてもよろしいですか?"
|
"resetTrafficContent" = "トラフィックをリセットしてもよろしいですか?"
|
||||||
"inboundUpdateSuccess" = "インバウンドが正常に更新されました。"
|
|
||||||
"inboundCreateSuccess" = "インバウンドが正常に作成されました。"
|
|
||||||
"copyLink" = "リンクをコピー"
|
"copyLink" = "リンクをコピー"
|
||||||
"address" = "アドレス"
|
"address" = "アドレス"
|
||||||
"network" = "ネットワーク"
|
"network" = "ネットワーク"
|
||||||
|
@ -562,21 +561,22 @@
|
||||||
"resetOutboundTrafficError" = "送信トラフィックのリセットエラー"
|
"resetOutboundTrafficError" = "送信トラフィックのリセットエラー"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ カスタムキーボードが閉じられました!"
|
"keyboardClosed" = "❌ キーボードを閉じました!"
|
||||||
"noResult" = "❗ 結果がありません!"
|
"noResult" = "❗ 結果がありません!"
|
||||||
"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!"
|
"noQuery" = "❌ クエリが見つかりません!コマンドを再利用してください!"
|
||||||
"wentWrong" = "❌ 問題が発生しました!"
|
"wentWrong" = "❌ 何かがうまくいかなかった!"
|
||||||
"noIpRecord" = "❗ IP記録がありません!"
|
"noIpRecord" = "❗ IPレコードがありません!"
|
||||||
"noInbounds" = "❗ インバウンド接続が見つかりません!"
|
"noInbounds" = "❗ インバウンドが見つかりません!"
|
||||||
"unlimited" = "♾ 無制限"
|
"unlimited" = "♾ 無制限(リセット)"
|
||||||
"add" = "追加"
|
"add" = "追加"
|
||||||
"month" = "月"
|
"month" = "月"
|
||||||
"months" = "月"
|
"months" = "ヶ月"
|
||||||
"day" = "日"
|
"day" = "日"
|
||||||
"days" = "日"
|
"days" = "日間"
|
||||||
"hours" = "時間"
|
"hours" = "時間"
|
||||||
|
"minutes" = "分"
|
||||||
"unknown" = "不明"
|
"unknown" = "不明"
|
||||||
"inbounds" = "インバウンド接続"
|
"inbounds" = "インバウンド"
|
||||||
"clients" = "クライアント"
|
"clients" = "クライアント"
|
||||||
"offline" = "🔴 オフライン"
|
"offline" = "🔴 オフライン"
|
||||||
"online" = "🟢 オンライン"
|
"online" = "🟢 オンライン"
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 更新時間:{{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 更新時間:{{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ はい"
|
"yes" = "✅ はい"
|
||||||
"no" = "❌ いいえ"
|
"no" = "❌ いいえ"
|
||||||
|
|
||||||
"received_id" = "🔑📥 IDが更新されました。"
|
"received_id" = "🔑📥 IDが更新されました。"
|
||||||
"received_password" = "🔑📥 パスワードが更新されました。"
|
"received_password" = "🔑📥 パスワードが更新されました。"
|
||||||
"received_email" = "📧📥 メールが更新されました。"
|
"received_email" = "📧📥 メールが更新されました。"
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 メール: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ エラー: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 メール: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ エラー: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 すべてのクライアントのトラフィックリセットが完了しました。"
|
"FinishProcess" = "🔚 すべてのクライアントのトラフィックリセットが完了しました。"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ キーボードを閉じる"
|
"closeKeyboard" = "❌ キーボードを閉じる"
|
||||||
"cancel" = "❌ キャンセル"
|
"cancel" = "❌ キャンセル"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 トラフィック制限"
|
"limitTraffic" = "🚧 トラフィック制限"
|
||||||
"getBanLogs" = "禁止ログ"
|
"getBanLogs" = "禁止ログ"
|
||||||
"allClients" = "すべてのクライアント"
|
"allClients" = "すべてのクライアント"
|
||||||
|
|
||||||
"addClient" = "クライアントを追加"
|
"addClient" = "クライアントを追加"
|
||||||
"submitDisable" = "無効として送信 ☑️"
|
"submitDisable" = "無効として送信 ☑️"
|
||||||
"submitEnable" = "有効として送信 ✅"
|
"submitEnable" = "有効として送信 ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "すべてのトラフィックをリセット"
|
"ResetAllTraffics" = "すべてのトラフィックをリセット"
|
||||||
"SortedTrafficUsageReport" = "ソートされたトラフィック使用レポート"
|
"SortedTrafficUsageReport" = "ソートされたトラフィック使用レポート"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ 成功!"
|
"successfulOperation" = "✅ 成功!"
|
||||||
"errorOperation" = "❗ 操作エラー。"
|
"errorOperation" = "❗ 操作エラー。"
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Tempo de Atividade"
|
"operationHours" = "Tempo de Atividade"
|
||||||
"systemLoad" = "Carga do Sistema"
|
"systemLoad" = "Carga do Sistema"
|
||||||
"systemLoadDesc" = "Média de carga do sistema nos últimos 1, 5 e 15 minutos"
|
"systemLoadDesc" = "Média de carga do sistema nos últimos 1, 5 e 15 minutos"
|
||||||
"connectionTcpCountDesc" = "Total de conexões TCP no sistema"
|
|
||||||
"connectionUdpCountDesc" = "Total de conexões UDP no sistema"
|
|
||||||
"connectionCount" = "Estatísticas de Conexão"
|
"connectionCount" = "Estatísticas de Conexão"
|
||||||
"ipAddresses" = "Endereços IP"
|
"ipAddresses" = "Endereços IP"
|
||||||
"toggleIpVisibility" = "Alternar visibilidade do IP"
|
"toggleIpVisibility" = "Alternar visibilidade do IP"
|
||||||
|
@ -134,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"
|
||||||
|
@ -160,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"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Excluir Cliente"
|
"deleteClient" = "Excluir Cliente"
|
||||||
"deleteClientContent" = "Tem certeza de que deseja excluir o cliente?"
|
"deleteClientContent" = "Tem certeza de que deseja excluir o cliente?"
|
||||||
"resetTrafficContent" = "Tem certeza de que deseja redefinir o tráfego?"
|
"resetTrafficContent" = "Tem certeza de que deseja redefinir o tráfego?"
|
||||||
"inboundUpdateSuccess" = "A entrada foi atualizada com sucesso."
|
|
||||||
"inboundCreateSuccess" = "A entrada foi criada com sucesso."
|
|
||||||
"copyLink" = "Copiar URL"
|
"copyLink" = "Copiar URL"
|
||||||
"address" = "Endereço"
|
"address" = "Endereço"
|
||||||
"network" = "Rede"
|
"network" = "Rede"
|
||||||
|
@ -562,21 +561,22 @@
|
||||||
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
|
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Teclado personalizado fechado!"
|
"keyboardClosed" = "❌ Teclado fechado!"
|
||||||
"noResult" = "❗ Nenhum resultado!"
|
"noResult" = "❗ Nenhum resultado!"
|
||||||
"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!"
|
"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!"
|
||||||
"wentWrong" = "❌ Algo deu errado!"
|
"wentWrong" = "❌ Algo deu errado!"
|
||||||
"noIpRecord" = "❗ Nenhum registro de IP!"
|
"noIpRecord" = "❗ Nenhum registro de IP!"
|
||||||
"noInbounds" = "❗ Nenhuma entrada encontrada!"
|
"noInbounds" = "❗ Nenhum inbound encontrado!"
|
||||||
"unlimited" = "♾ Ilimitado (Reiniciar)"
|
"unlimited" = "♾ Ilimitado (Reset)"
|
||||||
"add" = "Adicionar"
|
"add" = "Adicionar"
|
||||||
"month" = "Mês"
|
"month" = "Mês"
|
||||||
"months" = "Meses"
|
"months" = "Meses"
|
||||||
"day" = "Dia"
|
"day" = "Dia"
|
||||||
"days" = "Dias"
|
"days" = "Dias"
|
||||||
"hours" = "Horas"
|
"hours" = "Horas"
|
||||||
|
"minutes" = "Minutos"
|
||||||
"unknown" = "Desconhecido"
|
"unknown" = "Desconhecido"
|
||||||
"inbounds" = "Entradas"
|
"inbounds" = "Inbounds"
|
||||||
"clients" = "Clientes"
|
"clients" = "Clientes"
|
||||||
"offline" = "🔴 Offline"
|
"offline" = "🔴 Offline"
|
||||||
"online" = "🟢 Online"
|
"online" = "🟢 Online"
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Atualizado em: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Atualizado em: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Sim"
|
"yes" = "✅ Sim"
|
||||||
"no" = "❌ Não"
|
"no" = "❌ Não"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID atualizado."
|
"received_id" = "🔑📥 ID atualizado."
|
||||||
"received_password" = "🔑📥 Senha atualizada."
|
"received_password" = "🔑📥 Senha atualizada."
|
||||||
"received_email" = "📧📥 E-mail atualizado."
|
"received_email" = "📧📥 E-mail atualizado."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ❌ Falhou \n\n🛠️ Erro: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Resultado: ❌ Falhou \n\n🛠️ Erro: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Processo de redefinição de tráfego concluído para todos os clientes."
|
"FinishProcess" = "🔚 Processo de redefinição de tráfego concluído para todos os clientes."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Fechar teclado"
|
"closeKeyboard" = "❌ Fechar teclado"
|
||||||
"cancel" = "❌ Cancelar"
|
"cancel" = "❌ Cancelar"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Limite de tráfego"
|
"limitTraffic" = "🚧 Limite de tráfego"
|
||||||
"getBanLogs" = "Obter logs de banimento"
|
"getBanLogs" = "Obter logs de banimento"
|
||||||
"allClients" = "Todos os clientes"
|
"allClients" = "Todos os clientes"
|
||||||
|
|
||||||
"addClient" = "Adicionar Cliente"
|
"addClient" = "Adicionar Cliente"
|
||||||
"submitDisable" = "Enviar como Desativado ☑️"
|
"submitDisable" = "Enviar como Desativado ☑️"
|
||||||
"submitEnable" = "Enviar como Ativado ✅"
|
"submitEnable" = "Enviar como Ativado ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Redefinir Todo o Tráfego"
|
"ResetAllTraffics" = "Redefinir Todo o Tráfego"
|
||||||
"SortedTrafficUsageReport" = "Relatório de Uso de Tráfego Ordenado"
|
"SortedTrafficUsageReport" = "Relatório de Uso de Tráfego Ordenado"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ Operação bem-sucedida!"
|
"successfulOperation" = "✅ Operação bem-sucedida!"
|
||||||
"errorOperation" = "❗ Erro na operação."
|
"errorOperation" = "❗ Erro na operação."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Время работы системы"
|
"operationHours" = "Время работы системы"
|
||||||
"systemLoad" = "Нагрузка на систему"
|
"systemLoad" = "Нагрузка на систему"
|
||||||
"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут"
|
"systemLoadDesc" = "Средняя загрузка системы за последние 1, 5 и 15 минут"
|
||||||
"connectionTcpCountDesc" = "Общее количество подключений TCP по всем сетевым картам."
|
|
||||||
"connectionUdpCountDesc" = "Общее количество подключений UDP по всем сетевым картам."
|
|
||||||
"connectionCount" = "Количество соединений"
|
"connectionCount" = "Количество соединений"
|
||||||
"ipAddresses" = "IP-адреса сервера"
|
"ipAddresses" = "IP-адреса сервера"
|
||||||
"toggleIpVisibility" = "Переключить видимость IP-адресов сервера"
|
"toggleIpVisibility" = "Переключить видимость IP-адресов сервера"
|
||||||
|
@ -134,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray успешно обновлён"
|
"xraySwitchVersionPopover" = "Xray успешно обновлён"
|
||||||
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
|
"geofileUpdateDialog" = "Вы действительно хотите обновить геофайл?"
|
||||||
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
|
"geofileUpdateDialogDesc" = "Это обновит файл #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Это обновит все геофайлы."
|
||||||
|
"geofilesUpdateAll" = "Обновить все"
|
||||||
"geofileUpdatePopover" = "Геофайл успешно обновлён"
|
"geofileUpdatePopover" = "Геофайл успешно обновлён"
|
||||||
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
"dontRefresh" = "Установка в процессе. Не обновляйте страницу"
|
||||||
"logs" = "Журнал"
|
"logs" = "Журнал"
|
||||||
|
@ -160,6 +160,7 @@
|
||||||
"remark" = "Примечание"
|
"remark" = "Примечание"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
|
"portMap" = "Порт-маппинг"
|
||||||
"traffic" = "Трафик"
|
"traffic" = "Трафик"
|
||||||
"details" = "Подробнее"
|
"details" = "Подробнее"
|
||||||
"transportConfig" = "Транспорт"
|
"transportConfig" = "Транспорт"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Удалить клиента"
|
"deleteClient" = "Удалить клиента"
|
||||||
"deleteClientContent" = "Вы уверены, что хотите удалить клиента?"
|
"deleteClientContent" = "Вы уверены, что хотите удалить клиента?"
|
||||||
"resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?"
|
"resetTrafficContent" = "Вы уверены, что хотите сбросить трафик?"
|
||||||
"inboundUpdateSuccess" = "Инбаунд успешно обновлен."
|
|
||||||
"inboundCreateSuccess" = "Инбаунд успешно создан."
|
|
||||||
"copyLink" = "Копировать ссылку"
|
"copyLink" = "Копировать ссылку"
|
||||||
"address" = "Адрес"
|
"address" = "Адрес"
|
||||||
"network" = "Сеть"
|
"network" = "Сеть"
|
||||||
|
@ -575,6 +574,7 @@
|
||||||
"day" = "День"
|
"day" = "День"
|
||||||
"days" = "Дней"
|
"days" = "Дней"
|
||||||
"hours" = "Часов"
|
"hours" = "Часов"
|
||||||
|
"minutes" = "Минуты"
|
||||||
"unknown" = "Неизвестно"
|
"unknown" = "Неизвестно"
|
||||||
"inbounds" = "Инбаунды"
|
"inbounds" = "Инбаунды"
|
||||||
"clients" = "Клиенты"
|
"clients" = "Клиенты"
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Обновлено: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Да"
|
"yes" = "✅ Да"
|
||||||
"no" = "❌ Нет"
|
"no" = "❌ Нет"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID обновлён."
|
"received_id" = "🔑📥 ID обновлён."
|
||||||
"received_password" = "🔑📥 Пароль обновлён."
|
"received_password" = "🔑📥 Пароль обновлён."
|
||||||
"received_email" = "📧📥 Email обновлен."
|
"received_email" = "📧📥 Email обновлен."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ❌ Неудача \n\n🛠️ Ошибка: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Почта: {{ .ClientEmail }}\n🏁 Результат: ❌ Неудача \n\n🛠️ Ошибка: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Сброс трафика завершён для всех клиентов."
|
"FinishProcess" = "🔚 Сброс трафика завершён для всех клиентов."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Закрыть клавиатуру"
|
"closeKeyboard" = "❌ Закрыть клавиатуру"
|
||||||
"cancel" = "❌ Отмена"
|
"cancel" = "❌ Отмена"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Лимит трафика"
|
"limitTraffic" = "🚧 Лимит трафика"
|
||||||
"getBanLogs" = "📄 Лог банов"
|
"getBanLogs" = "📄 Лог банов"
|
||||||
"allClients" = "👥 Все клиенты"
|
"allClients" = "👥 Все клиенты"
|
||||||
|
|
||||||
"addClient" = "➕ Новый клиент"
|
"addClient" = "➕ Новый клиент"
|
||||||
"submitDisable" = "Добавить отключенным ☑️"
|
"submitDisable" = "Добавить отключенным ☑️"
|
||||||
"submitEnable" = "Добавить включенныи ✅"
|
"submitEnable" = "Добавить включенныи ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Сбросить весь трафик"
|
"ResetAllTraffics" = "Сбросить весь трафик"
|
||||||
"SortedTrafficUsageReport" = "Отсортированный отчет об использовании трафика"
|
"SortedTrafficUsageReport" = "Отсортированный отчет об использовании трафика"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ Успешно!"
|
"successfulOperation" = "✅ Успешно!"
|
||||||
"errorOperation" = "❗ Ошибка в операции."
|
"errorOperation" = "❗ Ошибка в операции."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Çalışma Süresi"
|
"operationHours" = "Çalışma Süresi"
|
||||||
"systemLoad" = "Sistem Yükü"
|
"systemLoad" = "Sistem Yükü"
|
||||||
"systemLoadDesc" = "Geçmiş 1, 5 ve 15 dakika için sistem yük ortalaması"
|
"systemLoadDesc" = "Geçmiş 1, 5 ve 15 dakika için sistem yük ortalaması"
|
||||||
"connectionTcpCountDesc" = "Sistem genelinde toplam TCP bağlantıları"
|
|
||||||
"connectionUdpCountDesc" = "Sistem genelinde toplam UDP bağlantıları"
|
|
||||||
"connectionCount" = "Bağlantı İstatistikleri"
|
"connectionCount" = "Bağlantı İstatistikleri"
|
||||||
"ipAddresses" = "IP adresleri"
|
"ipAddresses" = "IP adresleri"
|
||||||
"toggleIpVisibility" = "IP görünürlüğünü değiştir"
|
"toggleIpVisibility" = "IP görünürlüğünü değiştir"
|
||||||
|
@ -134,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"
|
||||||
|
@ -160,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"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Müşteriyi Sil"
|
"deleteClient" = "Müşteriyi Sil"
|
||||||
"deleteClientContent" = "Müşteriyi silmek istediğinizden emin misiniz?"
|
"deleteClientContent" = "Müşteriyi silmek istediğinizden emin misiniz?"
|
||||||
"resetTrafficContent" = "Trafiği sıfırlamak istediğinizden emin misiniz?"
|
"resetTrafficContent" = "Trafiği sıfırlamak istediğinizden emin misiniz?"
|
||||||
"inboundUpdateSuccess" = "Gelen bağlantı başarıyla güncellendi."
|
|
||||||
"inboundCreateSuccess" = "Gelen bağlantı başarıyla oluşturuldu."
|
|
||||||
"copyLink" = "URL'yi Kopyala"
|
"copyLink" = "URL'yi Kopyala"
|
||||||
"address" = "Adres"
|
"address" = "Adres"
|
||||||
"network" = "Ağ"
|
"network" = "Ağ"
|
||||||
|
@ -562,22 +561,23 @@
|
||||||
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
|
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Özel klavye kapalı!"
|
"keyboardClosed" = "❌ Klavye kapatıldı!"
|
||||||
"noResult" = "❗ Sonuç yok!"
|
"noResult" = "❗ Sonuç yok!"
|
||||||
"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!"
|
"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!"
|
||||||
"wentWrong" = "❌ Bir şeyler yanlış gitti!"
|
"wentWrong" = "❌ Bir şeyler yanlış gitti!"
|
||||||
"noIpRecord" = "❗ IP Kaydı yok!"
|
"noIpRecord" = "❗ IP Kaydı Yok!"
|
||||||
"noInbounds" = "❗ Gelen bulunamadı!"
|
"noInbounds" = "❗ Gelen bağlantı bulunamadı!"
|
||||||
"unlimited" = "♾ Sınırsız(Sıfırla)"
|
"unlimited" = "♾ Sınırsız (Sıfırla)"
|
||||||
"add" = "Ekle"
|
"add" = "Ekle"
|
||||||
"month" = "Ay"
|
"month" = "Ay"
|
||||||
"months" = "Aylar"
|
"months" = "Aylar"
|
||||||
"day" = "Gün"
|
"day" = "Gün"
|
||||||
"days" = "Günler"
|
"days" = "Günler"
|
||||||
"hours" = "Saatler"
|
"hours" = "Saatler"
|
||||||
"unknown" = "Bilinmiyor"
|
"minutes" = "Dakika"
|
||||||
|
"unknown" = "Bilinmeyen"
|
||||||
"inbounds" = "Gelenler"
|
"inbounds" = "Gelenler"
|
||||||
"clients" = "Müşteriler"
|
"clients" = "İstemciler"
|
||||||
"offline" = "🔴 Çevrimdışı"
|
"offline" = "🔴 Çevrimdışı"
|
||||||
"online" = "🟢 Çevrimiçi"
|
"online" = "🟢 Çevrimiçi"
|
||||||
|
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Yenilendi: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Yenilendi: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Evet"
|
"yes" = "✅ Evet"
|
||||||
"no" = "❌ Hayır"
|
"no" = "❌ Hayır"
|
||||||
|
|
||||||
"received_id" = "🔑📥 Kimlik güncellendi."
|
"received_id" = "🔑📥 Kimlik güncellendi."
|
||||||
"received_password" = "🔑📥 Şifre güncellendi."
|
"received_password" = "🔑📥 Şifre güncellendi."
|
||||||
"received_email" = "📧📥 E-posta güncellendi."
|
"received_email" = "📧📥 E-posta güncellendi."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ❌ Başarısız \n\n🛠️ Hata: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 E-posta: {{ .ClientEmail }}\n🏁 Sonuç: ❌ Başarısız \n\n🛠️ Hata: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Tüm müşteriler için trafik sıfırlama işlemi tamamlandı."
|
"FinishProcess" = "🔚 Tüm müşteriler için trafik sıfırlama işlemi tamamlandı."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Klavyeyi Kapat"
|
"closeKeyboard" = "❌ Klavyeyi Kapat"
|
||||||
"cancel" = "❌ İptal"
|
"cancel" = "❌ İptal"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Trafik Sınırı"
|
"limitTraffic" = "🚧 Trafik Sınırı"
|
||||||
"getBanLogs" = "Yasak Günlüklerini Al"
|
"getBanLogs" = "Yasak Günlüklerini Al"
|
||||||
"allClients" = "Tüm Müşteriler"
|
"allClients" = "Tüm Müşteriler"
|
||||||
|
|
||||||
"addClient" = "Müşteri Ekle"
|
"addClient" = "Müşteri Ekle"
|
||||||
"submitDisable" = "Devre Dışı Olarak Gönder ☑️"
|
"submitDisable" = "Devre Dışı Olarak Gönder ☑️"
|
||||||
"submitEnable" = "Etkin Olarak Gönder ✅"
|
"submitEnable" = "Etkin Olarak Gönder ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Tüm Trafikleri Sıfırla"
|
"ResetAllTraffics" = "Tüm Trafikleri Sıfırla"
|
||||||
"SortedTrafficUsageReport" = "Sıralı Trafik Kullanım Raporu"
|
"SortedTrafficUsageReport" = "Sıralı Trafik Kullanım Raporu"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ İşlem başarılı!"
|
"successfulOperation" = "✅ İşlem başarılı!"
|
||||||
"errorOperation" = "❗ İşlemde hata."
|
"errorOperation" = "❗ İşlemde hata."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Час роботи"
|
"operationHours" = "Час роботи"
|
||||||
"systemLoad" = "Завантаження системи"
|
"systemLoad" = "Завантаження системи"
|
||||||
"systemLoadDesc" = "Середнє завантаження системи за останні 1, 5 і 15 хвилин"
|
"systemLoadDesc" = "Середнє завантаження системи за останні 1, 5 і 15 хвилин"
|
||||||
"connectionTcpCountDesc" = "Загальна кількість TCP-з'єднань у системі"
|
|
||||||
"connectionUdpCountDesc" = "Загальна кількість UDP-з'єднань у системі"
|
|
||||||
"connectionCount" = "Статистика з'єднання"
|
"connectionCount" = "Статистика з'єднання"
|
||||||
"ipAddresses" = "IP-адреси"
|
"ipAddresses" = "IP-адреси"
|
||||||
"toggleIpVisibility" = "Перемкнути видимість IP"
|
"toggleIpVisibility" = "Перемкнути видимість IP"
|
||||||
|
@ -134,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray успішно оновлено"
|
"xraySwitchVersionPopover" = "Xray успішно оновлено"
|
||||||
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
|
"geofileUpdateDialog" = "Ви дійсно хочете оновити геофайл?"
|
||||||
"geofileUpdateDialogDesc" = "Це оновить файл #filename#."
|
"geofileUpdateDialogDesc" = "Це оновить файл #filename#."
|
||||||
|
"geofilesUpdateDialogDesc" = "Це оновить усі геофайли."
|
||||||
|
"geofilesUpdateAll" = "Оновити все"
|
||||||
"geofileUpdatePopover" = "Геофайл успішно оновлено"
|
"geofileUpdatePopover" = "Геофайл успішно оновлено"
|
||||||
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
|
"dontRefresh" = "Інсталяція триває, будь ласка, не оновлюйте цю сторінку"
|
||||||
"logs" = "Журнали"
|
"logs" = "Журнали"
|
||||||
|
@ -160,6 +160,7 @@
|
||||||
"remark" = "Примітка"
|
"remark" = "Примітка"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
|
"portMap" = "Порт-перехід"
|
||||||
"traffic" = "Трафік"
|
"traffic" = "Трафік"
|
||||||
"details" = "Деталі"
|
"details" = "Деталі"
|
||||||
"transportConfig" = "Транспорт"
|
"transportConfig" = "Транспорт"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Видалити клієнта"
|
"deleteClient" = "Видалити клієнта"
|
||||||
"deleteClientContent" = "Ви впевнені, що хочете видалити клієнт?"
|
"deleteClientContent" = "Ви впевнені, що хочете видалити клієнт?"
|
||||||
"resetTrafficContent" = "Ви впевнені, що хочете скинути трафік?"
|
"resetTrafficContent" = "Ви впевнені, що хочете скинути трафік?"
|
||||||
"inboundUpdateSuccess" = "Вхідне підключення успішно оновлено."
|
|
||||||
"inboundCreateSuccess" = "Вхідне підключення успішно створено."
|
|
||||||
"copyLink" = "Копіювати URL"
|
"copyLink" = "Копіювати URL"
|
||||||
"address" = "Адреса"
|
"address" = "Адреса"
|
||||||
"network" = "Мережа"
|
"network" = "Мережа"
|
||||||
|
@ -562,19 +561,20 @@
|
||||||
"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку"
|
"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Спеціальна клавіатура закрита!"
|
"keyboardClosed" = "❌ Клавіатуру закрито!"
|
||||||
"noResult" = "❗ Немає результату!"
|
"noResult" = "❗ Немає результату!"
|
||||||
"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!"
|
"noQuery" = "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!"
|
||||||
"wentWrong" = "❌ Щось пішло не так!"
|
"wentWrong" = "❌ Щось пішло не так!"
|
||||||
"noIpRecord" = "❗ Немає IP-запису!"
|
"noIpRecord" = "❗ Немає запису IP!"
|
||||||
"noInbounds" = "❗ Вхідних не знайдено!"
|
"noInbounds" = "❗ Вхідні не знайдені!"
|
||||||
"unlimited" = "♾ Необмежений (скинути)"
|
"unlimited" = "♾ Необмежено (Скинути)"
|
||||||
"add" = "Додати"
|
"add" = "Додати"
|
||||||
"month" = "Місяць"
|
"month" = "Місяць"
|
||||||
"months" = "Місяці"
|
"months" = "Місяці"
|
||||||
"day" = "День"
|
"day" = "День"
|
||||||
"days" = "Дні"
|
"days" = "Дні"
|
||||||
"hours" = "Годинник"
|
"hours" = "Години"
|
||||||
|
"minutes" = "Хвилини"
|
||||||
"unknown" = "Невідомо"
|
"unknown" = "Невідомо"
|
||||||
"inbounds" = "Вхідні"
|
"inbounds" = "Вхідні"
|
||||||
"clients" = "Клієнти"
|
"clients" = "Клієнти"
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Оновлено: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Оновлено: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Так"
|
"yes" = "✅ Так"
|
||||||
"no" = "❌ Ні"
|
"no" = "❌ Ні"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID оновлено."
|
"received_id" = "🔑📥 ID оновлено."
|
||||||
"received_password" = "🔑📥 Пароль оновлено."
|
"received_password" = "🔑📥 Пароль оновлено."
|
||||||
"received_email" = "📧📥 Електронна пошта оновлена."
|
"received_email" = "📧📥 Електронна пошта оновлена."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ❌ Невдача \n\n🛠️ Помилка: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Електронна пошта: {{ .ClientEmail }}\n🏁 Результат: ❌ Невдача \n\n🛠️ Помилка: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Процес скидання трафіку завершено для всіх клієнтів."
|
"FinishProcess" = "🔚 Процес скидання трафіку завершено для всіх клієнтів."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Закрити клавіатуру"
|
"closeKeyboard" = "❌ Закрити клавіатуру"
|
||||||
"cancel" = "❌ Скасувати"
|
"cancel" = "❌ Скасувати"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Ліміт трафіку"
|
"limitTraffic" = "🚧 Ліміт трафіку"
|
||||||
"getBanLogs" = "Отримати журнали заборон"
|
"getBanLogs" = "Отримати журнали заборон"
|
||||||
"allClients" = "Всі Клієнти"
|
"allClients" = "Всі Клієнти"
|
||||||
|
|
||||||
"addClient" = "Додати клієнта"
|
"addClient" = "Додати клієнта"
|
||||||
"submitDisable" = "Надіслати як вимкнено ☑️"
|
"submitDisable" = "Надіслати як вимкнено ☑️"
|
||||||
"submitEnable" = "Надіслати як увімкнено ✅"
|
"submitEnable" = "Надіслати як увімкнено ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Скинути весь трафік"
|
"ResetAllTraffics" = "Скинути весь трафік"
|
||||||
"SortedTrafficUsageReport" = "Відсортований звіт про використання трафіку"
|
"SortedTrafficUsageReport" = "Відсортований звіт про використання трафіку"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ Операція успішна!"
|
"successfulOperation" = "✅ Операція успішна!"
|
||||||
"errorOperation" = "❗ Помилка в роботі."
|
"errorOperation" = "❗ Помилка в роботі."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "Thời gian hoạt động"
|
"operationHours" = "Thời gian hoạt động"
|
||||||
"systemLoad" = "Tải hệ thống"
|
"systemLoad" = "Tải hệ thống"
|
||||||
"systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua"
|
"systemLoadDesc" = "trung bình tải hệ thống trong 1, 5 và 15 phút qua"
|
||||||
"connectionTcpCountDesc" = "Tổng số kết nối TCP trên tất cả các thẻ mạng."
|
|
||||||
"connectionUdpCountDesc" = "Tổng số kết nối UDP trên tất cả các thẻ mạng."
|
|
||||||
"connectionCount" = "Số lượng kết nối"
|
"connectionCount" = "Số lượng kết nối"
|
||||||
"ipAddresses" = "Địa chỉ IP"
|
"ipAddresses" = "Địa chỉ IP"
|
||||||
"toggleIpVisibility" = "Chuyển đổi hiển thị IP"
|
"toggleIpVisibility" = "Chuyển đổi hiển thị IP"
|
||||||
|
@ -134,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ý"
|
||||||
|
@ -160,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"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "Xóa người dùng"
|
"deleteClient" = "Xóa người dùng"
|
||||||
"deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?"
|
"deleteClientContent" = "Bạn có chắc chắn muốn xóa người dùng không?"
|
||||||
"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?"
|
"resetTrafficContent" = "Xác nhận đặt lại lưu lượng?"
|
||||||
"inboundUpdateSuccess" = "Đã cập nhật kết nối inbound thành công."
|
|
||||||
"inboundCreateSuccess" = "Đã tạo kết nối inbound thành công."
|
|
||||||
"copyLink" = "Sao chép liên kết"
|
"copyLink" = "Sao chép liên kết"
|
||||||
"address" = "Địa chỉ"
|
"address" = "Địa chỉ"
|
||||||
"network" = "Mạng"
|
"network" = "Mạng"
|
||||||
|
@ -562,22 +561,23 @@
|
||||||
"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi"
|
"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi"
|
||||||
|
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!"
|
"keyboardClosed" = "❌ Bàn phím đã đóng!"
|
||||||
"noResult" = "❗ Không có kết quả!"
|
"noResult" = "❗ Không có kết quả!"
|
||||||
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!"
|
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!"
|
||||||
"wentWrong" = "❌ Đã xảy ra lỗi!"
|
"wentWrong" = "❌ Đã xảy ra lỗi!"
|
||||||
"noIpRecord" = "❗ Không có bản ghi IP!"
|
"noIpRecord" = "❗ Không có bản ghi IP!"
|
||||||
"noInbounds" = "❗ Không tìm thấy inbound!"
|
"noInbounds" = "❗ Không tìm thấy inbound!"
|
||||||
"unlimited" = "♾ Không giới hạn"
|
"unlimited" = "♾ Không giới hạn (Đặt lại)"
|
||||||
"add" = "Thêm"
|
"add" = "Thêm"
|
||||||
"month" = "Tháng"
|
"month" = "Tháng"
|
||||||
"months" = "Tháng"
|
"months" = "Tháng"
|
||||||
"day" = "Ngày"
|
"day" = "Ngày"
|
||||||
"days" = "Ngày"
|
"days" = "Ngày"
|
||||||
"hours" = "Giờ"
|
"hours" = "Giờ"
|
||||||
"unknown" = "Không rõ"
|
"minutes" = "Phút"
|
||||||
"inbounds" = "Vào"
|
"unknown" = "Không xác định"
|
||||||
"clients" = "Các người dùng"
|
"inbounds" = "Inbound"
|
||||||
|
"clients" = "Client"
|
||||||
"offline" = "🔴 Ngoại tuyến"
|
"offline" = "🔴 Ngoại tuyến"
|
||||||
"online" = "🟢 Trực tuyến"
|
"online" = "🟢 Trực tuyến"
|
||||||
|
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 Đã cập nhật lần cuối vào: {{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ Có"
|
"yes" = "✅ Có"
|
||||||
"no" = "❌ Không"
|
"no" = "❌ Không"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID đã được cập nhật."
|
"received_id" = "🔑📥 ID đã được cập nhật."
|
||||||
"received_password" = "🔑📥 Mật khẩu đã được cập nhật."
|
"received_password" = "🔑📥 Mật khẩu đã được cập nhật."
|
||||||
"received_email" = "📧📥 Email đã được cập nhật."
|
"received_email" = "📧📥 Email đã được cập nhật."
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ❌ Thất bại \n\n🛠️ Lỗi: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 Email: {{ .ClientEmail }}\n🏁 Kết quả: ❌ Thất bại \n\n🛠️ Lỗi: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 Quá trình đặt lại lưu lượng đã hoàn tất cho tất cả khách hàng."
|
"FinishProcess" = "🔚 Quá trình đặt lại lưu lượng đã hoàn tất cho tất cả khách hàng."
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ Đóng Bàn Phím"
|
"closeKeyboard" = "❌ Đóng Bàn Phím"
|
||||||
"cancel" = "❌ Hủy"
|
"cancel" = "❌ Hủy"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 Giới hạn lưu lượng"
|
"limitTraffic" = "🚧 Giới hạn lưu lượng"
|
||||||
"getBanLogs" = "Cấm nhật ký"
|
"getBanLogs" = "Cấm nhật ký"
|
||||||
"allClients" = "Tất cả Khách hàng"
|
"allClients" = "Tất cả Khách hàng"
|
||||||
|
|
||||||
"addClient" = "Thêm Khách Hàng"
|
"addClient" = "Thêm Khách Hàng"
|
||||||
"submitDisable" = "Gửi Dưới Dạng Vô Hiệu ☑️"
|
"submitDisable" = "Gửi Dưới Dạng Vô Hiệu ☑️"
|
||||||
"submitEnable" = "Gửi Dưới Dạng Kích Hoạt ✅"
|
"submitEnable" = "Gửi Dưới Dạng Kích Hoạt ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "Đặt lại tất cả lưu lượng"
|
"ResetAllTraffics" = "Đặt lại tất cả lưu lượng"
|
||||||
"SortedTrafficUsageReport" = "Báo cáo sử dụng lưu lượng đã sắp xếp"
|
"SortedTrafficUsageReport" = "Báo cáo sử dụng lưu lượng đã sắp xếp"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ Thành công!"
|
"successfulOperation" = "✅ Thành công!"
|
||||||
"errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện."
|
"errorOperation" = "❗ Lỗi Trong Quá Trình Thực Hiện."
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "系统正常运行时间"
|
"operationHours" = "系统正常运行时间"
|
||||||
"systemLoad" = "系统负载"
|
"systemLoad" = "系统负载"
|
||||||
"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载"
|
"systemLoadDesc" = "过去 1、5 和 15 分钟的系统平均负载"
|
||||||
"connectionTcpCountDesc" = "系统中所有 TCP 连接数"
|
|
||||||
"connectionUdpCountDesc" = "系统中所有 UDP 连接数"
|
|
||||||
"connectionCount" = "连接数"
|
"connectionCount" = "连接数"
|
||||||
"ipAddresses" = "IP地址"
|
"ipAddresses" = "IP地址"
|
||||||
"toggleIpVisibility" = "切换IP可见性"
|
"toggleIpVisibility" = "切换IP可见性"
|
||||||
|
@ -134,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray 更新成功"
|
"xraySwitchVersionPopover" = "Xray 更新成功"
|
||||||
"geofileUpdateDialog" = "您确定要更新地理文件吗?"
|
"geofileUpdateDialog" = "您确定要更新地理文件吗?"
|
||||||
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
|
"geofileUpdateDialogDesc" = "这将更新 #filename# 文件。"
|
||||||
|
"geofilesUpdateDialogDesc" = "这将更新所有文件。"
|
||||||
|
"geofilesUpdateAll" = "全部更新"
|
||||||
"geofileUpdatePopover" = "地理文件更新成功"
|
"geofileUpdatePopover" = "地理文件更新成功"
|
||||||
"dontRefresh" = "安装中,请勿刷新此页面"
|
"dontRefresh" = "安装中,请勿刷新此页面"
|
||||||
"logs" = "日志"
|
"logs" = "日志"
|
||||||
|
@ -160,6 +160,7 @@
|
||||||
"remark" = "备注"
|
"remark" = "备注"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"port" = "端口"
|
"port" = "端口"
|
||||||
|
"portMap" = "端口映射"
|
||||||
"traffic" = "流量"
|
"traffic" = "流量"
|
||||||
"details" = "详细信息"
|
"details" = "详细信息"
|
||||||
"transportConfig" = "传输配置"
|
"transportConfig" = "传输配置"
|
||||||
|
@ -175,8 +176,6 @@
|
||||||
"deleteClient" = "删除客户端"
|
"deleteClient" = "删除客户端"
|
||||||
"deleteClientContent" = "确定要删除客户端吗?"
|
"deleteClientContent" = "确定要删除客户端吗?"
|
||||||
"resetTrafficContent" = "确定要重置流量吗?"
|
"resetTrafficContent" = "确定要重置流量吗?"
|
||||||
"inboundUpdateSuccess" = "入站连接已成功更新。"
|
|
||||||
"inboundCreateSuccess" = "入站连接已成功创建。"
|
|
||||||
"copyLink" = "复制链接"
|
"copyLink" = "复制链接"
|
||||||
"address" = "地址"
|
"address" = "地址"
|
||||||
"network" = "网络"
|
"network" = "网络"
|
||||||
|
@ -564,19 +563,20 @@
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ 自定义键盘已关闭!"
|
"keyboardClosed" = "❌ 自定义键盘已关闭!"
|
||||||
"noResult" = "❗ 没有结果!"
|
"noResult" = "❗ 没有结果!"
|
||||||
"noQuery" = "❌ 未找到查询!请重新使用命令!"
|
"noQuery" = "❌ 未找到查询!请再次使用该命令!"
|
||||||
"wentWrong" = "❌ 出了点问题!"
|
"wentWrong" = "❌ 出了点问题!"
|
||||||
"noIpRecord" = "❗ 没有 IP 记录!"
|
"noIpRecord" = "❗ 没有IP记录!"
|
||||||
"noInbounds" = "❗ 没有找到入站连接!"
|
"noInbounds" = "❗ 未找到入站!"
|
||||||
"unlimited" = "♾ 无限制"
|
"unlimited" = "♾ 无限(重置)"
|
||||||
"add" = "添加"
|
"add" = "添加"
|
||||||
"month" = "月"
|
"month" = "月"
|
||||||
"months" = "月"
|
"months" = "月"
|
||||||
"day" = "天"
|
"day" = "天"
|
||||||
"days" = "天"
|
"days" = "天"
|
||||||
"hours" = "小时"
|
"hours" = "小时"
|
||||||
|
"minutes" = "分钟"
|
||||||
"unknown" = "未知"
|
"unknown" = "未知"
|
||||||
"inbounds" = "入站连接"
|
"inbounds" = "入站"
|
||||||
"clients" = "客户端"
|
"clients" = "客户端"
|
||||||
"offline" = "🔴 离线"
|
"offline" = "🔴 离线"
|
||||||
"online" = "🟢 在线"
|
"online" = "🟢 在线"
|
||||||
|
@ -647,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 刷新时间:{{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ 是的"
|
"yes" = "✅ 是的"
|
||||||
"no" = "❌ 没有"
|
"no" = "❌ 没有"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID 已更新。"
|
"received_id" = "🔑📥 ID 已更新。"
|
||||||
"received_password" = "🔑📥 密码已更新。"
|
"received_password" = "🔑📥 密码已更新。"
|
||||||
"received_email" = "📧📥 邮箱已更新。"
|
"received_email" = "📧📥 邮箱已更新。"
|
||||||
|
@ -667,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 邮箱: {{ .ClientEmail }}\n🏁 结果: ❌ 失败 \n\n🛠️ 错误: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 所有客户的流量重置已完成。"
|
"FinishProcess" = "🔚 所有客户的流量重置已完成。"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ 关闭键盘"
|
"closeKeyboard" = "❌ 关闭键盘"
|
||||||
"cancel" = "❌ 取消"
|
"cancel" = "❌ 取消"
|
||||||
|
@ -701,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 流量限制"
|
"limitTraffic" = "🚧 流量限制"
|
||||||
"getBanLogs" = "禁止日志"
|
"getBanLogs" = "禁止日志"
|
||||||
"allClients" = "所有客户"
|
"allClients" = "所有客户"
|
||||||
|
|
||||||
"addClient" = "添加客户"
|
"addClient" = "添加客户"
|
||||||
"submitDisable" = "提交为禁用 ☑️"
|
"submitDisable" = "提交为禁用 ☑️"
|
||||||
"submitEnable" = "提交为启用 ✅"
|
"submitEnable" = "提交为启用 ✅"
|
||||||
|
@ -713,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "重置所有流量"
|
"ResetAllTraffics" = "重置所有流量"
|
||||||
"SortedTrafficUsageReport" = "排序的流量使用报告"
|
"SortedTrafficUsageReport" = "排序的流量使用报告"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ 成功!"
|
"successfulOperation" = "✅ 成功!"
|
||||||
"errorOperation" = "❗ 操作错误。"
|
"errorOperation" = "❗ 操作错误。"
|
||||||
|
|
|
@ -117,8 +117,6 @@
|
||||||
"operationHours" = "系統正常執行時間"
|
"operationHours" = "系統正常執行時間"
|
||||||
"systemLoad" = "系統負載"
|
"systemLoad" = "系統負載"
|
||||||
"systemLoadDesc" = "過去 1、5 和 15 分鐘的系統平均負載"
|
"systemLoadDesc" = "過去 1、5 和 15 分鐘的系統平均負載"
|
||||||
"connectionTcpCountDesc" = "系統中所有 TCP 連線數"
|
|
||||||
"connectionUdpCountDesc" = "系統中所有 UDP 連線數"
|
|
||||||
"connectionCount" = "連線數"
|
"connectionCount" = "連線數"
|
||||||
"ipAddresses" = "IP地址"
|
"ipAddresses" = "IP地址"
|
||||||
"toggleIpVisibility" = "切換IP可見性"
|
"toggleIpVisibility" = "切換IP可見性"
|
||||||
|
@ -134,6 +132,8 @@
|
||||||
"xraySwitchVersionPopover" = "Xray 更新成功"
|
"xraySwitchVersionPopover" = "Xray 更新成功"
|
||||||
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
|
"geofileUpdateDialog" = "您確定要更新地理檔案嗎?"
|
||||||
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
|
"geofileUpdateDialogDesc" = "這將更新 #filename# 檔案。"
|
||||||
|
"geofilesUpdateDialogDesc" = "這將更新所有文件。"
|
||||||
|
"geofilesUpdateAll" = "全部更新"
|
||||||
"geofileUpdatePopover" = "地理檔案更新成功"
|
"geofileUpdatePopover" = "地理檔案更新成功"
|
||||||
"dontRefresh" = "安裝中,請勿重新整理此頁面"
|
"dontRefresh" = "安裝中,請勿重新整理此頁面"
|
||||||
"logs" = "日誌"
|
"logs" = "日誌"
|
||||||
|
@ -160,6 +160,7 @@
|
||||||
"remark" = "備註"
|
"remark" = "備註"
|
||||||
"protocol" = "協議"
|
"protocol" = "協議"
|
||||||
"port" = "埠"
|
"port" = "埠"
|
||||||
|
"portMap" = "埠映射"
|
||||||
"traffic" = "流量"
|
"traffic" = "流量"
|
||||||
"details" = "詳細資訊"
|
"details" = "詳細資訊"
|
||||||
"transportConfig" = "傳輸配置"
|
"transportConfig" = "傳輸配置"
|
||||||
|
@ -169,16 +170,12 @@
|
||||||
"generalActions" = "通用操作"
|
"generalActions" = "通用操作"
|
||||||
"autoRefresh" = "自動刷新"
|
"autoRefresh" = "自動刷新"
|
||||||
"autoRefreshInterval" = "間隔"
|
"autoRefreshInterval" = "間隔"
|
||||||
"create" = "新增"
|
|
||||||
"update" = "修改"
|
|
||||||
"modifyInbound" = "修改入站"
|
"modifyInbound" = "修改入站"
|
||||||
"deleteInbound" = "刪除入站"
|
"deleteInbound" = "刪除入站"
|
||||||
"deleteInboundContent" = "確定要刪除入站嗎?"
|
"deleteInboundContent" = "確定要刪除入站嗎?"
|
||||||
"deleteClient" = "刪除客戶端"
|
"deleteClient" = "刪除客戶端"
|
||||||
"deleteClientContent" = "確定要刪除客戶端嗎?"
|
"deleteClientContent" = "確定要刪除客戶端嗎?"
|
||||||
"resetTrafficContent" = "確定要重置流量嗎?"
|
"resetTrafficContent" = "確定要重置流量嗎?"
|
||||||
"inboundUpdateSuccess" = "入站連接已成功更新。"
|
|
||||||
"inboundCreateSuccess" = "入站連接已成功建立。"
|
|
||||||
"copyLink" = "複製連結"
|
"copyLink" = "複製連結"
|
||||||
"address" = "地址"
|
"address" = "地址"
|
||||||
"network" = "網路"
|
"network" = "網路"
|
||||||
|
@ -566,22 +563,23 @@
|
||||||
[tgbot]
|
[tgbot]
|
||||||
"keyboardClosed" = "❌ 自定義鍵盤已關閉!"
|
"keyboardClosed" = "❌ 自定義鍵盤已關閉!"
|
||||||
"noResult" = "❗ 沒有結果!"
|
"noResult" = "❗ 沒有結果!"
|
||||||
"noQuery" = "❌ 未找到查詢!請重新使用命令!"
|
"noQuery" = "❌ 未找到查詢!請再次使用該命令!"
|
||||||
"wentWrong" = "❌ 出了點問題!"
|
"wentWrong" = "❌ 出了點問題!"
|
||||||
"noIpRecord" = "❗ 沒有 IP 記錄!"
|
"noIpRecord" = "❗ 沒有IP記錄!"
|
||||||
"noInbounds" = "❗ 沒有找到入站連線!"
|
"noInbounds" = "❗ 未找到入站!"
|
||||||
"unlimited" = "♾ 無限制"
|
"unlimited" = "♾ 無限(重置)"
|
||||||
"add" = "新增"
|
"add" = "添加"
|
||||||
"month" = "月"
|
"month" = "月"
|
||||||
"months" = "月"
|
"months" = "月"
|
||||||
"day" = "天"
|
"day" = "天"
|
||||||
"days" = "天"
|
"days" = "天"
|
||||||
"hours" = "小時"
|
"hours" = "小時"
|
||||||
|
"minutes" = "分鐘"
|
||||||
"unknown" = "未知"
|
"unknown" = "未知"
|
||||||
"inbounds" = "入站連線"
|
"inbounds" = "入站"
|
||||||
"clients" = "客戶端"
|
"clients" = "客戶端"
|
||||||
"offline" = "🔴 離線"
|
"offline" = "🔴 離線"
|
||||||
"online" = "🟢 線上"
|
"online" = "🟢 在線"
|
||||||
|
|
||||||
[tgbot.commands]
|
[tgbot.commands]
|
||||||
"unknown" = "❗ 未知命令"
|
"unknown" = "❗ 未知命令"
|
||||||
|
@ -649,7 +647,6 @@
|
||||||
"refreshedOn" = "\r\n📋🔄 重新整理時間:{{ .Time }}\r\n\r\n"
|
"refreshedOn" = "\r\n📋🔄 重新整理時間:{{ .Time }}\r\n\r\n"
|
||||||
"yes" = "✅ 是的"
|
"yes" = "✅ 是的"
|
||||||
"no" = "❌ 沒有"
|
"no" = "❌ 沒有"
|
||||||
|
|
||||||
"received_id" = "🔑📥 ID 已更新。"
|
"received_id" = "🔑📥 ID 已更新。"
|
||||||
"received_password" = "🔑📥 密碼已更新。"
|
"received_password" = "🔑📥 密碼已更新。"
|
||||||
"received_email" = "📧📥 電子郵件已更新。"
|
"received_email" = "📧📥 電子郵件已更新。"
|
||||||
|
@ -669,7 +666,6 @@
|
||||||
"FailedResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]"
|
"FailedResetTraffic" = "📧 電子郵件: {{ .ClientEmail }}\n🏁 結果: ❌ 失敗 \n\n🛠️ 錯誤: [ {{ .ErrorMessage }} ]"
|
||||||
"FinishProcess" = "🔚 所有客戶的流量重置已完成。"
|
"FinishProcess" = "🔚 所有客戶的流量重置已完成。"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.buttons]
|
[tgbot.buttons]
|
||||||
"closeKeyboard" = "❌ 關閉鍵盤"
|
"closeKeyboard" = "❌ 關閉鍵盤"
|
||||||
"cancel" = "❌ 取消"
|
"cancel" = "❌ 取消"
|
||||||
|
@ -703,7 +699,6 @@
|
||||||
"limitTraffic" = "🚧 流量限制"
|
"limitTraffic" = "🚧 流量限制"
|
||||||
"getBanLogs" = "禁止日誌"
|
"getBanLogs" = "禁止日誌"
|
||||||
"allClients" = "所有客戶"
|
"allClients" = "所有客戶"
|
||||||
|
|
||||||
"addClient" = "新增客戶"
|
"addClient" = "新增客戶"
|
||||||
"submitDisable" = "以停用方式送出 ☑️"
|
"submitDisable" = "以停用方式送出 ☑️"
|
||||||
"submitEnable" = "以啟用方式送出 ✅"
|
"submitEnable" = "以啟用方式送出 ✅"
|
||||||
|
@ -715,7 +710,6 @@
|
||||||
"ResetAllTraffics" = "重設所有流量"
|
"ResetAllTraffics" = "重設所有流量"
|
||||||
"SortedTrafficUsageReport" = "排序過的流量使用報告"
|
"SortedTrafficUsageReport" = "排序過的流量使用報告"
|
||||||
|
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ 成功!"
|
"successfulOperation" = "✅ 成功!"
|
||||||
"errorOperation" = "❗ 操作錯誤。"
|
"errorOperation" = "❗ 操作錯誤。"
|
||||||
|
|
249
x-ui.sh
249
x-ui.sh
|
@ -398,37 +398,6 @@ show_log() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
show_banlog() {
|
|
||||||
local system_log="/var/log/fail2ban.log"
|
|
||||||
|
|
||||||
echo -e "${green}Checking ban logs...${plain}\n"
|
|
||||||
|
|
||||||
if ! systemctl is-active --quiet fail2ban; then
|
|
||||||
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f "$system_log" ]]; then
|
|
||||||
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
|
|
||||||
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
|
|
||||||
echo ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -f "${iplimit_banned_log_path}" ]]; then
|
|
||||||
echo -e "${green}3X-IPL ban log entries:${plain}"
|
|
||||||
if [[ -s "${iplimit_banned_log_path}" ]]; then
|
|
||||||
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
|
|
||||||
else
|
|
||||||
echo -e "${yellow}Ban log file is empty${plain}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "\n${green}Current jail status:${plain}"
|
|
||||||
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
|
|
||||||
}
|
|
||||||
|
|
||||||
bbr_menu() {
|
bbr_menu() {
|
||||||
echo -e "${green}\t1.${plain} Enable BBR"
|
echo -e "${green}\t1.${plain} Enable BBR"
|
||||||
echo -e "${green}\t2.${plain} Disable BBR"
|
echo -e "${green}\t2.${plain} Disable BBR"
|
||||||
|
@ -1005,7 +974,7 @@ ssl_cert_issue() {
|
||||||
# install socat second
|
# install socat second
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian | armbian)
|
ubuntu | debian | armbian)
|
||||||
apt update && apt install socat -y
|
apt-get update && apt-get install socat -y
|
||||||
;;
|
;;
|
||||||
centos | rhel | almalinux | rocky | ol)
|
centos | rhel | almalinux | rocky | ol)
|
||||||
yum -y update && yum -y install socat
|
yum -y update && yum -y install socat
|
||||||
|
@ -1330,81 +1299,7 @@ run_speedtest() {
|
||||||
speedtest
|
speedtest
|
||||||
}
|
}
|
||||||
|
|
||||||
create_iplimit_jails() {
|
|
||||||
# Use default bantime if not passed => 30 minutes
|
|
||||||
local bantime="${1:-30}"
|
|
||||||
|
|
||||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
|
||||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
|
||||||
|
|
||||||
# On Debian 12+ fail2ban's default backend should be changed to systemd
|
|
||||||
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
|
|
||||||
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
|
|
||||||
[3x-ipl]
|
|
||||||
enabled=true
|
|
||||||
backend=auto
|
|
||||||
filter=3x-ipl
|
|
||||||
action=3x-ipl
|
|
||||||
logpath=${iplimit_log_path}
|
|
||||||
maxretry=2
|
|
||||||
findtime=32
|
|
||||||
bantime=${bantime}m
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
|
|
||||||
[Definition]
|
|
||||||
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
|
|
||||||
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
|
|
||||||
ignoreregex =
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
|
||||||
[INCLUDES]
|
|
||||||
before = iptables-allports.conf
|
|
||||||
|
|
||||||
[Definition]
|
|
||||||
actionstart = <iptables> -N f2b-<name>
|
|
||||||
<iptables> -A f2b-<name> -j <returntype>
|
|
||||||
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
|
||||||
|
|
||||||
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
|
||||||
<actionflush>
|
|
||||||
<iptables> -X f2b-<name>
|
|
||||||
|
|
||||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
|
||||||
|
|
||||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
|
||||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
|
|
||||||
|
|
||||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
|
||||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
|
||||||
|
|
||||||
[Init]
|
|
||||||
name = default
|
|
||||||
protocol = tcp
|
|
||||||
chain = INPUT
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
|
|
||||||
}
|
|
||||||
|
|
||||||
iplimit_remove_conflicts() {
|
|
||||||
local jail_files=(
|
|
||||||
/etc/fail2ban/jail.conf
|
|
||||||
/etc/fail2ban/jail.local
|
|
||||||
)
|
|
||||||
|
|
||||||
for file in "${jail_files[@]}"; do
|
|
||||||
# Check for [3x-ipl] config in jail file then remove it
|
|
||||||
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
|
|
||||||
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
|
|
||||||
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
ip_validation() {
|
ip_validation() {
|
||||||
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
|
ipv6_regex="^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
|
||||||
|
@ -1514,14 +1409,22 @@ install_iplimit() {
|
||||||
# Check the OS and install necessary packages
|
# Check the OS and install necessary packages
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu)
|
ubuntu)
|
||||||
|
apt-get update
|
||||||
if [[ "${os_version}" -ge 24 ]]; then
|
if [[ "${os_version}" -ge 24 ]]; then
|
||||||
apt update && apt install python3-pip -y
|
apt-get install python3-pip -y
|
||||||
python3 -m pip install pyasynchat --break-system-packages
|
python3 -m pip install pyasynchat --break-system-packages
|
||||||
fi
|
fi
|
||||||
apt update && apt install fail2ban -y
|
apt-get install fail2ban -y
|
||||||
;;
|
;;
|
||||||
debian | armbian)
|
debian)
|
||||||
apt update && apt install fail2ban -y
|
apt-get update
|
||||||
|
if [ "$os_version" -ge 12 ]; then
|
||||||
|
apt-get install -y python3-systemd
|
||||||
|
fi
|
||||||
|
apt-get install -y fail2ban
|
||||||
|
;;
|
||||||
|
armbian)
|
||||||
|
apt-get update && apt-get install fail2ban -y
|
||||||
;;
|
;;
|
||||||
centos | rhel | almalinux | rocky | ol)
|
centos | rhel | almalinux | rocky | ol)
|
||||||
yum update -y && yum install epel-release -y
|
yum update -y && yum install epel-release -y
|
||||||
|
@ -1632,11 +1535,129 @@ remove_iplimit() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
SSH_port_forwarding() {
|
show_banlog() {
|
||||||
local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
|
local system_log="/var/log/fail2ban.log"
|
||||||
if [ -z "$server_ip" ]; then
|
|
||||||
server_ip=$(curl -s --max-time 3 https://4.ident.me)
|
echo -e "${green}Checking ban logs...${plain}\n"
|
||||||
|
|
||||||
|
if ! systemctl is-active --quiet fail2ban; then
|
||||||
|
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$system_log" ]]; then
|
||||||
|
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
|
||||||
|
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "${iplimit_banned_log_path}" ]]; then
|
||||||
|
echo -e "${green}3X-IPL ban log entries:${plain}"
|
||||||
|
if [[ -s "${iplimit_banned_log_path}" ]]; then
|
||||||
|
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${yellow}Ban log file is empty${plain}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n${green}Current jail status:${plain}"
|
||||||
|
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
|
||||||
|
}
|
||||||
|
|
||||||
|
create_iplimit_jails() {
|
||||||
|
# Use default bantime if not passed => 30 minutes
|
||||||
|
local bantime="${1:-30}"
|
||||||
|
|
||||||
|
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||||
|
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||||
|
|
||||||
|
# On Debian 12+ fail2ban's default backend should be changed to systemd
|
||||||
|
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
|
||||||
|
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
|
||||||
|
[3x-ipl]
|
||||||
|
enabled=true
|
||||||
|
backend=auto
|
||||||
|
filter=3x-ipl
|
||||||
|
action=3x-ipl
|
||||||
|
logpath=${iplimit_log_path}
|
||||||
|
maxretry=2
|
||||||
|
findtime=32
|
||||||
|
bantime=${bantime}m
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
|
||||||
|
[Definition]
|
||||||
|
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
|
||||||
|
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
|
||||||
|
ignoreregex =
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||||
|
[INCLUDES]
|
||||||
|
before = iptables-allports.conf
|
||||||
|
|
||||||
|
[Definition]
|
||||||
|
actionstart = <iptables> -N f2b-<name>
|
||||||
|
<iptables> -A f2b-<name> -j <returntype>
|
||||||
|
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
||||||
|
|
||||||
|
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
||||||
|
<actionflush>
|
||||||
|
<iptables> -X f2b-<name>
|
||||||
|
|
||||||
|
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||||
|
|
||||||
|
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||||
|
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
|
||||||
|
|
||||||
|
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||||
|
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
||||||
|
|
||||||
|
[Init]
|
||||||
|
name = default
|
||||||
|
protocol = tcp
|
||||||
|
chain = INPUT
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
|
||||||
|
}
|
||||||
|
|
||||||
|
iplimit_remove_conflicts() {
|
||||||
|
local jail_files=(
|
||||||
|
/etc/fail2ban/jail.conf
|
||||||
|
/etc/fail2ban/jail.local
|
||||||
|
)
|
||||||
|
|
||||||
|
for file in "${jail_files[@]}"; do
|
||||||
|
# Check for [3x-ipl] config in jail file then remove it
|
||||||
|
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
|
||||||
|
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
|
||||||
|
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
SSH_port_forwarding() {
|
||||||
|
local URL_lists=(
|
||||||
|
"https://api4.ipify.org"
|
||||||
|
"https://ipv4.icanhazip.com"
|
||||||
|
"https://v4.api.ipinfo.io/ip"
|
||||||
|
"https://ipv4.myexternalip.com/raw"
|
||||||
|
"https://4.ident.me"
|
||||||
|
"https://check-host.net/ip"
|
||||||
|
)
|
||||||
|
local server_ip=""
|
||||||
|
for ip_address in "${URL_lists[@]}"; do
|
||||||
|
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
if [[ -n "${server_ip}" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||||
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||||
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
|
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
|
||||||
|
|
|
@ -239,7 +239,12 @@ func (p *process) Stop() error {
|
||||||
if !p.IsRunning() {
|
if !p.IsRunning() {
|
||||||
return errors.New("xray is not running")
|
return errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
return p.cmd.Process.Signal(syscall.SIGTERM)
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return p.cmd.Process.Kill()
|
||||||
|
} else {
|
||||||
|
return p.cmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCrashReport(m []byte) error {
|
func writeCrashReport(m []byte) error {
|
||||||
|
|
Loading…
Reference in a new issue