mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-14 05:23:09 +00:00
Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d14f381c2 | ||
|
|
088dd2e881 | ||
|
|
5909955f5d | ||
|
|
a3df597b5d | ||
|
|
49e43a2079 | ||
|
|
95afd3006a | ||
|
|
bcea56283f | ||
|
|
e70fb3daf5 | ||
|
|
322a74429d | ||
|
|
5b6daf9e70 | ||
|
|
e5184b81be | ||
|
|
35798c2c74 | ||
|
|
6781b0f7ae | ||
|
|
9d93468332 | ||
|
|
5e28644ec7 | ||
|
|
9b08f181a6 | ||
|
|
1a0c39693e | ||
|
|
18662b9c71 | ||
|
|
c39ad9cac1 | ||
|
|
b8f2195a3a | ||
|
|
57d437dff8 | ||
|
|
adead1cc39 | ||
|
|
41e4bb974b | ||
|
|
7dbb36a5d5 | ||
|
|
5b8ded95d2 | ||
|
|
1904a7b85c | ||
|
|
94c4becb34 | ||
|
|
8ba7f73736 | ||
|
|
8f5bead445 | ||
|
|
6bab6ce6c4 | ||
|
|
e835509acc | ||
|
|
d1606d1109 | ||
|
|
2ae72aa8d1 | ||
|
|
1b0828eb3e | ||
|
|
b6252151f4 | ||
|
|
832d561d52 | ||
|
|
d7fd3e2109 | ||
|
|
430ade7952 | ||
|
|
47ee9b503c | ||
|
|
4c98c241ed | ||
|
|
aa5ec7a343 | ||
|
|
7d9f01a621 | ||
|
|
5d07744c41 | ||
|
|
af54cca281 | ||
|
|
f2d4af1861 | ||
|
|
a3977ef6a1 | ||
|
|
3af4805eb6 | ||
|
|
1ceba486ce | ||
|
|
49990d7841 | ||
|
|
eb93fecdd1 | ||
|
|
8e992ff921 | ||
|
|
0456ed702e | ||
|
|
b11dc56cd0 | ||
|
|
2d60543026 | ||
|
|
221dda5a1a | ||
|
|
b50f7dd91a | ||
|
|
e752ad75be | ||
|
|
0ce3d1ad97 | ||
|
|
14b926582f | ||
|
|
be76dc993a | ||
|
|
2d659904d0 | ||
|
|
04e84386fc | ||
|
|
834e03cf4e | ||
|
|
244c394a6d | ||
|
|
58c58e5fbd | ||
|
|
e954ccd957 | ||
|
|
ee03836eeb | ||
|
|
44d34c8090 | ||
|
|
2cf66eb726 | ||
|
|
e9ea435d89 | ||
|
|
526a5ab8b4 | ||
|
|
2c83daef47 | ||
|
|
7c992f58af | ||
|
|
94978babda | ||
|
|
bd00bb5e7b | ||
|
|
0f1be9bfa1 | ||
|
|
ed52cd16d4 | ||
|
|
49cc3f4c26 | ||
|
|
08e7419a69 | ||
|
|
7058a2bd38 | ||
|
|
ae37131b4b | ||
|
|
33d7154a67 | ||
|
|
c540a0abc2 | ||
|
|
ecd9ae5f4a | ||
|
|
1d90dea014 | ||
|
|
274a753a4c | ||
|
|
b50592ec14 | ||
|
|
d1a178d483 | ||
|
|
dd49e89b2e | ||
|
|
fd3242ad31 | ||
|
|
8bdb45aabc | ||
|
|
ae38f3e2ab | ||
|
|
9dd76a4bff | ||
|
|
0610a6f5c3 | ||
|
|
4010ff6a77 | ||
|
|
e3be646c77 | ||
|
|
cedb8ea2ac | ||
|
|
3a7c00fc5f | ||
|
|
f2329c20df | ||
|
|
275fc4564d | ||
|
|
d28231b0f8 | ||
|
|
5403c50c11 | ||
|
|
70472ce0af | ||
|
|
53de73dd5a | ||
|
|
c75a4509cd | ||
|
|
5260ea91ac | ||
|
|
44d415f5f7 | ||
|
|
a27ca6d872 | ||
|
|
288a560687 | ||
|
|
2f61bbcfdd | ||
|
|
31cec0280a | ||
|
|
3b79e53d3c | ||
|
|
26fe3e19a3 | ||
|
|
7933da84c9 | ||
|
|
088eb6dd94 | ||
|
|
248c63296c | ||
|
|
ebf45c2131 | ||
|
|
d1725dbc23 | ||
|
|
de11119ed6 | ||
|
|
d75b4d3d9e | ||
|
|
aae79a44f8 | ||
|
|
36930b1bfd | ||
|
|
81086de306 | ||
|
|
1b9486784d | ||
|
|
1e61803add | ||
|
|
fe3ac97334 | ||
|
|
d5e19ce077 | ||
|
|
ce49394284 | ||
|
|
50dba7b5b7 | ||
|
|
e6bcb7534d | ||
|
|
68f108040a | ||
|
|
8ff38d603e | ||
|
|
56c5b0d507 | ||
|
|
fdd3df788e | ||
|
|
5be4188b5c | ||
|
|
582a7493b7 | ||
|
|
1432676c4d | ||
|
|
432f3570bf | ||
|
|
c6c43b9b47 | ||
|
|
3582876b82 | ||
|
|
7f00385541 | ||
|
|
b2465ae37f | ||
|
|
288a40d982 | ||
|
|
b06666c976 | ||
|
|
2b38c2abd1 | ||
|
|
5c5c0d79cf | ||
|
|
882a834388 | ||
|
|
ec2d8ccc86 | ||
|
|
9b00297a4b | ||
|
|
b27199d307 | ||
|
|
123b8828ab | ||
|
|
1264831dc0 | ||
|
|
e9504b559e | ||
|
|
f0f39090b6 | ||
|
|
f758e75a84 | ||
|
|
a75f4c03a5 | ||
|
|
c257b83a72 | ||
|
|
43be4f3dbe | ||
|
|
b0e85f3ab2 | ||
|
|
7b356fcbde | ||
|
|
84af1d616e | ||
|
|
aace0d3280 | ||
|
|
04f710b2f9 | ||
|
|
460cb5d4fb | ||
|
|
fe679ecef1 | ||
|
|
580169a1a1 | ||
|
|
0cd6e488bf | ||
|
|
b86bc3891d | ||
|
|
bf2e10e757 | ||
|
|
2f8d53fc8e | ||
|
|
477d5f68b4 | ||
|
|
aaabbdacaa | ||
|
|
509fff0592 | ||
|
|
a84c055fd8 | ||
|
|
91d107c5ae | ||
|
|
a9f295e4c6 | ||
|
|
c6fb39ab29 | ||
|
|
b1a57ac237 | ||
|
|
8584d933b3 | ||
|
|
f79f552eb8 | ||
|
|
57cec6fef3 | ||
|
|
ffd2d44d7f | ||
|
|
1f918542ee | ||
|
|
c521d10472 | ||
|
|
055db3e9fb | ||
|
|
f79a33d712 | ||
|
|
7594ef4fe0 |
14
.github/FUNDING.yml
vendored
Normal file
14
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: alireza0 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # removed
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
9
.github/workflows/docker.yml
vendored
9
.github/workflows/docker.yml
vendored
@@ -1,4 +1,9 @@
|
||||
name: Docker Image CI
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -10,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
@@ -31,6 +36,8 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
install: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
|
||||
216
.github/workflows/release.yml
vendored
216
.github/workflows/release.yml
vendored
@@ -1,13 +1,29 @@
|
||||
name: Release X-UI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "*.*.*"
|
||||
paths:
|
||||
- '.github/workflows/release.yml'
|
||||
- '**.js'
|
||||
- '**.css'
|
||||
- '**.html'
|
||||
- '**.sh'
|
||||
- '**.go'
|
||||
- 'go.mod'
|
||||
- 'go.sum'
|
||||
- 'x-ui.service'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
@@ -15,65 +31,51 @@ jobs:
|
||||
- arm64
|
||||
- armv7
|
||||
- armv6
|
||||
- armv5
|
||||
- 386
|
||||
- armv5
|
||||
- s390x
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install dependencies
|
||||
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 }}" == "armv5" ]; then
|
||||
sudo apt install gcc-arm-linux-gnueabi
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
sudo apt install gcc-i686-linux-gnu
|
||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||
sudo apt install gcc-s390x-linux-gnu
|
||||
fi
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Build x-ui
|
||||
run: |
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=linux
|
||||
export GOARCH=${{ matrix.platform }}
|
||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
export GOARCH=arm64
|
||||
export CC=aarch64-linux-gnu-gcc
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
export GOARCH=arm
|
||||
export GOARM=7
|
||||
export CC=arm-linux-gnueabihf-gcc
|
||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||
export GOARCH=arm
|
||||
export GOARM=6
|
||||
export CC=arm-linux-gnueabihf-gcc
|
||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||
export GOARCH=arm
|
||||
export GOARM=5
|
||||
export CC=arm-linux-gnueabi-gcc
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
export GOARCH=386
|
||||
export CC=i686-linux-gnu-gcc
|
||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||
export GOARCH=s390x
|
||||
export CC=s390x-linux-gnu-gcc
|
||||
fi
|
||||
|
||||
go build -o xui-release -v main.go
|
||||
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
|
||||
case "${{ matrix.platform }}" in
|
||||
amd64) BOOTLIN_ARCH="x86-64" ;;
|
||||
arm64) BOOTLIN_ARCH="aarch64" ;;
|
||||
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
|
||||
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
|
||||
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
|
||||
386) BOOTLIN_ARCH="x86-i686" ;;
|
||||
s390x) BOOTLIN_ARCH="s390x-z13" ;;
|
||||
esac
|
||||
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
|
||||
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
|
||||
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
|
||||
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
|
||||
echo "Downloading: $TARBALL_URL"
|
||||
cd /tmp
|
||||
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
|
||||
tar -xf "$(basename "$TARBALL_URL")"
|
||||
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
|
||||
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
|
||||
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
|
||||
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
|
||||
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
|
||||
cp xui-release x-ui/
|
||||
@@ -84,52 +86,136 @@ jobs:
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.17/"
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-64.zip
|
||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
rm -f Xray-linux-64.zip
|
||||
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
rm -f Xray-linux-arm64-v8a.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
|
||||
unzip Xray-linux-arm32-v7a.zip
|
||||
rm -f Xray-linux-arm32-v7a.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v6.zip
|
||||
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
|
||||
unzip Xray-linux-arm32-v6.zip
|
||||
rm -f Xray-linux-arm32-v6.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v5.zip
|
||||
unzip Xray-linux-arm32-v5.zip
|
||||
rm -f Xray-linux-arm32-v5.zip
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
wget ${Xray_URL}Xray-linux-32.zip
|
||||
wget -q ${Xray_URL}Xray-linux-32.zip
|
||||
unzip Xray-linux-32.zip
|
||||
rm -f Xray-linux-32.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
||||
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
|
||||
unzip Xray-linux-arm32-v5.zip
|
||||
rm -f Xray-linux-arm32-v5.zip
|
||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
||||
wget ${Xray_URL}Xray-linux-s390x.zip
|
||||
wget -q ${Xray_URL}Xray-linux-s390x.zip
|
||||
unzip Xray-linux-s390x.zip
|
||||
rm -f Xray-linux-s390x.zip
|
||||
fi
|
||||
rm -f geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
mv xray xray-linux-${{ matrix.platform }}
|
||||
cd ../..
|
||||
|
||||
- name: Package
|
||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x-ui-linux-${{ matrix.platform }}
|
||||
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: |
|
||||
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
overwrite: true
|
||||
prerelease: true
|
||||
|
||||
# =================================
|
||||
# Windows Build
|
||||
# =================================
|
||||
build-windows:
|
||||
name: Build for Windows
|
||||
permissions:
|
||||
contents: write
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- amd64
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
- name: Build X-UI for Windows
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:CGO_ENABLED="1"
|
||||
$env:GOOS="windows"
|
||||
$env:GOARCH="amd64"
|
||||
go build -ldflags "-w -s" -o xui-release.exe -v main.go
|
||||
|
||||
mkdir x-ui
|
||||
Copy-Item xui-release.exe x-ui\
|
||||
mkdir x-ui\bin
|
||||
cd x-ui\bin
|
||||
|
||||
# Download Xray for Windows
|
||||
$Xray_URL = "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/"
|
||||
Invoke-WebRequest -Uri "${Xray_URL}Xray-windows-64.zip" -OutFile "Xray-windows-64.zip"
|
||||
Expand-Archive -Path "Xray-windows-64.zip" -DestinationPath .
|
||||
Remove-Item "Xray-windows-64.zip"
|
||||
Remove-Item geoip.dat, geosite.dat -ErrorAction SilentlyContinue
|
||||
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" -OutFile "geoip.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" -OutFile "geosite.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -OutFile "geoip_IR.dat"
|
||||
Invoke-WebRequest -Uri "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -OutFile "geosite_IR.dat"
|
||||
Rename-Item xray.exe xray-windows-amd64.exe
|
||||
cd ..
|
||||
Copy-Item -Path ..\windows_files\* -Destination . -Recurse
|
||||
cd ..
|
||||
|
||||
- name: Package to Zip
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path .\x-ui -DestinationPath "x-ui-windows-amd64.zip"
|
||||
|
||||
- name: Upload files to Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: x-ui-windows-amd64
|
||||
path: ./x-ui-windows-amd64.zip
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: |
|
||||
(github.event_name == 'release' && github.event.action == 'published') ||
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/'))
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
file: x-ui-windows-amd64.zip
|
||||
asset_name: x-ui-windows-amd64.zip
|
||||
overwrite: true
|
||||
prerelease: true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,4 +14,5 @@ dist/
|
||||
release/
|
||||
/release.sh
|
||||
/x-ui
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
/dev/
|
||||
|
||||
94
CONTRIBUTING.md
Normal file
94
CONTRIBUTING.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Contribution guide
|
||||
|
||||
This document describes **development-only** setup options for contributing to the x-ui project.
|
||||
Production deployments are **out of scope** for this guide.
|
||||
|
||||
For safety, Docker-based development is **strongly recommended**.
|
||||
Local installation should be performed **only inside a VM or disposable environment**.
|
||||
|
||||
---
|
||||
|
||||
## Recommended: Docker development setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker
|
||||
- Docker Compose (v2+)
|
||||
- Git
|
||||
|
||||
### Steps
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alireza0/x-ui.git
|
||||
# or:
|
||||
# git clone https://github.com/<your_fork_name>/x-ui.git
|
||||
|
||||
docker compose -f docker-compose.yml -f docker-compose.dev.yml up --build
|
||||
```
|
||||
|
||||
### Explore panel
|
||||
|
||||
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
|
||||
- Default credentials: `admin` / `admin`
|
||||
|
||||
To stop and remove containers or `ctrl` + `C`:
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Environment
|
||||
|
||||
When using the Docker development setup, the [docker-compose.dev.yml](/docker-compose.dev.yml) file mounts the local database directory into the container:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./dev/db:/etc/x-ui
|
||||
```
|
||||
|
||||
This means:
|
||||
|
||||
- [./dev/db](/dev/db/) on the host is used to persist panel state during development
|
||||
- `/etc/x-ui` inside the container is the active database/config directory
|
||||
- Data will survive container restarts but is isolated from the host system
|
||||
- You can safely delete `./dev/db` to reset the development database
|
||||
|
||||
---
|
||||
|
||||
## Local development setup (unsafe on host OS)
|
||||
|
||||
This method runs scripts with root privileges and alters the host system.
|
||||
Use **only inside a VM, container, or disposable test environment**.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Ubuntu/Debian or Fedora (actually anything with systemd, just use proper package manager)
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
# Ubuntu / Debian
|
||||
sudo apt update
|
||||
sudo apt upgrade -y
|
||||
sudo apt install -y golang unzip git wget
|
||||
|
||||
# Fedora
|
||||
sudo dnf update -y
|
||||
sudo dnf install -y golang unzip git wget
|
||||
```
|
||||
|
||||
### Setup panel
|
||||
|
||||
```bash
|
||||
git clone https://github.com/alireza0/x-ui.git
|
||||
# or:
|
||||
# git clone https://github.com/<your_fork_name>/x-ui.git
|
||||
|
||||
cd x-ui/
|
||||
sudo bash x-ui.sh # Select -> 10. Start
|
||||
```
|
||||
|
||||
### Explore panel
|
||||
|
||||
- Panel should be available at localhost:54321 like: [http://127.0.0.1:54321](http://127.0.0.1:54321) for example
|
||||
- Default credentials: `admin` / `admin`
|
||||
@@ -14,7 +14,7 @@ case $1 in
|
||||
;;
|
||||
armv7 | arm | arm32)
|
||||
ARCH="arm32-v7a"
|
||||
FNAME="arm32"
|
||||
FNAME="arm"
|
||||
;;
|
||||
*)
|
||||
ARCH="64"
|
||||
@@ -23,12 +23,12 @@ case $1 in
|
||||
esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.17/Xray-linux-${ARCH}.zip"
|
||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v26.2.6/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
wget -q "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||
wget -q "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
cd ../../
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
FROM golang:1.22-alpine AS builder
|
||||
FROM golang:1.25-alpine AS builder
|
||||
WORKDIR /app
|
||||
ARG TARGETARCH
|
||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||
COPY . .
|
||||
ENV CGO_ENABLED=1
|
||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
||||
RUN go build -o build/x-ui main.go
|
||||
RUN go build -ldflags "-w -s" -o build/x-ui main.go
|
||||
RUN ./DockerInitFiles.sh "$TARGETARCH"
|
||||
|
||||
FROM alpine
|
||||
|
||||
57
README.md
57
README.md
@@ -13,10 +13,9 @@
|
||||
|
||||
[](https://www.buymeacoffee.com/alireza7)
|
||||
|
||||
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
||||
- Tezos (XTZ):
|
||||
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
|
||||
|
||||
<a href="https://nowpayments.io/donation/alireza7" target="_blank" rel="noreferrer noopener">
|
||||
<img src="https://nowpayments.io/images/embeds/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
|
||||
</a>
|
||||
|
||||
## Quick Overview
|
||||
| Features | Enable? |
|
||||
@@ -40,12 +39,12 @@
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
||||
```
|
||||
|
||||
## Install Custom Version
|
||||
## Install Legacy Version
|
||||
|
||||
**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `1.8.0`:
|
||||
**Step 1:** To install an old version, use following installation command. e.g., version `1.8.0`:
|
||||
|
||||
```sh
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 1.8.0
|
||||
VERSION=1.8.0 && bash <(curl -Ls "https://raw.githubusercontent.com/alireza0/x-ui/$VERSION/install.sh") $VERSION
|
||||
```
|
||||
|
||||
## Manual Install & Upgrade
|
||||
@@ -186,21 +185,6 @@ docker build -t x-ui .
|
||||
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
||||
- Dark/Light theme
|
||||
|
||||
## Recommended OS
|
||||
|
||||
- Ubuntu 20.04+
|
||||
- Debian 11+
|
||||
- CentOS 8+
|
||||
- Fedora 36+
|
||||
- Arch Linux
|
||||
- Parch Linux
|
||||
- Manjaro
|
||||
- Armbian
|
||||
- AlmaLinux 9+
|
||||
- Rocky Linux 9+
|
||||
- Oracle Linux 8+
|
||||
- OpenSUSE Tubleweed
|
||||
|
||||
## Preview
|
||||
|
||||

|
||||
@@ -224,7 +208,6 @@ docker build -t x-ui .
|
||||
| :----: | --------------------------------- | ----------------------------------------- |
|
||||
| `GET` | `"/"` | Get all inbounds |
|
||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||
| `POST` | `"/add"` | Add inbound |
|
||||
| `POST` | `"/del/:id"` | Delete inbound |
|
||||
| `POST` | `"/update/:id"` | Update inbound |
|
||||
@@ -239,11 +222,33 @@ docker build -t x-ui .
|
||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
|
||||
|
||||
|
||||
\*- The field `clientId` should be filled by:
|
||||
|
||||
- `client.id` for VMess and VLESS
|
||||
- `client.password` for Trojan
|
||||
- `client.email` for Shadowsocks
|
||||
- `client.id` for VMess and VLESS
|
||||
- `client.password` for Trojan
|
||||
- `client.email` for Shadowsocks
|
||||
|
||||
|
||||
- `/xui/API/server` base for following actions:
|
||||
|
||||
| Method | Path | Action |
|
||||
| :----: | --------------------------------- | ----------------------------------------- |
|
||||
| `GET` | `"/status"` | Get server status |
|
||||
| `GET` | `"/getDb"` | Get database backup |
|
||||
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
||||
| `GET` | `"/getConfigJson"` | Get config.json |
|
||||
| `GET` | `"/getXrayVersion"` | Get last xray versions |
|
||||
| `GET` | `"/getNewVlessEnc"` | Get new vless enc |
|
||||
| `GET` | `"/getNewX25519Cert"` | Get new x25519 cert |
|
||||
| `GET` | `"/getNewmldsa65"` | Get new mldsa65 |
|
||||
| `POST` | `"/getNewEchCert"` | Get new ech cert |
|
||||
| `POST` | `"/importDB"` | Import database to x-ui |
|
||||
| `POST` | `"/stopXrayService"` | Stop xray service |
|
||||
| `POST` | `"/restartXrayService"` | Restart xray service |
|
||||
| `POST` | `"/installXray/:version"` | Install specific version of xray |
|
||||
| `POST` | `"/logs/:count"` | Get panel/xray logs |
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -16,10 +18,10 @@ var name string
|
||||
type LogLevel string
|
||||
|
||||
const (
|
||||
Debug LogLevel = "debug"
|
||||
Info LogLevel = "info"
|
||||
Warn LogLevel = "warn"
|
||||
Error LogLevel = "error"
|
||||
Debug LogLevel = "debug"
|
||||
Info LogLevel = "info"
|
||||
Warning LogLevel = "warning"
|
||||
Error LogLevel = "error"
|
||||
)
|
||||
|
||||
func GetVersion() string {
|
||||
@@ -53,12 +55,32 @@ func GetBinFolderPath() string {
|
||||
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 {
|
||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||
if dbFolderPath == "" {
|
||||
dbFolderPath = "/etc/x-ui"
|
||||
if dbFolderPath != "" {
|
||||
return dbFolderPath
|
||||
}
|
||||
return dbFolderPath
|
||||
if runtime.GOOS == "windows" {
|
||||
return getBaseDir()
|
||||
}
|
||||
return "/etc/x-ui"
|
||||
}
|
||||
|
||||
func GetDBPath() string {
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.8.5
|
||||
1.10.2
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database/model"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@@ -94,6 +95,17 @@ func InitDB(dbPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloseDB() error {
|
||||
if db != nil {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDB() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
@@ -120,3 +132,26 @@ func Checkpoint() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateSQLiteDB(dbPath string) error {
|
||||
if _, err := os.Stat(dbPath); err != nil { // file must exist
|
||||
return err
|
||||
}
|
||||
gdb, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{Logger: logger.Discard})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sqlDB, err := gdb.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
var res string
|
||||
if err := gdb.Raw("PRAGMA integrity_check;").Scan(&res).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if res != "ok" {
|
||||
return common.NewError("sqlite integrity check failed: " + res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package model
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"x-ui/util/json_util"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/util/json_util"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
)
|
||||
|
||||
type Protocol string
|
||||
@@ -79,3 +79,10 @@ type Client struct {
|
||||
SubID string `json:"subId" form:"subId"`
|
||||
Reset int `json:"reset" form:"reset"`
|
||||
}
|
||||
|
||||
type VLESSSettings struct {
|
||||
Clients []Client `json:"clients"`
|
||||
Decryption string `json:"decryption"`
|
||||
Encryption string `json:"encryption"`
|
||||
Fallbacks []any `json:"fallbacks"`
|
||||
}
|
||||
|
||||
15
docker-compose.dev.yml
Normal file
15
docker-compose.dev.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
xui:
|
||||
build:
|
||||
context: .
|
||||
target: builder
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
- ./dev/db:/etc/x-ui
|
||||
environment:
|
||||
XUI_BIN_FOLDER: /app/dev/bin
|
||||
XUI_DB_FOLDER: /etc/x-ui
|
||||
XUI_LOG_LEVEL: debug
|
||||
XUI_DEBUG: "true"
|
||||
command: ["go","run","./main.go"]
|
||||
@@ -1,5 +1,4 @@
|
||||
---
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
xui:
|
||||
|
||||
126
go.mod
126
go.mod
@@ -1,94 +1,90 @@
|
||||
module x-ui
|
||||
module github.com/alireza0/x-ui
|
||||
|
||||
go 1.22.5
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/Calidity/gin-sessions v1.3.1
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/gin-contrib/gzip v1.2.5
|
||||
github.com/gin-contrib/sessions v1.0.4
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.2.2
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.24.6
|
||||
github.com/xtls/xray-core v1.8.17
|
||||
github.com/shirou/gopsutil/v4 v4.26.1
|
||||
github.com/xtls/xray-core v1.260206.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.16.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.11
|
||||
golang.org/x/text v0.34.0
|
||||
google.golang.org/grpc v1.79.1
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.11.9 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.9 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.3.0 // indirect
|
||||
github.com/gorilla/sessions v1.4.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/juju/ratelimit v1.0.2 // indirect
|
||||
github.com/klauspost/compress v1.18.3 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.34 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.9.2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/quic-go/quic-go v0.45.1 // indirect
|
||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/sagernet/sing v0.4.1 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/sagernet/sing v0.7.17 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
)
|
||||
|
||||
474
go.sum
474
go.sum
@@ -1,63 +1,42 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
|
||||
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
|
||||
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
|
||||
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
|
||||
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
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/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
||||
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
||||
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
@@ -67,325 +46,198 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
|
||||
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g=
|
||||
github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI=
|
||||
github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||
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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
|
||||
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA=
|
||||
github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
|
||||
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
|
||||
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v4 v4.24.6 h1:9qqCSYF2pgOU+t+NgJtp7Co5+5mHF/HyKBUckySQL64=
|
||||
github.com/shirou/gopsutil/v4 v4.24.6/go.mod h1:aoebb2vxetJ/yIDZISmduFvVNPHqXQ9SEJwRXxkf0RA=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
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/sagernet/sing v0.7.17 h1:Jg4RUYIaQWTi7iY5ROHi3/Zsgxn4SPoRTwbdt35mt50=
|
||||
github.com/sagernet/sing v0.7.17/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1 h1:TOkEyriIXk2HX9d4isZJtbjXbEjf5qyKPAzbzY0JWSo=
|
||||
github.com/shirou/gopsutil/v4 v4.26.1/go.mod h1:medLI9/UNAb0dOI9Q3/7yWSqKkj00u+1tgY8nvv41pc=
|
||||
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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.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.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.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d h1:+B97uD9uHLgAAulhigmys4BVwZZypzK7gPN3WtpgRJg=
|
||||
github.com/xtls/reality v0.0.0-20240712055506-48f0b2d5ed6d/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/xtls/xray-core v1.8.17 h1:T+A/hkBB33P0sEGXDiuKrQMZUOYxbZ84JlsYfyRiK8w=
|
||||
github.com/xtls/xray-core v1.8.17/go.mod h1:qEVGJD2suPN7EArG3r5EX6pYGV0QLiSRTlDMn0paJkc=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
|
||||
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
|
||||
github.com/xtls/xray-core v1.260206.0 h1:gY8IV6u76CW93txL9QmacgZ0Udxr2Q3e9qUxXAhdHqI=
|
||||
github.com/xtls/xray-core v1.260206.0/go.mod h1:GyFIgVGRJkt3eyV/NMcdxOKXcJPqGGpyupHzy16uJhU=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
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/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
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=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.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.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
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/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
|
||||
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||
|
||||
165
install.sh
165
install.sh
@@ -38,117 +38,88 @@ arch() {
|
||||
|
||||
echo "arch: $(arch)"
|
||||
|
||||
os_version=""
|
||||
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||
|
||||
if [[ "${release}" == "arch" ]]; then
|
||||
echo "Your OS is Arch Linux"
|
||||
elif [[ "${release}" == "parch" ]]; then
|
||||
echo "Your OS is Parch linux"
|
||||
elif [[ "${release}" == "manjaro" ]]; then
|
||||
echo "Your OS is Manjaro"
|
||||
elif [[ "${release}" == "armbian" ]]; then
|
||||
echo "Your OS is Armbian"
|
||||
elif [[ "${release}" == "opensuse-tumbleweed" ]]; then
|
||||
echo "Your OS is OpenSUSE Tumbleweed"
|
||||
elif [[ "${release}" == "centos" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
if [[ ${os_version} -lt 20 ]]; then
|
||||
echo -e "${red} Please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "fedora" ]]; then
|
||||
if [[ ${os_version} -lt 36 ]]; then
|
||||
echo -e "${red} Please use Fedora 36 or higher version!${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "debian" ]]; then
|
||||
if [[ ${os_version} -lt 11 ]]; then
|
||||
echo -e "${red} Please use Debian 11 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "almalinux" ]]; then
|
||||
if [[ ${os_version} -lt 9 ]]; then
|
||||
echo -e "${red} Please use AlmaLinux 9 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "rocky" ]]; then
|
||||
if [[ ${os_version} -lt 9 ]]; then
|
||||
echo -e "${red} Please use Rocky Linux 9 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "oracle" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use Oracle Linux 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${red}Your operating system is not supported by this script.${plain}\n"
|
||||
echo "Please ensure you are using one of the following supported operating systems:"
|
||||
echo "- Ubuntu 20.04+"
|
||||
echo "- Debian 11+"
|
||||
echo "- CentOS 8+"
|
||||
echo "- Fedora 36+"
|
||||
echo "- Arch Linux"
|
||||
echo "- Parch Linux"
|
||||
echo "- Manjaro"
|
||||
echo "- Armbian"
|
||||
echo "- AlmaLinux 9+"
|
||||
echo "- Rocky Linux 9+"
|
||||
echo "- Oracle Linux 8+"
|
||||
echo "- OpenSUSE Tumbleweed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install_dependencies() {
|
||||
case "${release}" in
|
||||
centos | almalinux | rocky | oracle)
|
||||
yum -y update && yum install -y -q wget curl tar tzdata
|
||||
ubuntu | debian | armbian)
|
||||
apt-get update && apt-get install -y -q wget curl tar tzdata cron
|
||||
;;
|
||||
fedora)
|
||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
||||
centos | almalinux | rocky | ol)
|
||||
yum -y update && yum install -y -q wget curl tar tzdata cronie
|
||||
;;
|
||||
fedora | amzn)
|
||||
dnf -y update && dnf install -y -q wget curl tar tzdata cronie
|
||||
;;
|
||||
arch | manjaro | parch)
|
||||
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
|
||||
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata cronie
|
||||
;;
|
||||
opensuse-tumbleweed)
|
||||
zypper refresh && zypper -q install -y wget curl tar timezone
|
||||
zypper refresh && zypper -q install -y wget curl tar timezone cron
|
||||
;;
|
||||
*)
|
||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
||||
apt-get update && apt install -y -q wget curl tar tzdata cron
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#This function will be called when user installed x-ui out of security
|
||||
gen_random_string() {
|
||||
local length="$1"
|
||||
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1)
|
||||
echo "$random_string"
|
||||
}
|
||||
|
||||
config_after_install() {
|
||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
read -p "Please set up your username:" config_account
|
||||
echo -e "${yellow}Your username will be:${config_account}${plain}"
|
||||
read -p "Please set up your password:" config_password
|
||||
echo -e "${yellow}Your password will be:${config_password}${plain}"
|
||||
read -p "Please set up the panel port:" config_port
|
||||
echo -e "${yellow}Your panel port is:${config_port}${plain}"
|
||||
echo -e "${yellow}Initializing, please wait...${plain}"
|
||||
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
||||
echo -e "${yellow}Account name and password set successfully!${plain}"
|
||||
/usr/local/x-ui/x-ui setting -port ${config_port}
|
||||
echo -e "${yellow}Panel port set successfully!${plain}"
|
||||
else
|
||||
echo -e "${red}cancel...${plain}"
|
||||
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
|
||||
local usernameTemp=$(head -c 6 /dev/urandom | base64)
|
||||
local passwordTemp=$(head -c 6 /dev/urandom | base64)
|
||||
/usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp}
|
||||
echo -e "this is a fresh installation,will generate random login info for security concerns:"
|
||||
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}')
|
||||
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
|
||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||
|
||||
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
||||
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||
local config_webBasePath=$(gen_random_string 15)
|
||||
local config_username=$(gen_random_string 10)
|
||||
local config_password=$(gen_random_string 10)
|
||||
|
||||
read -p "Would you like to customize the Panel Port settings? (If not, random port will be applied) [y/n]: " config_confirm
|
||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||
read -p "Please set up the panel port: " config_port
|
||||
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
||||
else
|
||||
local config_port=$(shuf -i 1024-62000 -n 1)
|
||||
echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
||||
fi
|
||||
|
||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
|
||||
echo -e "This is a fresh installation, generating random login info for security concerns:"
|
||||
echo -e "###############################################"
|
||||
echo -e "${green}username:${usernameTemp}${plain}"
|
||||
echo -e "${green}password:${passwordTemp}${plain}"
|
||||
echo -e "${green}Username: ${config_username}${plain}"
|
||||
echo -e "${green}Password: ${config_password}${plain}"
|
||||
echo -e "${green}Port: ${config_port}${plain}"
|
||||
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
|
||||
echo -e "###############################################"
|
||||
echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}"
|
||||
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
||||
else
|
||||
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
|
||||
local config_webBasePath=$(gen_random_string 15)
|
||||
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
||||
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
|
||||
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
|
||||
fi
|
||||
else
|
||||
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||
local config_username=$(gen_random_string 10)
|
||||
local config_password=$(gen_random_string 10)
|
||||
|
||||
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
|
||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}"
|
||||
echo -e "Generated new random login credentials:"
|
||||
echo -e "###############################################"
|
||||
echo -e "${green}Username: ${config_username}${plain}"
|
||||
echo -e "${green}Password: ${config_password}${plain}"
|
||||
echo -e "###############################################"
|
||||
echo -e "${yellow}If you forgot your login info, you can type 'x-ui settings' to check${plain}"
|
||||
else
|
||||
echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}"
|
||||
fi
|
||||
fi
|
||||
|
||||
/usr/local/x-ui/x-ui migrate
|
||||
}
|
||||
|
||||
@@ -185,10 +156,10 @@ install_x-ui() {
|
||||
else
|
||||
last_version=$1
|
||||
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
|
||||
echo -e "Beginning to install x-ui v$1"
|
||||
echo -e "Beginning to install x-ui $1"
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "${red}download x-ui v$1 failed,please check the version exists${plain}"
|
||||
echo -e "${red}download x-ui $1 failed,please check the version exists${plain}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
@@ -225,8 +196,11 @@ install_x-ui() {
|
||||
systemctl daemon-reload
|
||||
systemctl enable x-ui
|
||||
systemctl start x-ui
|
||||
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
|
||||
echo -e "${green}x-ui ${last_version}${plain} installation finished, it is up and running now..."
|
||||
echo -e ""
|
||||
echo -e "You may access the Panel with following URL(s):${yellow}"
|
||||
/usr/local/x-ui/x-ui uri
|
||||
echo -e "${plain}"
|
||||
echo "X-UI Control Menu Usage"
|
||||
echo "------------------------------------------"
|
||||
echo "SUBCOMMANDS:"
|
||||
@@ -235,6 +209,7 @@ install_x-ui() {
|
||||
echo "x-ui stop - Stop"
|
||||
echo "x-ui restart - Restart"
|
||||
echo "x-ui status - Current Status"
|
||||
echo "x-ui settings - Current Settings"
|
||||
echo "x-ui enable - Enable Autostart on OS Startup"
|
||||
echo "x-ui disable - Disable Autostart on OS Startup"
|
||||
echo "x-ui log - Check Logs"
|
||||
|
||||
233
main.go
233
main.go
@@ -3,21 +3,26 @@ package main
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
_ "unsafe"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/logger"
|
||||
"x-ui/sub"
|
||||
"x-ui/web"
|
||||
"x-ui/web/global"
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/sub"
|
||||
"github.com/alireza0/x-ui/util/sys"
|
||||
"github.com/alireza0/x-ui/web"
|
||||
"github.com/alireza0/x-ui/web/global"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
|
||||
"github.com/op/go-logging"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
func runWebServer() {
|
||||
@@ -28,7 +33,7 @@ func runWebServer() {
|
||||
logger.InitLogger(logging.DEBUG)
|
||||
case config.Info:
|
||||
logger.InitLogger(logging.INFO)
|
||||
case config.Warn:
|
||||
case config.Warning:
|
||||
logger.InitLogger(logging.WARNING)
|
||||
case config.Error:
|
||||
logger.InitLogger(logging.ERROR)
|
||||
@@ -63,7 +68,7 @@ func runWebServer() {
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
// Trap shutdown signals
|
||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, sys.SIGUSR1)
|
||||
for {
|
||||
sig := <-sigCh
|
||||
|
||||
@@ -94,6 +99,12 @@ func runWebServer() {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
case sys.SIGUSR1:
|
||||
logger.Info("Received USR1 signal, restarting xray-core...")
|
||||
err := server.RestartXray()
|
||||
if err != nil {
|
||||
logger.Error("Failed to restart xray-core:", err)
|
||||
}
|
||||
default:
|
||||
server.Stop()
|
||||
subServer.Stop()
|
||||
@@ -123,22 +134,35 @@ func showSetting(show bool) {
|
||||
settingService := service.SettingService{}
|
||||
port, err := settingService.GetPort()
|
||||
if err != nil {
|
||||
fmt.Println("get current port failed,error info:", err)
|
||||
fmt.Println("get current port failed, error info:", err)
|
||||
}
|
||||
|
||||
webBasePath, err := settingService.GetBasePath()
|
||||
if err != nil {
|
||||
fmt.Println("get webBasePath failed, error info:", err)
|
||||
}
|
||||
|
||||
userService := service.UserService{}
|
||||
userModel, err := userService.GetFirstUser()
|
||||
if err != nil {
|
||||
fmt.Println("get current user info failed,error info:", err)
|
||||
fmt.Println("get current user info failed, error info:", err)
|
||||
}
|
||||
|
||||
username := userModel.Username
|
||||
userpasswd := userModel.Password
|
||||
if (username == "") || (userpasswd == "") {
|
||||
if username == "" || userpasswd == "" {
|
||||
fmt.Println("current username or password is empty")
|
||||
}
|
||||
|
||||
fmt.Println("current panel settings as follows:")
|
||||
fmt.Println("username:", username)
|
||||
fmt.Println("userpasswd:", userpasswd)
|
||||
fmt.Println("password:", userpasswd)
|
||||
fmt.Println("port:", port)
|
||||
if webBasePath != "" {
|
||||
fmt.Println("webBasePath:", webBasePath)
|
||||
} else {
|
||||
fmt.Println("webBasePath is not set")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,7 +225,72 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||
}
|
||||
}
|
||||
|
||||
func updateSetting(port int, username string, password string) {
|
||||
func updateSetting(port int, username string, password string, webBasePath string) {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println("Database initialization failed:", err)
|
||||
return
|
||||
}
|
||||
|
||||
settingService := service.SettingService{}
|
||||
userService := service.UserService{}
|
||||
|
||||
if port > 0 {
|
||||
err := settingService.SetPort(port)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to set port:", err)
|
||||
} else {
|
||||
fmt.Printf("Port set successfully: %v\n", port)
|
||||
}
|
||||
}
|
||||
|
||||
if username != "" || password != "" {
|
||||
err := userService.UpdateFirstUser(username, password)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to update username and password:", err)
|
||||
} else {
|
||||
fmt.Println("Username and password updated successfully")
|
||||
}
|
||||
}
|
||||
|
||||
if webBasePath != "" {
|
||||
err := settingService.SetBasePath(webBasePath)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to set base URI path:", err)
|
||||
} else {
|
||||
fmt.Println("Base URI path set successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateCert(publicKey string, privateKey string) {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (privateKey != "" && publicKey != "") || (privateKey == "" && publicKey == "") {
|
||||
settingService := service.SettingService{}
|
||||
err = settingService.SetCertFile(publicKey)
|
||||
if err != nil {
|
||||
fmt.Println("set certificate public key failed:", err)
|
||||
} else {
|
||||
fmt.Println("set certificate public key success")
|
||||
}
|
||||
|
||||
err = settingService.SetKeyFile(privateKey)
|
||||
if err != nil {
|
||||
fmt.Println("set certificate private key failed:", err)
|
||||
} else {
|
||||
fmt.Println("set certificate private key success")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("both public and private key should be entered.")
|
||||
}
|
||||
}
|
||||
|
||||
func getPanelURI() {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -210,21 +299,64 @@ func updateSetting(port int, username string, password string) {
|
||||
|
||||
settingService := service.SettingService{}
|
||||
|
||||
if port > 0 {
|
||||
err := settingService.SetPort(port)
|
||||
if err != nil {
|
||||
fmt.Println("set port failed:", err)
|
||||
} else {
|
||||
fmt.Printf("set port %v success", port)
|
||||
Port, _ := settingService.GetPort()
|
||||
BasePath, _ := settingService.GetBasePath()
|
||||
Listen, _ := settingService.GetListen()
|
||||
Domain, _ := settingService.GetWebDomain()
|
||||
KeyFile, _ := settingService.GetKeyFile()
|
||||
CertFile, _ := settingService.GetCertFile()
|
||||
|
||||
TLS := false
|
||||
if KeyFile != "" && CertFile != "" {
|
||||
TLS = true
|
||||
}
|
||||
|
||||
Proto := ""
|
||||
if TLS {
|
||||
Proto = "https://"
|
||||
} else {
|
||||
Proto = "http://"
|
||||
}
|
||||
|
||||
PortText := fmt.Sprintf(":%d", Port)
|
||||
if (Port == 443 && TLS) || (Port == 80 && !TLS) {
|
||||
PortText = ""
|
||||
}
|
||||
|
||||
if len(Domain) > 0 {
|
||||
fmt.Println(Proto + Domain + PortText + BasePath)
|
||||
return
|
||||
}
|
||||
|
||||
if len(Listen) > 0 {
|
||||
fmt.Println(Proto + Listen + PortText + BasePath)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Local address:")
|
||||
|
||||
// get ip address
|
||||
netInterfaces, _ := net.Interfaces()
|
||||
for i := 0; i < len(netInterfaces); i++ {
|
||||
if len(netInterfaces[i].Flags) > 2 && netInterfaces[i].Flags[0] == "up" && netInterfaces[i].Flags[1] != "loopback" {
|
||||
addrs := netInterfaces[i].Addrs
|
||||
for _, address := range addrs {
|
||||
IP := strings.Split(address.Addr, "/")[0]
|
||||
if strings.Contains(address.Addr, ".") {
|
||||
fmt.Println(Proto + IP + PortText + BasePath)
|
||||
} else if address.Addr[0:6] != "fe80::" {
|
||||
fmt.Println(Proto + "[" + IP + "]" + PortText + BasePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if username != "" || password != "" {
|
||||
userService := service.UserService{}
|
||||
err := userService.UpdateFirstUser(username, password)
|
||||
if err != nil {
|
||||
fmt.Println("set username and password failed:", err)
|
||||
} else {
|
||||
fmt.Println("set username and password success")
|
||||
|
||||
resp, err := http.Get("https://api.ipify.org?format=text")
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
ip, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
fmt.Printf("\nGlobal address:\n%s%s%s%s\n", Proto, ip, PortText, BasePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,30 +388,37 @@ func main() {
|
||||
var port int
|
||||
var username string
|
||||
var password string
|
||||
var webBasePath string
|
||||
var webCertFile string
|
||||
var webKeyFile string
|
||||
var tgbottoken string
|
||||
var tgbotchatid string
|
||||
var enabletgbot bool
|
||||
var tgbotRuntime string
|
||||
var reset bool
|
||||
var show bool
|
||||
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
|
||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
|
||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
|
||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||
settingCmd.BoolVar(&show, "show", false, "Show current settings")
|
||||
settingCmd.IntVar(&port, "port", 0, "Set panel port")
|
||||
settingCmd.StringVar(&username, "username", "", "Set login username")
|
||||
settingCmd.StringVar(&password, "password", "", "Set login password")
|
||||
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
||||
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
||||
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
|
||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set telegram bot cron time")
|
||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set telegram bot chat id")
|
||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable telegram bot notify")
|
||||
|
||||
oldUsage := flag.Usage
|
||||
flag.Usage = func() {
|
||||
oldUsage()
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" run run web panel")
|
||||
fmt.Println(" migrate migrate form other/old x-ui")
|
||||
fmt.Println(" setting set settings")
|
||||
fmt.Println(" run Run web panel")
|
||||
fmt.Println(" uri Show panel URI")
|
||||
fmt.Println(" migrate Migrate form other/old x-ui")
|
||||
fmt.Println(" setting Set settings")
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
@@ -296,6 +435,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
runWebServer()
|
||||
case "uri":
|
||||
getPanelURI()
|
||||
case "migrate":
|
||||
migrateDb()
|
||||
case "setting":
|
||||
@@ -307,7 +448,7 @@ func main() {
|
||||
if reset {
|
||||
resetSetting()
|
||||
} else {
|
||||
updateSetting(port, username, password)
|
||||
updateSetting(port, username, password, webBasePath)
|
||||
}
|
||||
if show {
|
||||
showSetting(show)
|
||||
@@ -318,6 +459,18 @@ func main() {
|
||||
if enabletgbot {
|
||||
updateTgbotEnableSts(enabletgbot)
|
||||
}
|
||||
case "cert":
|
||||
err := settingCmd.Parse(os.Args[2:])
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
if reset {
|
||||
updateCert("", "")
|
||||
} else {
|
||||
updateCert(webCertFile, webKeyFile)
|
||||
}
|
||||
|
||||
default:
|
||||
fmt.Println("Invalid subcommands")
|
||||
fmt.Println()
|
||||
|
||||
@@ -43,11 +43,13 @@
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "direct",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP"
|
||||
}
|
||||
"domainStrategy": "UseIP",
|
||||
"noises": [],
|
||||
"redirect": ""
|
||||
},
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "block",
|
||||
|
||||
19
sub/sub.go
19
sub/sub.go
@@ -8,12 +8,12 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/web/middleware"
|
||||
"x-ui/web/network"
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/web/middleware"
|
||||
"github.com/alireza0/x-ui/web/network"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -92,6 +92,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
SubJsonFragment = ""
|
||||
}
|
||||
|
||||
SubJsonNoises, err := s.settingService.GetSubJsonNoises()
|
||||
if err != nil {
|
||||
SubJsonNoises = ""
|
||||
}
|
||||
|
||||
SubJsonMux, err := s.settingService.GetSubJsonMux()
|
||||
if err != nil {
|
||||
SubJsonMux = ""
|
||||
@@ -106,7 +111,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
|
||||
s.sub = NewSUBController(
|
||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||
SubJsonFragment, SubJsonMux, SubJsonRules)
|
||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules)
|
||||
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package sub
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -26,6 +27,7 @@ func NewSUBController(
|
||||
rModel string,
|
||||
update string,
|
||||
jsonFragment string,
|
||||
jsonNoise string,
|
||||
jsonMux string,
|
||||
jsonRules string,
|
||||
) *SUBController {
|
||||
@@ -37,7 +39,7 @@ func NewSUBController(
|
||||
updateInterval: update,
|
||||
|
||||
subService: sub,
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
|
||||
}
|
||||
a.initRouter(g)
|
||||
return a
|
||||
@@ -54,7 +56,10 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||
|
||||
func (a *SUBController) subs(c *gin.Context) {
|
||||
subId := c.Param("subid")
|
||||
host, _, _ := net.SplitHostPort(c.Request.Host)
|
||||
host := c.Request.Host
|
||||
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||
}
|
||||
subs, header, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
@@ -79,7 +84,10 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||
|
||||
func (a *SUBController) subJsons(c *gin.Context) {
|
||||
subId := c.Param("subid")
|
||||
host, _, _ := net.SplitHostPort(c.Request.Host)
|
||||
host := c.Request.Host
|
||||
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||
}
|
||||
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
||||
if err != nil || len(jsonSub) == 0 {
|
||||
c.String(400, "Error!")
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/json_util"
|
||||
"x-ui/util/random"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/json_util"
|
||||
"github.com/alireza0/x-ui/util/random"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
)
|
||||
|
||||
//go:embed default.json
|
||||
@@ -20,14 +20,14 @@ var defaultJson string
|
||||
type SubJsonService struct {
|
||||
configJson map[string]interface{}
|
||||
defaultOutbounds []json_util.RawMessage
|
||||
fragment string
|
||||
fragmentOrNoises bool
|
||||
mux string
|
||||
|
||||
inboundService service.InboundService
|
||||
SubService *SubService
|
||||
}
|
||||
|
||||
func NewSubJsonService(fragment string, mux string, rules string, subService *SubService) *SubJsonService {
|
||||
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
|
||||
var configJson map[string]interface{}
|
||||
var defaultOutbounds []json_util.RawMessage
|
||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||
@@ -38,6 +38,31 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
|
||||
}
|
||||
}
|
||||
|
||||
fragmentOrNoises := false
|
||||
if fragment != "" || noises != "" {
|
||||
fragmentOrNoises = true
|
||||
defaultOutboundsSettings := map[string]interface{}{
|
||||
"domainStrategy": "UseIP",
|
||||
"redirect": "",
|
||||
}
|
||||
|
||||
if fragment != "" {
|
||||
defaultOutboundsSettings["fragment"] = json_util.RawMessage(fragment)
|
||||
}
|
||||
|
||||
if noises != "" {
|
||||
defaultOutboundsSettings["noises"] = json_util.RawMessage(noises)
|
||||
}
|
||||
|
||||
defaultDirectOutbound := map[string]interface{}{
|
||||
"protocol": "freedom",
|
||||
"settings": defaultOutboundsSettings,
|
||||
"tag": "direct_out",
|
||||
}
|
||||
jsonBytes, _ := json.MarshalIndent(defaultDirectOutbound, "", " ")
|
||||
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||
}
|
||||
|
||||
if rules != "" {
|
||||
var newRules []interface{}
|
||||
routing, _ := configJson["routing"].(map[string]interface{})
|
||||
@@ -48,14 +73,10 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
|
||||
configJson["routing"] = routing
|
||||
}
|
||||
|
||||
if fragment != "" {
|
||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
||||
}
|
||||
|
||||
return &SubJsonService{
|
||||
configJson: configJson,
|
||||
defaultOutbounds: defaultOutbounds,
|
||||
fragment: fragment,
|
||||
fragmentOrNoises: fragmentOrNoises,
|
||||
mux: mux,
|
||||
SubService: subService,
|
||||
}
|
||||
@@ -165,12 +186,12 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
case "tls":
|
||||
if newStream["security"] != "tls" {
|
||||
newStream["security"] = "tls"
|
||||
newStream["tslSettings"] = map[string]interface{}{}
|
||||
newStream["tlsSettings"] = map[string]interface{}{}
|
||||
}
|
||||
case "none":
|
||||
if newStream["security"] != "none" {
|
||||
newStream["security"] = "none"
|
||||
delete(newStream, "tslSettings")
|
||||
delete(newStream, "tlsSettings")
|
||||
}
|
||||
}
|
||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
||||
@@ -178,8 +199,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
||||
var newOutbounds []json_util.RawMessage
|
||||
|
||||
switch inbound.Protocol {
|
||||
case "vmess", "vless":
|
||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
||||
case "vmess":
|
||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client, ""))
|
||||
case "vless":
|
||||
var vlessSettings model.VLESSSettings
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||
|
||||
newOutbounds = append(newOutbounds,
|
||||
s.genVnext(inbound, streamSettings, client, vlessSettings.Encryption))
|
||||
case "trojan", "shadowsocks":
|
||||
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
||||
}
|
||||
@@ -210,8 +237,8 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||
}
|
||||
delete(streamSettings, "sockopt")
|
||||
|
||||
if s.fragment != "" {
|
||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
|
||||
if s.fragmentOrNoises {
|
||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "direct_out", "tcpKeepAliveIdle": 100}`)
|
||||
}
|
||||
|
||||
// remove proxy protocol
|
||||
@@ -257,6 +284,7 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
||||
rltyData["show"] = false
|
||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
||||
rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
|
||||
|
||||
// Set random data
|
||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
||||
@@ -276,32 +304,43 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
||||
return rltyData
|
||||
}
|
||||
|
||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, encryption string) json_util.RawMessage {
|
||||
outbound := Outbound{}
|
||||
usersData := make([]UserVnext, 1)
|
||||
|
||||
usersData[0].ID = client.ID
|
||||
usersData[0].Level = 8
|
||||
if inbound.Protocol == model.VLESS {
|
||||
usersData[0].Flow = client.Flow
|
||||
usersData[0].Encryption = "none"
|
||||
}
|
||||
|
||||
vnextData := make([]VnextSetting, 1)
|
||||
vnextData[0] = VnextSetting{
|
||||
Address: inbound.Listen,
|
||||
Port: inbound.Port,
|
||||
Users: usersData,
|
||||
}
|
||||
|
||||
outbound.Protocol = string(inbound.Protocol)
|
||||
outbound.Tag = "proxy"
|
||||
|
||||
if s.mux != "" {
|
||||
outbound.Mux = json_util.RawMessage(s.mux)
|
||||
}
|
||||
|
||||
outbound.StreamSettings = streamSettings
|
||||
outbound.Settings = OutboundSettings{
|
||||
Vnext: vnextData,
|
||||
|
||||
// Build standard Xray/V2Ray schema
|
||||
user := map[string]any{
|
||||
"id": client.ID,
|
||||
"level": 8,
|
||||
}
|
||||
|
||||
if inbound.Protocol == model.VLESS {
|
||||
user["encryption"] = encryption
|
||||
if client.Flow != "" {
|
||||
user["flow"] = client.Flow
|
||||
}
|
||||
} else {
|
||||
// VMess
|
||||
user["alterId"] = 0
|
||||
user["security"] = "auto"
|
||||
}
|
||||
|
||||
vnext := map[string]any{
|
||||
"address": inbound.Listen,
|
||||
"port": inbound.Port,
|
||||
"users": []any{user},
|
||||
}
|
||||
|
||||
outbound.Settings = map[string]any{
|
||||
"vnext": []any{vnext},
|
||||
}
|
||||
|
||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||
@@ -339,8 +378,8 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
||||
outbound.Mux = json_util.RawMessage(s.mux)
|
||||
}
|
||||
outbound.StreamSettings = streamSettings
|
||||
outbound.Settings = OutboundSettings{
|
||||
Servers: serverData,
|
||||
outbound.Settings = map[string]any{
|
||||
"servers": serverData,
|
||||
}
|
||||
|
||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||
@@ -348,30 +387,11 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
Protocol string `json:"protocol"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
||||
Settings OutboundSettings `json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundSettings struct {
|
||||
Vnext []VnextSetting `json:"vnext,omitempty"`
|
||||
Servers []ServerSetting `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
type VnextSetting struct {
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Users []UserVnext `json:"users"`
|
||||
}
|
||||
|
||||
type UserVnext struct {
|
||||
Encryption string `json:"encryption,omitempty"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Level int `json:"level"`
|
||||
Protocol string `json:"protocol"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||
Settings map[string]any `json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
type ServerSetting struct {
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/util/random"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/util/random"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
@@ -200,17 +200,6 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
obj["net"] = "h2"
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
obj["path"], _ = http["path"].(string)
|
||||
obj["host"] = searchHost(http)
|
||||
case "quic":
|
||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||
header := quic["header"].(map[string]interface{})
|
||||
obj["type"], _ = header["type"].(string)
|
||||
obj["host"], _ = quic["security"].(string)
|
||||
obj["path"], _ = quic["key"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
obj["path"], _ = grpc["serviceName"].(string)
|
||||
@@ -227,15 +216,16 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
obj["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
obj["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
obj["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
obj["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -315,6 +305,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
if inbound.Protocol != model.VLESS {
|
||||
return ""
|
||||
}
|
||||
var vlessSettings model.VLESSSettings
|
||||
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||
|
||||
var stream map[string]interface{}
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||
clients, _ := s.inboundService.GetClients(inbound)
|
||||
@@ -329,6 +322,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
port := inbound.Port
|
||||
streamNetwork := stream["network"].(string)
|
||||
params := make(map[string]string)
|
||||
if vlessSettings.Encryption != "" {
|
||||
params["encryption"] = vlessSettings.Encryption
|
||||
}
|
||||
params["type"] = streamNetwork
|
||||
|
||||
switch streamNetwork {
|
||||
@@ -358,16 +354,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
params["host"] = searchHost(http)
|
||||
case "quic":
|
||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||
params["quicSecurity"] = quic["security"].(string)
|
||||
params["key"] = quic["key"].(string)
|
||||
header := quic["header"].(map[string]interface{})
|
||||
params["headerType"] = header["type"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
@@ -384,15 +370,16 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
params["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
@@ -448,6 +435,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -561,16 +553,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
params["host"] = searchHost(http)
|
||||
case "quic":
|
||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||
params["quicSecurity"] = quic["security"].(string)
|
||||
params["key"] = quic["key"].(string)
|
||||
header := quic["header"].(map[string]interface{})
|
||||
params["headerType"] = header["type"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
@@ -587,15 +569,16 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
params["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
security, _ := stream["security"].(string)
|
||||
if security == "tls" {
|
||||
@@ -647,6 +630,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -760,16 +748,6 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
params["host"] = searchHost(http)
|
||||
case "quic":
|
||||
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||
params["quicSecurity"] = quic["security"].(string)
|
||||
params["key"] = quic["key"].(string)
|
||||
header := quic["header"].(map[string]interface{})
|
||||
params["headerType"] = header["type"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
@@ -786,15 +764,16 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "splithttp":
|
||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
||||
params["path"] = splithttp["path"].(string)
|
||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
||||
case "xhttp":
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||
params["path"] = xhttp["path"].(string)
|
||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
||||
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
params["mode"] = xhttp["mode"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"x-ui/logger"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
)
|
||||
|
||||
func NewErrorf(format string, a ...interface{}) error {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package random
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -15,8 +15,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
numSeq[i] = rune('0' + i)
|
||||
}
|
||||
@@ -39,11 +37,20 @@ func init() {
|
||||
func Seq(n int) string {
|
||||
runes := make([]rune, n)
|
||||
for i := 0; i < n; i++ {
|
||||
runes[i] = allSeq[rand.Intn(len(allSeq))]
|
||||
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(allSeq))))
|
||||
if err != nil {
|
||||
panic("crypto/rand failed: " + err.Error())
|
||||
}
|
||||
runes[i] = allSeq[idx.Int64()]
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func Num(n int) int {
|
||||
return rand.Intn(n)
|
||||
bn := big.NewInt(int64(n))
|
||||
r, err := rand.Int(rand.Reader, bn)
|
||||
if err != nil {
|
||||
panic("crypto/rand failed: " + err.Error())
|
||||
}
|
||||
return int(r.Int64())
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
var SIGUSR1 = syscall.SIGUSR1
|
||||
|
||||
func GetTCPCount() (int, error) {
|
||||
stats, err := net.Connections("tcp")
|
||||
if err != nil {
|
||||
|
||||
@@ -8,8 +8,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var SIGUSR1 = syscall.SIGUSR1
|
||||
|
||||
func getLinesNum(filename string) (int, error) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
package sys
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
)
|
||||
|
||||
var SIGUSR1 = syscall.Signal(0)
|
||||
|
||||
func GetTCPCount() (int, error) {
|
||||
stats, err := net.Connections("tcp")
|
||||
if err != nil {
|
||||
|
||||
3
web/assets/axios/axios.min.js
vendored
3
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -34,13 +34,13 @@ function getLang() {
|
||||
lang = window.navigator.language || window.navigator.userLanguage;
|
||||
|
||||
if (isSupportLang(lang)) {
|
||||
setCookie('lang', lang, 150);
|
||||
setCookie('lang', lang);
|
||||
} else {
|
||||
setCookie('lang', 'en-US', 150);
|
||||
setCookie('lang', 'en-US');
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
setCookie('lang', 'en-US', 150);
|
||||
setCookie('lang', 'en-US');
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ function setLang(lang) {
|
||||
lang = 'en-US';
|
||||
}
|
||||
|
||||
setCookie('lang', lang, 150);
|
||||
setCookie('lang', lang);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,7 @@ class AllSetting {
|
||||
this.subURI = "";
|
||||
this.subJsonURI = "";
|
||||
this.subJsonFragment = "";
|
||||
this.subJsonNoises = "";
|
||||
this.subJsonMux = "";
|
||||
this.subJsonRules = "";
|
||||
|
||||
|
||||
@@ -95,10 +95,13 @@ function getCookie(cname) {
|
||||
}
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
let expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
let expires = "";
|
||||
if (exdays > 0) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||
expires = "expires=" + d.toUTCString();
|
||||
}
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=" + axios.defaults.baseURL;
|
||||
}
|
||||
|
||||
function usageColor(data, threshold, total) {
|
||||
|
||||
3
web/assets/moment/moment.min.js
vendored
3
web/assets/moment/moment.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -9,27 +9,36 @@ import (
|
||||
type APIController struct {
|
||||
BaseController
|
||||
inboundController *InboundController
|
||||
serverController *ServerController
|
||||
Tgbot service.Tgbot
|
||||
}
|
||||
|
||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||
a := &APIController{}
|
||||
func NewAPIController(g *gin.RouterGroup, s *ServerController) *APIController {
|
||||
a := &APIController{
|
||||
serverController: s,
|
||||
}
|
||||
a.initRouter(g)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/xui/API/inbounds")
|
||||
g.Use(a.checkLogin)
|
||||
api := g.Group("/xui/API")
|
||||
api.Use(a.checkLogin)
|
||||
|
||||
a.inboundController = NewInboundController(g)
|
||||
a.inboundApi(api)
|
||||
a.serverApi(api)
|
||||
}
|
||||
|
||||
func (a *APIController) inboundApi(api *gin.RouterGroup) {
|
||||
inboundsApi := api.Group("/inbounds")
|
||||
|
||||
a.inboundController = &InboundController{}
|
||||
|
||||
inboundRoutes := []struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler gin.HandlerFunc
|
||||
}{
|
||||
{"GET", "/createbackup", a.createBackup},
|
||||
{"GET", "/", a.inboundController.getInbounds},
|
||||
{"GET", "/get/:id", a.inboundController.getInbound},
|
||||
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
||||
@@ -48,7 +57,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
}
|
||||
|
||||
for _, route := range inboundRoutes {
|
||||
g.Handle(route.Method, route.Path, route.Handler)
|
||||
inboundsApi.Handle(route.Method, route.Path, route.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *APIController) serverApi(api *gin.RouterGroup) {
|
||||
serverApi := api.Group("/server")
|
||||
|
||||
serverRoutes := []struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler gin.HandlerFunc
|
||||
}{
|
||||
{"GET", "/status", a.serverController.status},
|
||||
{"GET", "/getDb", a.serverController.getDb},
|
||||
{"GET", "/createbackup", a.createBackup},
|
||||
{"GET", "/getConfigJson", a.serverController.getConfigJson},
|
||||
{"GET", "/getXrayVersion", a.serverController.getXrayVersion},
|
||||
{"GET", "/getNewVlessEnc", a.serverController.getNewVlessEnc},
|
||||
{"GET", "/getNewX25519Cert", a.serverController.getNewX25519Cert},
|
||||
{"GET", "/getNewmldsa65", a.serverController.getNewmldsa65},
|
||||
|
||||
{"POST", "/getNewEchCert", a.serverController.getNewEchCert},
|
||||
{"POST", "/importDB", a.serverController.importDB},
|
||||
{"POST", "/stopXrayService", a.serverController.stopXrayService},
|
||||
{"POST", "/restartXrayService", a.serverController.restartXrayService},
|
||||
{"POST", "/installXray/:version", a.serverController.installXray},
|
||||
{"POST", "/logs/:count", a.serverController.getLogs},
|
||||
}
|
||||
|
||||
for _, route := range serverRoutes {
|
||||
serverApi.Handle(route.Method, route.Path, route.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ package controller
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/web/session"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/web/locale"
|
||||
"github.com/alireza0/x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"x-ui/database/model"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -99,8 +99,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||
inbound, needRestart, err := a.inboundService.AddInbound(inbound)
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||
if err == nil && needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
@@ -113,8 +112,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
||||
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||
return
|
||||
}
|
||||
needRestart := true
|
||||
needRestart, err = a.inboundService.DelInbound(id)
|
||||
needRestart, err := a.inboundService.DelInbound(id)
|
||||
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||
if err == nil && needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
@@ -135,8 +133,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||
return
|
||||
}
|
||||
needRestart := true
|
||||
inbound, needRestart, err = a.inboundService.UpdateInbound(inbound)
|
||||
inbound, needRestart, err := a.inboundService.UpdateInbound(inbound)
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||
if err == nil && needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
@@ -151,9 +148,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := true
|
||||
|
||||
needRestart, err = a.inboundService.AddInboundClient(data)
|
||||
needRestart, err := a.inboundService.AddInboundClient(data)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
@@ -172,9 +167,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
}
|
||||
clientId := c.Param("clientId")
|
||||
|
||||
needRestart := true
|
||||
|
||||
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
|
||||
needRestart, err := a.inboundService.DelInboundClient(id, clientId)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
@@ -195,9 +188,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
needRestart := true
|
||||
|
||||
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||
needRestart, err := a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
@@ -216,9 +207,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
}
|
||||
email := c.Param("email")
|
||||
|
||||
needRestart := true
|
||||
|
||||
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
|
||||
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
|
||||
if err != nil {
|
||||
jsonMsg(c, "Something went wrong!", err)
|
||||
return
|
||||
@@ -292,8 +281,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
||||
inbound.ClientStats[index].Enable = true
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||
inbound, needRestart, err := a.inboundService.AddInbound(inbound)
|
||||
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||
if err == nil && needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -75,20 +75,12 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 1)
|
||||
}
|
||||
|
||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||
if err != nil {
|
||||
logger.Info("Unable to get session's max age from DB")
|
||||
}
|
||||
|
||||
if sessionMaxAge > 0 {
|
||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||
if err != nil {
|
||||
logger.Info("Unable to set session's max age")
|
||||
}
|
||||
}
|
||||
|
||||
err = session.SetLoginUser(c, user)
|
||||
logger.Infof("%s logged in successfully", user.Username)
|
||||
if err == nil {
|
||||
logger.Infof("%s logged in successfully", user.Username)
|
||||
} else {
|
||||
logger.Error("Unable to set login user")
|
||||
}
|
||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"x-ui/web/global"
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/global"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -39,16 +39,20 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/server")
|
||||
|
||||
g.Use(a.checkLogin)
|
||||
g.POST("/status", a.status)
|
||||
g.POST("/getXrayVersion", a.getXrayVersion)
|
||||
g.GET("/status", a.status)
|
||||
g.GET("/getDb", a.getDb)
|
||||
g.GET("/getConfigJson", a.getConfigJson)
|
||||
g.GET("/getNewmldsa65", a.getNewmldsa65)
|
||||
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
|
||||
g.GET("/getXrayVersion", a.getXrayVersion)
|
||||
g.GET("/getNewX25519Cert", a.getNewX25519Cert)
|
||||
|
||||
g.POST("/getNewEchCert", a.getNewEchCert)
|
||||
g.POST("/stopXrayService", a.stopXrayService)
|
||||
g.POST("/restartXrayService", a.restartXrayService)
|
||||
g.POST("/installXray/:version", a.installXray)
|
||||
g.POST("/logs/:count", a.getLogs)
|
||||
g.POST("/getConfigJson", a.getConfigJson)
|
||||
g.GET("/getDb", a.getDb)
|
||||
g.POST("/importDB", a.importDB)
|
||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
||||
}
|
||||
|
||||
func (a *ServerController) refreshStatus() {
|
||||
@@ -186,3 +190,31 @@ func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
||||
}
|
||||
jsonObj(c, cert, nil)
|
||||
}
|
||||
|
||||
func (a *ServerController) getNewmldsa65(c *gin.Context) {
|
||||
cert, err := a.serverService.GetNewmldsa65()
|
||||
if err != nil {
|
||||
jsonMsg(c, "get mldsa65 certificate", err)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func (a *ServerController) getNewVlessEnc(c *gin.Context) {
|
||||
out, err := a.serverService.GetNewVlessEnc()
|
||||
if err != nil {
|
||||
jsonMsg(c, "Failed to generate vless encryption config", err)
|
||||
return
|
||||
}
|
||||
jsonObj(c, out, nil)
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"x-ui/web/entity"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
"github.com/alireza0/x-ui/web/entity"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/session"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/entity"
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/web/entity"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -61,7 +61,11 @@ func html(c *gin.Context, name string, title string, data gin.H) {
|
||||
data = gin.H{}
|
||||
}
|
||||
data["title"] = title
|
||||
data["host"], _, _ = net.SplitHostPort(c.Request.Host)
|
||||
host := c.Request.Host
|
||||
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||
}
|
||||
data["host"] = host
|
||||
data["request_uri"] = c.Request.RequestURI
|
||||
data["base_path"] = c.GetString("base_path")
|
||||
c.HTML(http.StatusOK, name, getContext(data))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
)
|
||||
|
||||
type Msg struct {
|
||||
@@ -50,6 +50,7 @@ type AllSetting struct {
|
||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
|
||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
|
||||
@@ -25,7 +25,7 @@
|
||||
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
<a-input-number v-model.number="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
@@ -67,7 +67,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> GB
|
||||
<a-input-number v-model.number="clientsBulkModal.totalGB" :min="0"></a-input-number> GB
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||
|
||||
@@ -1,96 +1,114 @@
|
||||
{{define "dnsModal"}}
|
||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
||||
:closable="true" :mask-closable="false"
|
||||
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
||||
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
|
||||
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
||||
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
||||
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||
<a-select
|
||||
v-model="dnsModal.dnsServer.queryStrategy"
|
||||
style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
||||
[[ l ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Skip Fallback' v-if="isAdvanced">
|
||||
<a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true"
|
||||
:mask-closable="false" :ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}'
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
||||
<a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')"></a-button>
|
||||
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
||||
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
||||
<a-button icon="minus" size="small" slot="addonAfter"
|
||||
@click="dnsModal.dnsServer.domains.splice(index,1)"></a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||
<a-select v-model="dnsModal.dnsServer.queryStrategy" style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']"> [[ l ]] </a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Skip Fallback' v-if="isAdvanced">
|
||||
<a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.expectIPs"}}'>
|
||||
<a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.expectIPs.push('')"></a-button>
|
||||
<template v-for="(domain, index) in dnsModal.dnsServer.expectIPs">
|
||||
<a-input v-model.trim="dnsModal.dnsServer.expectIPs[index]">
|
||||
<a-button icon="minus" size="small" slot="addonAfter"
|
||||
@click="dnsModal.dnsServer.expectIPs.splice(index,1)"></a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
const dnsModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
dnsServer: {
|
||||
address: "localhost",
|
||||
domains: [],
|
||||
queryStrategy: 'UseIP',
|
||||
skipFallback: true,
|
||||
},
|
||||
ok() {
|
||||
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
|
||||
dnsModal.dnsServer.domains = domains;
|
||||
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
||||
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if(isEdit) {
|
||||
if (typeof dnsServer == 'object'){
|
||||
this.dnsServer = dnsServer;
|
||||
} else {
|
||||
this.dnsServer = {
|
||||
address: dnsServer?? "",
|
||||
domains: [],
|
||||
queryStrategy: 'UseIP',
|
||||
skipFallback: true,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.dnsServer = {
|
||||
address: "localhost",
|
||||
domains: [],
|
||||
queryStrategy: 'UseIP',
|
||||
skipFallback: true,
|
||||
}
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
},
|
||||
close() {
|
||||
dnsModal.visible = false;
|
||||
},
|
||||
};
|
||||
const dnsModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
dnsServer: {
|
||||
address: "localhost",
|
||||
domains: [],
|
||||
expectIPs: [],
|
||||
queryStrategy: 'UseIP',
|
||||
skipFallback: true,
|
||||
},
|
||||
ok() {
|
||||
domains = dnsModal.dnsServer.domains.filter(d => d.length > 0);
|
||||
expectIPs = dnsModal.dnsServer.expectIPs.filter(ip => ip.length > 0);
|
||||
dnsModal.dnsServer.domains = domains;
|
||||
dnsModal.dnsServer.expectIPs = expectIPs;
|
||||
newDnsServer = (domains.length > 0 || expectIPs.length > 0) ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
||||
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
||||
},
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#dns-modal',
|
||||
data: {
|
||||
dnsModal: dnsModal,
|
||||
},
|
||||
computed: {
|
||||
isAdvanced: {
|
||||
get: function () { return dnsModal.dnsServer.domains.length>0 }
|
||||
show({
|
||||
title = '',
|
||||
okText = '{{ i18n "confirm" }}',
|
||||
dnsServer,
|
||||
confirm = (dnsServer) => { },
|
||||
isEdit = false
|
||||
}) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if (isEdit) {
|
||||
if (typeof dnsServer == 'object') {
|
||||
this.dnsServer = dnsServer;
|
||||
} else {
|
||||
this.dnsServer = {
|
||||
address: dnsServer ?? "",
|
||||
domains: [],
|
||||
expectIPs: [],
|
||||
queryStrategy: 'UseIP',
|
||||
skipFallback: true,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
this.dnsServer = {
|
||||
address: "localhost",
|
||||
domains: [],
|
||||
expectIPs: [],
|
||||
queryStrategy: 'UseIP',
|
||||
skipFallback: true,
|
||||
}
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
},
|
||||
close() {
|
||||
dnsModal.visible = false;
|
||||
},
|
||||
};
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#dns-modal',
|
||||
data: {
|
||||
dnsModal: dnsModal,
|
||||
},
|
||||
computed: {
|
||||
isAdvanced: {
|
||||
get: function () {
|
||||
return dnsModal.dnsServer.domains.length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -7,7 +7,7 @@
|
||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
||||
<a-input type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input>
|
||||
<a-input-number v-model.number="fakednsModal.fakeDns.poolSize" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> GB
|
||||
<a-input-number v-model.number="client._totalGB" :min="0"></a-input-number> GB
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.port" :min="1" :max="65535"></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
@@ -41,7 +41,7 @@
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> GB
|
||||
<a-input-number v-model.number="dbInbound.totalGB" :min="0"></a-input-number> GB
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
@@ -100,6 +100,11 @@
|
||||
{{template "form/wireguard"}}
|
||||
</template>
|
||||
|
||||
<!-- tun -->
|
||||
<template v-if="inbound.protocol === Protocols.TUN">
|
||||
{{template "form/tun"}}
|
||||
</template>
|
||||
|
||||
<!-- stream settings -->
|
||||
<template v-if="inbound.canEnableStream()">
|
||||
{{template "form/streamSettings"}}
|
||||
@@ -112,7 +117,9 @@
|
||||
</template>
|
||||
|
||||
<!-- sniffing -->
|
||||
<template>
|
||||
{{template "form/sniffing"}}
|
||||
</template>
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='Sniffing'>
|
||||
{{template "form/sniffing"}}
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
{{end}}
|
||||
|
||||
@@ -17,56 +17,92 @@
|
||||
|
||||
<!-- freedom settings-->
|
||||
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||
<a-form-item label='Strategy'>
|
||||
<a-select
|
||||
v-model="outbound.settings.domainStrategy"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Fragment'>
|
||||
<a-switch
|
||||
:checked="Object.keys(outbound.settings.fragment).length >0"
|
||||
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Strategy'>
|
||||
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Redirect'>
|
||||
<a-input v-model="outbound.settings.redirect"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Fragment'>
|
||||
<a-switch :checked="Object.keys(outbound.settings.fragment).length >0"
|
||||
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
||||
<a-form-item label='Packets'>
|
||||
<a-select
|
||||
v-model="outbound.settings.fragment.packets"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Length'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Interval'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Packets'>
|
||||
<a-select v-model="outbound.settings.fragment.packets" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Length'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Interval'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Switch for Noises -->
|
||||
<a-form-item label='Noises'>
|
||||
<a-switch :checked="outbound.settings.noises.length > 0"
|
||||
@change="checked => outbound.settings.noises = checked ? [new Outbound.FreedomSettings.Noise()] : []">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Add Noise Button -->
|
||||
<template v-if="outbound.settings.noises.length > 0">
|
||||
<a-form-item label="Noises">
|
||||
<a-button icon="plus" type="primary" size="small" @click="outbound.settings.addNoise()"></a-button>
|
||||
</a-form-item>
|
||||
|
||||
<!-- Noise Configurations -->
|
||||
<a-form v-for="(noise, index) in outbound.settings.noises" :key="index" :colon="false" :label-col="{ md: {span:8} }"
|
||||
:wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;"> Noise [[ index + 1 ]]
|
||||
<a-icon v-if="outbound.settings.noises.length > 1" type="delete" @click="() => outbound.settings.delNoise(index)"
|
||||
style="color: rgb(255, 77, 79); cursor: pointer;"></a-icon>
|
||||
</a-divider>
|
||||
<a-form-item label='Type'>
|
||||
<a-select v-model="noise.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['rand','base64','str','hex']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Packet'>
|
||||
<a-input v-model.trim="noise.packet"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Delay'>
|
||||
<a-input v-model.trim="noise.delay"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- blackhole settings -->
|
||||
<template v-if="outbound.protocol === Protocols.Blackhole">
|
||||
<a-form-item label='Response Type'>
|
||||
<a-select
|
||||
v-model="outbound.settings.type"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Response Type'>
|
||||
<a-select v-model="outbound.settings.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- dns settings -->
|
||||
<template v-if="outbound.protocol === Protocols.DNS">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select
|
||||
v-model="outbound.settings.network"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select v-model="outbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='non-IP queries'>
|
||||
<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>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types'>
|
||||
<a-input v-model.number="outbound.settings.blockTypes"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- wireguard settings -->
|
||||
@@ -110,8 +146,8 @@
|
||||
<a-form-item label='Workers'>
|
||||
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Mode'>
|
||||
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
|
||||
<a-form-item label='No Kernel Tun'>
|
||||
<a-switch v-model="outbound.settings.noKernelTun"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
@@ -169,12 +205,27 @@
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Vnext (vless/vmess) settings -->
|
||||
<!-- vless/vmess user settings -->
|
||||
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||
<a-form-item label='ID'>
|
||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||
<a-form-item label='ID'>
|
||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<!-- vmess settings -->
|
||||
<template v-if="outbound.protocol === Protocols.VMess">
|
||||
<a-form-item label='Security'>
|
||||
<a-select v-model="outbound.settings.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in USERS_SECURITY" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- vless settings -->
|
||||
<template v-if="outbound.protocol === Protocols.VLESS">
|
||||
<a-form-item label='encryption'>
|
||||
<a-input v-model.trim="outbound.settings.encryption"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-if="outbound.canEnableTlsFlow()">
|
||||
<a-form-item label='Flow'>
|
||||
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
@@ -183,6 +234,28 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- XTLS Vision Advanced Settings -->
|
||||
<template v-if="outbound.canEnableVisionSeed()">
|
||||
<a-form-item label="Vision Pre-Connect">
|
||||
<a-input-number v-model.number="outbound.settings.testpre" :min="0" :max="10" :style="{ width: '100%' }" placeholder="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Vision Seed">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="6">
|
||||
<a-input-number v-model.number="outbound.settings.testseed[0]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input-number v-model.number="outbound.settings.testseed[1]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input-number v-model.number="outbound.settings.testseed[2]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input-number v-model.number="outbound.settings.testseed[3]" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||
@@ -212,7 +285,14 @@
|
||||
<a-form-item label='UDP over TCP'>
|
||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
<!-- hysteria settings -->
|
||||
<template v-if="outbound.protocol === Protocols.Hysteria">
|
||||
<a-form-item label='Version'>
|
||||
<a-input-number v-model.number="outbound.settings.version" :min="2"
|
||||
:max="2" disabled></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- stream settings -->
|
||||
@@ -220,14 +300,17 @@
|
||||
<a-form-item label='{{ i18n "transmission" }}'>
|
||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP/2</a-select-option>
|
||||
<a-select-option value="quic">QUIC</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||
<a-select-option value="splithttp">SplitHTTP</a-select-option>
|
||||
<template v-if="outbound.protocol != Protocols.Hysteria">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-select-option value="hysteria">Hysteria2</a-select-option>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.network === 'tcp'">
|
||||
@@ -248,20 +331,6 @@
|
||||
|
||||
<!-- kcp -->
|
||||
<template v-if="outbound.stream.network === 'kcp'">
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
<a-select-option value="dns">DNS</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model="outbound.stream.kcp.seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
@@ -294,41 +363,7 @@
|
||||
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
<template v-if="outbound.stream.network === 'http'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- quic -->
|
||||
<template v-if="outbound.stream.network === 'quic'">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- grpc -->
|
||||
<template v-if="outbound.stream.network === 'grpc'">
|
||||
<a-form-item label='Service Name'>
|
||||
@@ -351,16 +386,169 @@
|
||||
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- splithttp -->
|
||||
<template v-if="outbound.stream.network === 'splithttp'">
|
||||
|
||||
<!-- xhttp -->
|
||||
<template v-if="outbound.stream.network === 'xhttp'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model="outbound.stream.splithttp.host"></a-input>
|
||||
<a-input v-model="outbound.stream.xhttp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.splithttp.path"></a-input>
|
||||
<a-input v-model.trim="outbound.stream.xhttp.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label='Mode'>
|
||||
<a-select v-model="outbound.stream.xhttp.mode" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="No gRPC Header"
|
||||
v-if="outbound.stream.xhttp.mode === 'stream-up' || outbound.stream.xhttp.mode === 'stream-one'">
|
||||
<a-switch v-model="outbound.stream.xhttp.noGRPCHeader"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Min Upload Interval (Ms)" v-if="outbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-input v-model.trim="outbound.stream.xhttp.scMinPostsIntervalMs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Concurrency" v-if="!outbound.stream.xhttp.xmux.maxConnections">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.maxConcurrency"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Connections" v-if="!outbound.stream.xhttp.xmux.maxConcurrency">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.maxConnections"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Reuse Times">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.cMaxReuseTimes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Request Times">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.hMaxRequestTimes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Reusable Secs">
|
||||
<a-input v-model="outbound.stream.xhttp.xmux.hMaxReusableSecs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Keep Alive Period'>
|
||||
<a-input-number v-model.number="outbound.stream.xhttp.xmux.hKeepAlivePeriod"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- hysteria -->
|
||||
<template v-if="outbound.stream.network === 'hysteria'">
|
||||
<a-form-item label='Auth Password'>
|
||||
<a-input v-model.trim="outbound.stream.hysteria.auth"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Congestion'>
|
||||
<a-select v-model="outbound.stream.hysteria.congestion"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>BBR (Auto)</a-select-option>
|
||||
<a-select-option value="brutal">Brutal</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Upload Speed'>
|
||||
<a-input v-model.trim="outbound.stream.hysteria.up"
|
||||
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Download Speed'>
|
||||
<a-input v-model.trim="outbound.stream.hysteria.down"
|
||||
placeholder="0 (BBR mode), e.g., 100 mbps"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='UDP Hop Port'>
|
||||
<a-input v-model.trim="outbound.stream.hysteria.udphopPort"
|
||||
placeholder="e.g., 1145-1919 or 11,13,15-17"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Hop Interval Min (s)'
|
||||
v-if="outbound.stream.hysteria.udphopPort">
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.udphopIntervalMin"
|
||||
:min="5"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Hop Interval Max (s)'
|
||||
v-if="outbound.stream.hysteria.udphopPort">
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.udphopIntervalMax"
|
||||
:min="5"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Init Stream Receive'>
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.initStreamReceiveWindow"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Max Stream Receive'>
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.maxStreamReceiveWindow"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Init Connection Receive'>
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.initConnectionReceiveWindow"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Max Connection Receive'>
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.maxConnectionReceiveWindow"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Max Idle Timeout (s)'>
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.maxIdleTimeout" :min="4"
|
||||
:max="120"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Keep Alive Period (s)'>
|
||||
<a-input-number
|
||||
v-model.number="outbound.stream.hysteria.keepAlivePeriod" :min="0"
|
||||
:max="60"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Disable Path MTU'>
|
||||
<a-switch
|
||||
v-model="outbound.stream.hysteria.disablePathMTUDiscovery"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- finalmask settings -->
|
||||
<template v-if="outbound.canEnableStream()">
|
||||
<a-form-item label="UDP Masks">
|
||||
<a-button icon="plus" type="primary" size="small"
|
||||
@click="outbound.stream.addUdpMask(outbound.protocol === Protocols.Hysteria ? 'salamander' : (outbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns'))"></a-button>
|
||||
</a-form-item>
|
||||
<template
|
||||
v-if="outbound.stream.finalmask.udp && outbound.stream.finalmask.udp.length > 0">
|
||||
<a-form v-for="(mask, index) in outbound.stream.finalmask.udp"
|
||||
:key="index" :colon="false"
|
||||
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
|
||||
<a-icon type="delete"
|
||||
@click="() => outbound.stream.delUdpMask(index)"
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||
</a-divider>
|
||||
<a-form-item label='Type'>
|
||||
<a-select v-model="mask.type"
|
||||
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option
|
||||
v-if="outbound.protocol === Protocols.Hysteria"
|
||||
value="salamander">
|
||||
Salamander (Hysteria2)
|
||||
</a-select-option>
|
||||
<template v-if="outbound.stream.network === 'kcp'">
|
||||
<a-select-option value="mkcp-aes128gcm">mKCP AES-128-GCM</a-select-option>
|
||||
<a-select-option value="header-dns">Header DNS</a-select-option>
|
||||
<a-select-option value="header-dtls">Header DTLS 1.2</a-select-option>
|
||||
<a-select-option value="header-srtp">Header SRTP</a-select-option>
|
||||
<a-select-option value="header-utp">Header uTP</a-select-option>
|
||||
<a-select-option value="header-wechat">Header WeChat Video</a-select-option>
|
||||
<a-select-option value="header-wireguard">Header WireGuard</a-select-option>
|
||||
<a-select-option value="mkcp-original">mKCP Original</a-select-option>
|
||||
</template>
|
||||
<a-select-option
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(outbound.stream.network)"
|
||||
value="xdns">
|
||||
xDNS (Experimental)
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Password'
|
||||
v-if="['salamander', 'mkcp-aes128gcm'].includes(mask.type)">
|
||||
<a-input v-model.trim="mask.settings.password"
|
||||
placeholder="Obfuscation password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Domain'
|
||||
v-if="['header-dns', 'xdns'].includes(mask.type)">
|
||||
<a-input v-model.trim="mask.settings.domain"
|
||||
placeholder="e.g., www.example.com"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- tls settings -->
|
||||
@@ -392,9 +580,20 @@
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="ECH Config List">
|
||||
<a-input v-model.trim="outbound.stream.tls.echConfigList"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow Insecure">
|
||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Verify Peer Cert By Name">
|
||||
<a-input v-model.trim="outbound.stream.tls.verifyPeerCertByName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Pinned Peer Cert">
|
||||
<a-input v-model.trim="outbound.stream.tls.pinnedPeerCertSha256"
|
||||
placeholder="Enter SHA256 fingerprints (base64)">
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
@@ -417,6 +616,9 @@
|
||||
<a-form-item label="Public Key">
|
||||
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="mldsa65 Verify">
|
||||
<a-input v-model.trim="outbound.stream.reality.mldsa65Verify"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -430,14 +632,31 @@
|
||||
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Address Port Strategy'>
|
||||
<a-select v-model="outbound.stream.sockopt.addressPortStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in Address_Port_Strategy" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Keep Alive Interval">
|
||||
<a-input-number v-model.number="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Fast Open">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Keep Alive Interval">
|
||||
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
<a-form-item label="Multipath TCP">
|
||||
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP No-Delay">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
|
||||
<a-form-item label="Penetrate">
|
||||
<a-switch v-model="outbound.stream.sockopt.penetrate"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Trusted X-Forwarded-For">
|
||||
<a-select mode="tags" v-model="outbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
|
||||
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
|
||||
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
|
||||
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
@@ -448,10 +667,10 @@
|
||||
</a-form-item>
|
||||
<template v-if="outbound.mux.enabled">
|
||||
<a-form-item label="Concurrency">
|
||||
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
|
||||
<a-input-number v-model.number="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="xudp Concurrency">
|
||||
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
|
||||
<a-input-number v-model.number="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="xudp UDP 443">
|
||||
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
@@ -465,7 +684,7 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||
<a-form-item style="margin: 10px 0">
|
||||
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss://"></a-input>
|
||||
Link: <a-input v-model.trim="outModal.link" style="width: 300px; margin-right: 5px;" placeholder="vmess:// vless:// trojan:// ss:// hysteria2://"></a-input>
|
||||
<a-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
||||
</a-form-item>
|
||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||
|
||||
@@ -6,6 +6,19 @@
|
||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||
</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-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||
@@ -16,8 +29,9 @@
|
||||
<a-form-item label='Follow Redirect'>
|
||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Timeout'>
|
||||
<a-input-number v-model.number="inbound.settings.timeout" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- sockopt -->
|
||||
<template>
|
||||
{{template "form/streamSockopt"}}
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
{{define "form/http"}}
|
||||
<a-form>
|
||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||
<tr>
|
||||
<td width="45%">{{ i18n "username" }}</td>
|
||||
<td width="45%">{{ i18n "password" }}</td>
|
||||
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||
<tr>
|
||||
<td width="45%">{{ i18n "username" }}</td>
|
||||
<td width="45%">{{ i18n "password" }}</td>
|
||||
<td><a-button size="small"
|
||||
@click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
<a-form-item label="Allow Transparent">
|
||||
<a-switch v-model="inbound.settings.allowTransparent" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -45,6 +45,9 @@
|
||||
<a-select-option value="udp">UDP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='ivCheck'>
|
||||
<a-switch v-model="inbound.settings.ivCheck"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
{{end}}
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='xVer'>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:5px 0;"></a-divider>
|
||||
|
||||
44
web/html/xui/form/protocol/tun.html
Normal file
44
web/html/xui/form/protocol/tun.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{{define "form/tun"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||
:wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.tun.nameDesc" }}</span>
|
||||
</template>
|
||||
Interface Name
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.settings.name"
|
||||
placeholder="xray0"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.tun.mtuDesc" }}</span>
|
||||
</template>
|
||||
MTU
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model.number="inbound.settings.mtu" :min="1"
|
||||
:max="9000" placeholder="1500"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.tun.userLevelDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.xray.tun.userLevel" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model.number="inbound.settings.userLevel" :min="0"
|
||||
placeholder="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -20,7 +20,29 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<template v-if="inbound.isTcp && !inbound.stream.isReality">
|
||||
<template v-if="!inbound.stream.isTLS || !inbound.stream.isReality">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Authentication">
|
||||
<a-select v-model="inbound.settings.selectedAuth" @change="getNewVlessEnc" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="X25519, not Post-Quantum">X25519 (not Post-Quantum)</a-select-option>
|
||||
<a-select-option value="ML-KEM-768, Post-Quantum">ML-KEM-768 (Post-Quantum)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="decryption">
|
||||
<a-input v-model.trim="inbound.settings.decryption"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="encryption">
|
||||
<a-input v-model="inbound.settings.encryption"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-space>
|
||||
<a-button type="primary" icon="import" @click="getNewVlessEnc">Get New keys</a-button>
|
||||
<a-button danger @click="clearVlessEnc">Clear</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
<template v-if="inbound.isTcp && !inbound.settings.selectedAuth">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Fallbacks">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
||||
@@ -48,9 +70,38 @@
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='xVer'>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
<a-input-number v-model.number="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:5px 0;"></a-divider>
|
||||
</template>
|
||||
<template v-if="inbound.canEnableVisionSeed()">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Vision Seed">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="6">
|
||||
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[0] !== undefined) ? inbound.settings.testseed[0] : 900" @change="(val) => updateTestseed(0, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[0]"></a-input-number>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[1] !== undefined) ? inbound.settings.testseed[1] : 500" @change="(val) => updateTestseed(1, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="500" addon-before="[1]"></a-input-number>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[2] !== undefined) ? inbound.settings.testseed[2] : 900" @change="(val) => updateTestseed(2, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="900" addon-before="[2]"></a-input-number>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-input-number :value="(inbound.settings.testseed && inbound.settings.testseed[3] !== undefined) ? inbound.settings.testseed[3] : 256" @change="(val) => updateTestseed(3, val)" :min="0" :max="9999" :style="{ width: '100%' }" placeholder="256" addon-before="[3]"></a-input-number>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-space :size="8" :style="{ marginTop: '8px' }">
|
||||
<a-button type="primary" @click="setRandomTestseed">
|
||||
Rand
|
||||
</a-button>
|
||||
<a-button @click="resetTestseed">
|
||||
Reset
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider :style="{ margin: '5px 0' }"></a-divider>
|
||||
</template>
|
||||
{{end}}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Mode'>
|
||||
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
|
||||
<a-form-item label='No Kernel Tun'>
|
||||
<a-switch v-model="inbound.settings.noKernelTun"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Peers">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
||||
|
||||
61
web/html/xui/form/reality_settings.html
Normal file
61
web/html/xui/form/reality_settings.html
Normal file
@@ -0,0 +1,61 @@
|
||||
{{define "form/realitySettings"}}
|
||||
<template>
|
||||
<a-form-item label='Show'>
|
||||
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Xver'>
|
||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='uTLS'>
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Dest'>
|
||||
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SNI'>
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
Short IDs
|
||||
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
|
||||
</a-icon>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SpiderX'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Private Key'>
|
||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Public Key'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-space>
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
||||
<a-button danger @click="clearX25519Cert">Clear</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="mldsa65 Seed">
|
||||
<a-input v-model="inbound.stream.reality.mldsa65Seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="mldsa65 Verify">
|
||||
<a-textarea v-model="inbound.stream.reality.settings.mldsa65Verify"></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-space>
|
||||
<a-button type="primary" icon="import" @click="getNewmldsa65">Get New Seed</a-button>
|
||||
<a-button danger @click="clearMldsa65">Clear</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</template>
|
||||
{{end}}
|
||||
@@ -1,9 +1,8 @@
|
||||
{{define "form/sniffing"}}
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
Sniffing
|
||||
{{ i18n "enabled" }}
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
</template>
|
||||
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65535"></a-input-number>
|
||||
</a-tooltip>
|
||||
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||
</a-input-group>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
69
web/html/xui/form/stream/stream_finalmask.html
Normal file
69
web/html/xui/form/stream/stream_finalmask.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{{define "form/streamFinalMask"}}
|
||||
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||
:wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="UDP Masks">
|
||||
<a-button icon="plus" type="primary" size="small"
|
||||
@click="inbound.stream.addUdpMask(inbound.stream.network === 'kcp' ? 'mkcp-aes128gcm' : 'xdns')"></a-button>
|
||||
</a-form-item>
|
||||
<template
|
||||
v-if="inbound.stream.finalmask.udp && inbound.stream.finalmask.udp.length > 0">
|
||||
<a-form v-for="(mask, index) in inbound.stream.finalmask.udp"
|
||||
:key="index" :colon="false"
|
||||
:label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider :style="{ margin: '0' }"> UDP Mask [[ index + 1 ]]
|
||||
<a-icon type="delete"
|
||||
@click="() => inbound.stream.delUdpMask(index)"
|
||||
:style="{ color: 'rgb(255, 77, 79)', cursor: 'pointer' }"></a-icon>
|
||||
</a-divider>
|
||||
<a-form-item label='Type'>
|
||||
<a-select v-model="mask.type"
|
||||
@change="(type) => mask.settings = mask._getDefaultSettings(type, {})"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="mkcp-aes128gcm">
|
||||
mKCP AES-128-GCM</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="header-dns">
|
||||
Header DNS</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="header-dtls">
|
||||
Header DTLS 1.2</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="header-srtp">
|
||||
Header SRTP</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="header-utp">
|
||||
Header uTP</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="header-wechat">
|
||||
Header WeChat Video</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="header-wireguard">
|
||||
Header WireGuard</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="mkcp-original">
|
||||
mKCP Original</a-select-option>
|
||||
<a-select-option
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp'].includes(inbound.stream.network)"
|
||||
value="xdns">
|
||||
xDNS (Experimental)</a-select-option>
|
||||
<a-select-option v-if="inbound.stream.network === 'kcp'"
|
||||
value="xicmp">
|
||||
xICMP (Experimental)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Password'
|
||||
v-if="['mkcp-aes128gcm'].includes(mask.type)">
|
||||
<a-input v-model.trim="mask.settings.password"
|
||||
placeholder="Obfuscation password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Domain'
|
||||
v-if="['header-dns', 'xdns'].includes(mask.type)">
|
||||
<a-input v-model.trim="mask.settings.domain"
|
||||
placeholder="e.g., www.example.com"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,19 +0,0 @@
|
||||
{{define "form/streamHTTP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">{{ i18n "host" }}
|
||||
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
|
||||
</template>
|
||||
<template v-for="(host, index) in inbound.stream.http.host">
|
||||
<a-input v-model.trim="inbound.stream.http.host[index]">
|
||||
<a-button size="small" slot="addonAfter"
|
||||
@click="inbound.stream.http.removeHost(index)"
|
||||
v-if="inbound.stream.http.host.length>1">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,48 +1,32 @@
|
||||
{{define "form/streamKCP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
<a-select-option value="dns">DNS</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "password" }}
|
||||
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||
:wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576"
|
||||
:max="1460"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='TTI (ms)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10"
|
||||
:max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Uplink (MB/s)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"
|
||||
:min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Downlink (MB/s)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"
|
||||
:min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Congestion'>
|
||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Read Buffer (MB)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"
|
||||
:min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Write Buffer (MB)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"
|
||||
:min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
{{define "form/streamQUIC"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "password" }}
|
||||
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,17 +1,15 @@
|
||||
{{define "form/streamSettings"}}
|
||||
<!-- select stream network -->
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="{{ i18n "transmission" }}">
|
||||
<a-form-item label='{{ i18n "transmission" }}'>
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP/2</a-select-option>
|
||||
<a-select-option value="quic">QUIC</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HttpUpgrade</a-select-option>
|
||||
<a-select-option value="splithttp">SplitHTTP</a-select-option>
|
||||
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -31,16 +29,6 @@
|
||||
{{template "form/streamWS"}}
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
<template v-if="inbound.stream.network === 'http'">
|
||||
{{template "form/streamHTTP"}}
|
||||
</template>
|
||||
|
||||
<!-- quic -->
|
||||
<template v-if="inbound.stream.network === 'quic'">
|
||||
{{template "form/streamQUIC"}}
|
||||
</template>
|
||||
|
||||
<!-- grpc -->
|
||||
<template v-if="inbound.stream.network === 'grpc'">
|
||||
{{template "form/streamGRPC"}}
|
||||
@@ -51,9 +39,15 @@
|
||||
{{template "form/streamHTTPUPGRADE"}}
|
||||
</template>
|
||||
|
||||
<!-- splithttp -->
|
||||
<template v-if="inbound.stream.network === 'splithttp'">
|
||||
{{template "form/streamSplitHTTP"}}
|
||||
<!-- xhttp -->
|
||||
<template v-if="inbound.stream.network === 'xhttp'">
|
||||
{{template "form/streamXHTTP"}}
|
||||
</template>
|
||||
|
||||
<!-- finalmask - only for TCP, WS, HTTPUpgrade, XHTTP, mKCP -->
|
||||
<template
|
||||
v-if="['tcp', 'ws', 'httpupgrade', 'xhttp', 'kcp'].includes(inbound.stream.network)">
|
||||
{{template "form/streamFinalMask"}}
|
||||
</template>
|
||||
|
||||
<!-- sockopt -->
|
||||
|
||||
@@ -1,24 +1,73 @@
|
||||
{{define "form/streamSockopt"}}
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
<a-divider :style="{ margin: '5px 0 0' }"></a-divider>
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="TPROXY">
|
||||
<a-form-item label="Sockopt">
|
||||
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="inbound.stream.sockoptSwitch">
|
||||
<a-form-item label="PROXY Protocol">
|
||||
<a-form-item label="Route Mark">
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Keep Alive Interval">
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Keep Alive Idle">
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpKeepAliveIdle" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Max Seg">
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpMaxSeg" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP User Timeout">
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpUserTimeout" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Window Clamp">
|
||||
<a-input-number v-model.number="inbound.stream.sockopt.tcpWindowClamp" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Proxy Protocol">
|
||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Fast Open">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Route Mark">
|
||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
<a-form-item label="Multipath TCP">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="TPROXY">
|
||||
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-form-item label="Penetrate">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.penetrate"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="V6 Only">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.V6Only"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Domain Strategy'>
|
||||
<a-select v-model="inbound.stream.sockopt.domainStrategy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in DOMAIN_STRATEGY_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='TCP Congestion'>
|
||||
<a-select v-model="inbound.stream.sockopt.tcpcongestion" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TCP_CONGESTION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="TProxy">
|
||||
<a-select v-model="inbound.stream.sockopt.tproxy" :style="{ width: '50%' }" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="off">Off</a-select-option>
|
||||
<a-select-option value="redirect">Redirect</a-select-option>
|
||||
<a-select-option value="tproxy">TPROXY</a-select-option>
|
||||
<a-select-option value="tproxy">TProxy</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Dialer Proxy">
|
||||
<a-input v-model="inbound.stream.sockopt.dialerProxy"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Interface Name">
|
||||
<a-input v-model="inbound.stream.sockopt.interfaceName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Trusted X-Forwarded-For">
|
||||
<a-select mode="tags" v-model="inbound.stream.sockopt.trustedXForwardedFor" :style="{ width: '100%' }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="CF-Connecting-IP">CF-Connecting-IP</a-select-option>
|
||||
<a-select-option value="X-Real-IP">X-Real-IP</a-select-option>
|
||||
<a-select-option value="True-Client-IP">True-Client-IP</a-select-option>
|
||||
<a-select-option value="X-Client-IP">X-Client-IP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{{define "form/streamSplitHTTP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="inbound.stream.splithttp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.splithttp.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.splithttp.addHeader('host', '')"></a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.splithttp.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="inbound.stream.splithttp.removeHeader(index)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Upload Size (MB)">
|
||||
<a-input-number v-model="inbound.stream.splithttp.maxUploadSize" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Concurrent Upload">
|
||||
<a-input-number v-model="inbound.stream.splithttp.maxConcurrentUploads" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
135
web/html/xui/form/stream/stream_xhttp.html
Normal file
135
web/html/xui/form/stream/stream_xhttp.html
Normal file
@@ -0,0 +1,135 @@
|
||||
{{define "form/streamXHTTP"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="inbound.stream.xhttp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.xhttp.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button icon="plus" size="small" @click="inbound.stream.xhttp.addHeader('', '')"></a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.xhttp.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="inbound.stream.xhttp.removeHeader(index)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='Mode'>
|
||||
<a-select v-model="inbound.stream.xhttp.mode" style="width: 50%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in MODE_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Buffered Upload" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-input-number v-model.number="inbound.stream.xhttp.scMaxBufferedPosts"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="Max Upload Size (Byte)" v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.scMaxEachPostBytes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Stream-Up Server" v-if="inbound.stream.xhttp.mode === 'stream-up'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.scStreamUpServerSecs"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Bytes">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.xPaddingBytes"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Obfs Mode">
|
||||
<a-switch v-model="inbound.stream.xhttp.xPaddingObfsMode"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="inbound.stream.xhttp.xPaddingObfsMode">
|
||||
<a-form-item label="Padding Key">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.xPaddingKey"
|
||||
placeholder="x_padding"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Header">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.xPaddingHeader"
|
||||
placeholder="X-Padding"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Placement">
|
||||
<a-select v-model="inbound.stream.xhttp.xPaddingPlacement"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>Default (queryInHeader)</a-select-option>
|
||||
<a-select-option
|
||||
value="queryInHeader">queryInHeader</a-select-option>
|
||||
<a-select-option value="header">header</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Padding Method">
|
||||
<a-select v-model="inbound.stream.xhttp.xPaddingMethod"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>Default (repeat-x)</a-select-option>
|
||||
<a-select-option value="repeat-x">repeat-x</a-select-option>
|
||||
<a-select-option value="tokenish">tokenish</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label="Uplink HTTP Method">
|
||||
<a-select v-model="inbound.stream.xhttp.uplinkHTTPMethod"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>Default (POST)</a-select-option>
|
||||
<a-select-option value="POST">POST</a-select-option>
|
||||
<a-select-option value="PUT">PUT</a-select-option>
|
||||
<a-select-option value="GET">GET (packet-up only)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Session Placement">
|
||||
<a-select v-model="inbound.stream.xhttp.sessionPlacement"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>Default (path)</a-select-option>
|
||||
<a-select-option value="path">path</a-select-option>
|
||||
<a-select-option value="header">header</a-select-option>
|
||||
<a-select-option value="cookie">cookie</a-select-option>
|
||||
<a-select-option value="query">query</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Session Key"
|
||||
v-if="inbound.stream.xhttp.sessionPlacement && inbound.stream.xhttp.sessionPlacement !== 'path'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.sessionKey"
|
||||
placeholder="x_session"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Sequence Placement">
|
||||
<a-select v-model="inbound.stream.xhttp.seqPlacement"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>Default (path)</a-select-option>
|
||||
<a-select-option value="path">path</a-select-option>
|
||||
<a-select-option value="header">header</a-select-option>
|
||||
<a-select-option value="cookie">cookie</a-select-option>
|
||||
<a-select-option value="query">query</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Sequence Key"
|
||||
v-if="inbound.stream.xhttp.seqPlacement && inbound.stream.xhttp.seqPlacement !== 'path'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.seqKey"
|
||||
placeholder="x_seq"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Uplink Data Placement"
|
||||
v-if="inbound.stream.xhttp.mode === 'packet-up'">
|
||||
<a-select v-model="inbound.stream.xhttp.uplinkDataPlacement"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value>Default (body)</a-select-option>
|
||||
<a-select-option value="body">body</a-select-option>
|
||||
<a-select-option value="header">header</a-select-option>
|
||||
<a-select-option value="query">query</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Uplink Data Key"
|
||||
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
|
||||
<a-input v-model.trim="inbound.stream.xhttp.uplinkDataKey"
|
||||
placeholder="x_data"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Uplink Chunk Size"
|
||||
v-if="inbound.stream.xhttp.mode === 'packet-up' && inbound.stream.xhttp.uplinkDataPlacement && inbound.stream.xhttp.uplinkDataPlacement !== 'body'">
|
||||
<a-input-number v-model.number="inbound.stream.xhttp.uplinkChunkSize"
|
||||
:min="0" placeholder="0 (unlimited)"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="No SSE Header">
|
||||
<a-switch v-model="inbound.stream.xhttp.noSSEHeader"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -10,126 +10,113 @@
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- tls settings -->
|
||||
<template v-if="inbound.stream.isTls">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Cipher Suites">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="">Auto</a-select-option>
|
||||
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Min/Max Version">
|
||||
<a-input-group compact>
|
||||
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
<!-- tls settings -->
|
||||
<template v-if="inbound.stream.isTls">
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Cipher Suites">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="">Auto</a-select-option>
|
||||
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
</a-form-item>
|
||||
<a-form-item label="Min/Max Version">
|
||||
<a-input-group compact>
|
||||
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="ALPN">
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="inbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow Insecure">
|
||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Reject Unknown SNI">
|
||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<template v-if="cert.useFile">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||
<a-form-item label="ALPN">
|
||||
<a-select mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="inbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow Insecure">
|
||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Reject Unknown SNI">
|
||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Disable System Root">
|
||||
<a-switch v-model="inbound.stream.tls.disableSystemRoot"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Session Resumption">
|
||||
<a-switch v-model="inbound.stream.tls.enableSessionResumption"></a-switch>
|
||||
</a-form-item>
|
||||
<a-divider :style="{ margin: '0' }"></a-divider>
|
||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()"
|
||||
style="margin-left: 10px">+</a-button>
|
||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small"
|
||||
@click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||
</a-form-item>
|
||||
<template v-if="cert.useFile">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n
|
||||
"pages.inbounds.setDefaultCert" }}</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label='OCSP stapling'>
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</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="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||
<a-space>
|
||||
<a-button type="primary" icon="import" @click="getNewEchCert">Get New ECH Cert</a-button>
|
||||
<a-button danger @click="clearEchCert">Clear</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<a-form-item label='OCSP stapling'>
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
<template v-if="inbound.stream.isReality">
|
||||
<a-form-item label='Show'>
|
||||
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Xver'>
|
||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='uTLS'>
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Dest'>
|
||||
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SNI'>
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
Short IDs
|
||||
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
|
||||
</a-icon>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SpiderX'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Private Key'>
|
||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Public Key'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- reality settings -->
|
||||
<template v-if="inbound.stream.isReality">
|
||||
{{template "form/realitySettings"}}
|
||||
</template>
|
||||
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -25,7 +25,7 @@
|
||||
<tr>
|
||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
|
||||
</tr>
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade || inbound.isSplithttp">
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isHttpupgrade || inbound.isXHTTP">
|
||||
<tr>
|
||||
<td>{{ i18n "host" }}</td>
|
||||
<td v-if="inbound.host">
|
||||
@@ -44,18 +44,14 @@
|
||||
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isQuic">
|
||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
|
||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isKcp">
|
||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
|
||||
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isXHTTP">
|
||||
<tr>
|
||||
<td>Mode</td>
|
||||
<td>
|
||||
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-if="inbound.isGrpc">
|
||||
<tr><td>grpc serviceName</td><td>
|
||||
<a-tooltip :title="[[ inbound.serviceName ]]">
|
||||
@@ -70,9 +66,18 @@
|
||||
{{ i18n "security" }}
|
||||
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||
<br />
|
||||
<td>Authentication</td>
|
||||
<a-tag :color="inbound.settings.selectedAuth ? 'green' : 'red'">[[ inbound.settings.selectedAuth ?
|
||||
inbound.settings.selectedAuth : '' ]]</a-tag>
|
||||
<br />
|
||||
{{ i18n "encryption" }}
|
||||
<a-tag :color="inbound.settings.encryption ? 'green' : 'red'">[[ inbound.settings.encryption ?
|
||||
inbound.settings.encryption : '' ]]</a-tag>
|
||||
<br />
|
||||
<template v-if="inbound.stream.security != 'none'">
|
||||
{{ i18n "domainName" }}
|
||||
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
{{ i18n "domainName" }}
|
||||
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : ''
|
||||
]]</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||
@@ -284,8 +289,8 @@
|
||||
<td>[[ inbound.settings.mtu ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kernel Mode</td>
|
||||
<td>[[ inbound.settings.kernelMode ]]</td>
|
||||
<td>No Kernel Tun</td>
|
||||
<td>[[ inbound.settings.noKernelTun ]]</td>
|
||||
</tr>
|
||||
<template v-for="(peer, index) in inbound.settings.peers">
|
||||
<tr>
|
||||
@@ -433,4 +438,4 @@
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -26,6 +26,11 @@
|
||||
} else {
|
||||
this.inbound = new Inbound();
|
||||
}
|
||||
if (this.inbound.protocol === Protocols.VLESS && this.inbound.settings) {
|
||||
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed) || this.inbound.settings.testseed.length < 4) {
|
||||
this.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||
}
|
||||
}
|
||||
if (dbInbound) {
|
||||
this.dbInbound = new DBInbound(dbInbound);
|
||||
} else {
|
||||
@@ -42,6 +47,27 @@
|
||||
loading(loading=true) {
|
||||
inModal.confirmLoading = loading;
|
||||
},
|
||||
updateTestseed(index, value) {
|
||||
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed)) {
|
||||
inModal.inbound.settings.testseed = [900, 500, 900, 256];
|
||||
}
|
||||
while (inModal.inbound.settings.testseed.length <= index) {
|
||||
inModal.inbound.settings.testseed.push(0);
|
||||
}
|
||||
inModal.inbound.settings.testseed[index] = value;
|
||||
},
|
||||
setRandomTestseed() {
|
||||
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||
if (!inModal.inbound.settings.testseed || !Array.isArray(inModal.inbound.settings.testseed) || inModal.inbound.settings.testseed.length < 4) {
|
||||
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||
}
|
||||
inModal.inbound.settings.testseed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
|
||||
},
|
||||
resetTestseed() {
|
||||
if (!inModal.inbound || !inModal.inbound.settings) return;
|
||||
inModal.inbound.settings.testseed = [900, 500, 900, 256].slice();
|
||||
}
|
||||
};
|
||||
|
||||
new Vue({
|
||||
@@ -124,7 +150,7 @@
|
||||
},
|
||||
async getNewX25519Cert(){
|
||||
inModal.loading(true);
|
||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
||||
const msg = await HttpUtil.get('/server/getNewX25519Cert');
|
||||
inModal.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
@@ -132,6 +158,80 @@
|
||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
||||
},
|
||||
clearX25519Cert() {
|
||||
this.inbound.stream.reality.privateKey = '';
|
||||
this.inbound.stream.reality.settings.publicKey = '';
|
||||
},
|
||||
async getNewmldsa65() {
|
||||
inModal.loading(true);
|
||||
const msg = await HttpUtil.get('/server/getNewmldsa65');
|
||||
inModal.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
inModal.inbound.stream.reality.mldsa65Seed = msg.obj.seed;
|
||||
inModal.inbound.stream.reality.settings.mldsa65Verify = msg.obj.verify;
|
||||
},
|
||||
clearMldsa65() {
|
||||
this.inbound.stream.reality.mldsa65Seed = '';
|
||||
this.inbound.stream.reality.settings.mldsa65Verify = '';
|
||||
},
|
||||
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;
|
||||
},
|
||||
clearEchCert() {
|
||||
this.inbound.stream.tls.echServerKeys = '';
|
||||
this.inbound.stream.tls.settings.echConfigList = '';
|
||||
},
|
||||
async getNewVlessEnc() {
|
||||
inModal.loading(true);
|
||||
const msg = await HttpUtil.get('/server/getNewVlessEnc');
|
||||
inModal.loading(false);
|
||||
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auths = msg.obj.auths || [];
|
||||
const selected = inModal.inbound.settings.selectedAuth;
|
||||
const block = auths.find(a => a.label === selected);
|
||||
|
||||
if (!block) {
|
||||
console.error("No auth block for", selected);
|
||||
return;
|
||||
}
|
||||
|
||||
inModal.inbound.settings.decryption = block.decryption;
|
||||
inModal.inbound.settings.encryption = block.encryption;
|
||||
},
|
||||
clearVlessEnc() {
|
||||
this.inbound.settings.decryption = 'none';
|
||||
this.inbound.settings.encryption = 'none';
|
||||
this.inbound.settings.selectedAuth = undefined;
|
||||
},
|
||||
updateTestseed(index, value) {
|
||||
if (!this.inbound.settings.testseed || !Array.isArray(this.inbound.settings.testseed)) {
|
||||
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
|
||||
}
|
||||
while (this.inbound.settings.testseed.length <= index) {
|
||||
this.inbound.settings.testseed.push(0);
|
||||
}
|
||||
this.$set(this.inbound.settings.testseed, index, value);
|
||||
},
|
||||
setRandomTestseed() {
|
||||
const newSeed = [Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000), Math.floor(Math.random()*1000)];
|
||||
this.$set(this.inbound.settings, 'testseed', newSeed);
|
||||
},
|
||||
resetTestseed() {
|
||||
this.$set(this.inbound.settings, 'testseed', [900, 500, 900, 256]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -451,7 +451,7 @@
|
||||
<script src="{{ .base_path }}assets/qrcode/qrious.min.js"></script>
|
||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/uri/URI.min.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/xray.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/inbound.js?{{ .cur_ver }}"></script>
|
||||
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||
{{template "component/themeSwitcher" .}}
|
||||
<script>
|
||||
@@ -925,7 +925,11 @@
|
||||
protocol: inbound.protocol,
|
||||
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();
|
||||
|
||||
await this.submit('/xui/inbound/add', data, inModal);
|
||||
@@ -944,7 +948,11 @@
|
||||
protocol: inbound.protocol,
|
||||
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();
|
||||
|
||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
||||
|
||||
@@ -296,6 +296,7 @@
|
||||
<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-select v-model="logModal.level" style="width:100px;"
|
||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
@@ -534,7 +535,7 @@
|
||||
this.loadingTip = tip;
|
||||
},
|
||||
async getStatus() {
|
||||
const msg = await HttpUtil.post('/server/status');
|
||||
const msg = await HttpUtil.get('/server/status');
|
||||
if (msg.success) {
|
||||
this.setStatus(msg.obj);
|
||||
}
|
||||
@@ -544,7 +545,7 @@
|
||||
},
|
||||
async openSelectV2rayVersion() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/getXrayVersion');
|
||||
const msg = await HttpUtil.get('server/getXrayVersion');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
@@ -594,7 +595,7 @@
|
||||
},
|
||||
async openConfig() {
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('server/getConfigJson');
|
||||
const msg = await HttpUtil.get('server/getConfigJson');
|
||||
this.loading(false);
|
||||
if (!msg.success) {
|
||||
return;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -145,7 +145,7 @@ new Vue({
|
||||
publicKey: peer.public_key,
|
||||
endpoint: peer.endpoint.host,
|
||||
}],
|
||||
kernelMode: false
|
||||
noKernelTun: false
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -174,10 +174,10 @@ new Vue({
|
||||
},
|
||||
async register(){
|
||||
warpModal.loading(true);
|
||||
keys = Wireguard.generateKeypair();
|
||||
const keys = Wireguard.generateKeypair();
|
||||
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
|
||||
if (msg.success) {
|
||||
resp = JSON.parse(msg.obj);
|
||||
const resp = JSON.parse(msg.obj);
|
||||
warpModal.warpData = resp.data;
|
||||
warpModal.warpConfig = resp.config;
|
||||
this.collectConfig();
|
||||
|
||||
@@ -160,41 +160,69 @@
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-row style="padding: 10px 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
|
||||
description='{{ i18n "pages.xray.logLevelDesc" }}' />
|
||||
description='{{ i18n "pages.xray.logLevelDesc" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select
|
||||
v-model="logLevel"
|
||||
style="width: 100%" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="level in ['none', 'debug', 'info', 'warning', 'error']" :value="level">[[ level ]]</a-select-option>
|
||||
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||
<a-select-option v-for="s in log.loglevel" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="padding: 10px 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
|
||||
description='{{ i18n "pages.xray.accessLogDesc" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select v-model="accessLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||
<a-select-option value=''>Empty</a-select-option>
|
||||
<a-select-option v-for="s in log.access" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="padding: 10px 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
|
||||
description='{{ i18n "pages.xray.errorLogDesc" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select v-model="errorLog" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||
<a-select-option value=''>Empty</a-select-option>
|
||||
<a-select-option v-for="s in log.error" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="padding: 10px 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.maskAddress" }}'
|
||||
description='{{ i18n "pages.xray.maskAddressDesc" }}'>
|
||||
</a-list-item-meta>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select v-model="maskAddressLog" :dropdown-class-name="themeSwitcher.currentTheme"
|
||||
style="width: 100%">
|
||||
<a-select-option value=''>Empty</a-select-option>
|
||||
<a-select-option v-for="s in log.maskAddress" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.dnsLog"}}' desc='{{ i18n "pages.xray.dnsLogDesc"}}'
|
||||
v-model="dnslog"></setting-list-item>
|
||||
</a-list-item>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.accessLog" }}'
|
||||
description='{{ i18n "pages.xray.accessLogDesc" }}' />
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-input v-model="logAccess"></a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row style="padding: 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='{{ i18n "pages.xray.errorLog" }}'
|
||||
description='{{ i18n "pages.xray.errorLogDesc" }}' />
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-input v-model="logError"></a-input>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
@@ -206,69 +234,155 @@
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.Torrent"}}' desc='{{ i18n "pages.xray.TorrentDesc"}}' v-model="torrentSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.PrivateIp"}}' desc='{{ i18n "pages.xray.PrivateIpDesc"}}' v-model="privateIpSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.Ads"}}' desc='{{ i18n "pages.xray.AdsDesc"}}' v-model="AdsSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.Family"}}' desc='{{ i18n "pages.xray.FamilyDesc"}}' v-model="familyProtectSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.blockCountryConfigs"}}'>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.basicRouting"}}'>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" style="text-align: center;">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||
{{ i18n "pages.xray.blockCountryConfigsDesc" }}
|
||||
{{ i18n "pages.xray.blockConnectionsConfigsDesc" }}
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRIp"}}' desc='{{ i18n "pages.xray.IRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRDomain"}}' desc='{{ i18n "pages.xray.IRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaIp"}}' desc='{{ i18n "pages.xray.ChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaDomain"}}' desc='{{ i18n "pages.xray.ChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaIp"}}' desc='{{ i18n "pages.xray.RussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaDomain"}}' desc='{{ i18n "pages.xray.RussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}'>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.blockips" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select mode="tags" v-model="blockedIPs" style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.IPsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.blockdomains" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="blockedDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.DomainsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" style="text-align: center;">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||
{{ i18n "pages.xray.directCountryConfigsDesc" }}
|
||||
{{ i18n "pages.xray.directConnectionsConfigsDesc" }}
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRIp"}}' desc='{{ i18n "pages.xray.DirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRDomain"}}' desc='{{ i18n "pages.xray.DirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaIp"}}' desc='{{ i18n "pages.xray.DirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaDomain"}}' desc='{{ i18n "pages.xray.DirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaIp"}}' desc='{{ i18n "pages.xray.DirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaDomain"}}' desc='{{ i18n "pages.xray.DirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.ipv4Configs"}}'>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.directips" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="directIPs"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.IPsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.directdomains" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="directDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.DomainsOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" style="text-align: center;">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||
{{ i18n "pages.xray.ipv4ConfigsDesc" }}
|
||||
{{ i18n "pages.xray.ipv4RoutingDesc" }}
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.warpConfigs"}}'>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.ipv4Routing" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="ipv4Domains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.ServicesOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<a-row :xs="24" :sm="24" :lg="12">
|
||||
<a-alert type="warning" style="text-align: center;">
|
||||
<template slot="message">
|
||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||
{{ i18n "pages.xray.warpConfigsDesc" }}
|
||||
{{ i18n "pages.xray.warpRoutingDesc" }}
|
||||
</template>
|
||||
</a-alert>
|
||||
</a-row>
|
||||
<template v-if="WarpExist">
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleWARP"}}' desc='{{ i18n "pages.xray.GoogleWARPDesc"}}' v-model="GoogleWARPSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.MetaWARP"}}' desc='{{ i18n "pages.xray.MetaWARPDesc"}}' v-model="MetaWARPSettings"></setting-list-item>
|
||||
<a-list-item>
|
||||
<a-row style="padding: 0 20px">
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta
|
||||
title='{{ i18n "pages.xray.warpRouting" }}'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select mode="tags" style="width: 100%"
|
||||
v-model="warpDomains"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p.value" :label="p.label"
|
||||
v-for="p in settingsData.ServicesOptions"> [[ p.label ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</template>
|
||||
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
||||
</a-collapse-panel>
|
||||
@@ -571,6 +685,9 @@
|
||||
<template slot="domain" slot-scope="dns,index">
|
||||
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
||||
</template>
|
||||
<template slot="expectIPs" slot-scope="dns,index">
|
||||
<span v-if="typeof dns == 'object'">[[ dns.expectIPs.join(",") ]]</span>
|
||||
</template>
|
||||
</a-table>
|
||||
<a-divider>Fake DNS</a-divider>
|
||||
<a-button type="primary" icon="plus" @click="addFakedns()" style="margin-bottom: 10px;">{{ i18n "pages.xray.fakedns.add" }}</a-button>
|
||||
@@ -680,6 +797,7 @@
|
||||
{ title: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
||||
{ title: '{{ i18n "pages.xray.dns.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
||||
{ title: '{{ i18n "pages.xray.dns.expectIPs"}}', align: 'center', width: 50, scopedSlots: { customRender: 'expectIPs' } },
|
||||
];
|
||||
|
||||
const fakednsColumns = [
|
||||
@@ -738,41 +856,43 @@
|
||||
tag: "direct",
|
||||
protocol: "freedom"
|
||||
},
|
||||
routingDomainStrategies: ["AsIs", "IPIfNonMatch", "IPOnDemand"],
|
||||
routingDomainStrategies: ["AsIs", "IpIfNonMatch", "IpOnDemand"],
|
||||
log: {
|
||||
loglevel: ["none", "debug", "info", "warning", "error"],
|
||||
access: ["none", "./access.log"],
|
||||
error: ["none", "./error.log"],
|
||||
dnsLog: false,
|
||||
maskAddress: ["quarter", "half", "full"],
|
||||
},
|
||||
settingsData: {
|
||||
protocols: {
|
||||
bittorrent: ["bittorrent"],
|
||||
},
|
||||
ips: {
|
||||
local: ["geoip:private"],
|
||||
cn: ["geoip:cn"],
|
||||
ir: ["ext:geoip_IR.dat:ir"],
|
||||
ru: ["geoip:ru"],
|
||||
},
|
||||
domains: {
|
||||
ads: [
|
||||
"geosite:category-ads-all",
|
||||
"ext:geosite_IR.dat:category-ads-all"
|
||||
],
|
||||
openai: ["geosite:openai"],
|
||||
google: ["geosite:google"],
|
||||
spotify: ["geosite:spotify"],
|
||||
netflix: ["geosite:netflix"],
|
||||
meta: ["geosite:meta"],
|
||||
cn: [
|
||||
"geosite:cn",
|
||||
"regexp:.*\\.cn$"
|
||||
],
|
||||
ru: [
|
||||
"geosite:category-gov-ru",
|
||||
"regexp:.*\\.ru$"
|
||||
],
|
||||
ir: [
|
||||
"regexp:.*\\.ir$",
|
||||
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران
|
||||
"ext:geosite_IR.dat:ir"
|
||||
]
|
||||
},
|
||||
IPsOptions: [
|
||||
{ label: 'Private IP', value: 'geoip:private' },
|
||||
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
|
||||
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
||||
],
|
||||
DomainsOptions: [
|
||||
{ label: 'Ads All', value: 'geosite:category-ads-all' },
|
||||
{ label: 'Ads IR', value: 'ext:geosite_IR.dat:category-ads-all' },
|
||||
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
|
||||
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
|
||||
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
||||
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
||||
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
|
||||
],
|
||||
ServicesOptions: [
|
||||
{ label: 'Apple', value: 'geosite:apple' },
|
||||
{ label: 'Meta', value: 'geosite:meta' },
|
||||
{ label: 'Google', value: 'geosite:google' },
|
||||
{ label: 'OpenAI', value: 'geosite:openai' },
|
||||
{ label: 'Spotify', value: 'geosite:spotify' },
|
||||
{ label: 'Netflix', value: 'geosite:netflix' },
|
||||
],
|
||||
familyProtectDNS: {
|
||||
"servers": [
|
||||
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
||||
@@ -967,7 +1087,9 @@
|
||||
switch(o.protocol){
|
||||
case Protocols.VMess:
|
||||
case Protocols.VLESS:
|
||||
serverObj = o.settings.vnext;
|
||||
if (o.settings && o.settings.address && o.settings.port) {
|
||||
return [o.settings.address + ':' + o.settings.port];
|
||||
}
|
||||
break;
|
||||
case Protocols.HTTP:
|
||||
case Protocols.Socks:
|
||||
@@ -1138,8 +1260,7 @@
|
||||
}
|
||||
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
||||
this.templateSettings = newTemplateSettings;
|
||||
if (balancer.strategy == 'leastPing' || balancer.strategy == 'leastLoad')
|
||||
this.updateObservatorySelectors();
|
||||
this.updateObservatorySelectors();
|
||||
balancerModal.close();
|
||||
this.changeObsCode();
|
||||
},
|
||||
@@ -1187,8 +1308,7 @@
|
||||
});
|
||||
}
|
||||
this.templateSettings = newTemplateSettings;
|
||||
if (balancer.strategy == 'leastPing' || balancer.strategy == 'leastLoad')
|
||||
this.updateObservatorySelectors();
|
||||
this.updateObservatorySelectors();
|
||||
balancerModal.close();
|
||||
this.changeObsCode();
|
||||
},
|
||||
@@ -1198,7 +1318,11 @@
|
||||
updateObservatorySelectors(){
|
||||
newTemplateSettings = this.templateSettings;
|
||||
const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
|
||||
const leastLoads = this.balancersData.filter((b) => b.strategy == 'leastLoad');
|
||||
const leastLoads = this.balancersData.filter((b) =>
|
||||
b.strategy === 'leastLoad' ||
|
||||
b.strategy === 'roundRobin' ||
|
||||
b.strategy === 'random'
|
||||
);
|
||||
if (leastPings.length>0){
|
||||
if (!newTemplateSettings.observatory)
|
||||
newTemplateSettings.observatory = this.defaultObservatory;
|
||||
@@ -1377,28 +1501,59 @@
|
||||
},
|
||||
},
|
||||
logLevel: {
|
||||
get: function () { return this.logSettings?.loglevel?? 'none'; },
|
||||
set: function (newValue) {
|
||||
newLogSettings = this.logSettings;
|
||||
newLogSettings.loglevel = newValue;
|
||||
this.logSettings = newLogSettings;
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
||||
return this.templateSettings.log.loglevel;
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.log.loglevel = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
logAccess: {
|
||||
get: function () { return this.logSettings?.access?? ''; },
|
||||
set: function (newValue) {
|
||||
newLogSettings = this.logSettings;
|
||||
newValue == "" ? delete newLogSettings.access : newLogSettings.access = newValue;
|
||||
this.logSettings = newLogSettings;
|
||||
accessLog: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
|
||||
return this.templateSettings.log.access;
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.log.access = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
logError: {
|
||||
get: function () { return this.logSettings?.error?? ''; },
|
||||
set: function (newValue) {
|
||||
newLogSettings = this.logSettings;
|
||||
newValue == "" ? delete newLogSettings.error : newLogSettings.error = newValue;
|
||||
this.logSettings = newLogSettings;
|
||||
errorLog: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
|
||||
return this.templateSettings.log.error;
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.log.error = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
dnslog: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.dnsLog) return false;
|
||||
return this.templateSettings.log.dnsLog;
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.log.dnsLog = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
maskAddressLog: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.maskAddress) return "";
|
||||
return this.templateSettings.log.maskAddress;
|
||||
},
|
||||
set: function (newValue) {
|
||||
newTemplateSettings = this.templateSettings;
|
||||
newTemplateSettings.log.maskAddress = newValue;
|
||||
this.templateSettings = newTemplateSettings;
|
||||
}
|
||||
},
|
||||
inboundSettings: {
|
||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||
@@ -1616,30 +1771,6 @@
|
||||
}
|
||||
},
|
||||
},
|
||||
privateIpSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.local, this.blockedIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.local];
|
||||
} else {
|
||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.local.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
AdsSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.ads, this.blockedDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ads];
|
||||
} else {
|
||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ads.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
familyProtectSettings: {
|
||||
get: function () {
|
||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||
@@ -1655,239 +1786,11 @@
|
||||
this.templateSettings = newTemplateSettings;
|
||||
},
|
||||
},
|
||||
GoogleIPv4Settings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.google, this.ipv4Domains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.google];
|
||||
} else {
|
||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
NetflixIPv4Settings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.netflix, this.ipv4Domains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.ipv4Domains = [...this.ipv4Domains, ...this.settingsData.domains.netflix];
|
||||
} else {
|
||||
this.ipv4Domains = this.ipv4Domains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
IRIpSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.ir, this.blockedIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ir];
|
||||
} else {
|
||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
IRDomainSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.ir, this.blockedDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ir];
|
||||
} else {
|
||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
ChinaIpSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.cn, this.blockedIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.cn];
|
||||
} else {
|
||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
ChinaDomainSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.cn, this.blockedDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.cn];
|
||||
} else {
|
||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
RussiaIpSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.ru, this.blockedIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedIPs = [...this.blockedIPs, ...this.settingsData.ips.ru];
|
||||
} else {
|
||||
this.blockedIPs = this.blockedIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
RussiaDomainSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.ru, this.blockedDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.blockedDomains = [...this.blockedDomains, ...this.settingsData.domains.ru];
|
||||
} else {
|
||||
this.blockedDomains = this.blockedDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
IRIpDirectSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.ir, this.directIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ir];
|
||||
} else {
|
||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ir.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
IRDomainDirectSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.ir, this.directDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ir];
|
||||
} else {
|
||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ir.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
ChinaIpDirectSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.cn, this.directIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.cn];
|
||||
} else {
|
||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.cn.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
ChinaDomainDirectSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.cn, this.directDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.cn];
|
||||
} else {
|
||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.cn.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
RussiaIpDirectSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.ips.ru, this.directIPs);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.directIPs = [...this.directIPs, ...this.settingsData.ips.ru];
|
||||
} else {
|
||||
this.directIPs = this.directIPs.filter(data => !this.settingsData.ips.ru.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
RussiaDomainDirectSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.ru, this.directDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.directDomains = [...this.directDomains, ...this.settingsData.domains.ru];
|
||||
} else {
|
||||
this.directDomains = this.directDomains.filter(data => !this.settingsData.domains.ru.includes(data));
|
||||
}
|
||||
}
|
||||
},
|
||||
WarpExist: {
|
||||
get: function() {
|
||||
return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp")>=0 : false;
|
||||
},
|
||||
},
|
||||
GoogleWARPSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.google, this.warpDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.google];
|
||||
} else {
|
||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.google.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
OpenAIWARPSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.openai, this.warpDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.openai];
|
||||
} else {
|
||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.openai.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
NetflixWARPSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.netflix, this.warpDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.netflix];
|
||||
} else {
|
||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.netflix.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
MetaWARPSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.meta, this.warpDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.meta];
|
||||
} else {
|
||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.meta.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
SpotifyWARPSettings: {
|
||||
get: function () {
|
||||
return doAllItemsExist(this.settingsData.domains.spotify, this.warpDomains);
|
||||
},
|
||||
set: function (newValue) {
|
||||
if (newValue) {
|
||||
this.warpDomains = [...this.warpDomains, ...this.settingsData.domains.spotify];
|
||||
} else {
|
||||
this.warpDomains = this.warpDomains.filter(data => !this.settingsData.domains.spotify.includes(data));
|
||||
}
|
||||
},
|
||||
},
|
||||
enableDNS: {
|
||||
get: function () {
|
||||
return this.templateSettings ? this.templateSettings.dns != null : false;
|
||||
|
||||
@@ -220,13 +220,13 @@
|
||||
newRule = {};
|
||||
rule.type = "field";
|
||||
rule.domainMatcher = value.domainMatcher;
|
||||
rule.domain = value.domain.length>0 ? value.domain.split(',') : [];
|
||||
rule.ip = value.ip.length>0 ? value.ip.split(',') : [];
|
||||
rule.domain = value.domain.length>0 ? value.domain.split(',').map(s => s.trim()) : [];
|
||||
rule.ip = value.ip.length>0 ? value.ip.split(',').map(s => s.trim()) : [];
|
||||
rule.port = value.port;
|
||||
rule.sourcePort = value.sourcePort;
|
||||
rule.network = value.network;
|
||||
rule.source = value.source.length>0 ? value.source.split(',') : [];
|
||||
rule.user = value.user.length>0 ? value.user.split(',') : [];
|
||||
rule.source = value.source.length>0 ? value.source.split(',').map(s => s.trim()) : [];
|
||||
rule.user = value.user.length>0 ? value.user.split(',').map(s => s.trim()) : [];
|
||||
rule.inboundTag = value.inboundTag;
|
||||
rule.protocol = value.protocol;
|
||||
rule.attrs = Object.fromEntries(value.attrs);
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
)
|
||||
@@ -20,7 +20,11 @@ func NewCheckCpuJob() *CheckCpuJob {
|
||||
|
||||
// Here run is a interface method of Job interface
|
||||
func (j *CheckCpuJob) Run() {
|
||||
threshold, _ := j.settingService.GetTgCpu()
|
||||
threshold, err := j.settingService.GetTgCpu()
|
||||
if err != nil || threshold <= 0 {
|
||||
// If threshold cannot be retrieved or is not set, skip sending notifications
|
||||
return
|
||||
}
|
||||
|
||||
// get latest status of server
|
||||
percent, err := cpu.Percent(1*time.Second, false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package job
|
||||
|
||||
import "x-ui/web/service"
|
||||
import "github.com/alireza0/x-ui/web/service"
|
||||
|
||||
type CheckXrayRunningJob struct {
|
||||
xrayService service.XrayService
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
)
|
||||
|
||||
type LoginStatus byte
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package job
|
||||
|
||||
import (
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/web/service"
|
||||
)
|
||||
|
||||
type XrayTrafficJob struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"io/fs"
|
||||
"strings"
|
||||
|
||||
"x-ui/logger"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
@@ -48,10 +48,10 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
||||
func createTemplateData(params []string, separator ...string) map[string]interface{} {
|
||||
var sep string = "=="
|
||||
if len(seperator) > 0 {
|
||||
sep = seperator[0]
|
||||
if len(separator) > 0 {
|
||||
sep = separator[0]
|
||||
}
|
||||
|
||||
templateData := make(map[string]interface{})
|
||||
|
||||
@@ -1,34 +1,44 @@
|
||||
{
|
||||
"log": {
|
||||
"loglevel": "warning"
|
||||
},
|
||||
"api": {
|
||||
"tag": "api",
|
||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
||||
"services": [
|
||||
"HandlerService",
|
||||
"LoggerService",
|
||||
"StatsService"
|
||||
],
|
||||
"tag": "api"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"tag": "api",
|
||||
"listen": "127.0.0.1",
|
||||
"port": 62789,
|
||||
"protocol": "dokodemo-door",
|
||||
"settings": {
|
||||
"address": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"tag": "api"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"access": "none",
|
||||
"dnsLog": false,
|
||||
"error": "",
|
||||
"loglevel": "warning",
|
||||
"maskAddress": ""
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "direct",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP"
|
||||
}
|
||||
"domainStrategy": "UseIP",
|
||||
"noises": [],
|
||||
"redirect": ""
|
||||
},
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"tag": "blocked",
|
||||
"protocol": "blackhole",
|
||||
"settings": {}
|
||||
"settings": {},
|
||||
"tag": "blocked"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
@@ -47,19 +57,25 @@
|
||||
"domainStrategy": "AsIs",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"inboundTag": ["api"],
|
||||
"outboundTag": "api"
|
||||
"inboundTag": [
|
||||
"api"
|
||||
],
|
||||
"outboundTag": "api",
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"ip": [
|
||||
"geoip:private"
|
||||
],
|
||||
"outboundTag": "blocked",
|
||||
"ip": ["geoip:private"]
|
||||
"type": "field"
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"outboundTag": "blocked",
|
||||
"protocol": ["bittorrent"]
|
||||
"protocol": [
|
||||
"bittorrent"
|
||||
],
|
||||
"type": "field"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -19,6 +19,10 @@ type InboundService struct {
|
||||
xrayApi xray.XrayAPI
|
||||
}
|
||||
|
||||
const (
|
||||
safeBatchSize = 500
|
||||
)
|
||||
|
||||
func (s *InboundService) GetInbounds(userId int) ([]*model.Inbound, error) {
|
||||
db := database.GetDB()
|
||||
var inbounds []*model.Inbound
|
||||
@@ -98,8 +102,9 @@ func (s *InboundService) getAllEmails() ([]string, error) {
|
||||
}
|
||||
|
||||
func (s *InboundService) contains(slice []string, str string) bool {
|
||||
lowerStr := strings.ToLower(str)
|
||||
for _, s := range slice {
|
||||
if s == str {
|
||||
if strings.ToLower(s) == lowerStr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -518,11 +523,13 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
|
||||
interfaceClients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
needApiDel := false
|
||||
for _, client := range interfaceClients {
|
||||
c := client.(map[string]interface{})
|
||||
c_id := c[client_key].(string)
|
||||
if c_id == clientId {
|
||||
email = c["email"].(string)
|
||||
email, _ = c["email"].(string)
|
||||
needApiDel, _ = c["enable"].(bool)
|
||||
} else {
|
||||
newClients = append(newClients, client)
|
||||
}
|
||||
@@ -541,23 +548,36 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
oldInbound.Settings = string(newSettings)
|
||||
|
||||
db := database.GetDB()
|
||||
err = s.DelClientStat(db, email)
|
||||
if err != nil {
|
||||
logger.Error("Delete stats Data Error")
|
||||
return false, err
|
||||
}
|
||||
needRestart := false
|
||||
|
||||
if len(email) > 0 {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client deleted by api:", email)
|
||||
needRestart = false
|
||||
} else {
|
||||
logger.Debug("Unable to del client by api:", err1)
|
||||
needRestart = true
|
||||
notDepleted := true
|
||||
err = db.Model(xray.ClientTraffic{}).Select("enable").Where("email = ?", email).First(¬Depleted).Error
|
||||
if err != nil {
|
||||
logger.Error("Get stats error")
|
||||
return false, err
|
||||
}
|
||||
err = s.DelClientStat(db, email)
|
||||
if err != nil {
|
||||
logger.Error("Delete stats Data Error")
|
||||
return false, err
|
||||
}
|
||||
if needApiDel && notDepleted {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client deleted by api:", email)
|
||||
needRestart = false
|
||||
} else {
|
||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", email)) {
|
||||
logger.Debug("User is already deleted. Nothing to do more...")
|
||||
} else {
|
||||
logger.Debug("Error in deleting client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
}
|
||||
return needRestart, db.Save(oldInbound).Error
|
||||
}
|
||||
@@ -667,12 +687,18 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
needRestart := false
|
||||
if len(oldEmail) > 0 {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||
if err1 == nil {
|
||||
logger.Debug("Old client deleted by api:", clients[0].Email)
|
||||
} else {
|
||||
logger.Debug("Error in deleting client by api:", err1)
|
||||
needRestart = true
|
||||
if oldClients[clientIndex].Enable {
|
||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||
if err1 == nil {
|
||||
logger.Debug("Old client deleted by api:", oldEmail)
|
||||
} else {
|
||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", oldEmail)) {
|
||||
logger.Debug("User is already deleted. Nothing to do more...")
|
||||
} else {
|
||||
logger.Debug("Error in deleting client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if clients[0].Enable {
|
||||
cipher := ""
|
||||
@@ -767,6 +793,11 @@ func (s *InboundService) addInboundTraffic(tx *gorm.DB, traffics []*xray.Traffic
|
||||
return nil
|
||||
}
|
||||
|
||||
type newExpiryTime struct {
|
||||
Email string
|
||||
NewExpiryTime int64
|
||||
}
|
||||
|
||||
func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTraffic) (err error) {
|
||||
if len(traffics) == 0 {
|
||||
// Empty onlineUsers
|
||||
@@ -783,9 +814,18 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
||||
emails = append(emails, traffic.Email)
|
||||
}
|
||||
dbClientTraffics := make([]*xray.ClientTraffic, 0, len(traffics))
|
||||
err = tx.Model(xray.ClientTraffic{}).Where("email IN (?)", emails).Find(&dbClientTraffics).Error
|
||||
if err != nil {
|
||||
return err
|
||||
for i := 0; i < len(emails); i += safeBatchSize {
|
||||
end := i + safeBatchSize
|
||||
if end > len(emails) {
|
||||
end = len(emails)
|
||||
}
|
||||
|
||||
batchClientTraffics := make([]*xray.ClientTraffic, 0, end-i)
|
||||
err = tx.Model(xray.ClientTraffic{}).Where("email IN ?", emails[i:end]).Find(&batchClientTraffics).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dbClientTraffics = append(dbClientTraffics, batchClientTraffics...)
|
||||
}
|
||||
|
||||
// Avoid empty slice error
|
||||
@@ -793,7 +833,20 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
||||
return nil
|
||||
}
|
||||
|
||||
dbClientTraffics, err = s.adjustTraffics(tx, dbClientTraffics)
|
||||
inboundExpiryTimeMap := make(map[int][]newExpiryTime, 0)
|
||||
for index, t := range dbClientTraffics {
|
||||
if t.ExpiryTime < 0 {
|
||||
newClientExpiryTime := (time.Now().Unix() * 1000) - int64(t.ExpiryTime)
|
||||
newExpiryTime := newExpiryTime{
|
||||
Email: t.Email,
|
||||
NewExpiryTime: newClientExpiryTime,
|
||||
}
|
||||
inboundExpiryTimeMap[t.InboundId] = append(inboundExpiryTimeMap[t.InboundId], newExpiryTime)
|
||||
dbClientTraffics[index].ExpiryTime = newClientExpiryTime
|
||||
}
|
||||
}
|
||||
|
||||
err = s.adjustTraffics(tx, inboundExpiryTimeMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -814,66 +867,90 @@ func (s *InboundService) addClientTraffic(tx *gorm.DB, traffics []*xray.ClientTr
|
||||
}
|
||||
|
||||
// Set onlineUsers
|
||||
p.SetOnlineClients(onlineClients)
|
||||
if p != nil {
|
||||
p.SetOnlineClients(onlineClients)
|
||||
}
|
||||
|
||||
err = tx.Save(dbClientTraffics).Error
|
||||
if err != nil {
|
||||
logger.Warning("AddClientTraffic update data ", err)
|
||||
for i := 0; i < len(dbClientTraffics); i += safeBatchSize {
|
||||
end := i + safeBatchSize
|
||||
if end > len(dbClientTraffics) {
|
||||
end = len(dbClientTraffics)
|
||||
}
|
||||
|
||||
err = tx.Save(dbClientTraffics[i:end]).Error
|
||||
if err != nil {
|
||||
logger.Warning("AddClientTraffic update data ", err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) adjustTraffics(tx *gorm.DB, dbClientTraffics []*xray.ClientTraffic) ([]*xray.ClientTraffic, error) {
|
||||
inboundIds := make([]int, 0, len(dbClientTraffics))
|
||||
for _, dbClientTraffic := range dbClientTraffics {
|
||||
if dbClientTraffic.ExpiryTime < 0 {
|
||||
inboundIds = append(inboundIds, dbClientTraffic.InboundId)
|
||||
func (s *InboundService) adjustTraffics(tx *gorm.DB, inboundExpiryTimeMap map[int][]newExpiryTime) error {
|
||||
if len(inboundExpiryTimeMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inboundIds := make([]int, 0)
|
||||
for inId := range inboundExpiryTimeMap {
|
||||
inboundIds = append(inboundIds, inId)
|
||||
}
|
||||
inbounds := make([]*model.Inbound, 0, len(inboundIds))
|
||||
for i := 0; i < len(inboundIds); i += safeBatchSize {
|
||||
end := i + safeBatchSize
|
||||
if end > len(inboundIds) {
|
||||
end = len(inboundIds)
|
||||
}
|
||||
|
||||
batchInbounds := make([]*model.Inbound, 0, end-i)
|
||||
err := tx.Model(model.Inbound{}).Where("id IN ?", inboundIds[i:end]).Find(&batchInbounds).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inbounds = append(inbounds, batchInbounds...)
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
inbEmails := inboundExpiryTimeMap[inbounds[inbound_index].Id]
|
||||
if ok {
|
||||
var newClients []interface{}
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
for index := range inbEmails {
|
||||
if c["email"] == inbEmails[index].Email {
|
||||
c["expiryTime"] = inbEmails[index].NewExpiryTime
|
||||
break
|
||||
}
|
||||
}
|
||||
newClients = append(newClients, interface{}(c))
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||
}
|
||||
}
|
||||
|
||||
if len(inboundIds) > 0 {
|
||||
var inbounds []*model.Inbound
|
||||
err := tx.Model(model.Inbound{}).Where("id IN (?)", inboundIds).Find(&inbounds).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for i := 0; i < len(inbounds); i += safeBatchSize {
|
||||
end := i + safeBatchSize
|
||||
if end > len(inbounds) {
|
||||
end = len(inbounds)
|
||||
}
|
||||
for inbound_index := range inbounds {
|
||||
settings := map[string]interface{}{}
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
var newClients []interface{}
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
for traffic_index := range dbClientTraffics {
|
||||
if dbClientTraffics[traffic_index].ExpiryTime < 0 && c["email"] == dbClientTraffics[traffic_index].Email {
|
||||
oldExpiryTime := c["expiryTime"].(float64)
|
||||
newExpiryTime := (time.Now().Unix() * 1000) - int64(oldExpiryTime)
|
||||
c["expiryTime"] = newExpiryTime
|
||||
dbClientTraffics[traffic_index].ExpiryTime = newExpiryTime
|
||||
break
|
||||
}
|
||||
}
|
||||
newClients = append(newClients, interface{}(c))
|
||||
}
|
||||
settings["clients"] = newClients
|
||||
modifiedSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inbounds[inbound_index].Settings = string(modifiedSettings)
|
||||
}
|
||||
}
|
||||
err = tx.Save(inbounds).Error
|
||||
err := tx.Save(inbounds[i:end]).Error
|
||||
if err != nil {
|
||||
logger.Warning("AddClientTraffic update inbounds ", err)
|
||||
logger.Error(inbounds)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return dbClientTraffics, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InboundService) autoRenewClients(tx *gorm.DB) (bool, int64, error) {
|
||||
@@ -1030,8 +1107,12 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
|
||||
if err1 == nil {
|
||||
logger.Debug("Client disabled by api:", result.Email)
|
||||
} else {
|
||||
logger.Debug("Error in disabling client by api:", err1)
|
||||
needRestart = true
|
||||
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) {
|
||||
logger.Debug("User is already disabled. Nothing to do more...")
|
||||
} else {
|
||||
logger.Debug("Error in disabling client by api:", err1)
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
}
|
||||
s.xrayApi.Close()
|
||||
@@ -1062,7 +1143,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
||||
clientTraffic.Email = client.Email
|
||||
clientTraffic.Total = client.TotalGB
|
||||
clientTraffic.ExpiryTime = client.ExpiryTime
|
||||
clientTraffic.Enable = true
|
||||
clientTraffic.Enable = client.Enable
|
||||
clientTraffic.Up = 0
|
||||
clientTraffic.Down = 0
|
||||
clientTraffic.Reset = client.Reset
|
||||
@@ -1075,7 +1156,7 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
|
||||
result := tx.Model(xray.ClientTraffic{}).
|
||||
Where("email = ?", email).
|
||||
Updates(map[string]interface{}{
|
||||
"enable": true,
|
||||
"enable": client.Enable,
|
||||
"email": client.Email,
|
||||
"total": client.TotalGB,
|
||||
"expiry_time": client.ExpiryTime,
|
||||
@@ -1107,7 +1188,7 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
|
||||
return false, err
|
||||
}
|
||||
for _, client := range clients {
|
||||
if client.Email == clientEmail {
|
||||
if client.Email == clientEmail && client.Enable {
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
cipher := ""
|
||||
if string(inbound.Protocol) == "shadowsocks" {
|
||||
@@ -1292,8 +1373,8 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, error) {
|
||||
db := database.GetDB()
|
||||
func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, error) {
|
||||
db := database.GetDB()
|
||||
var traffics []xray.ClientTraffic
|
||||
|
||||
err := db.Model(xray.ClientTraffic{}).Where(`email IN(
|
||||
@@ -1378,6 +1459,9 @@ func (s *InboundService) MigrationRequirements() {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
tx.Commit()
|
||||
if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
|
||||
logger.Warningf("VACUUM failed: %v", dbErr)
|
||||
}
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"x-ui/logger"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
)
|
||||
|
||||
type PanelService struct{}
|
||||
|
||||
@@ -11,17 +11,18 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/util/sys"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/util/sys"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
@@ -243,7 +244,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
}
|
||||
var versions []string
|
||||
for _, release := range releases {
|
||||
if release.TagName >= "v1.8.0" {
|
||||
if release.TagName >= "v26.1.23" {
|
||||
versions = append(versions, release.TagName)
|
||||
}
|
||||
}
|
||||
@@ -279,6 +280,8 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
switch osName {
|
||||
case "darwin":
|
||||
osName = "macos"
|
||||
case "windows":
|
||||
osName = "windows"
|
||||
}
|
||||
|
||||
switch arch {
|
||||
@@ -322,19 +325,23 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
||||
}
|
||||
|
||||
func (s *ServerService) UpdateXray(version string) error {
|
||||
// 1. Stop xray before doing anything
|
||||
if err := s.StopXrayService(); err != nil {
|
||||
logger.Warning("failed to stop xray before update:", err)
|
||||
}
|
||||
|
||||
// 2. Download the zip
|
||||
zipFileName, err := s.downloadXRay(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(zipFileName)
|
||||
|
||||
zipFile, err := os.Open(zipFileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
zipFile.Close()
|
||||
os.Remove(zipFileName)
|
||||
}()
|
||||
defer zipFile.Close()
|
||||
|
||||
stat, err := zipFile.Stat()
|
||||
if err != nil {
|
||||
@@ -345,19 +352,14 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
s.xrayService.StopXray()
|
||||
defer func() {
|
||||
err := s.xrayService.RestartXray(true)
|
||||
if err != nil {
|
||||
logger.Error("start xray failed:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// 3. Helper to extract files
|
||||
copyZipFile := func(zipName string, fileName string) error {
|
||||
zipFile, err := reader.Open(zipName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer zipFile.Close()
|
||||
os.MkdirAll(filepath.Dir(fileName), 0755)
|
||||
os.Remove(fileName)
|
||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
||||
if err != nil {
|
||||
@@ -368,11 +370,23 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = copyZipFile("xray", xray.GetBinaryPath())
|
||||
// 4. Extract correct binary
|
||||
if runtime.GOOS == "windows" {
|
||||
targetBinary := filepath.Join("bin", "xray-windows-amd64.exe")
|
||||
err = copyZipFile("xray.exe", targetBinary)
|
||||
} else {
|
||||
err = copyZipFile("xray", xray.GetBinaryPath())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. Restart xray
|
||||
if err := s.xrayService.RestartXray(true); err != nil {
|
||||
logger.Error("start xray failed:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -381,14 +395,39 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
||||
var lines []string
|
||||
|
||||
if syslog == "true" {
|
||||
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
|
||||
// Run the command
|
||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||
// Check if running on Windows - journalctl is not available
|
||||
if runtime.GOOS == "windows" {
|
||||
return []string{"Syslog is not supported on Windows. Please use application logs instead by unchecking the 'Syslog' option."}
|
||||
}
|
||||
|
||||
// Validate and sanitize count parameter
|
||||
countInt, err := strconv.Atoi(count)
|
||||
if err != nil || countInt < 1 || countInt > 10000 {
|
||||
return []string{"Invalid count parameter - must be a number between 1 and 10000"}
|
||||
}
|
||||
|
||||
// Validate level parameter - only allow valid syslog levels
|
||||
validLevels := map[string]bool{
|
||||
"0": true, "emerg": true,
|
||||
"1": true, "alert": true,
|
||||
"2": true, "crit": true,
|
||||
"3": true, "err": true,
|
||||
"4": true, "warning": true,
|
||||
"5": true, "notice": true,
|
||||
"6": true, "info": true,
|
||||
"7": true, "debug": true,
|
||||
}
|
||||
if !validLevels[level] {
|
||||
return []string{"Invalid level parameter - must be a valid syslog level"}
|
||||
}
|
||||
|
||||
// Use hardcoded command with validated parameters
|
||||
cmd := exec.Command("journalctl", "-u", "x-ui", "--no-pager", "-n", strconv.Itoa(countInt), "-p", level)
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return []string{"Failed to run journalctl command!"}
|
||||
return []string{"Failed to run journalctl command! Make sure systemd is available and x-ui service is registered."}
|
||||
}
|
||||
lines = strings.Split(out.String(), "\n")
|
||||
} else {
|
||||
@@ -481,14 +520,26 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
||||
return common.NewErrorf("Error saving db: %v", err)
|
||||
}
|
||||
|
||||
// Check if we can init db or not
|
||||
err = database.InitDB(tempPath)
|
||||
if err != nil {
|
||||
return common.NewErrorf("Error checking db: %v", err)
|
||||
// Close temp file before opening via sqlite
|
||||
if err = tempFile.Close(); err != nil {
|
||||
return common.NewErrorf("Error closing temporary db file: %v", err)
|
||||
}
|
||||
tempFile = nil
|
||||
|
||||
// Validate integrity (no migrations / side effects)
|
||||
if err = database.ValidateSQLiteDB(tempPath); err != nil {
|
||||
return common.NewErrorf("Invalid or corrupt db file: %v", err)
|
||||
}
|
||||
|
||||
// Stop Xray
|
||||
s.StopXrayService()
|
||||
// Stop Xray (ignore error but log)
|
||||
if errStop := s.StopXrayService(); errStop != nil {
|
||||
logger.Warningf("Failed to stop Xray before DB import: %v", errStop)
|
||||
}
|
||||
|
||||
// Close existing DB to release file locks (especially on Windows)
|
||||
if errClose := database.CloseDB(); errClose != nil {
|
||||
logger.Warningf("Failed to close existing DB before replacement: %v", errClose)
|
||||
}
|
||||
|
||||
// Backup the current database for fallback
|
||||
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
||||
@@ -564,3 +615,93 @@ func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
||||
|
||||
return keyPair, nil
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewmldsa65() (any, error) {
|
||||
// Run the command
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "mldsa65")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
|
||||
SeedLine := strings.Split(lines[0], ":")
|
||||
VerifyLine := strings.Split(lines[1], ":")
|
||||
|
||||
seed := strings.TrimSpace(SeedLine[1])
|
||||
verify := strings.TrimSpace(VerifyLine[1])
|
||||
|
||||
keyPair := map[string]any{
|
||||
"seed": seed,
|
||||
"verify": verify,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s *ServerService) GetNewVlessEnc() (any, error) {
|
||||
cmd := exec.Command(xray.GetBinaryPath(), "vlessenc")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
var auths []map[string]string
|
||||
var current map[string]string
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "Authentication:") {
|
||||
if current != nil {
|
||||
auths = append(auths, current)
|
||||
}
|
||||
current = map[string]string{
|
||||
"label": strings.TrimSpace(strings.TrimPrefix(line, "Authentication:")),
|
||||
}
|
||||
} else if strings.HasPrefix(line, `"decryption"`) || strings.HasPrefix(line, `"encryption"`) {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) == 2 && current != nil {
|
||||
key := strings.Trim(parts[0], `" `)
|
||||
val := strings.Trim(parts[1], `" `)
|
||||
current[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if current != nil {
|
||||
auths = append(auths, current)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"auths": auths,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/util/random"
|
||||
"x-ui/util/reflect_util"
|
||||
"x-ui/web/entity"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/util/random"
|
||||
"github.com/alireza0/x-ui/util/reflect_util"
|
||||
"github.com/alireza0/x-ui/web/entity"
|
||||
)
|
||||
|
||||
//go:embed config.json
|
||||
@@ -59,6 +59,7 @@ var defaultValueMap = map[string]string{
|
||||
"subJsonPath": "/json/",
|
||||
"subJsonURI": "",
|
||||
"subJsonFragment": "",
|
||||
"subJsonNoises": "",
|
||||
"subJsonMux": "",
|
||||
"subJsonRules": "",
|
||||
"warp": "",
|
||||
@@ -282,10 +283,18 @@ func (s *SettingService) SetPort(port int) error {
|
||||
return s.setInt("webPort", port)
|
||||
}
|
||||
|
||||
func (s *SettingService) SetCertFile(webCertFile string) error {
|
||||
return s.setString("webCertFile", webCertFile)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetCertFile() (string, error) {
|
||||
return s.getString("webCertFile")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetKeyFile(webKeyFile string) error {
|
||||
return s.setString("webKeyFile", webKeyFile)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetKeyFile() (string, error) {
|
||||
return s.getString("webKeyFile")
|
||||
}
|
||||
@@ -317,6 +326,16 @@ func (s *SettingService) GetSecret() ([]byte, error) {
|
||||
return []byte(secret), err
|
||||
}
|
||||
|
||||
func (s *SettingService) SetBasePath(basePath string) error {
|
||||
if !strings.HasPrefix(basePath, "/") {
|
||||
basePath = "/" + basePath
|
||||
}
|
||||
if !strings.HasSuffix(basePath, "/") {
|
||||
basePath += "/"
|
||||
}
|
||||
return s.setString("webBasePath", basePath)
|
||||
}
|
||||
|
||||
func (s *SettingService) GetBasePath() (string, error) {
|
||||
basePath, err := s.getString("webBasePath")
|
||||
if err != nil {
|
||||
@@ -340,7 +359,12 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
||||
if err != nil {
|
||||
defaultLocation := defaultValueMap["timeLocation"]
|
||||
logger.Errorf("location <%v> not exist, using default location: %v", l, defaultLocation)
|
||||
return time.LoadLocation(defaultLocation)
|
||||
location, err = time.LoadLocation(defaultLocation)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to load default location, using UTC: %v", err)
|
||||
return time.UTC, nil
|
||||
}
|
||||
return location, nil
|
||||
}
|
||||
return location, nil
|
||||
}
|
||||
@@ -405,6 +429,10 @@ func (s *SettingService) GetSubJsonFragment() (string, error) {
|
||||
return s.getString("subJsonFragment")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonNoises() (string, error) {
|
||||
return s.getString("subJsonNoises")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonMux() (string, error) {
|
||||
return s.getString("subJsonMux")
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/config"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/web/locale"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
@@ -3,9 +3,9 @@ package service
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"github.com/alireza0/x-ui/database"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"x-ui/logger"
|
||||
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
)
|
||||
|
||||
type WarpService struct {
|
||||
@@ -55,8 +57,7 @@ func (s *WarpService) GetWarpConfig() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -86,8 +87,7 @@ func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -143,12 +143,22 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
||||
buffer.Reset()
|
||||
buffer := &bytes.Buffer{}
|
||||
_, err = buffer.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal(buffer.Bytes(), &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if response["success"] == false {
|
||||
errorArr, _ := response["errors"].([]interface{})
|
||||
errorObj := errorArr[0].(map[string]interface{})
|
||||
return "", common.NewError(errorObj["code"], errorObj["message"])
|
||||
}
|
||||
|
||||
warpData["license_key"] = license
|
||||
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
||||
@@ -156,7 +166,6 @@ func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||
return "", err
|
||||
}
|
||||
s.SettingService.SetWarp(string(newWarpData))
|
||||
println(string(newWarpData))
|
||||
|
||||
return string(newWarpData), nil
|
||||
}
|
||||
|
||||
@@ -3,10 +3,11 @@ package service
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/logger"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
@@ -32,7 +33,19 @@ func (s *XrayService) GetXrayErr() error {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
return p.GetErr()
|
||||
|
||||
err := p.GetErr()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -45,7 +58,15 @@ func (s *XrayService) GetXrayResult() string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
|
||||
"x-ui/util/common"
|
||||
"x-ui/xray"
|
||||
"github.com/alireza0/x-ui/util/common"
|
||||
"github.com/alireza0/x-ui/xray"
|
||||
)
|
||||
|
||||
type XraySettingService struct {
|
||||
|
||||
@@ -3,9 +3,9 @@ package session
|
||||
import (
|
||||
"encoding/gob"
|
||||
|
||||
"x-ui/database/model"
|
||||
"github.com/alireza0/x-ui/database/model"
|
||||
|
||||
sessions "github.com/Calidity/gin-sessions"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -19,23 +19,10 @@ func init() {
|
||||
|
||||
func SetLoginUser(c *gin.Context, user *model.User) error {
|
||||
s := sessions.Default(c)
|
||||
s.Options(sessions.Options{
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
})
|
||||
s.Set(loginUser, user)
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func SetMaxAge(c *gin.Context, maxAge int) error {
|
||||
s := sessions.Default(c)
|
||||
s.Options(sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: maxAge,
|
||||
})
|
||||
return s.Save()
|
||||
}
|
||||
|
||||
func GetLoginUser(c *gin.Context) *model.User {
|
||||
s := sessions.Default(c)
|
||||
obj := s.Get(loginUser)
|
||||
@@ -53,8 +40,12 @@ func IsLogin(c *gin.Context) bool {
|
||||
func ClearSession(c *gin.Context) {
|
||||
s := sessions.Default(c)
|
||||
s.Clear()
|
||||
basePath := c.GetString("base_path")
|
||||
if basePath == "" {
|
||||
basePath = "/"
|
||||
}
|
||||
s.Options(sessions.Options{
|
||||
Path: "/",
|
||||
Path: basePath,
|
||||
MaxAge: -1,
|
||||
})
|
||||
s.Save()
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"remark" = "Remark"
|
||||
"protocol" = "Protocol"
|
||||
"port" = "Port"
|
||||
"portMap" = "Port Mapping"
|
||||
"traffic" = "Traffic"
|
||||
"details" = "Details"
|
||||
"transportConfig" = "Transport Config"
|
||||
@@ -221,9 +222,6 @@
|
||||
"requestHeader" = "Request Header"
|
||||
"responseHeader" = "Response Header"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "Encryption"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "Panel Settings"
|
||||
"save" = "Save"
|
||||
@@ -301,6 +299,14 @@
|
||||
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies."
|
||||
"fragment" = "Fragmentation"
|
||||
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
|
||||
"fragmentSett" = "Fragmentation Settings"
|
||||
"noisesDesc" = "Enable Noises."
|
||||
"noisesSett" = "Noises Settings"
|
||||
"mux" = "Mux"
|
||||
"muxDesc" = "Transmit multiple independent data streams within an established data stream."
|
||||
"muxSett" = "Mux Settings"
|
||||
"direct" = "Direct Connection"
|
||||
"directDesc" = "Directly establishes connections with domains or IP ranges of a specific country."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Modify Settings"
|
||||
@@ -321,14 +327,17 @@
|
||||
"logConfigsDesc" = "Enabling logs may affect your server's efficiency. It is recommended to enable it wisely only when necessary."
|
||||
"blockConfigs" = "Protection Shield"
|
||||
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
||||
"blockCountryConfigs" = "Block Country"
|
||||
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
|
||||
"directCountryConfigs" = "Direct Country"
|
||||
"directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country."
|
||||
"ipv4Configs" = "IPv4 Routing"
|
||||
"ipv4ConfigsDesc" = "These options will route traffic based on specific requested destination via server's IPv4."
|
||||
"warpConfigs" = "WARP Config"
|
||||
"warpConfigsDesc" = "These options will route traffic based on specific requested destination via WARP."
|
||||
"basicRouting" = "Basic Routing"
|
||||
"blockConnectionsConfigsDesc" = "These options will block traffic based on the specific requested country."
|
||||
"directConnectionsConfigsDesc" = "A direct connection ensures that specific traffic is not routed through another server."
|
||||
"blockips" = "Block IPs"
|
||||
"blockdomains" = "Block Domains"
|
||||
"directips" = "Direct IPs"
|
||||
"directdomains" = "Direct Domains"
|
||||
"ipv4Routing" = "IPv4 Routing"
|
||||
"ipv4RoutingDesc" = "These options will route traffic based on specific requested destination via server's IPv4."
|
||||
"warpRouting" = "WARP Routing"
|
||||
"warpRoutingDesc" = "These options will route traffic based on specific requested destination via WARP."
|
||||
"Template" = "Advanced Xray Configuration Template"
|
||||
"TemplateDesc" = "The final Xray config file will be generated based on this template."
|
||||
"FreedomStrategy" = "Freedom Protocol Strategy"
|
||||
@@ -337,50 +346,8 @@
|
||||
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
|
||||
"Torrent" = "Block BitTorrent Protocol"
|
||||
"TorrentDesc" = "Blocks BitTorrent protocol."
|
||||
"PrivateIp" = "Block Connection to Private IPs"
|
||||
"PrivateIpDesc" = "Blocks establishing connections to private IP ranges."
|
||||
"Ads" = "Block Ads"
|
||||
"AdsDesc" = "Blocks advertising websites."
|
||||
"Family" = "Family Protection"
|
||||
"FamilyDesc" = "Blocks adult content, and malware websites."
|
||||
"IRIp" = "Block Connection to Iran IPs"
|
||||
"IRIpDesc" = "Blocks establishing connections to Iran IP ranges."
|
||||
"IRDomain" = "Block Connection to Iran Domains"
|
||||
"IRDomainDesc" = "Blocks establishing connections to Iran domains."
|
||||
"ChinaIp" = "Block Connection to China IPs"
|
||||
"ChinaIpDesc" = "Blocks establishing connections to China IP ranges."
|
||||
"ChinaDomain" = "Block Connection to China Domains"
|
||||
"ChinaDomainDesc" = "Blocks establishing connections to China domains."
|
||||
"RussiaIp" = "Block Connection to Russia IPs"
|
||||
"RussiaIpDesc" = "Blocks establishing connections to Russia IP ranges."
|
||||
"RussiaDomain" = "Block Connection to Russia Domains"
|
||||
"RussiaDomainDesc" = "Blocks establishing connections to Russia domains."
|
||||
"DirectIRIp" = "Direct Connection to Iran IPs"
|
||||
"DirectIRIpDesc" = "Directly establishes connections to Iran IP ranges."
|
||||
"DirectIRDomain" = "Direct Connection to Iran Domains"
|
||||
"DirectIRDomainDesc" = "Directly establishes connections to Iran domains."
|
||||
"DirectChinaIp" = "Direct Connection to China IPs"
|
||||
"DirectChinaIpDesc" = "Directly establishes connections to China IP ranges."
|
||||
"DirectChinaDomain" = "Direct Connection to China Domains"
|
||||
"DirectChinaDomainDesc" = "Directly establishes connections to China domains."
|
||||
"DirectRussiaIp" = "Direct Connection to Russia IPs"
|
||||
"DirectRussiaIpDesc" = "Directly establishes connections to Russia IP ranges."
|
||||
"DirectRussiaDomain" = "Direct Connection to Russia Domains"
|
||||
"DirectRussiaDomainDesc" = "Directly establishes connections to Russia domains."
|
||||
"GoogleIPv4" = "Google"
|
||||
"GoogleIPv4Desc" = "Routes traffic to Google via IPv4."
|
||||
"NetflixIPv4" = "Netflix"
|
||||
"NetflixIPv4Desc" = "Routes traffic to Netflix via IPv4."
|
||||
"GoogleWARP" = "Google"
|
||||
"GoogleWARPDesc" = "Routes traffic to Google via WARP."
|
||||
"OpenAIWARP" = "ChatGPT"
|
||||
"OpenAIWARPDesc" = "Routes traffic to ChatGPT via WARP."
|
||||
"NetflixWARP" = "Netflix"
|
||||
"NetflixWARPDesc" = "Routes traffic to Netflix via WARP."
|
||||
"MetaWARP" = "Meta"
|
||||
"MetaWARPDesc" = "Routes traffic to Meta (Instagram, Facebook, WhatsApp, Threads,...) via WARP."
|
||||
"SpotifyWARP" = "Spotify"
|
||||
"SpotifyWARPDesc" = "Routes traffic to Spotify via WARP."
|
||||
"completeTemplate" = "All"
|
||||
"Inbounds" = "Inbounds"
|
||||
"Outbounds" = "Outbounds"
|
||||
@@ -393,6 +360,10 @@
|
||||
"accessLogDesc" = "The file path for the access log."
|
||||
"errorLog" = "Error Log"
|
||||
"errorLogDesc" = "The file path for the error log."
|
||||
"dnsLog" = "DNS Log"
|
||||
"dnsLogDesc" = "Whether to enable DNS query logs"
|
||||
"maskAddress" = "Mask Address"
|
||||
"maskAddressDesc" = "IP address mask, when enabled, will automatically replace the IP address that appears in the log."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "First"
|
||||
@@ -446,6 +417,12 @@
|
||||
"psk" = "PreShared Key"
|
||||
"domainStrategy" = "Domain Strategy"
|
||||
|
||||
[pages.xray.tun]
|
||||
"nameDesc" = "The name of the TUN interface. Default is 'xrayN', where N is some number"
|
||||
"mtuDesc" = "Maximum Transmission Unit. The maximum size of data packets. Default is 1500"
|
||||
"userLevel" = "User Level"
|
||||
"userLevelDesc" = "All connections made through this inbound will use this user level. Default is 0"
|
||||
|
||||
[pages.xray.dns]
|
||||
"enable" = "Enable DNS"
|
||||
"enableDesc" = "Enables built-in DNS server."
|
||||
@@ -456,6 +433,7 @@
|
||||
"add" = "Add Server"
|
||||
"edit" = "Edit Server"
|
||||
"domains" = "Domains"
|
||||
"expectIPs" = "Expect IPs"
|
||||
|
||||
[pages.xray.fakedns]
|
||||
"add" = "Add Fake DNS"
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"remark" = "نام"
|
||||
"protocol" = "پروتکل"
|
||||
"port" = "پورت"
|
||||
"portMap" = "پورتهای نظیر"
|
||||
"traffic" = "ترافیک"
|
||||
"details" = "جزئیات"
|
||||
"transportConfig" = "نحوه اتصال"
|
||||
@@ -220,9 +221,6 @@
|
||||
"requestHeader" = "سربرگ درخواست"
|
||||
"responseHeader" = "سربرگ پاسخ"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "رمزنگاری"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "تنظیمات پنل"
|
||||
"save" = "ذخیره"
|
||||
@@ -300,6 +298,14 @@
|
||||
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسیهای معکوس تنظیم شده، استفاده خواهدکرد"
|
||||
"fragment" = "تکهتکه شدن"
|
||||
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
||||
"fragmentSett" = "تنظیمات فرگمنت"
|
||||
"noisesDesc" = "فعال کردن Noises."
|
||||
"noisesSett" = "تنظیمات Noises"
|
||||
"mux" = "ماکس"
|
||||
"muxDesc" = "چندین جریان داده مستقل را در یک جریان داده ثابت منتقل می کند"
|
||||
"muxSett" = "تنظیمات ماکس"
|
||||
"direct" = "اتصال مستقیم"
|
||||
"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آیپی یک کشور خاص ارتباط برقرار می کند"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "ویرایش تنظیمات"
|
||||
@@ -320,14 +326,17 @@
|
||||
"logConfigsDesc" = "فعال کردن گزارش ممکن است بر عملکرد سرور شما تأثیر بگذارد. توصیه میشود فقط در صورت لزوم آن را با دقت فعال کنید"
|
||||
"blockConfigs" = "سپر محافظ"
|
||||
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
||||
"blockCountryConfigs" = "مسدودسازی کشور"
|
||||
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
|
||||
"directCountryConfigs" = "اتصال مستقیم کشور"
|
||||
"directCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص بصورت مستقیم ارسال میکند"
|
||||
"ipv4Configs" = "IPv4 مسیریابی"
|
||||
"ipv4ConfigsDesc" = "این گزینهها ترافیک را از طریق آیپی نسخه4 سرور، به مقصد هدایت میکند"
|
||||
"warpConfigs" = "WARP تنظمیات"
|
||||
"warpConfigsDesc" = "این گزینهها ترافیک را از طریق وارپ کلادفلر به مقصد هدایت میکند"
|
||||
"basicRouting" = "مسیریابی پایه"
|
||||
"blockConnectionsConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستشده خاص مسدود میکنند."
|
||||
"directConnectionsConfigsDesc" = "یک اتصال مستقیم تضمین میکند که ترافیک خاص از طریق سرور دیگری مسیریابی نشود."
|
||||
"blockips" = "مسدود کردن آیپیها"
|
||||
"blockdomains" = "مسدود کردن دامنهها"
|
||||
"directips" = "آیپیهای مستقیم"
|
||||
"directdomains" = "دامنههای مستقیم"
|
||||
"ipv4Routing" = "IPv4 مسیریابی"
|
||||
"ipv4RoutingDesc" = "این گزینهها ترافیک را از طریق آیپی نسخه4 سرور، به مقصد هدایت میکند"
|
||||
"warpRouting" = "WARP مسیریابی"
|
||||
"warpRoutingDesc" = "این گزینهها ترافیک را از طریق وارپ کلادفلر به مقصد هدایت میکند"
|
||||
"Template" = "پیکربندی پیشرفته الگو ایکسری"
|
||||
"TemplateDesc" = "فایل پیکربندی نهایی ایکسری بر اساس این الگو ایجاد میشود"
|
||||
"FreedomStrategy" = "Freedom استراتژی پروتکل"
|
||||
@@ -336,51 +345,8 @@
|
||||
"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواستها را تعیین میکند"
|
||||
"Torrent" = "مسدودسازی پروتکل بیتتورنت"
|
||||
"TorrentDesc" = "پروتکل بیت تورنت را مسدود میکند"
|
||||
"PrivateIp" = "مسدودسازی اتصال آیپیهای خصوصی"
|
||||
"PrivateIpDesc" = "اتصال به آیپیهای رنج خصوصی را مسدود میکند"
|
||||
"Ads" = "مسدودسازی تبلیغات"
|
||||
"AdsDesc" = "وبسایتهای تبلیغاتی را مسدود میکند"
|
||||
"Family" = "محافظ خانواده"
|
||||
"FamilyDesc" = "محتوای مخصوص بزرگسالان، و وبسایتهای ناامن را مسدود میکند"
|
||||
"IRIp" = "مسدودسازی اتصال به آیپیهای ایران"
|
||||
"IRIpDesc" = "اتصال به آیپیهای کشور ایران را مسدود میکند"
|
||||
"IRDomain" = "مسدودسازی اتصال به دامنههای ایران"
|
||||
"IRDomainDesc" = "اتصال به دامنههای کشور ایران را مسدود میکند"
|
||||
"ChinaIp" = "مسدودسازی اتصال به آیپیهای چین"
|
||||
"ChinaIpDesc" = "اتصال به آیپیهای کشور چین را مسدود میکند"
|
||||
"ChinaDomain" = "مسدودسازی اتصال به دامنههای چین"
|
||||
"ChinaDomainDesc" = "اتصال به دامنههای کشور چین را مسدود میکند"
|
||||
"RussiaIp" = "مسدودسازی اتصال به آیپیهای روسیه"
|
||||
"RussiaIpDesc" = "اتصال به آیپیهای کشور روسیه را مسدود میکند"
|
||||
"RussiaDomain" = "مسدودسازی اتصال به دامنههای روسیه"
|
||||
"RussiaDomainDesc" = "اتصال به دامنههای کشور روسیه را مسدود میکند"
|
||||
"DirectIRIp" = "اتصال مستقیم آیپیهای ایران"
|
||||
"DirectIRIpDesc" = "اتصال مستقیم به آیپیهای کشور ایران"
|
||||
"DirectIRDomain" = "اتصال مستقیم دامنههای ایران"
|
||||
"DirectIRDomainDesc" = "اتصال مستقیم به دامنههای کشور ایران"
|
||||
"DirectChinaIp" = "اتصال مستقیم آیپیهای چین"
|
||||
"DirectChinaIpDesc" = "اتصال مستقیم به آیپیهای کشور چین"
|
||||
"DirectChinaDomain" = "ارتباط مستقیم دامنههای چین"
|
||||
"DirectChinaDomainDesc" = "اتصال مستقیم به دامنههای کشور چین"
|
||||
"DirectRussiaIp" = "ارتباط مستقیم آیپیهای روسیه"
|
||||
"DirectRussiaIpDesc" = "اتصال مستقیم به آیپیهای کشور روسیه"
|
||||
"DirectRussiaDomain" = "ارتباط مستقیم دامنههای روسیه"
|
||||
"DirectRussiaDomainDesc" = "اتصال مستقیم به دامنههای کشور روسیه"
|
||||
"GoogleIPv4" = "گوگل"
|
||||
"GoogleIPv4Desc" = "ترافیک را از طریق آیپی نسخه4، به گوگل هدایت میکند"
|
||||
"NetflixIPv4" = "نتفلیکس"
|
||||
"NetflixIPv4Desc" = "ترافیک را از طریق آیپی نسخه4، به نتفلیکس هدایت میکند"
|
||||
"completeTemplate" = "کامل"
|
||||
"GoogleWARP" = "گوگل"
|
||||
"GoogleWARPDesc" = "ترافیک را از طریق وارپ به گوگل هدایت میکند"
|
||||
"OpenAIWARP" = "چت جیپیتی"
|
||||
"OpenAIWARPDesc" = "ترافیک را از طریق وارپ به چت جیپیتی هدایت میکند"
|
||||
"NetflixWARP" = "نتفلیکس"
|
||||
"NetflixWARPDesc" = "ترافیک را از طریق وارپ به نتفلیکس هدایت میکند"
|
||||
"MetaWARP" = "متا"
|
||||
"MetaWARPDesc" = "ترافیک را از طریق وارپ به متا (اینستاگرام، فیس بوک، واتساپ، تردز و...) هدایت می کند."
|
||||
"SpotifyWARP" = "اسپاتیفای"
|
||||
"SpotifyWARPDesc" = " ترافیک را از طریق وارپ به اسپاتیفای هدایت میکند"
|
||||
"Inbounds" = "ورودیها"
|
||||
"Outbounds" = "خروجیها"
|
||||
"Routings" = "قوانین مسیریابی"
|
||||
@@ -392,6 +358,10 @@
|
||||
"accessLogDesc" = "مسیر فایل گزارش دسترسی"
|
||||
"errorLog" = "گزارش خطا"
|
||||
"errorLogDesc" = "مسیر فایل گزارش خطا"
|
||||
"dnsLog" = "گزارش DNS"
|
||||
"dnsLogDesc" = "آیا ثبتهای درخواست DNS را فعال کنید"
|
||||
"maskAddress" = "پنهان کردن آدرس"
|
||||
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال میشود، به طور خودکار آدرس IP که در لاگ ظاهر میشود را جایگزین میکند."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "اولین"
|
||||
@@ -445,6 +415,12 @@
|
||||
"psk" = "کلید مشترک"
|
||||
"domainStrategy" = "استراتژی حل دامنه"
|
||||
|
||||
[pages.xray.tun]
|
||||
"nameDesc" = "نام رابط TUN. مقدار پیشفرض 'xrayN', N یک عدد است"
|
||||
"mtuDesc" = "واحد انتقال حداکثر. بیشترین اندازه بستههای داده. مقدار پیشفرض 1500 است"
|
||||
"userLevel" = "سطح کاربر"
|
||||
"userLevelDesc" = "تمام اتصالات انجامشده از طریق این ورودی از این سطح کاربری استفاده خواهند کرد. مقدار پیشفرض 0 است"
|
||||
|
||||
[pages.xray.dns]
|
||||
"enable" = "فعال کردن حل دامنه"
|
||||
"enableDesc" = "سرور حل دامنه داخلی را فعال میکند"
|
||||
@@ -455,6 +431,7 @@
|
||||
"add" = "افزودن سرور"
|
||||
"edit" = "ویرایش سرور"
|
||||
"domains" = "دامنهها"
|
||||
"expectIPs" = "آیپیهای مورد انتظار"
|
||||
|
||||
[pages.xray.fakedns]
|
||||
"add" = "افزودن دیاناس جعلی"
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"remark" = "Примечание"
|
||||
"protocol" = "Протокол"
|
||||
"port" = "Порт"
|
||||
"portMap" = "Порт-перенаправление"
|
||||
"traffic" = "Трафик"
|
||||
"details" = "Подробнее"
|
||||
"transportConfig" = "Перенести"
|
||||
@@ -221,9 +222,6 @@
|
||||
"requestHeader" = "Заголовок запроса"
|
||||
"responseHeader" = "Заголовок ответа"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "Шифрование"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "Настройки"
|
||||
"save" = "Сохранить"
|
||||
@@ -301,6 +299,14 @@
|
||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||
"fragment" = "Фрагментация"
|
||||
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
||||
"fragmentSett" = "Настройки фрагментации"
|
||||
"noisesDesc" = "Включить Noises."
|
||||
"noisesSett" = "Настройки Noises"
|
||||
"mux" = "Mux"
|
||||
"muxDesc" = "Передача нескольких независимых потоков данных в рамках установленного потока данных."
|
||||
"muxSett" = "Mux Настройки"
|
||||
"direct" = "Прямая связь"
|
||||
"directDesc" = "Напрямую устанавливает соединения с доменами или диапазонами IP конкретной страны."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Изменение настроек"
|
||||
@@ -321,14 +327,17 @@
|
||||
"logConfigsDesc" = "Включение журнала может повлиять на эффективность вашего сервера. Рекомендуется включать его разумно, только когда это необходимо."
|
||||
"blockConfigs" = "Блокирующие конфигурации"
|
||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
||||
"blockCountryConfigs" = "Конфигурация блокировки стран"
|
||||
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны."
|
||||
"directCountryConfigs" = "Прямые настройки стран"
|
||||
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
||||
"ipv4Configs" = "Настройки IPv4"
|
||||
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
||||
"warpConfigs" = "Настройки WARP"
|
||||
"warpConfigsDesc" = "WARP будет направлять трафик на веб-сайты через серверы Cloudflare"
|
||||
"basicRouting" = "Базовые соединения"
|
||||
"blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от запрашиваемой страны."
|
||||
"directConnectionsConfigsDesc" = "Прямое соединение гарантирует, что определенный трафик не будет перенаправлен через другой сервер."
|
||||
"blockips" = "Блокировать IP"
|
||||
"blockdomains" = "Блокировать домены"
|
||||
"directips" = "Прямые IP"
|
||||
"directdomains" = "Прямые домены"
|
||||
"ipv4Routing" = "Правила IPv4"
|
||||
"ipv4RoutingDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
||||
"warpRouting" = "Правила WARP"
|
||||
"warpRoutingDesc" = "WARP будет направлять трафик на веб-сайты через серверы Cloudflare"
|
||||
"Template" = "Шаблон конфигурации Xray"
|
||||
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
||||
"FreedomStrategy" = "Настроить стратегию протокола Freedom"
|
||||
@@ -337,50 +346,8 @@
|
||||
"RoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
||||
"Torrent" = "Запретить использование BitTorrent"
|
||||
"TorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
||||
"PrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
||||
"PrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов."
|
||||
"Ads" = "Блокировка рекламы"
|
||||
"AdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу."
|
||||
"Family" = "Включить семейную конфигурацию"
|
||||
"FamilyDesc" = "Избегать подключения к небезопасным веб-сайтам для всей семьи"
|
||||
"IRIp" = "Отключить подключение к диапазонам IP-адресов Ирана"
|
||||
"IRIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Ирана."
|
||||
"IRDomain" = "Отключить подключение к доменам Ирана"
|
||||
"IRDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Ирана."
|
||||
"ChinaIp" = "Отключить подключение к диапазонам IP-адресов Китая"
|
||||
"ChinaIpDesc" = "Измените конфигурацию, чтобы отключить подключение к диапазонам IP-адресов Китая."
|
||||
"ChinaDomain" = "Отключить подключение к доменам Китая"
|
||||
"ChinaDomainDesc" = "Измените конфигурацию, чтобы отключить подключение к доменам Китая."
|
||||
"RussiaIp" = "Отключить подключение к диапазонам IP-адресов России"
|
||||
"RussiaIpDesc" = "Измените конфигурацию, чтобы отключить соединения с диапазонами IP-адресов России."
|
||||
"RussiaDomain" = "Отключить подключение к доменам России"
|
||||
"RussiaDomainDesc" = "Измените конфигурацию, чтобы избежать подключения к доменам России."
|
||||
"DirectIRIp" = "Прямое подключение к диапазонам IP-адресов Ирана"
|
||||
"DirectIRIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Ирана"
|
||||
"DirectIRDomain" = "Прямое подключение к доменам Ирана"
|
||||
"DirectIRDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Ирана"
|
||||
"DirectChinaIp" = "Прямое подключение к диапазонам IP-адресов Китая"
|
||||
"DirectChinaIpDesc" = "Измените шаблон конфигурации для прямого подключения к диапазонам IP-адресов Китая"
|
||||
"DirectChinaDomain" = "Прямое подключение к доменам Китая"
|
||||
"DirectChinaDomainDesc" = "Измените шаблон конфигурации для прямого подключения к доменам Китая"
|
||||
"DirectRussiaIp" = "Прямое подключение к диапазонам IP-адресов России"
|
||||
"DirectRussiaIpDesc" = "Изменить шаблон конфигурации для прямого подключения к диапазонам IP-адресов России"
|
||||
"DirectRussiaDomain" = "Прямое подключение к доменам России"
|
||||
"DirectRussiaDomainDesc" = "Изменить шаблон конфигурации для прямого подключения к доменам России"
|
||||
"GoogleIPv4" = "Google"
|
||||
"GoogleIPv4Desc" = "Применить маршрутизацию Google для подключения к IPv4."
|
||||
"NetflixIPv4" = "Netflix"
|
||||
"NetflixIPv4Desc" = "Применить маршрутизацию Netflix для подключения к IPv4."
|
||||
"GoogleWARP" = "Google"
|
||||
"GoogleWARPDesc" = "Добавить маршрутизацию для Google через WARP"
|
||||
"OpenAIWARP" = "ChatGPT"
|
||||
"OpenAIWARPDesc" = "Добавить маршрутизацию для ChatGPT через WARP"
|
||||
"NetflixWARP" = "Netflix"
|
||||
"NetflixWARPDesc" = "Добавить маршрутизацию для Netflix через WARP"
|
||||
"MetaWARP" = "Meta"
|
||||
"MetaWARPDesc" = "Направляет трафик в Meta (Instagram, Facebook, WhatsApp, Threads...) через WARP."
|
||||
"SpotifyWARP" = "Spotify"
|
||||
"SpotifyWARPDesc" = "Добавить маршрутизацию для Spotify через WARP"
|
||||
"completeTemplate" = "Все"
|
||||
"Inbounds" = "Входящие"
|
||||
"Outbounds" = "Исходящие"
|
||||
@@ -393,6 +360,10 @@
|
||||
"accessLogDesc" = "Путь к файлу журнала доступа."
|
||||
"errorLog" = "Журнал ошибок"
|
||||
"errorLogDesc" = "Путь к файлу журнала ошибок."
|
||||
"dnsLog" = "DNS Журнал"
|
||||
"dnsLogDesc" = "Включить логи запросов DNS"
|
||||
"maskAddress" = "Маскировать Адрес"
|
||||
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Первый"
|
||||
@@ -446,6 +417,12 @@
|
||||
"psk" = "Общий ключ"
|
||||
"domainStrategy" = "Стратегия домена"
|
||||
|
||||
[pages.xray.tun]
|
||||
"nameDesc" = "Имя интерфейса TUN. Значение по умолчанию - 'xrayN', где N - номер интерфейса."
|
||||
"mtuDesc" = "Максимальная единица передачи. Максимальный размер пакетов данных. Значение по умолчанию - 1500"
|
||||
"userLevel" = "Уровень пользователя"
|
||||
"userLevelDesc" = "Все соединения, установленные через этот входящий поток, будут использовать этот уровень пользователя. Значение по умолчанию - 0"
|
||||
|
||||
[pages.xray.dns]
|
||||
"enable" = "Включить DNS"
|
||||
"enableDesc" = "Включить встроенный DNS-сервер"
|
||||
@@ -456,6 +433,7 @@
|
||||
"add" = "Добавить сервер"
|
||||
"edit" = "Редактировать сервер"
|
||||
"domains" = "Домены"
|
||||
"expectIPs" = "Ожидаемые IP"
|
||||
|
||||
[pages.xray.fakedns]
|
||||
"add" = "Добавить поддельный DNS"
|
||||
|
||||
@@ -123,6 +123,7 @@
|
||||
"remark" = "Nhận xét"
|
||||
"protocol" = "Giao thức"
|
||||
"port" = "Cổng"
|
||||
"portMap" = "Cổng điều chính"
|
||||
"traffic" = "Lưu lượng"
|
||||
"details" = "Chi tiết"
|
||||
"transportConfig" = "Cấu hình vận chuyển"
|
||||
@@ -221,9 +222,6 @@
|
||||
"requestHeader" = "Tiêu đề yêu cầu"
|
||||
"responseHeader" = "Tiêu đề phản hồi"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "Mã hóa"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "Cài đặt"
|
||||
"save" = "Lưu"
|
||||
@@ -301,6 +299,14 @@
|
||||
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
|
||||
"fragment" = "Sự phân mảnh"
|
||||
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
|
||||
"fragmentSett" = "Cài đặt phân mảnh"
|
||||
"noisesDesc" = "Bật Noises."
|
||||
"noisesSett" = "Cài đặt Noises"
|
||||
"mux" = "Mux"
|
||||
"muxDesc" = "Truyền nhiều luồng dữ liệu độc lập trong luồng dữ liệu đã thiết lập."
|
||||
"muxSett" = "Mux Cài đặt"
|
||||
"direct" = "Kết nối trực tiếp"
|
||||
"directDesc" = "Trực tiếp thiết lập kết nối với tên miền hoặc dải IP của một quốc gia cụ thể."
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "Sửa đổi cài đặt"
|
||||
@@ -318,17 +324,20 @@
|
||||
"generalConfigs" = "Cấu hình Chung"
|
||||
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
|
||||
"logConfigs" = "Cài đặt nhật ký"
|
||||
"logConfigsDesc" = "Bật nhật ký có thể ảnh hưởng đến hiệu suất của máy chủ của bạn. Đề xuất chỉ nên bật khi cần thiết và một cách sáng suốt."
|
||||
"logConfigsDesc" = "Bật nhật ký có thể ảnh hưởng đến hiệu suất của máy chủ của bạn. Đề xuất chỉ nên bật khi cần thiết và một cách sáng suốt."
|
||||
"blockConfigs" = "Cấu hình Chặn"
|
||||
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
|
||||
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
||||
"blockCountryConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các tên miền quốc gia cụ thể."
|
||||
"directCountryConfigs" = "Cấu hình Kết nối Trực tiếp Quốc gia"
|
||||
"directCountryConfigsDesc" = "Những tùy chọn này sẽ kết nối người dùng trực tiếp đến các tên miền quốc gia cụ thể."
|
||||
"ipv4Configs" = "Cấu hình IPv4"
|
||||
"ipv4ConfigsDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4."
|
||||
"warpConfigs" = "Cấu hình WARP"
|
||||
"warpConfigsDesc" = "WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare."
|
||||
"basicRouting" = "Định tuyến Cơ bản"
|
||||
"blockConnectionsConfigsDesc" = "Các tùy chọn này sẽ chặn lưu lượng truy cập dựa trên quốc gia được yêu cầu cụ thể."
|
||||
"directConnectionsConfigsDesc" = "Kết nối trực tiếp đảm bảo rằng lưu lượng truy cập cụ thể không được định tuyến qua máy chủ khác."
|
||||
"blockips" = "Chặn IP"
|
||||
"blockdomains" = "Chặn Tên Miền"
|
||||
"directips" = "IP Trực Tiếp"
|
||||
"directdomains" = "Tên Miền Trực Tiếp"
|
||||
"ipv4Routing" = "Định tuyến IPv4"
|
||||
"ipv4RoutingDesc" = "Những tùy chọn này sẽ chỉ định kết nối đến các tên miền mục tiêu qua IPv4."
|
||||
"warpRouting" = "Định tuyến WARP"
|
||||
"warpRoutingDesc" = "WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare."
|
||||
"Template" = "Mẫu cấu hình Xray"
|
||||
"TemplateDesc" = "Tạo tệp cấu hình Xray cuối cùng dựa trên mẫu này."
|
||||
"FreedomStrategy" = "Cấu hình chiến lược cho giao thức tự do"
|
||||
@@ -337,50 +346,8 @@
|
||||
"RoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể để phân giải DNS."
|
||||
"Torrent" = "Cấm sử dụng BitTorrent"
|
||||
"TorrentDesc" = "Thay đổi mẫu cấu hình để tránh việc người dùng sử dụng BitTorrent."
|
||||
"PrivateIp" = "Cấm dãy IP riêng để kết nối"
|
||||
"PrivateIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối với dải IP riêng."
|
||||
"Ads" = "Chặn quảng cáo"
|
||||
"AdsDesc" = "Thay đổi mẫu cấu hình để chặn quảng cáo"
|
||||
"Family" = "Kích hoạt cấu hình thân thiện với gia đình"
|
||||
"FamilyDesc" = "Tránh kết nối đến các trang web không an toàn để bảo vệ gia đình."
|
||||
"IRIp" = "Vô hiệu hóa kết nối với dải IP Iran"
|
||||
"IRIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối với dãy IP Iran."
|
||||
"IRDomain" = "Vô hiệu hóa kết nối với tên miền Iran"
|
||||
"IRDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối với tên miền Iran."
|
||||
"ChinaIp" = "Vô hiệu hóa kết nối với dải IP Trung Quốc"
|
||||
"ChinaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối tới dãy IP Trung Quốc."
|
||||
"ChinaDomain" = "Vô hiệu hóa kết nối với tên miền Trung Quốc"
|
||||
"ChinaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối với miền Trung Quốc."
|
||||
"RussiaIp" = "Vô hiệu hóa kết nối với dải IP của Nga"
|
||||
"RussiaIpDesc" = "Thay đổi mẫu cấu hình để tránh kết nối với dãy IP của Nga."
|
||||
"RussiaDomain" = "Vô hiệu hóa kết nối với tên miền của Nga"
|
||||
"RussiaDomainDesc" = "Thay đổi mẫu cấu hình để tránh kết nối với miền Nga."
|
||||
"DirectIRIp" = "Kết nối trực tiếp tới dãy IP Iran"
|
||||
"DirectIRIpDesc" = "Thay đổi mẫu cấu hình để kết nối trực tiếp với dải IP Iran."
|
||||
"DirectIRDomain" = "Kết nối trực tiếp tới các miền của Iran"
|
||||
"DirectIRDomainDesc" = "Thay đổi mẫu cấu hình để kết nối trực tiếp với miền Iran."
|
||||
"DirectChinaIp" = "Thay đổi mẫu cấu hình để kết nối trực tiếp với miền Iran."
|
||||
"DirectChinaIpDesc" = "Thay đổi mẫu cấu hình để kết nối trực tiếp với dải IP Trung Quốc."
|
||||
"DirectChinaDomain" = "Kết nối trực tiếp tới các miền Trung Quốc"
|
||||
"DirectChinaDomainDesc" = "Kết nối trực tiếp tới các miền Trung Quốc"
|
||||
"DirectRussiaIp" = "Kết nối trực tiếp tới dãy IP của Nga"
|
||||
"DirectRussiaIpDesc" = "Thay đổi mẫu cấu hình để kết nối trực tiếp với dải IP của Nga."
|
||||
"DirectRussiaDomain" = "Kết nối trực tiếp tới các miền của Nga"
|
||||
"DirectRussiaDomainDesc" = "Thay đổi mẫu cấu hình để kết nối trực tiếp với miền Nga."
|
||||
"GoogleIPv4" = "Google"
|
||||
"GoogleIPv4Desc" = "Thêm định tuyến để Google kết nối với IPv4."
|
||||
"NetflixIPv4" = "Netflix"
|
||||
"NetflixIPv4Desc" = "Thêm định tuyến cho Netflix để kết nối với IPv4."
|
||||
"GoogleWARP" = "Google"
|
||||
"GoogleWARPDesc" = "Thêm định tuyến cho Google qua WARP."
|
||||
"OpenAIWARP" = "ChatGPT"
|
||||
"OpenAIWARPDesc" = "Thêm định tuyến cho ChatGPT qua WARP."
|
||||
"NetflixWARP" = "Netflix"
|
||||
"NetflixWARPDesc" = "Thêm định tuyến cho Netflix qua WARP."
|
||||
"MetaWARP" = "Meta"
|
||||
"MetaWARPDesc" = "Định tuyến lưu lượng truy cập tới Meta (Instagram, Facebook, WhatsApp, Threads,...) thông qua WARP."
|
||||
"SpotifyWARP" = "Spotify"
|
||||
"SpotifyWARPDesc" = "Thêm định tuyến cho Spotify qua WARP."
|
||||
"completeTemplate" = "Tất cả"
|
||||
"Inbounds" = "Đầu vào"
|
||||
"Outbounds" = "Đầu ra"
|
||||
@@ -393,6 +360,10 @@
|
||||
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập."
|
||||
"errorLog" = "Nhật ký lỗi"
|
||||
"errorLogDesc" = "Đường dẫn tệp cho nhật ký lỗi."
|
||||
"dnsLog" = "Nhật ký DNS"
|
||||
"dnsLogDesc" = "Có bật nhật ký truy vấn DNS không"
|
||||
"maskAddress" = "Ẩn Địa Chỉ"
|
||||
"maskAddressDesc" = "Mặt nạ địa chỉ IP, khi được bật, sẽ tự động thay thế địa chỉ IP xuất hiện trong nhật ký."
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "Đầu tiên"
|
||||
@@ -446,6 +417,12 @@
|
||||
"psk" = "Khóa chia sẻ"
|
||||
"domainStrategy" = "Chiến lược tên miền"
|
||||
|
||||
[pages.xray.tun]
|
||||
"nameDesc" = "Tên của giao diện TUN. Giá trị mặc định là 'xrayN', với N là số nguyen."
|
||||
"mtuDesc" = "Đơn vị Truyền Tối đa. Kích thước tối đa của các gói dữ liệu. Giá trị mặc định là 1500"
|
||||
"userLevel" = "Mức Người Dùng"
|
||||
"userLevelDesc" = "Tất cả các kết nối được thực hiện thông qua inbound này sẽ sử dụng mức người dùng này. Giá trị mặc định là 0"
|
||||
|
||||
[pages.xray.dns]
|
||||
"enable" = "Kích hoạt DNS"
|
||||
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
||||
@@ -456,6 +433,7 @@
|
||||
"add" = "Thêm máy chủ"
|
||||
"edit" = "Chỉnh sửa máy chủ"
|
||||
"domains" = "Tên miền"
|
||||
"expectIPs" = "Các IP Dự Kiến"
|
||||
|
||||
[pages.xray.fakedns]
|
||||
"add" = "Thêm DNS giả"
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
"emptyUsername" = "请输入用户名"
|
||||
"emptyPassword" = "请输入密码"
|
||||
"wrongUsernameOrPassword" = "用户名或密码错误"
|
||||
"successLogin" = "登录成功"
|
||||
"successLogin" = "登录"
|
||||
|
||||
[pages.index]
|
||||
"title" = "系统状态"
|
||||
@@ -123,6 +123,7 @@
|
||||
"remark" = "备注"
|
||||
"protocol" = "协议"
|
||||
"port" = "端口"
|
||||
"portMap" = "端口映射"
|
||||
"traffic" = "流量"
|
||||
"details" = "详细信息"
|
||||
"transportConfig" = "传输配置"
|
||||
@@ -221,9 +222,6 @@
|
||||
"requestHeader" = "请求头"
|
||||
"responseHeader" = "响应头"
|
||||
|
||||
[pages.inbounds.stream.quic]
|
||||
"encryption" = "加密"
|
||||
|
||||
[pages.settings]
|
||||
"title" = "设置"
|
||||
"save" = "保存配置"
|
||||
@@ -301,6 +299,14 @@
|
||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||
"fragment" = "碎片"
|
||||
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
||||
"fragmentSett" = "设置"
|
||||
"noisesDesc" = "启用 Noises."
|
||||
"noisesSett" = "Noises 设置"
|
||||
"mux" = "多路复用器"
|
||||
"muxDesc" = "在已建立的数据流内传输多个独立的数据流"
|
||||
"muxSett" = "复用器设置"
|
||||
"direct" = "直接连接"
|
||||
"directDesc" = "直接与特定国家的域或IP范围建立连接"
|
||||
|
||||
[pages.settings.toasts]
|
||||
"modifySettings" = "修改设置"
|
||||
@@ -321,14 +327,17 @@
|
||||
"logConfigsDesc" = "启用日志可能会影响服务器的效率。建议仅在必要时明智地启用它。"
|
||||
"blockConfigs" = "阻塞配置"
|
||||
"blockConfigsDesc" = "这些选项将禁止用户连接到特定协议和网站"
|
||||
"blockCountryConfigs" = "禁连国家配置"
|
||||
"blockCountryConfigsDesc" = "这些选项将禁止用户连接到特定国家/地区的域。"
|
||||
"directCountryConfigs" = "直连国家配置"
|
||||
"directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。"
|
||||
"ipv4Configs" = "IPv4 配置"
|
||||
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
||||
"warpConfigs" = "WARP 配置"
|
||||
"warpConfigsDesc" = "WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
||||
"basicRouting" = "基本路由"
|
||||
"blockConnectionsConfigsDesc" = "这些选项将根据特定的请求国家阻止流量。"
|
||||
"directConnectionsConfigsDesc" = "直接连接确保特定的流量不会通过其他服务器路由。"
|
||||
"blockips" = "阻止IP"
|
||||
"blockdomains" = "阻止域名"
|
||||
"directips" = "直接IP"
|
||||
"directdomains" = "直接域名"
|
||||
"ipv4Routing" = "IPv4 路由"
|
||||
"ipv4RoutingDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
||||
"warpRouting" = "WARP 路由"
|
||||
"warpRoutingDesc" = "WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
||||
"Template" = "Xray 配置模板"
|
||||
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件,重新启动面板生成效率"
|
||||
"FreedomStrategy" = "配置自由协议的策略"
|
||||
@@ -337,50 +346,8 @@
|
||||
"RoutingStrategyDesc" = "设置DNS解析的整体路由策略"
|
||||
"Torrent" = "禁止使用 bitTorrent"
|
||||
"TorrentDesc" = "更改配置模板避免用户使用 bitTorrent"
|
||||
"PrivateIp" = "禁止私人 IP 范围连接"
|
||||
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
|
||||
"Ads" = "屏蔽广告"
|
||||
"AdsDesc" = "修改配置模板屏蔽广告"
|
||||
"Family" = "启用家庭友好配置"
|
||||
"FamilyDesc" = "避免为家人连接到不安全的网站"
|
||||
"IRIp" = "禁止伊朗 IP 范围连接"
|
||||
"IRIpDesc" = "修改配置模板避免连接伊朗IP段"
|
||||
"IRDomain" = "禁止伊朗域连接"
|
||||
"IRDomainDesc" = "更改配置模板避免连接伊朗域名"
|
||||
"ChinaIp" = "禁止中国 IP 范围连接"
|
||||
"ChinaIpDesc" = "修改配置模板避免连接中国IP段"
|
||||
"ChinaDomain" = "禁止中国域名连接"
|
||||
"ChinaDomainDesc" = "更改配置模板避免连接中国域"
|
||||
"RussiaIp" = "禁止俄罗斯 IP 范围连接"
|
||||
"RussiaIpDesc" = "修改配置模板避免连接俄罗斯IP范围"
|
||||
"RussiaDomain" = "禁止俄罗斯域连接"
|
||||
"RussiaDomainDesc" = "更改配置模板避免连接俄罗斯域"
|
||||
"DirectIRIp" = "直接连接到伊朗 IP 范围"
|
||||
"DirectIRIpDesc" = "更改直接连接到伊朗 IP 范围的配置模板"
|
||||
"DirectIRDomain" = "直接连接到伊朗域"
|
||||
"DirectIRDomainDesc" = "更改直接连接到伊朗域的配置模板"
|
||||
"DirectChinaIp" = "直连中国IP范围"
|
||||
"DirectChinaIpDesc" = "更改直连中国 IP 范围的配置模板"
|
||||
"DirectChinaDomain" = "直连中国域名"
|
||||
"DirectChinaDomainDesc" = "修改中国域名直连配置模板"
|
||||
"DirectRussiaIp" = "直接连接到俄罗斯 IP 范围"
|
||||
"DirectRussiaIpDesc" = "更改直接连接到俄罗斯 IP 范围的配置模板"
|
||||
"DirectRussiaDomain" = "直接连接到俄罗斯域"
|
||||
"DirectRussiaDomainDesc" = "更改直接连接到俄罗斯域的配置模板"
|
||||
"GoogleIPv4" = "Google"
|
||||
"GoogleIPv4Desc" = "添加谷歌连接IPv4的路由"
|
||||
"NetflixIPv4" = "Netflix"
|
||||
"NetflixIPv4Desc" = "添加Netflix连接IPv4的路由"
|
||||
"GoogleWARP" = "将谷歌路由到 WARP"
|
||||
"GoogleWARPDesc" = "为谷歌添加路由到WARP"
|
||||
"OpenAIWARP" = "ChatGPT"
|
||||
"OpenAIWARPDesc" = "将 ChatGPT 路由添加到WARP"
|
||||
"NetflixWARP" = "Netflix"
|
||||
"NetflixWARPDesc" = "为Netflix添加路由到WARP"
|
||||
"MetaWARP"="Meta"
|
||||
"MetaWARPDesc" = "通过 WARP 将流量路由到 Meta(Instagram, Facebook, WhatsApp, Threads,...)"
|
||||
"SpotifyWARP" = "Spotify"
|
||||
"SpotifyWARPDesc" = "将Spotify添加路由到WARP"
|
||||
"completeTemplate" = "全部"
|
||||
"Inbounds" = "界内"
|
||||
"Outbounds" = "出站"
|
||||
@@ -393,6 +360,10 @@
|
||||
"accessLogDesc" = "访问日志的文件路径。"
|
||||
"errorLog" = "错误日志"
|
||||
"errorLogDesc" = "错误日志的文件路径。"
|
||||
"dnsLog" = "DNS 日志"
|
||||
"dnsLogDesc" = "是否启用 DNS 查询日志"
|
||||
"maskAddress" = "隐藏地址"
|
||||
"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。"
|
||||
|
||||
[pages.xray.rules]
|
||||
"first" = "第一个"
|
||||
@@ -446,6 +417,12 @@
|
||||
"psk" = "共享密钥"
|
||||
"domainStrategy" = "域策略"
|
||||
|
||||
[pages.xray.tun]
|
||||
"nameDesc" = "TUN 接口的名称。默认值为 'xrayN', 其中 N 是接口的编号。"
|
||||
"mtuDesc" = "最大传输单元。数据包的最大大小。默认值为 1500"
|
||||
"userLevel" = "用户级别"
|
||||
"userLevelDesc" = "通过此入站的所有连接都将使用此用户级别。默认值为 0"
|
||||
|
||||
[pages.xray.dns]
|
||||
"enable" = "启用 DNS"
|
||||
"enableDesc" = "启用内置 DNS 服务器"
|
||||
@@ -456,6 +433,7 @@
|
||||
"add" = "添加服务器"
|
||||
"edit" = "编辑服务器"
|
||||
"domains" = "域"
|
||||
"expectIPs" = "预期 IP"
|
||||
|
||||
[pages.xray.fakedns]
|
||||
"add" = "添加假 DNS"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user