mirror of
https://github.com/alireza0/x-ui.git
synced 2026-03-20 15:55:48 +00:00
Compare commits
194 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
f50bc16b61 | ||
|
|
0bd090d087 | ||
|
|
54b48123c5 | ||
|
|
6affec09d3 | ||
|
|
22ac7fe3cc | ||
|
|
f5ce84ae68 | ||
|
|
f6535dd83e | ||
|
|
4d9c68dd7b | ||
|
|
84ecdae7ca | ||
|
|
073bfb8991 | ||
|
|
3e5660e1af | ||
|
|
970eba043f | ||
|
|
890191d106 | ||
|
|
a7fb001902 | ||
|
|
a443d3187e | ||
|
|
ac2429bbbf | ||
|
|
19a7a8574c | ||
|
|
038faf0197 | ||
|
|
2211838592 | ||
|
|
3deaff08cd | ||
|
|
689b462a5f | ||
|
|
abe5a06340 | ||
|
|
966c4e718e | ||
|
|
40b3a115e5 | ||
|
|
8f286663ea | ||
|
|
34020c5445 | ||
|
|
66645b30b7 | ||
|
|
ae6f9093a9 |
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"
|
|
||||||
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
@@ -1,4 +1,9 @@
|
|||||||
name: Docker Image CI
|
name: Docker Image CI
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -10,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
@@ -31,6 +36,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@@ -50,6 +57,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64/v8, linux/arm/v7, linux/386
|
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
199
.github/workflows/release.yml
vendored
199
.github/workflows/release.yml
vendored
@@ -1,58 +1,81 @@
|
|||||||
name: Release X-UI
|
name: Release X-UI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "*"
|
|
||||||
workflow_dispatch:
|
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:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform:
|
platform:
|
||||||
- amd64
|
- amd64
|
||||||
- arm64
|
- arm64
|
||||||
- armv7
|
- armv7
|
||||||
|
- armv6
|
||||||
- 386
|
- 386
|
||||||
runs-on: ubuntu-20.04
|
- armv5
|
||||||
|
- s390x
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
- 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 }}" == "386" ]; then
|
|
||||||
sudo apt install gcc-i686-linux-gnu
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build x-ui
|
- name: Build x-ui
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
export CGO_ENABLED=1
|
||||||
export GOOS=linux
|
export GOOS=linux
|
||||||
export GOARCH=${{ matrix.platform }}
|
export GOARCH=${{ matrix.platform }}
|
||||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
|
||||||
export GOARCH=arm64
|
case "${{ matrix.platform }}" in
|
||||||
export CC=aarch64-linux-gnu-gcc
|
amd64) BOOTLIN_ARCH="x86-64" ;;
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
arm64) BOOTLIN_ARCH="aarch64" ;;
|
||||||
export GOARCH=arm
|
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
|
||||||
export GOARM=7
|
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
|
||||||
export CC=arm-linux-gnueabihf-gcc
|
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
386) BOOTLIN_ARCH="x86-i686" ;;
|
||||||
export GOARCH=386
|
s390x) BOOTLIN_ARCH="s390x-z13" ;;
|
||||||
export CC=i686-linux-gnu-gcc
|
esac
|
||||||
fi
|
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
|
||||||
go build -o xui-release -v main.go
|
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
|
mkdir x-ui
|
||||||
cp xui-release x-ui/
|
cp xui-release x-ui/
|
||||||
@@ -63,40 +86,136 @@ jobs:
|
|||||||
cd x-ui/bin
|
cd x-ui/bin
|
||||||
|
|
||||||
# Download dependencies
|
# Download dependencies
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.15/"
|
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v26.1.31/"
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
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
|
unzip Xray-linux-64.zip
|
||||||
rm -f Xray-linux-64.zip
|
rm -f Xray-linux-64.zip
|
||||||
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
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
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
rm -f Xray-linux-arm64-v8a.zip
|
rm -f Xray-linux-arm64-v8a.zip
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
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
|
unzip Xray-linux-arm32-v7a.zip
|
||||||
rm -f Xray-linux-arm32-v7a.zip
|
rm -f Xray-linux-arm32-v7a.zip
|
||||||
|
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
||||||
|
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 }}" == "386" ]; then
|
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
|
unzip Xray-linux-32.zip
|
||||||
rm -f 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 -q ${Xray_URL}Xray-linux-s390x.zip
|
||||||
|
unzip Xray-linux-s390x.zip
|
||||||
|
rm -f Xray-linux-s390x.zip
|
||||||
fi
|
fi
|
||||||
rm -f geoip.dat geosite.dat
|
rm -f geoip.dat geosite.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget -q 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 -q 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 -q -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 -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
mv xray xray-linux-${{ matrix.platform }}
|
mv xray xray-linux-${{ matrix.platform }}
|
||||||
cd ../..
|
cd ../..
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
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
|
- name: Upload files to GH release
|
||||||
uses: svenstaro/upload-release-action@v2
|
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:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
|
overwrite: true
|
||||||
prerelease: 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.1.31/"
|
||||||
|
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
|
||||||
@@ -14,7 +14,7 @@ case $1 in
|
|||||||
;;
|
;;
|
||||||
armv7 | arm | arm32)
|
armv7 | arm | arm32)
|
||||||
ARCH="arm32-v7a"
|
ARCH="arm32-v7a"
|
||||||
FNAME="arm32"
|
FNAME="arm"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
ARCH="64"
|
ARCH="64"
|
||||||
@@ -23,12 +23,12 @@ case $1 in
|
|||||||
esac
|
esac
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.15/Xray-linux-${ARCH}.zip"
|
wget -q "https://github.com/XTLS/Xray-core/releases/download/v26.1.31/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
wget -q "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 -q "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 -q -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 -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||||
cd ../../
|
cd ../../
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
FROM golang:1.22-alpine AS builder
|
FROM golang:1.25-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV CGO_ENABLED=1
|
ENV CGO_ENABLED=1
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
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"
|
RUN ./DockerInitFiles.sh "$TARGETARCH"
|
||||||
|
|
||||||
FROM alpine
|
FROM alpine
|
||||||
|
|||||||
50
README.md
50
README.md
@@ -13,10 +13,9 @@
|
|||||||
|
|
||||||
[](https://www.buymeacoffee.com/alireza7)
|
[](https://www.buymeacoffee.com/alireza7)
|
||||||
|
|
||||||
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
<a href="https://nowpayments.io/donation/alireza7" target="_blank" rel="noreferrer noopener">
|
||||||
- Tezos (XTZ):
|
<img src="https://nowpayments.io/images/embeds/donation-button-black.svg" alt="Crypto donation button by NOWPayments">
|
||||||
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
|
</a>
|
||||||
|
|
||||||
|
|
||||||
## Quick Overview
|
## Quick Overview
|
||||||
| Features | Enable? |
|
| Features | Enable? |
|
||||||
@@ -40,12 +39,12 @@
|
|||||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
|
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
|
```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
|
## Manual Install & Upgrade
|
||||||
@@ -186,13 +185,6 @@ docker build -t x-ui .
|
|||||||
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
||||||
- Dark/Light theme
|
- Dark/Light theme
|
||||||
|
|
||||||
## Recommended OS
|
|
||||||
|
|
||||||
- CentOS 8+
|
|
||||||
- Ubuntu 20+
|
|
||||||
- Debian 10+
|
|
||||||
- Fedora 36+
|
|
||||||
|
|
||||||
## Preview
|
## Preview
|
||||||
|
|
||||||

|

|
||||||
@@ -216,7 +208,6 @@ docker build -t x-ui .
|
|||||||
| :----: | --------------------------------- | ----------------------------------------- |
|
| :----: | --------------------------------- | ----------------------------------------- |
|
||||||
| `GET` | `"/"` | Get all inbounds |
|
| `GET` | `"/"` | Get all inbounds |
|
||||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| `GET` | `"/createbackup"` | Telegram bot sends backup to admins |
|
|
||||||
| `POST` | `"/add"` | Add inbound |
|
| `POST` | `"/add"` | Add inbound |
|
||||||
| `POST` | `"/del/:id"` | Delete inbound |
|
| `POST` | `"/del/:id"` | Delete inbound |
|
||||||
| `POST` | `"/update/:id"` | Update inbound |
|
| `POST` | `"/update/:id"` | Update inbound |
|
||||||
@@ -224,17 +215,40 @@ docker build -t x-ui .
|
|||||||
| `POST` | `"/:id/delClient/:clientId"` | Delete client by clientId\* |
|
| `POST` | `"/:id/delClient/:clientId"` | Delete client by clientId\* |
|
||||||
| `POST` | `"/updateClient/:clientId"` | Update client by clientId\* |
|
| `POST` | `"/updateClient/:clientId"` | Update client by clientId\* |
|
||||||
| `GET` | `"/getClientTraffics/:email"` | Get client's traffic |
|
| `GET` | `"/getClientTraffics/:email"` | Get client's traffic |
|
||||||
|
| `GET` | `"/getClientTrafficsById/:id"` | Get client's traffic By ID |
|
||||||
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset client's traffic |
|
| `POST` | `"/:id/resetClientTraffic/:email"` | Reset client's traffic |
|
||||||
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
| `POST` | `"/resetAllTraffics"` | Reset traffics of all inbounds |
|
||||||
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
| `POST` | `"/resetAllClientTraffics/:id"` | Reset inbound clients traffics (-1: all) |
|
||||||
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
| `POST` | `"/delDepletedClients/:id"` | Delete inbound depleted clients (-1: all) |
|
||||||
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
|
| `POST` | `"/onlines"` | Get online users ( list of emails ) |
|
||||||
|
|
||||||
|
|
||||||
\*- The field `clientId` should be filled by:
|
\*- The field `clientId` should be filled by:
|
||||||
|
|
||||||
- `client.id` for VMess and VLESS
|
- `client.id` for VMess and VLESS
|
||||||
- `client.password` for Trojan
|
- `client.password` for Trojan
|
||||||
- `client.email` for Shadowsocks
|
- `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>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,10 +18,10 @@ var name string
|
|||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Debug LogLevel = "debug"
|
Debug LogLevel = "debug"
|
||||||
Info LogLevel = "info"
|
Info LogLevel = "info"
|
||||||
Warn LogLevel = "warn"
|
Warning LogLevel = "warning"
|
||||||
Error LogLevel = "error"
|
Error LogLevel = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
@@ -53,12 +55,32 @@ func GetBinFolderPath() string {
|
|||||||
return binFolderPath
|
return binFolderPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getBaseDir() string {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
exeDir := filepath.Dir(exePath)
|
||||||
|
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
|
||||||
|
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
return wd
|
||||||
|
}
|
||||||
|
return exeDir
|
||||||
|
}
|
||||||
|
|
||||||
func GetDBFolderPath() string {
|
func GetDBFolderPath() string {
|
||||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||||
if dbFolderPath == "" {
|
if dbFolderPath != "" {
|
||||||
dbFolderPath = "/etc/x-ui"
|
return dbFolderPath
|
||||||
}
|
}
|
||||||
return dbFolderPath
|
if runtime.GOOS == "windows" {
|
||||||
|
return getBaseDir()
|
||||||
|
}
|
||||||
|
return "/etc/x-ui"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.8.4
|
1.10.0
|
||||||
@@ -7,9 +7,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -94,6 +95,17 @@ func InitDB(dbPath string) error {
|
|||||||
return nil
|
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 {
|
func GetDB() *gorm.DB {
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
@@ -120,3 +132,26 @@ func Checkpoint() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"github.com/alireza0/x-ui/util/json_util"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Protocol string
|
type Protocol string
|
||||||
@@ -79,3 +79,10 @@ type Client struct {
|
|||||||
SubID string `json:"subId" form:"subId"`
|
SubID string `json:"subId" form:"subId"`
|
||||||
Reset int `json:"reset" form:"reset"`
|
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"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
---
|
---
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
xui:
|
xui:
|
||||||
|
|||||||
130
go.mod
130
go.mod
@@ -1,96 +1,90 @@
|
|||||||
module x-ui
|
module github.com/alireza0/x-ui
|
||||||
|
|
||||||
go 1.22.0
|
go 1.25.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Calidity/gin-sessions v1.3.1
|
github.com/gin-contrib/gzip v1.2.5
|
||||||
github.com/gin-gonic/gin v1.10.0
|
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/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/goccy/go-json v0.10.3
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/xtls/xray-core v1.8.15
|
github.com/shirou/gopsutil/v4 v4.25.12
|
||||||
|
github.com/xtls/xray-core v1.260131.0
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.16.0
|
golang.org/x/text v0.33.0
|
||||||
google.golang.org/grpc v1.64.0
|
google.golang.org/grpc v1.78.0
|
||||||
gorm.io/driver/sqlite v1.5.6
|
gorm.io/driver/sqlite v1.6.0
|
||||||
gorm.io/gorm v1.25.10
|
gorm.io/gorm v1.31.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // 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
|
||||||
require (
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/ebitengine/purego v0.9.1 // indirect
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
github.com/cloudflare/circl v1.3.9 // indirect
|
github.com/gin-contrib/sse v1.1.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.3 // indirect
|
|
||||||
github.com/gin-contrib/gzip v1.0.1
|
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.2.2 // indirect
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.8 // indirect
|
github.com/juju/ratelimit v1.0.2 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 // 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/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.33 // indirect
|
||||||
|
github.com/miekg/dns v1.1.72 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
github.com/pires/go-proxyproto v0.9.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.45.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.6.6 // indirect
|
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/sagernet/sing v0.4.1 // indirect
|
github.com/sagernet/sing v0.7.17 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.9 // indirect
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
github.com/shirou/gopsutil/v4 v4.24.5
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/vishvananda/netlink v1.3.1 // indirect
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
github.com/vishvananda/netns v0.0.5 // indirect
|
||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // 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
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.23.0 // indirect
|
||||||
golang.org/x/crypto v0.24.0 // indirect
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
|
||||||
golang.org/x/mod v0.18.0 // indirect
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
golang.org/x/net v0.26.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/tools v0.22.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/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||||
google.golang.org/protobuf v1.34.2 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
476
go.sum
476
go.sum
@@ -1,63 +1,40 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178 h1:bSq8n+gX4oO/qnM3MKf4kroW75n+phO9Qp6nigJKZ1E=
|
||||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
github.com/apernet/quic-go v0.57.2-0.20260111184307-eec823306178/go.mod h1:N1WIjPphkqs4efXWuyDNQ6OjjIK04vM3h+bEgwV+eVU=
|
||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0 h1:Wo41lDOevRJSGpevP+8Pk5bANX7fJacO2w04aqLiC5I=
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
github.com/OmarTariq612/goech v0.0.0-20240405204721-8e2e1dafd3a0/go.mod h1:FVGavL/QEBQDcBpr3fAojoK17xX5k9bicBphrOpP7uM=
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
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.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
|
||||||
github.com/bytedance/sonic v1.11.6/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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
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.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
||||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
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.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
@@ -67,325 +44,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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
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-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 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
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.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
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/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
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/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.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
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/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba h1:ql1qNgCyOB7iAEk8JTNM+zJrgIbnyCKX/wdlyPufP5g=
|
|
||||||
github.com/google/pprof v0.0.0-20240528025155-186aa0362fba/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/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
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/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 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
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 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
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 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
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/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
|
||||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
|
||||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
|
||||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
|
||||||
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/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
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/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 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.9.2 h1:H1UdHn695zUVVmB0lQ354lOWHOy6TZSpzBl3tgN0s1U=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.9.2/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a h1:XCUtNgBnZfUBhdfCX2QK+fslr9vevSsUg3W3peZwlak=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
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/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||||
github.com/quic-go/quic-go v0.45.0 h1:OHmkQGM37luZITyTSu6ff03HP/2IrwDX1ZFiNEhSFUE=
|
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||||
github.com/quic-go/quic-go v0.45.0/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
|
|
||||||
github.com/refraction-networking/utls v1.6.6/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/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
github.com/sagernet/sing v0.7.17 h1:Jg4RUYIaQWTi7iY5ROHi3/Zsgxn4SPoRTwbdt35mt50=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
github.com/sagernet/sing v0.7.17/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
github.com/sagernet/sing-shadowsocks v0.2.9 h1:Paep5zCszRKsEn8587O0MnhFWKJwDW1Y4zOYYlIxMkM=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/sagernet/sing-shadowsocks v0.2.9/go.mod h1:TE/Z6401Pi8tgr0nBZcM/xawAI6u3F6TTbz4nH/qw+8=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
|
||||||
github.com/shirou/gopsutil/v4 v4.24.5 h1:gGsArG5K6vmsh5hcFOHaPm87UD003CaDMkAOweSQjhM=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.24.5/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
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.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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
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 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
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.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 h1:tkMT5pTye+1NlKIXETU78NXw0fyjnaNHmJyyLyzw8+U=
|
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237 h1:UXjrmniKlY+ZbIqpN91lejB3pszQQQRVu1vqH/p/aGM=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3/go.mod h1:cAAsePK2e15YDAMJNyOpGYEWNe4sIghTY7gpz4cX/Ik=
|
github.com/xtls/reality v0.0.0-20251116175510-cd53f7d50237/go.mod h1:vbHCV/3VWUvy1oKvTxxWJRPEWSeR1sYgQHIh6u/JiZQ=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/xtls/xray-core v1.260131.0 h1:gPBykLhUvRZ8sfubNerkwWqV3c15UtmSYQG2cgKqrV4=
|
||||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
github.com/xtls/xray-core v1.260131.0/go.mod h1:cxzYFZrxu1B1NtPjHsqv4UzgDvRA71mV4rXYH4KtO7Q=
|
||||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc h1:0Nj8T1n7F7+v4vRVroaJIvY6R0vNABLfPH+lzPHRJvI=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/xtls/reality v0.0.0-20240429224917-ecc4401070cc/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
|
||||||
github.com/xtls/xray-core v1.8.15 h1:8lmDV0TaqSz0Agdh4dqQstg2QJa183j2D59PKOTVc+Y=
|
|
||||||
github.com/xtls/xray-core v1.8.15/go.mod h1:tjzDQQJpFORuhf7fBsiswiexLVEeJpAfMsD0NE5xV7M=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
go.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.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
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 h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
|
||||||
golang.org/x/mod v0.18.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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
|
||||||
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/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
|
||||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
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-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
|
||||||
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.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
|
|
||||||
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
|
|
||||||
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=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
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.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-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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
|
||||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0 h1:Lk6hARj5UPY47dBep70OD/TIMwikJ5fGUGX0Rm3Xigk=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
gvisor.dev/gvisor v0.0.0-20260122175437-89a5d21be8f0/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
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=
|
|
||||||
|
|||||||
137
install.sh
137
install.sh
@@ -29,82 +29,97 @@ arch() {
|
|||||||
i*86 | x86) echo '386' ;;
|
i*86 | x86) echo '386' ;;
|
||||||
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
||||||
armv7* | armv7 | arm) echo 'armv7' ;;
|
armv7* | armv7 | arm) echo 'armv7' ;;
|
||||||
|
armv6* | armv6) echo 'armv6' ;;
|
||||||
|
armv5* | armv5) echo 'armv5' ;;
|
||||||
|
s390x) echo 's390x' ;;
|
||||||
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "arch: $(arch)"
|
echo "arch: $(arch)"
|
||||||
|
|
||||||
os_version=""
|
|
||||||
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
|
||||||
|
|
||||||
if [[ "${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 10 ]]; then
|
|
||||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
install_dependencies() {
|
install_dependencies() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
centos)
|
ubuntu | debian | armbian)
|
||||||
yum -y update && yum install -y -q wget curl tar tzdata
|
apt-get update && apt-get install -y -q wget curl tar tzdata cron
|
||||||
;;
|
;;
|
||||||
fedora)
|
centos | almalinux | rocky | ol)
|
||||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
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 cronie
|
||||||
|
;;
|
||||||
|
opensuse-tumbleweed)
|
||||||
|
zypper refresh && zypper -q install -y wget curl tar timezone cron
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
apt-get update && apt install -y -q wget curl tar tzdata
|
apt-get update && apt install -y -q wget curl tar tzdata cron
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
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() {
|
config_after_install() {
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
local existing_username=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'username: .+' | awk '{print $2}')
|
||||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
local existing_password=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'password: .+' | awk '{print $2}')
|
||||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||||
read -p "Please set up your username:" config_account
|
|
||||||
echo -e "${yellow}Your username will be:${config_account}${plain}"
|
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
||||||
read -p "Please set up your password:" config_password
|
if [[ "$existing_username" == "admin" && "$existing_password" == "admin" ]]; then
|
||||||
echo -e "${yellow}Your password will be:${config_password}${plain}"
|
local config_webBasePath=$(gen_random_string 15)
|
||||||
read -p "Please set up the panel port:" config_port
|
local config_username=$(gen_random_string 10)
|
||||||
echo -e "${yellow}Your panel port is:${config_port}${plain}"
|
local config_password=$(gen_random_string 10)
|
||||||
echo -e "${yellow}Initializing, please wait...${plain}"
|
|
||||||
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
read -p "Would you like to customize the Panel Port settings? (If not, random port will be applied) [y/n]: " config_confirm
|
||||||
echo -e "${yellow}Account name and password set successfully!${plain}"
|
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
||||||
/usr/local/x-ui/x-ui setting -port ${config_port}
|
read -p "Please set up the panel port: " config_port
|
||||||
echo -e "${yellow}Panel port set successfully!${plain}"
|
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
||||||
else
|
else
|
||||||
echo -e "${red}cancel...${plain}"
|
local config_port=$(shuf -i 1024-62000 -n 1)
|
||||||
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
|
echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
||||||
local usernameTemp=$(head -c 6 /dev/urandom | base64)
|
fi
|
||||||
local passwordTemp=$(head -c 6 /dev/urandom | base64)
|
|
||||||
/usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp}
|
/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,will generate random login info for security concerns:"
|
echo -e "This is a fresh installation, generating random login info for security concerns:"
|
||||||
echo -e "###############################################"
|
echo -e "###############################################"
|
||||||
echo -e "${green}username:${usernameTemp}${plain}"
|
echo -e "${green}Username: ${config_username}${plain}"
|
||||||
echo -e "${green}password:${passwordTemp}${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 "###############################################"
|
||||||
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
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
/usr/local/x-ui/x-ui migrate
|
/usr/local/x-ui/x-ui migrate
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,10 +156,10 @@ install_x-ui() {
|
|||||||
else
|
else
|
||||||
last_version=$1
|
last_version=$1
|
||||||
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
|
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}
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
||||||
if [[ $? -ne 0 ]]; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -181,8 +196,11 @@ install_x-ui() {
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable x-ui
|
systemctl enable x-ui
|
||||||
systemctl start 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 ""
|
||||||
|
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 "X-UI Control Menu Usage"
|
||||||
echo "------------------------------------------"
|
echo "------------------------------------------"
|
||||||
echo "SUBCOMMANDS:"
|
echo "SUBCOMMANDS:"
|
||||||
@@ -191,6 +209,7 @@ install_x-ui() {
|
|||||||
echo "x-ui stop - Stop"
|
echo "x-ui stop - Stop"
|
||||||
echo "x-ui restart - Restart"
|
echo "x-ui restart - Restart"
|
||||||
echo "x-ui status - Current Status"
|
echo "x-ui status - Current Status"
|
||||||
|
echo "x-ui settings - Current Settings"
|
||||||
echo "x-ui enable - Enable Autostart on OS Startup"
|
echo "x-ui enable - Enable Autostart on OS Startup"
|
||||||
echo "x-ui disable - Disable Autostart on OS Startup"
|
echo "x-ui disable - Disable Autostart on OS Startup"
|
||||||
echo "x-ui log - Check Logs"
|
echo "x-ui log - Check Logs"
|
||||||
|
|||||||
231
main.go
231
main.go
@@ -3,21 +3,26 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/sub"
|
"github.com/alireza0/x-ui/sub"
|
||||||
"x-ui/web"
|
"github.com/alireza0/x-ui/web"
|
||||||
"x-ui/web/global"
|
"github.com/alireza0/x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
|
"github.com/shirou/gopsutil/v4/net"
|
||||||
|
xrayCore "github.com/xtls/xray-core/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runWebServer() {
|
func runWebServer() {
|
||||||
@@ -28,7 +33,7 @@ func runWebServer() {
|
|||||||
logger.InitLogger(logging.DEBUG)
|
logger.InitLogger(logging.DEBUG)
|
||||||
case config.Info:
|
case config.Info:
|
||||||
logger.InitLogger(logging.INFO)
|
logger.InitLogger(logging.INFO)
|
||||||
case config.Warn:
|
case config.Warning:
|
||||||
logger.InitLogger(logging.WARNING)
|
logger.InitLogger(logging.WARNING)
|
||||||
case config.Error:
|
case config.Error:
|
||||||
logger.InitLogger(logging.ERROR)
|
logger.InitLogger(logging.ERROR)
|
||||||
@@ -123,22 +128,35 @@ func showSetting(show bool) {
|
|||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
port, err := settingService.GetPort()
|
port, err := settingService.GetPort()
|
||||||
if err != nil {
|
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{}
|
userService := service.UserService{}
|
||||||
userModel, err := userService.GetFirstUser()
|
userModel, err := userService.GetFirstUser()
|
||||||
if err != nil {
|
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
|
username := userModel.Username
|
||||||
userpasswd := userModel.Password
|
userpasswd := userModel.Password
|
||||||
if (username == "") || (userpasswd == "") {
|
if username == "" || userpasswd == "" {
|
||||||
fmt.Println("current username or password is empty")
|
fmt.Println("current username or password is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("current panel settings as follows:")
|
fmt.Println("current panel settings as follows:")
|
||||||
fmt.Println("username:", username)
|
fmt.Println("username:", username)
|
||||||
fmt.Println("userpasswd:", userpasswd)
|
fmt.Println("password:", userpasswd)
|
||||||
fmt.Println("port:", port)
|
fmt.Println("port:", port)
|
||||||
|
if webBasePath != "" {
|
||||||
|
fmt.Println("webBasePath:", webBasePath)
|
||||||
|
} else {
|
||||||
|
fmt.Println("webBasePath is not set")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +219,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())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
@@ -210,21 +293,64 @@ func updateSetting(port int, username string, password string) {
|
|||||||
|
|
||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
|
|
||||||
if port > 0 {
|
Port, _ := settingService.GetPort()
|
||||||
err := settingService.SetPort(port)
|
BasePath, _ := settingService.GetBasePath()
|
||||||
if err != nil {
|
Listen, _ := settingService.GetListen()
|
||||||
fmt.Println("set port failed:", err)
|
Domain, _ := settingService.GetWebDomain()
|
||||||
} else {
|
KeyFile, _ := settingService.GetKeyFile()
|
||||||
fmt.Printf("set port %v success", port)
|
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{}
|
resp, err := http.Get("https://api.ipify.org?format=text")
|
||||||
err := userService.UpdateFirstUser(username, password)
|
if err == nil {
|
||||||
if err != nil {
|
defer resp.Body.Close()
|
||||||
fmt.Println("set username and password failed:", err)
|
ip, err := io.ReadAll(resp.Body)
|
||||||
} else {
|
if err == nil {
|
||||||
fmt.Println("set username and password success")
|
fmt.Printf("\nGlobal address:\n%s%s%s%s\n", Proto, ip, PortText, BasePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -256,30 +382,37 @@ func main() {
|
|||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
var password string
|
var password string
|
||||||
|
var webBasePath string
|
||||||
|
var webCertFile string
|
||||||
|
var webKeyFile string
|
||||||
var tgbottoken string
|
var tgbottoken string
|
||||||
var tgbotchatid string
|
var tgbotchatid string
|
||||||
var enabletgbot bool
|
var enabletgbot bool
|
||||||
var tgbotRuntime string
|
var tgbotRuntime string
|
||||||
var reset bool
|
var reset bool
|
||||||
var show bool
|
var show bool
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
||||||
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
settingCmd.BoolVar(&show, "show", false, "Show current settings")
|
||||||
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
settingCmd.IntVar(&port, "port", 0, "Set panel port")
|
||||||
settingCmd.StringVar(&username, "username", "", "set login username")
|
settingCmd.StringVar(&username, "username", "", "Set login username")
|
||||||
settingCmd.StringVar(&password, "password", "", "set login password")
|
settingCmd.StringVar(&password, "password", "", "Set login password")
|
||||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegram bot token")
|
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
||||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegram bot cron time")
|
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
||||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegram bot chat id")
|
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
||||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
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
|
oldUsage := flag.Usage
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
oldUsage()
|
oldUsage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run Run web panel")
|
||||||
fmt.Println(" migrate migrate form other/old x-ui")
|
fmt.Println(" uri Show panel URI")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" migrate Migrate form other/old x-ui")
|
||||||
|
fmt.Println(" setting Set settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@@ -296,6 +429,8 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWebServer()
|
runWebServer()
|
||||||
|
case "uri":
|
||||||
|
getPanelURI()
|
||||||
case "migrate":
|
case "migrate":
|
||||||
migrateDb()
|
migrateDb()
|
||||||
case "setting":
|
case "setting":
|
||||||
@@ -307,7 +442,7 @@ func main() {
|
|||||||
if reset {
|
if reset {
|
||||||
resetSetting()
|
resetSetting()
|
||||||
} else {
|
} else {
|
||||||
updateSetting(port, username, password)
|
updateSetting(port, username, password, webBasePath)
|
||||||
}
|
}
|
||||||
if show {
|
if show {
|
||||||
showSetting(show)
|
showSetting(show)
|
||||||
@@ -318,6 +453,18 @@ func main() {
|
|||||||
if enabletgbot {
|
if enabletgbot {
|
||||||
updateTgbotEnableSts(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:
|
default:
|
||||||
fmt.Println("Invalid subcommands")
|
fmt.Println("Invalid subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
@@ -326,3 +473,9 @@ func main() {
|
|||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startXray() {
|
||||||
|
conf := xrayCore.Config{}
|
||||||
|
core, _ := xrayCore.New(&conf)
|
||||||
|
core.Start()
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,11 +43,13 @@
|
|||||||
},
|
},
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"tag": "direct",
|
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {
|
"settings": {
|
||||||
"domainStrategy": "UseIP"
|
"domainStrategy": "UseIP",
|
||||||
}
|
"noises": [],
|
||||||
|
"redirect": ""
|
||||||
|
},
|
||||||
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "block",
|
"tag": "block",
|
||||||
|
|||||||
19
sub/sub.go
19
sub/sub.go
@@ -8,12 +8,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/web/middleware"
|
"github.com/alireza0/x-ui/web/middleware"
|
||||||
"x-ui/web/network"
|
"github.com/alireza0/x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -92,6 +92,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
SubJsonFragment = ""
|
SubJsonFragment = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubJsonNoises, err := s.settingService.GetSubJsonNoises()
|
||||||
|
if err != nil {
|
||||||
|
SubJsonNoises = ""
|
||||||
|
}
|
||||||
|
|
||||||
SubJsonMux, err := s.settingService.GetSubJsonMux()
|
SubJsonMux, err := s.settingService.GetSubJsonMux()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SubJsonMux = ""
|
SubJsonMux = ""
|
||||||
@@ -106,7 +111,7 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
|
|
||||||
s.sub = NewSUBController(
|
s.sub = NewSUBController(
|
||||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||||
SubJsonFragment, SubJsonMux, SubJsonRules)
|
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules)
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sub
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -26,6 +27,7 @@ func NewSUBController(
|
|||||||
rModel string,
|
rModel string,
|
||||||
update string,
|
update string,
|
||||||
jsonFragment string,
|
jsonFragment string,
|
||||||
|
jsonNoise string,
|
||||||
jsonMux string,
|
jsonMux string,
|
||||||
jsonRules string,
|
jsonRules string,
|
||||||
) *SUBController {
|
) *SUBController {
|
||||||
@@ -37,7 +39,7 @@ func NewSUBController(
|
|||||||
updateInterval: update,
|
updateInterval: update,
|
||||||
|
|
||||||
subService: sub,
|
subService: sub,
|
||||||
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
|
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
|
||||||
}
|
}
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
@@ -54,7 +56,10 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
|||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
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)
|
subs, header, err := a.subService.GetSubs(subId, host)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
@@ -79,7 +84,10 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
|
|
||||||
func (a *SUBController) subJsons(c *gin.Context) {
|
func (a *SUBController) subJsons(c *gin.Context) {
|
||||||
subId := c.Param("subid")
|
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)
|
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
||||||
if err != nil || len(jsonSub) == 0 {
|
if err != nil || len(jsonSub) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/json_util"
|
"github.com/alireza0/x-ui/util/json_util"
|
||||||
"x-ui/util/random"
|
"github.com/alireza0/x-ui/util/random"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed default.json
|
//go:embed default.json
|
||||||
@@ -21,13 +21,14 @@ type SubJsonService struct {
|
|||||||
configJson map[string]interface{}
|
configJson map[string]interface{}
|
||||||
defaultOutbounds []json_util.RawMessage
|
defaultOutbounds []json_util.RawMessage
|
||||||
fragment string
|
fragment string
|
||||||
|
noises string
|
||||||
mux string
|
mux string
|
||||||
|
|
||||||
inboundService service.InboundService
|
inboundService service.InboundService
|
||||||
SubService *SubService
|
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 configJson map[string]interface{}
|
||||||
var defaultOutbounds []json_util.RawMessage
|
var defaultOutbounds []json_util.RawMessage
|
||||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||||
@@ -52,10 +53,15 @@ func NewSubJsonService(fragment string, mux string, rules string, subService *Su
|
|||||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if noises != "" {
|
||||||
|
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(noises))
|
||||||
|
}
|
||||||
|
|
||||||
return &SubJsonService{
|
return &SubJsonService{
|
||||||
configJson: configJson,
|
configJson: configJson,
|
||||||
defaultOutbounds: defaultOutbounds,
|
defaultOutbounds: defaultOutbounds,
|
||||||
fragment: fragment,
|
fragment: fragment,
|
||||||
|
noises: noises,
|
||||||
mux: mux,
|
mux: mux,
|
||||||
SubService: subService,
|
SubService: subService,
|
||||||
}
|
}
|
||||||
@@ -165,12 +171,12 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
case "tls":
|
case "tls":
|
||||||
if newStream["security"] != "tls" {
|
if newStream["security"] != "tls" {
|
||||||
newStream["security"] = "tls"
|
newStream["security"] = "tls"
|
||||||
newStream["tslSettings"] = map[string]interface{}{}
|
newStream["tlsSettings"] = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
case "none":
|
case "none":
|
||||||
if newStream["security"] != "none" {
|
if newStream["security"] != "none" {
|
||||||
newStream["security"] = "none"
|
newStream["security"] = "none"
|
||||||
delete(newStream, "tslSettings")
|
delete(newStream, "tlsSettings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
||||||
@@ -178,8 +184,14 @@ func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client,
|
|||||||
var newOutbounds []json_util.RawMessage
|
var newOutbounds []json_util.RawMessage
|
||||||
|
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess", "vless":
|
case "vmess":
|
||||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
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":
|
case "trojan", "shadowsocks":
|
||||||
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
||||||
}
|
}
|
||||||
@@ -211,7 +223,7 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
|||||||
delete(streamSettings, "sockopt")
|
delete(streamSettings, "sockopt")
|
||||||
|
|
||||||
if s.fragment != "" {
|
if s.fragment != "" {
|
||||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
|
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "penetrate": true}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove proxy protocol
|
// remove proxy protocol
|
||||||
@@ -257,6 +269,7 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
|||||||
rltyData["show"] = false
|
rltyData["show"] = false
|
||||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||||
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
||||||
|
rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
|
||||||
|
|
||||||
// Set random data
|
// Set random data
|
||||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
rltyData["spiderX"] = "/" + random.Seq(15)
|
||||||
@@ -276,23 +289,8 @@ func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]in
|
|||||||
return rltyData
|
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{}
|
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.Protocol = string(inbound.Protocol)
|
||||||
outbound.Tag = "proxy"
|
outbound.Tag = "proxy"
|
||||||
@@ -300,9 +298,15 @@ func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_ut
|
|||||||
outbound.Mux = json_util.RawMessage(s.mux)
|
outbound.Mux = json_util.RawMessage(s.mux)
|
||||||
}
|
}
|
||||||
outbound.StreamSettings = streamSettings
|
outbound.StreamSettings = streamSettings
|
||||||
outbound.Settings = OutboundSettings{
|
settings := make(map[string]any)
|
||||||
Vnext: vnextData,
|
settings["address"] = inbound.Listen
|
||||||
|
settings["port"] = inbound.Port
|
||||||
|
settings["id"] = client.ID
|
||||||
|
if inbound.Protocol == model.VLESS {
|
||||||
|
settings["flow"] = client.Flow
|
||||||
|
settings["encryption"] = encryption
|
||||||
}
|
}
|
||||||
|
outbound.Settings = settings
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
return result
|
return result
|
||||||
@@ -339,8 +343,8 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
|||||||
outbound.Mux = json_util.RawMessage(s.mux)
|
outbound.Mux = json_util.RawMessage(s.mux)
|
||||||
}
|
}
|
||||||
outbound.StreamSettings = streamSettings
|
outbound.StreamSettings = streamSettings
|
||||||
outbound.Settings = OutboundSettings{
|
outbound.Settings = map[string]any{
|
||||||
Servers: serverData,
|
"servers": serverData,
|
||||||
}
|
}
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||||
@@ -348,30 +352,11 @@ func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_u
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Outbound struct {
|
type Outbound struct {
|
||||||
Protocol string `json:"protocol"`
|
Protocol string `json:"protocol"`
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
Settings map[string]any `json:"settings,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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerSetting struct {
|
type ServerSetting struct {
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/util/random"
|
"github.com/alireza0/x-ui/util/random"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"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{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
obj["host"] = searchHost(headers)
|
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":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
obj["path"], _ = grpc["serviceName"].(string)
|
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{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
obj["path"] = splithttp["path"].(string)
|
obj["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
obj["host"] = host
|
obj["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
obj["host"] = searchHost(headers)
|
obj["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
obj["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
@@ -315,6 +305,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VLESS {
|
if inbound.Protocol != model.VLESS {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
var vlessSettings model.VLESSSettings
|
||||||
|
_ = json.Unmarshal([]byte(inbound.Settings), &vlessSettings)
|
||||||
|
|
||||||
var stream map[string]interface{}
|
var stream map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
@@ -329,6 +322,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
port := inbound.Port
|
port := inbound.Port
|
||||||
streamNetwork := stream["network"].(string)
|
streamNetwork := stream["network"].(string)
|
||||||
params := make(map[string]string)
|
params := make(map[string]string)
|
||||||
|
if vlessSettings.Encryption != "" {
|
||||||
|
params["encryption"] = vlessSettings.Encryption
|
||||||
|
}
|
||||||
params["type"] = streamNetwork
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
switch streamNetwork {
|
switch streamNetwork {
|
||||||
@@ -358,16 +354,6 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
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":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
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{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
params["path"] = splithttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
params["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
@@ -448,6 +435,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
||||||
|
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
||||||
|
params["pqv"] = pqv
|
||||||
|
}
|
||||||
|
}
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,16 +553,6 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
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":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
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{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
params["path"] = splithttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
params["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
@@ -647,6 +630,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
||||||
|
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
||||||
|
params["pqv"] = pqv
|
||||||
|
}
|
||||||
|
}
|
||||||
params["spx"] = "/" + random.Seq(15)
|
params["spx"] = "/" + random.Seq(15)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -760,16 +748,6 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
headers, _ := ws["headers"].(map[string]interface{})
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
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":
|
case "grpc":
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
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{})
|
headers, _ := httpupgrade["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
case "splithttp":
|
case "xhttp":
|
||||||
splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
|
xhttp, _ := stream["xhttpSettings"].(map[string]interface{})
|
||||||
params["path"] = splithttp["path"].(string)
|
params["path"] = xhttp["path"].(string)
|
||||||
if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
|
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
||||||
params["host"] = host
|
params["host"] = host
|
||||||
} else {
|
} else {
|
||||||
headers, _ := splithttp["headers"].(map[string]interface{})
|
headers, _ := xhttp["headers"].(map[string]interface{})
|
||||||
params["host"] = searchHost(headers)
|
params["host"] = searchHost(headers)
|
||||||
}
|
}
|
||||||
|
params["mode"] = xhttp["mode"].(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
@@ -933,9 +912,36 @@ func (s *SubService) genRemark(inbound *model.Inbound, email string, extra strin
|
|||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
switch exp := stats.ExpiryTime / 1000; {
|
switch exp := stats.ExpiryTime / 1000; {
|
||||||
case exp > 0:
|
case exp > 0:
|
||||||
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
|
remainingSeconds := exp - now
|
||||||
|
days := remainingSeconds / 86400
|
||||||
|
hours := (remainingSeconds % 86400) / 3600
|
||||||
|
minutes := (remainingSeconds % 3600) / 60
|
||||||
|
if days > 0 {
|
||||||
|
if hours > 0 {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
|
||||||
|
} else {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dD⏳", days))
|
||||||
|
}
|
||||||
|
} else if hours > 0 {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
|
||||||
|
} else {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
|
||||||
|
}
|
||||||
case exp < 0:
|
case exp < 0:
|
||||||
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
|
days := exp / -86400
|
||||||
|
hours := (exp % -86400) / 3600
|
||||||
|
minutes := (exp % -3600) / 60
|
||||||
|
if days > 0 {
|
||||||
|
if hours > 0 {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
|
||||||
|
} else {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dD⏳", days))
|
||||||
|
}
|
||||||
|
} else if hours > 0 {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
|
||||||
|
} else {
|
||||||
|
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewErrorf(format string, a ...interface{}) error {
|
func NewErrorf(format string, a ...interface{}) error {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package random
|
package random
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"crypto/rand"
|
||||||
"time"
|
"math/big"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -15,8 +15,6 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
numSeq[i] = rune('0' + i)
|
numSeq[i] = rune('0' + i)
|
||||||
}
|
}
|
||||||
@@ -39,11 +37,20 @@ func init() {
|
|||||||
func Seq(n int) string {
|
func Seq(n int) string {
|
||||||
runes := make([]rune, n)
|
runes := make([]rune, n)
|
||||||
for i := 0; i < n; i++ {
|
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)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Num(n int) int {
|
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())
|
||||||
}
|
}
|
||||||
|
|||||||
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
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.subURI = "";
|
||||||
this.subJsonURI = "";
|
this.subJsonURI = "";
|
||||||
this.subJsonFragment = "";
|
this.subJsonFragment = "";
|
||||||
|
this.subJsonNoises = "";
|
||||||
this.subJsonMux = "";
|
this.subJsonMux = "";
|
||||||
this.subJsonRules = "";
|
this.subJsonRules = "";
|
||||||
|
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ class RandomUtil {
|
|||||||
|
|
||||||
static randomShortId() {
|
static randomShortId() {
|
||||||
let shortIds = new Array(24).fill('');
|
let shortIds = new Array(24).fill('');
|
||||||
for (var ii = 0; ii < 24; ii++) {
|
for (var ii = 1; ii < 24; ii++) {
|
||||||
for (var jj = 0; jj < this.randomInt(16); jj++){
|
for (var jj = 0; jj <= this.randomInt(7); jj++){
|
||||||
let randomNum = this.randomInt(256);
|
let randomNum = this.randomInt(256);
|
||||||
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
|
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -9,30 +9,40 @@ import (
|
|||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
|
serverController *ServerController
|
||||||
Tgbot service.Tgbot
|
Tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
func NewAPIController(g *gin.RouterGroup, s *ServerController) *APIController {
|
||||||
a := &APIController{}
|
a := &APIController{
|
||||||
|
serverController: s,
|
||||||
|
}
|
||||||
a.initRouter(g)
|
a.initRouter(g)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/xui/API/inbounds")
|
api := g.Group("/xui/API")
|
||||||
g.Use(a.checkLogin)
|
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 {
|
inboundRoutes := []struct {
|
||||||
Method string
|
Method string
|
||||||
Path string
|
Path string
|
||||||
Handler gin.HandlerFunc
|
Handler gin.HandlerFunc
|
||||||
}{
|
}{
|
||||||
{"GET", "/createbackup", a.createBackup},
|
|
||||||
{"GET", "/", a.inboundController.getInbounds},
|
{"GET", "/", a.inboundController.getInbounds},
|
||||||
{"GET", "/get/:id", a.inboundController.getInbound},
|
{"GET", "/get/:id", a.inboundController.getInbound},
|
||||||
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
||||||
|
{"GET", "/getClientTrafficsById/:id", a.inboundController.getClientTrafficsById},
|
||||||
{"POST", "/add", a.inboundController.addInbound},
|
{"POST", "/add", a.inboundController.addInbound},
|
||||||
{"POST", "/del/:id", a.inboundController.delInbound},
|
{"POST", "/del/:id", a.inboundController.delInbound},
|
||||||
{"POST", "/update/:id", a.inboundController.updateInbound},
|
{"POST", "/update/:id", a.inboundController.updateInbound},
|
||||||
@@ -47,7 +57,37 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, route := range inboundRoutes {
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/locale"
|
"github.com/alireza0/x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -75,6 +75,15 @@ func (a *InboundController) getClientTraffics(c *gin.Context) {
|
|||||||
jsonObj(c, clientTraffics, nil)
|
jsonObj(c, clientTraffics, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *InboundController) getClientTrafficsById(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
clientTraffics, err := a.inboundService.GetClientTrafficByID(id)
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, "Error getting traffics", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jsonObj(c, clientTraffics, nil)
|
||||||
|
}
|
||||||
func (a *InboundController) addInbound(c *gin.Context) {
|
func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
@@ -90,8 +99,7 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
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)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -104,8 +112,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
jsonMsg(c, I18nWeb(c, "delete"), err)
|
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.DelInbound(id)
|
||||||
needRestart, err = a.inboundService.DelInbound(id)
|
|
||||||
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -126,8 +133,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
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)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -142,9 +148,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.AddInboundClient(data)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.AddInboundClient(data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -163,9 +167,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.DelInboundClient(id, clientId)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.DelInboundClient(id, clientId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -186,9 +188,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.UpdateInboundClient(inbound, clientId)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
@@ -207,14 +207,12 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
needRestart := true
|
needRestart, err := a.inboundService.ResetClientTraffic(id, email)
|
||||||
|
|
||||||
needRestart, err = a.inboundService.ResetClientTraffic(id, email)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "traffic reseted", nil)
|
jsonMsg(c, "traffic has been reset", nil)
|
||||||
if needRestart {
|
if needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
@@ -228,7 +226,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics reseted", nil)
|
jsonMsg(c, "All traffics has been reset", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
@@ -245,7 +243,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client has been reset", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
@@ -259,7 +257,7 @@ func (a *InboundController) delDepletedClients(c *gin.Context) {
|
|||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All delpeted clients are deleted", nil)
|
jsonMsg(c, "All depleted clients are deleted", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) importInbound(c *gin.Context) {
|
func (a *InboundController) importInbound(c *gin.Context) {
|
||||||
@@ -283,8 +281,7 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
|||||||
inbound.ClientStats[index].Enable = true
|
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)
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
if err == nil && needRestart {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
@@ -292,5 +289,5 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) onlines(c *gin.Context) {
|
func (a *InboundController) onlines(c *gin.Context) {
|
||||||
jsonObj(c, a.inboundService.GetOnlineClinets(), nil)
|
jsonObj(c, a.inboundService.GetOnlineClients(), nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -62,37 +63,39 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
|
|
||||||
user := a.userService.CheckUser(form.Username, form.Password)
|
user := a.userService.CheckUser(form.Username, form.Password)
|
||||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
safeUser := template.HTMLEscapeString(form.Username)
|
||||||
|
safePass := template.HTMLEscapeString(form.Password)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
logger.Infof("wrong username or password: \"%s\" \"%s\"", safeUser, safePass)
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 0)
|
||||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%s login success ,Ip Address: %s\n", form.Username, getRemoteIp(c))
|
logger.Infof("%s Successful Login ,Ip Address: %s\n", safeUser, getRemoteIp(c))
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(safeUser, getRemoteIp(c), timeStr, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
sessionMaxAge, err := a.settingService.GetSessionMaxAge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("Unable to get session's max age from DB")
|
logger.Info("Unable to get session's max age from DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionMaxAge > 0 {
|
if sessionMaxAge > 0 {
|
||||||
err = session.SetMaxAge(c, sessionMaxAge*60)
|
err = session.SetMaxAge(c, sessionMaxAge*60)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("Unable to set session's max age")
|
logger.Info("Unable to set session's max age")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = session.SetLoginUser(c, user)
|
err = session.SetLoginUser(c, user)
|
||||||
logger.Info("user", user.Id, "login success")
|
logger.Infof("%s logged in successfully", user.Username)
|
||||||
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
jsonMsg(c, I18nWeb(c, "pages.login.toasts.successLogin"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *IndexController) logout(c *gin.Context) {
|
func (a *IndexController) logout(c *gin.Context) {
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
logger.Info("user", user.Id, "logout")
|
logger.Infof("%s logged out successfully", user.Username)
|
||||||
}
|
}
|
||||||
session.ClearSession(c)
|
session.ClearSession(c)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/global"
|
"github.com/alireza0/x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -39,16 +39,20 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
|
|||||||
g = g.Group("/server")
|
g = g.Group("/server")
|
||||||
|
|
||||||
g.Use(a.checkLogin)
|
g.Use(a.checkLogin)
|
||||||
g.POST("/status", a.status)
|
g.GET("/status", a.status)
|
||||||
g.POST("/getXrayVersion", a.getXrayVersion)
|
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("/stopXrayService", a.stopXrayService)
|
||||||
g.POST("/restartXrayService", a.restartXrayService)
|
g.POST("/restartXrayService", a.restartXrayService)
|
||||||
g.POST("/installXray/:version", a.installXray)
|
g.POST("/installXray/:version", a.installXray)
|
||||||
g.POST("/logs/:count", a.getLogs)
|
g.POST("/logs/:count", a.getLogs)
|
||||||
g.POST("/getConfigJson", a.getConfigJson)
|
|
||||||
g.GET("/getDb", a.getDb)
|
|
||||||
g.POST("/importDB", a.importDB)
|
g.POST("/importDB", a.importDB)
|
||||||
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) refreshStatus() {
|
func (a *ServerController) refreshStatus() {
|
||||||
@@ -105,7 +109,7 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
|
|||||||
jsonMsg(c, "", err)
|
jsonMsg(c, "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Xray stoped", err)
|
jsonMsg(c, "Xray stopped", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) restartXrayService(c *gin.Context) {
|
func (a *ServerController) restartXrayService(c *gin.Context) {
|
||||||
@@ -186,3 +190,31 @@ func (a *ServerController) getNewX25519Cert(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonObj(c, cert, nil)
|
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"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/entity"
|
"github.com/alireza0/x-ui/web/entity"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
"x-ui/web/session"
|
"github.com/alireza0/x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/entity"
|
"github.com/alireza0/x-ui/web/entity"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"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 = gin.H{}
|
||||||
}
|
}
|
||||||
data["title"] = title
|
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["request_uri"] = c.Request.RequestURI
|
||||||
data["base_path"] = c.GetString("base_path")
|
data["base_path"] = c.GetString("base_path")
|
||||||
c.HTML(http.StatusOK, name, getContext(data))
|
c.HTML(http.StatusOK, name, getContext(data))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -11,6 +11,7 @@ type XraySettingController struct {
|
|||||||
SettingService service.SettingService
|
SettingService service.SettingService
|
||||||
InboundService service.InboundService
|
InboundService service.InboundService
|
||||||
XrayService service.XrayService
|
XrayService service.XrayService
|
||||||
|
WarpService service.WarpService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
func NewXraySettingController(g *gin.RouterGroup) *XraySettingController {
|
||||||
@@ -69,16 +70,18 @@ func (a *XraySettingController) warp(c *gin.Context) {
|
|||||||
var err error
|
var err error
|
||||||
switch action {
|
switch action {
|
||||||
case "data":
|
case "data":
|
||||||
resp, err = a.XraySettingService.GetWarp()
|
resp, err = a.WarpService.GetWarpData()
|
||||||
|
case "del":
|
||||||
|
err = a.WarpService.DelWarpData()
|
||||||
case "config":
|
case "config":
|
||||||
resp, err = a.XraySettingService.GetWarpConfig()
|
resp, err = a.WarpService.GetWarpConfig()
|
||||||
case "reg":
|
case "reg":
|
||||||
skey := c.PostForm("privateKey")
|
skey := c.PostForm("privateKey")
|
||||||
pkey := c.PostForm("publicKey")
|
pkey := c.PostForm("publicKey")
|
||||||
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
resp, err = a.WarpService.RegWarp(skey, pkey)
|
||||||
case "license":
|
case "license":
|
||||||
license := c.PostForm("license")
|
license := c.PostForm("license")
|
||||||
resp, err = a.XraySettingService.SetWarpLicence(license)
|
resp, err = a.WarpService.SetWarpLicense(license)
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj(c, resp, err)
|
jsonObj(c, resp, err)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
@@ -50,6 +50,7 @@ type AllSetting struct {
|
|||||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||||
|
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"`
|
||||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="renderer" content="webkit">
|
<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">
|
<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/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">
|
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||||
|
|||||||
@@ -71,8 +71,8 @@
|
|||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elmentId, content) {
|
copyToClipboard(elementId, content) {
|
||||||
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
|
this.qrModal.clipboard = new ClipboardJS('#' + elementId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.qrModal.clipboard.on('success', () => {
|
this.qrModal.clipboard.on('success', () => {
|
||||||
@@ -80,9 +80,9 @@
|
|||||||
this.qrModal.clipboard.destroy();
|
this.qrModal.clipboard.destroy();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setQrCode(elmentId, content) {
|
setQrCode(elementId, content) {
|
||||||
new QRious({
|
new QRious({
|
||||||
element: document.querySelector('#' + elmentId),
|
element: document.querySelector('#' + elementId),
|
||||||
size: 260,
|
size: 260,
|
||||||
value: content,
|
value: content,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||||
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
|
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
|
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
<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>
|
||||||
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||||
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||||
|
|||||||
@@ -1,96 +1,114 @@
|
|||||||
{{define "dnsModal"}}
|
{{define "dnsModal"}}
|
||||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok" :closable="true"
|
||||||
:closable="true" :mask-closable="false"
|
:mask-closable="false" :ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}'
|
||||||
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
:class="themeSwitcher.currentTheme">
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
||||||
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
|
<a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')"></a-button>
|
||||||
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
||||||
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
<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-button icon="minus" size="small" slot="addonAfter"
|
||||||
</a-input>
|
@click="dnsModal.dnsServer.domains.splice(index,1)"></a-button>
|
||||||
</template>
|
</a-input>
|
||||||
</a-form-item>
|
</template>
|
||||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
</a-form-item>
|
||||||
<a-select
|
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||||
v-model="dnsModal.dnsServer.queryStrategy"
|
<a-select v-model="dnsModal.dnsServer.queryStrategy" style="width: 100%"
|
||||||
style="width: 100%"
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
: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-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
</a-select>
|
||||||
[[ l ]]
|
</a-form-item>
|
||||||
</a-select-option>
|
<a-form-item label='Skip Fallback' v-if="isAdvanced">
|
||||||
</a-select>
|
<a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Skip Fallback' v-if="isAdvanced">
|
<a-form-item label='{{ i18n "pages.xray.dns.expectIPs"}}'>
|
||||||
<a-switch v-model="dnsModal.dnsServer.skipFallback"></a-switch>
|
<a-button icon="plus" size="small" type="primary" @click="dnsModal.dnsServer.expectIPs.push('')"></a-button>
|
||||||
</a-form-item>
|
<template v-for="(domain, index) in dnsModal.dnsServer.expectIPs">
|
||||||
</a-form>
|
<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>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
const dnsModal = {
|
const dnsModal = {
|
||||||
title: '',
|
title: '',
|
||||||
visible: false,
|
visible: false,
|
||||||
okText: '{{ i18n "confirm" }}',
|
okText: '{{ i18n "confirm" }}',
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
confirm: null,
|
confirm: null,
|
||||||
dnsServer: {
|
dnsServer: {
|
||||||
address: "localhost",
|
address: "localhost",
|
||||||
domains: [],
|
domains: [],
|
||||||
queryStrategy: 'UseIP',
|
expectIPs: [],
|
||||||
skipFallback: true,
|
queryStrategy: 'UseIP',
|
||||||
},
|
skipFallback: true,
|
||||||
ok() {
|
},
|
||||||
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
|
ok() {
|
||||||
dnsModal.dnsServer.domains = domains;
|
domains = dnsModal.dnsServer.domains.filter(d => d.length > 0);
|
||||||
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
expectIPs = dnsModal.dnsServer.expectIPs.filter(ip => ip.length > 0);
|
||||||
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
dnsModal.dnsServer.domains = domains;
|
||||||
},
|
dnsModal.dnsServer.expectIPs = expectIPs;
|
||||||
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
|
newDnsServer = (domains.length > 0 || expectIPs.length > 0) ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
||||||
this.title = title;
|
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
||||||
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;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
new Vue({
|
show({
|
||||||
delimiters: ['[[', ']]'],
|
title = '',
|
||||||
el: '#dns-modal',
|
okText = '{{ i18n "confirm" }}',
|
||||||
data: {
|
dnsServer,
|
||||||
dnsModal: dnsModal,
|
confirm = (dnsServer) => { },
|
||||||
},
|
isEdit = false
|
||||||
computed: {
|
}) {
|
||||||
isAdvanced: {
|
this.title = title;
|
||||||
get: function () { return dnsModal.dnsServer.domains.length>0 }
|
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>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
<a-input-number v-model.number="inbound.port" :min="1" :max="65535"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<a-icon type="question-circle"></a-icon>
|
<a-icon type="question-circle"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</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>
|
||||||
|
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -100,6 +100,11 @@
|
|||||||
{{template "form/wireguard"}}
|
{{template "form/wireguard"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- tun -->
|
||||||
|
<template v-if="inbound.protocol === Protocols.TUN">
|
||||||
|
{{template "form/tun"}}
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
<template v-if="inbound.canEnableStream()">
|
<template v-if="inbound.canEnableStream()">
|
||||||
{{template "form/streamSettings"}}
|
{{template "form/streamSettings"}}
|
||||||
@@ -112,7 +117,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- sniffing -->
|
<!-- sniffing -->
|
||||||
<template>
|
<a-collapse>
|
||||||
{{template "form/sniffing"}}
|
<a-collapse-panel header='Sniffing'>
|
||||||
</template>
|
{{template "form/sniffing"}}
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -17,56 +17,92 @@
|
|||||||
|
|
||||||
<!-- freedom settings-->
|
<!-- freedom settings-->
|
||||||
<template v-if="outbound.protocol === Protocols.Freedom">
|
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||||
<a-form-item label='Strategy'>
|
<a-form-item label='Strategy'>
|
||||||
<a-select
|
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="outbound.settings.domainStrategy"
|
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
</a-select>
|
||||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
</a-form-item>
|
||||||
</a-select>
|
<a-form-item label='Redirect'>
|
||||||
</a-form-item>
|
<a-input v-model="outbound.settings.redirect"></a-input>
|
||||||
<a-form-item label='Fragment'>
|
</a-form-item>
|
||||||
<a-switch
|
<a-form-item label='Fragment'>
|
||||||
:checked="Object.keys(outbound.settings.fragment).length >0"
|
<a-switch :checked="Object.keys(outbound.settings.fragment).length >0"
|
||||||
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||||
</a-switch>
|
</a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
||||||
<a-form-item label='Packets'>
|
<a-form-item label='Packets'>
|
||||||
<a-select
|
<a-select v-model="outbound.settings.fragment.packets" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="outbound.settings.fragment.packets"
|
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
</a-select>
|
||||||
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
</a-form-item>
|
||||||
</a-select>
|
<a-form-item label='Length'>
|
||||||
</a-form-item>
|
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
|
||||||
<a-form-item label='Length'>
|
</a-form-item>
|
||||||
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
|
<a-form-item label='Interval'>
|
||||||
</a-form-item>
|
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||||
<a-form-item label='Interval'>
|
</a-form-item>
|
||||||
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
</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 -->
|
<!-- blackhole settings -->
|
||||||
<template v-if="outbound.protocol === Protocols.Blackhole">
|
<template v-if="outbound.protocol === Protocols.Blackhole">
|
||||||
<a-form-item label='Response Type'>
|
<a-form-item label='Response Type'>
|
||||||
<a-select
|
<a-select v-model="outbound.settings.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="outbound.settings.type"
|
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
</a-select>
|
||||||
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
</a-form-item>
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- dns settings -->
|
<!-- dns settings -->
|
||||||
<template v-if="outbound.protocol === Protocols.DNS">
|
<template v-if="outbound.protocol === Protocols.DNS">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
<a-select
|
<a-select v-model="outbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
v-model="outbound.settings.network"
|
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
</a-select>
|
||||||
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
</a-form-item>
|
||||||
</a-select>
|
<a-form-item label='non-IP queries'>
|
||||||
</a-form-item>
|
<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>
|
</template>
|
||||||
|
|
||||||
<!-- wireguard settings -->
|
<!-- wireguard settings -->
|
||||||
@@ -110,8 +146,8 @@
|
|||||||
<a-form-item label='Workers'>
|
<a-form-item label='Workers'>
|
||||||
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
|
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Kernel Mode'>
|
<a-form-item label='No Kernel Tun'>
|
||||||
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
|
<a-switch v-model="outbound.settings.noKernelTun"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<template slot="label">
|
<template slot="label">
|
||||||
@@ -169,12 +205,27 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Vnext (vless/vmess) settings -->
|
<!-- vless/vmess user settings -->
|
||||||
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||||
<a-form-item label='ID'>
|
<a-form-item label='ID'>
|
||||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 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>
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- vless settings -->
|
<!-- 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()">
|
<template v-if="outbound.canEnableTlsFlow()">
|
||||||
<a-form-item label='Flow'>
|
<a-form-item label='Flow'>
|
||||||
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -183,6 +234,28 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</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>
|
</template>
|
||||||
|
|
||||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||||
@@ -212,7 +285,14 @@
|
|||||||
<a-form-item label='UDP over TCP'>
|
<a-form-item label='UDP over TCP'>
|
||||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 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>
|
</template>
|
||||||
|
|
||||||
<!-- stream settings -->
|
<!-- stream settings -->
|
||||||
@@ -220,14 +300,17 @@
|
|||||||
<a-form-item label='{{ i18n "transmission" }}'>
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<template v-if="outbound.protocol != Protocols.Hysteria">
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="tcp">TCP</a-select-option>
|
||||||
<a-select-option value="ws">WebSocket</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
<a-select-option value="http">HTTP/2</a-select-option>
|
<a-select-option value="ws">WebSocket</a-select-option>
|
||||||
<a-select-option value="quic">QUIC</a-select-option>
|
<a-select-option value="grpc">gRPC</a-select-option>
|
||||||
<a-select-option value="grpc">gRPC</a-select-option>
|
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
||||||
<a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
|
<a-select-option value="xhttp">XHTTP</a-select-option>
|
||||||
<a-select-option value="splithttp">SplitHTTP</a-select-option>
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-select-option value="hysteria">Hysteria2</a-select-option>
|
||||||
|
</template>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.stream.network === 'tcp'">
|
<template v-if="outbound.stream.network === 'tcp'">
|
||||||
@@ -248,20 +331,6 @@
|
|||||||
|
|
||||||
<!-- kcp -->
|
<!-- kcp -->
|
||||||
<template v-if="outbound.stream.network === '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-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -294,41 +363,7 @@
|
|||||||
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</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 -->
|
<!-- grpc -->
|
||||||
<template v-if="outbound.stream.network === 'grpc'">
|
<template v-if="outbound.stream.network === 'grpc'">
|
||||||
<a-form-item label='Service Name'>
|
<a-form-item label='Service Name'>
|
||||||
@@ -351,16 +386,169 @@
|
|||||||
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- splithttp -->
|
<!-- xhttp -->
|
||||||
<template v-if="outbound.stream.network === 'splithttp'">
|
<template v-if="outbound.stream.network === 'xhttp'">
|
||||||
<a-form-item label='{{ i18n "host" }}'>
|
<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>
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<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>
|
</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>
|
</template>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
@@ -392,9 +580,15 @@
|
|||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</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-form-item label="Allow Insecure">
|
||||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="VerifyPeerCertByNames">
|
||||||
|
<a-input v-model.trim="outbound.stream.tls.verifyPeerCertByNames"></a-input>
|
||||||
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- reality settings -->
|
<!-- reality settings -->
|
||||||
@@ -417,6 +611,9 @@
|
|||||||
<a-form-item label="Public Key">
|
<a-form-item label="Public Key">
|
||||||
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
||||||
</a-form-item>
|
</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>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -430,14 +627,31 @@
|
|||||||
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</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-form-item label="TCP Fast Open">
|
||||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Keep Alive Interval">
|
<a-form-item label="Multipath TCP">
|
||||||
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
<a-switch v-model.trim="outbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP No-Delay">
|
<a-form-item label="Penetrate">
|
||||||
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
|
<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>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -448,10 +662,10 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="outbound.mux.enabled">
|
<template v-if="outbound.mux.enabled">
|
||||||
<a-form-item label="Concurrency">
|
<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>
|
||||||
<a-form-item label="xudp Concurrency">
|
<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>
|
||||||
<a-form-item label="xudp UDP 443">
|
<a-form-item label="xudp UDP 443">
|
||||||
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -465,7 +679,7 @@
|
|||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||||
<a-form-item style="margin: 10px 0">
|
<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-button @click="convertLink" type="primary"><a-icon type="form"></a-icon></a-button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||||
|
|||||||
@@ -6,6 +6,19 @@
|
|||||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.portMap"}}'>
|
||||||
|
<a-button size="small" @click="inbound.settings.portMap.push({name: '', value: ''})">+</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :wrapper-col="{span:24}">
|
||||||
|
<a-input-group compact v-for="(pm, index) in inbound.settings.portMap">
|
||||||
|
<a-input style="width: 50%" v-model.trim="pm.name" placeholder='{{ i18n "pages.inbounds.port"}}'>
|
||||||
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
|
</a-input>
|
||||||
|
<a-input style="width: 50%" v-model.trim="pm.value" placeholder='{{ i18n "pages.inbounds.targetAddress" }}'>
|
||||||
|
<a-button slot="addonAfter" size="small" @click="inbound.settings.portMap.splice(index,1)">-</a-button>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||||
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||||
@@ -16,8 +29,9 @@
|
|||||||
<a-form-item label='Follow Redirect'>
|
<a-form-item label='Follow Redirect'>
|
||||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Timeout'>
|
|
||||||
<a-input-number v-model.number="inbound.settings.timeout" :min="0"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<!-- sockopt -->
|
||||||
|
<template>
|
||||||
|
{{template "form/streamSockopt"}}
|
||||||
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form>
|
<a-form>
|
||||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="45%">{{ i18n "username" }}</td>
|
<td width="45%">{{ i18n "username" }}</td>
|
||||||
<td width="45%">{{ i18n "password" }}</td>
|
<td width="45%">{{ i18n "password" }}</td>
|
||||||
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
<td><a-button size="small"
|
||||||
</tr>
|
@click="inbound.settings.addAccount(new Inbound.HttpSettings.HttpAccount())">+</a-button></td>
|
||||||
</table>
|
</tr>
|
||||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
</table>
|
||||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||||
</a-input>
|
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
</a-input>
|
||||||
<template slot="addonAfter">
|
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
<template slot="addonAfter">
|
||||||
</template>
|
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
</a-input>
|
</template>
|
||||||
</a-input-group>
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
<a-form-item label="Allow Transparent">
|
||||||
|
<a-switch v-model="inbound.settings.allowTransparent" />
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -45,6 +45,9 @@
|
|||||||
<a-select-option value="udp">UDP</a-select-option>
|
<a-select-option value="udp">UDP</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='ivCheck'>
|
||||||
|
<a-switch v-model="inbound.settings.ivCheck"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:0;"></a-divider>
|
<a-divider style="margin:0;"></a-divider>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<a-input v-model="fallback.dest"></a-input>
|
<a-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='xVer'>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<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>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="Fallbacks">
|
<a-form-item label="Fallbacks">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addFallback()">+</a-button>
|
<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-input v-model="fallback.dest"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='xVer'>
|
<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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-divider style="margin:5px 0;"></a-divider>
|
<a-divider style="margin:5px 0;"></a-divider>
|
||||||
</template>
|
</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}}
|
{{end}}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
<a-form-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Kernel Mode'>
|
<a-form-item label='No Kernel Tun'>
|
||||||
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
|
<a-switch v-model="inbound.settings.noKernelTun"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Peers">
|
<a-form-item label="Peers">
|
||||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
<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"}}
|
{{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 :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Sniffing
|
{{ i18n "enabled" }}
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||||
|
|||||||
@@ -17,10 +17,10 @@
|
|||||||
</template>
|
</template>
|
||||||
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
<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-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-tooltip>
|
||||||
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
<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-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
66
web/html/xui/form/stream/stream_finalmask.html
Normal file
66
web/html/xui/form/stream/stream_finalmask.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{{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>
|
||||||
|
</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"}}
|
{{define "form/streamKCP"}}
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }"
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
:wrapper-col="{ md: {span:14} }">
|
||||||
<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-item label='MTU'>
|
<a-form-item label='MTU'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.mtu" :min="576"
|
||||||
|
:max="1460"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='TTI (ms)'>
|
<a-form-item label='TTI (ms)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.tti" :min="10"
|
||||||
|
:max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Uplink (MB/s)'>
|
<a-form-item label='Uplink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.upCap"
|
||||||
</a-form-item>
|
:min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='Downlink (MB/s)'>
|
<a-form-item label='Downlink (MB/s)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.downCap"
|
||||||
|
:min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Congestion'>
|
<a-form-item label='Congestion'>
|
||||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Read Buffer (MB)'>
|
<a-form-item label='Read Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"
|
||||||
|
:min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='Write Buffer (MB)'>
|
<a-form-item label='Write Buffer (MB)'>
|
||||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"
|
||||||
|
:min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -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"}}
|
{{define "form/streamSettings"}}
|
||||||
<!-- select stream network -->
|
<!-- select stream network -->
|
||||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||||
<a-form-item label="{{ i18n "transmission" }}">
|
<a-form-item label='{{ i18n "transmission" }}'>
|
||||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="tcp">TCP</a-select-option>
|
<a-select-option value="tcp">TCP (RAW)</a-select-option>
|
||||||
<a-select-option value="kcp">mKCP</a-select-option>
|
<a-select-option value="kcp">mKCP</a-select-option>
|
||||||
<a-select-option value="ws">WebSocket</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="grpc">gRPC</a-select-option>
|
||||||
<a-select-option value="httpupgrade">HttpUpgrade</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-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -31,16 +29,6 @@
|
|||||||
{{template "form/streamWS"}}
|
{{template "form/streamWS"}}
|
||||||
</template>
|
</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 -->
|
<!-- grpc -->
|
||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
@@ -51,9 +39,15 @@
|
|||||||
{{template "form/streamHTTPUPGRADE"}}
|
{{template "form/streamHTTPUPGRADE"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- splithttp -->
|
<!-- xhttp -->
|
||||||
<template v-if="inbound.stream.network === 'splithttp'">
|
<template v-if="inbound.stream.network === 'xhttp'">
|
||||||
{{template "form/streamSplitHTTP"}}
|
{{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>
|
</template>
|
||||||
|
|
||||||
<!-- sockopt -->
|
<!-- sockopt -->
|
||||||
|
|||||||
@@ -1,24 +1,73 @@
|
|||||||
{{define "form/streamSockopt"}}
|
{{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 :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-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.sockoptSwitch">
|
<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-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TCP Fast Open">
|
<a-form-item label="TCP Fast Open">
|
||||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Route Mark">
|
<a-form-item label="Multipath TCP">
|
||||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpMptcp"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="TPROXY">
|
<a-form-item label="Penetrate">
|
||||||
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
<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="off">Off</a-select-option>
|
||||||
<a-select-option value="redirect">Redirect</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-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</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,119 @@
|
|||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<template v-if="inbound.stream.isTls">
|
<template v-if="inbound.stream.isTls">
|
||||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||||
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Cipher Suites">
|
<a-form-item label="Cipher Suites">
|
||||||
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value="">Auto</a-select-option>
|
<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-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>
|
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
</a-form-item>
|
||||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
<a-form-item label="Min/Max Version">
|
||||||
</a-select>
|
<a-input-group compact>
|
||||||
</a-input-group>
|
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;"
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="uTLS">
|
|
||||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-select-option value=''>None</a-select-option>
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
</a-select>
|
||||||
</a-select>
|
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;"
|
||||||
</a-form-item>
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
<a-form-item label="ALPN">
|
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||||
<a-select
|
</a-select>
|
||||||
mode="multiple"
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<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="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-form-item label="Pinned Peer Cert">
|
||||||
|
<a-select mode="tags" v-model="inbound.stream.tls.pinnedPeerCertSha256"
|
||||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||||
v-model="inbound.stream.tls.alpn">
|
placeholder="Enter SHA256 fingerprints (base64)">
|
||||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
|
||||||
</a-select>
|
</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-form-item>
|
</a-form-item>
|
||||||
<template v-if="cert.useFile">
|
<a-divider :style="{ margin: '0' }"></a-divider>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<a-form-item label='ECH config'>
|
||||||
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
<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>
|
||||||
<a-form-item label=" ">
|
<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>
|
</a-form-item>
|
||||||
</template>
|
</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 -->
|
<!-- reality settings -->
|
||||||
<template v-if="inbound.stream.isReality">
|
<template v-if="inbound.stream.isReality">
|
||||||
<a-form-item label='Show'>
|
{{template "form/realitySettings"}}
|
||||||
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
</template>
|
||||||
</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>
|
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td>{{ i18n "host" }}</td>
|
<td>{{ i18n "host" }}</td>
|
||||||
<td v-if="inbound.host">
|
<td v-if="inbound.host">
|
||||||
@@ -44,13 +44,14 @@
|
|||||||
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
|
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="inbound.isXHTTP">
|
||||||
<template v-if="inbound.isQuic">
|
<tr>
|
||||||
<tr><td>quic {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.quicSecurity ]]</a-tag></td></tr>
|
<td>Mode</td>
|
||||||
<tr><td>quic {{ i18n "password" }}</td><td><a-tag>[[ inbound.quicKey ]]</a-tag></td></tr>
|
<td>
|
||||||
<tr><td>quic {{ i18n "camouflage" }}</td><td><a-tag>[[ inbound.quicType ]]</a-tag></td></tr>
|
<a-tag>[[ inbound.stream.xhttp.mode ]]</a-tag>
|
||||||
</template>
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
<template v-if="inbound.isKcp">
|
<template v-if="inbound.isKcp">
|
||||||
<tr><td>kcp {{ i18n "encryption" }}</td><td><a-tag>[[ inbound.kcpType ]]</a-tag></td></tr>
|
<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>
|
<tr><td>kcp {{ i18n "password" }}</td><td><a-tag>[[ inbound.kcpSeed ]]</a-tag></td></tr>
|
||||||
@@ -70,9 +71,18 @@
|
|||||||
{{ i18n "security" }}
|
{{ i18n "security" }}
|
||||||
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||||
<br />
|
<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'">
|
<template v-if="inbound.stream.security != 'none'">
|
||||||
{{ i18n "domainName" }}
|
{{ i18n "domainName" }}
|
||||||
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : ''
|
||||||
|
]]</a-tag>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||||
@@ -284,8 +294,8 @@
|
|||||||
<td>[[ inbound.settings.mtu ]]</td>
|
<td>[[ inbound.settings.mtu ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Kernel Mode</td>
|
<td>No Kernel Tun</td>
|
||||||
<td>[[ inbound.settings.kernelMode ]]</td>
|
<td>[[ inbound.settings.noKernelTun ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="(peer, index) in inbound.settings.peers">
|
<template v-for="(peer, index) in inbound.settings.peers">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -412,8 +422,8 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elmentId,content) {
|
copyToClipboard(elementId,content) {
|
||||||
this.infoModal.clipboard = new ClipboardJS('#' + elmentId, {
|
this.infoModal.clipboard = new ClipboardJS('#' + elementId, {
|
||||||
text: () => content,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.infoModal.clipboard.on('success', () => {
|
this.infoModal.clipboard.on('success', () => {
|
||||||
|
|||||||
@@ -26,6 +26,11 @@
|
|||||||
} else {
|
} else {
|
||||||
this.inbound = new Inbound();
|
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) {
|
if (dbInbound) {
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
} else {
|
} else {
|
||||||
@@ -42,6 +47,27 @@
|
|||||||
loading(loading=true) {
|
loading(loading=true) {
|
||||||
inModal.confirmLoading = loading;
|
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({
|
new Vue({
|
||||||
@@ -124,7 +150,7 @@
|
|||||||
},
|
},
|
||||||
async getNewX25519Cert(){
|
async getNewX25519Cert(){
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/server/getNewX25519Cert');
|
const msg = await HttpUtil.get('/server/getNewX25519Cert');
|
||||||
inModal.loading(false);
|
inModal.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
@@ -132,6 +158,80 @@
|
|||||||
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
inModal.inbound.stream.reality.privateKey = msg.obj.privateKey;
|
||||||
inModal.inbound.stream.reality.settings.publicKey = msg.obj.publicKey;
|
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/qrcode/qrious.min.js"></script>
|
||||||
<script src="{{ .base_path }}assets/clipboard/clipboard.min.js?{{ .cur_ver }}"></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/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>
|
<script src="{{ .base_path }}assets/js/model/dbinbound.js?{{ .cur_ver }}"></script>
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
<script>
|
<script>
|
||||||
@@ -925,7 +925,11 @@
|
|||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()){
|
||||||
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
} else if (inbound.stream?.sockopt) {
|
||||||
|
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||||
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit('/xui/inbound/add', data, inModal);
|
await this.submit('/xui/inbound/add', data, inModal);
|
||||||
@@ -944,7 +948,11 @@
|
|||||||
protocol: inbound.protocol,
|
protocol: inbound.protocol,
|
||||||
settings: inbound.settings.toString(),
|
settings: inbound.settings.toString(),
|
||||||
};
|
};
|
||||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
if (inbound.canEnableStream()){
|
||||||
|
data.streamSettings = inbound.stream.toString();
|
||||||
|
} else if (inbound.stream?.sockopt) {
|
||||||
|
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||||
|
}
|
||||||
data.sniffing = inbound.sniffing.toString();
|
data.sniffing = inbound.sniffing.toString();
|
||||||
|
|
||||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
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="20">20</a-select-option>
|
||||||
<a-select-option value="50">50</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="100">100</a-select-option>
|
||||||
|
<a-select-option value="500">500</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select v-model="logModal.level" style="width:100px;"
|
<a-select v-model="logModal.level" style="width:100px;"
|
||||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
@@ -534,7 +535,7 @@
|
|||||||
this.loadingTip = tip;
|
this.loadingTip = tip;
|
||||||
},
|
},
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
const msg = await HttpUtil.get('/server/status');
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.setStatus(msg.obj);
|
this.setStatus(msg.obj);
|
||||||
}
|
}
|
||||||
@@ -544,7 +545,7 @@
|
|||||||
},
|
},
|
||||||
async openSelectV2rayVersion() {
|
async openSelectV2rayVersion() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getXrayVersion');
|
const msg = await HttpUtil.get('server/getXrayVersion');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
@@ -594,7 +595,7 @@
|
|||||||
},
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/getConfigJson');
|
const msg = await HttpUtil.get('server/getConfigJson');
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@
|
|||||||
<td>[[ warpModal.warpData.private_key ]]</td>
|
<td>[[ warpModal.warpData.private_key ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<a-button @click="delConfig" :loading="warpModal.confirmLoading" type="danger">{{ i18n "delete" }}</a-button>
|
||||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
||||||
<a-collapse style="margin: 10px 0;">
|
<a-collapse style="margin: 10px 0;">
|
||||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||||
@@ -91,114 +92,153 @@
|
|||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const warpModal = {
|
const warpModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
confirmLoading: false,
|
confirmLoading: false,
|
||||||
warpData: null,
|
warpData: null,
|
||||||
warpConfig: null,
|
warpConfig: null,
|
||||||
warpOutbound: null,
|
warpOutbound: null,
|
||||||
show() {
|
show() {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.warpConfig = null;
|
this.warpConfig = null;
|
||||||
this.getData();
|
this.getData();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
this.loading(false);
|
||||||
|
},
|
||||||
|
loading(loading=true) {
|
||||||
|
this.confirmLoading = loading;
|
||||||
|
},
|
||||||
|
async getData(){
|
||||||
|
this.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/xui/xray/warp/data');
|
||||||
|
this.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
delimiters: ['[[', ']]'],
|
||||||
|
el: '#warp-modal',
|
||||||
|
data: {
|
||||||
|
warpModal: warpModal,
|
||||||
|
warpPlus: '',
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
collectConfig() {
|
||||||
|
config = warpModal.warpConfig.config;
|
||||||
|
peer = config.peers[0];
|
||||||
|
if(config){
|
||||||
|
warpModal.warpOutbound = Outbound.fromJson({
|
||||||
|
tag: 'warp',
|
||||||
|
protocol: Protocols.Wireguard,
|
||||||
|
settings: {
|
||||||
|
mtu: 1420,
|
||||||
|
secretKey: warpModal.warpData.private_key,
|
||||||
|
address: this.getAddresses(config.interface.addresses),
|
||||||
|
reserved: this.getResolved(config.client_id),
|
||||||
|
domainStrategy: 'ForceIP',
|
||||||
|
peers: [{
|
||||||
|
publicKey: peer.public_key,
|
||||||
|
endpoint: peer.endpoint.host,
|
||||||
|
}],
|
||||||
|
noKernelTun: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
close() {
|
getAddresses(addrs){
|
||||||
this.visible = false;
|
let addresses = [];
|
||||||
this.loading(false);
|
if (addrs.v4) addresses.push(addrs.v4 + "/32");
|
||||||
|
if (addrs.v6) addresses.push(addrs.v6 + "/128");
|
||||||
|
return addresses;
|
||||||
},
|
},
|
||||||
loading(loading=true) {
|
getResolved(client_id){
|
||||||
this.confirmLoading = loading;
|
let reserved = [];
|
||||||
|
let decoded = atob(client_id);
|
||||||
|
let hexString = '';
|
||||||
|
for (let i = 0; i < decoded.length; i++) {
|
||||||
|
let hex = decoded.charCodeAt(i).toString(16);
|
||||||
|
hexString += (hex.length === 1 ? '0' : '') + hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < hexString.length; i += 2) {
|
||||||
|
let hexByte = hexString.slice(i, i + 2);
|
||||||
|
let decValue = parseInt(hexByte, 16);
|
||||||
|
reserved.push(decValue);
|
||||||
|
}
|
||||||
|
return reserved;
|
||||||
},
|
},
|
||||||
async getData(){
|
async register(){
|
||||||
this.loading(true);
|
warpModal.loading(true);
|
||||||
const msg = await HttpUtil.post('/xui/xray/warp/data');
|
const keys = Wireguard.generateKeypair();
|
||||||
this.loading(false);
|
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
|
||||||
if (msg.success) {
|
if (msg.success) {
|
||||||
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
|
const resp = JSON.parse(msg.obj);
|
||||||
|
warpModal.warpData = resp.data;
|
||||||
|
warpModal.warpConfig = resp.config;
|
||||||
|
this.collectConfig();
|
||||||
|
}
|
||||||
|
warpModal.loading(false);
|
||||||
|
},
|
||||||
|
async updateLicense(l){
|
||||||
|
warpModal.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l});
|
||||||
|
if (msg.success) {
|
||||||
|
warpModal.warpData = JSON.parse(msg.obj);
|
||||||
|
warpModal.warpConfig = null;
|
||||||
|
this.warpPlus = '';
|
||||||
|
}
|
||||||
|
warpModal.loading(false);
|
||||||
|
},
|
||||||
|
async getConfig(){
|
||||||
|
warpModal.loading(true);
|
||||||
|
const msg = await HttpUtil.post('/xui/xray/warp/config');
|
||||||
|
warpModal.loading(false);
|
||||||
|
if (msg.success) {
|
||||||
|
warpModal.warpConfig = JSON.parse(msg.obj);
|
||||||
|
this.collectConfig();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
async delConfig(){
|
||||||
|
warpModal.loading(true);
|
||||||
new Vue({
|
const msg = await HttpUtil.post('/xui/xray/warp/del');
|
||||||
delimiters: ['[[', ']]'],
|
warpModal.loading(false);
|
||||||
el: '#warp-modal',
|
if (msg.success) {
|
||||||
data: {
|
warpModal.warpData = null;
|
||||||
warpModal: warpModal,
|
warpModal.warpConfig = null;
|
||||||
warpPlus: '',
|
this.delOutbound();
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
collectConfig() {
|
|
||||||
config = warpModal.warpConfig.config;
|
|
||||||
peer = config.peers[0];
|
|
||||||
if(config){
|
|
||||||
warpModal.warpOutbound = Outbound.fromJson({
|
|
||||||
tag: 'warp',
|
|
||||||
protocol: Protocols.Wireguard,
|
|
||||||
settings: {
|
|
||||||
mtu: 1420,
|
|
||||||
secretKey: warpModal.warpData.private_key,
|
|
||||||
address: Object.values(config.interface.addresses),
|
|
||||||
domainStrategy: 'ForceIP',
|
|
||||||
peers: [{
|
|
||||||
publicKey: peer.public_key,
|
|
||||||
endpoint: peer.endpoint.host,
|
|
||||||
}],
|
|
||||||
kernelMode: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async register(){
|
|
||||||
warpModal.loading(true);
|
|
||||||
keys = Wireguard.generateKeypair();
|
|
||||||
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
|
|
||||||
if (msg.success) {
|
|
||||||
resp = JSON.parse(msg.obj);
|
|
||||||
warpModal.warpData = resp.data;
|
|
||||||
warpModal.warpConfig = resp.config;
|
|
||||||
this.collectConfig();
|
|
||||||
}
|
|
||||||
warpModal.loading(false);
|
|
||||||
},
|
|
||||||
async updateLicense(l){
|
|
||||||
warpModal.loading(true);
|
|
||||||
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l});
|
|
||||||
if (msg.success) {
|
|
||||||
warpModal.warpData = JSON.parse(msg.obj);
|
|
||||||
warpModal.warpConfig = null;
|
|
||||||
this.warpPlus = '';
|
|
||||||
}
|
|
||||||
warpModal.loading(false);
|
|
||||||
},
|
|
||||||
async getConfig(){
|
|
||||||
warpModal.loading(true);
|
|
||||||
const msg = await HttpUtil.post('/xui/xray/warp/config');
|
|
||||||
warpModal.loading(false);
|
|
||||||
if (msg.success) {
|
|
||||||
warpModal.warpConfig = JSON.parse(msg.obj);
|
|
||||||
this.collectConfig();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
addOutbound(){
|
|
||||||
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
|
|
||||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
|
||||||
warpModal.close();
|
|
||||||
},
|
|
||||||
resetOutbound(){
|
|
||||||
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
|
|
||||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
|
||||||
warpModal.close();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
addOutbound(){
|
||||||
warpOutboundIndex: {
|
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
|
||||||
get: function() {
|
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||||
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
|
warpModal.close();
|
||||||
}
|
},
|
||||||
|
resetOutbound(){
|
||||||
|
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
|
||||||
|
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||||
|
warpModal.close();
|
||||||
|
},
|
||||||
|
delOutbound(){
|
||||||
|
if (this.warpOutboundIndex != -1){
|
||||||
|
app.templateSettings.outbounds.splice(this.warpOutboundIndex,1);
|
||||||
|
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||||
|
}
|
||||||
|
warpModal.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
warpOutboundIndex: {
|
||||||
|
get: function() {
|
||||||
|
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -160,41 +160,69 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-list-item>
|
<a-list-item>
|
||||||
<a-row style="padding: 20px">
|
<a-row style="padding: 10px 20px">
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<a-list-item-meta title='{{ i18n "pages.xray.logLevel" }}'
|
<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>
|
||||||
<a-col :lg="24" :xl="12">
|
<a-col :lg="24" :xl="12">
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select v-model="logLevel" :dropdown-class-name="themeSwitcher.currentTheme" style="width: 100%">
|
||||||
v-model="logLevel"
|
<a-select-option v-for="s in log.loglevel" :value="s">[[ s ]]</a-select-option>
|
||||||
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>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</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-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>
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
<a-collapse-panel header='{{ i18n "pages.xray.blockConfigs"}}'>
|
||||||
<a-row :xs="24" :sm="24" :lg="12">
|
<a-row :xs="24" :sm="24" :lg="12">
|
||||||
@@ -206,69 +234,155 @@
|
|||||||
</a-alert>
|
</a-alert>
|
||||||
</a-row>
|
</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.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>
|
<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>
|
||||||
<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-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" style="text-align: center;">
|
<a-alert type="warning" style="text-align: center;">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
{{ i18n "pages.xray.blockCountryConfigsDesc" }}
|
{{ i18n "pages.xray.blockConnectionsConfigsDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</a-row>
|
</a-row>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRIp"}}' desc='{{ i18n "pages.xray.IRIpDesc"}}' v-model="IRIpSettings"></setting-list-item>
|
<a-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.IRDomain"}}' desc='{{ i18n "pages.xray.IRDomainDesc"}}' v-model="IRDomainSettings"></setting-list-item>
|
<a-row style="padding: 0 20px">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaIp"}}' desc='{{ i18n "pages.xray.ChinaIpDesc"}}' v-model="ChinaIpSettings"></setting-list-item>
|
<a-col :lg="24" :xl="12">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.ChinaDomain"}}' desc='{{ i18n "pages.xray.ChinaDomainDesc"}}' v-model="ChinaDomainSettings"></setting-list-item>
|
<a-list-item-meta
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaIp"}}' desc='{{ i18n "pages.xray.RussiaIpDesc"}}' v-model="RussiaIpSettings"></setting-list-item>
|
title='{{ i18n "pages.xray.blockips" }}'/>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.RussiaDomain"}}' desc='{{ i18n "pages.xray.RussiaDomainDesc"}}' v-model="RussiaDomainSettings"></setting-list-item>
|
</a-col>
|
||||||
</a-collapse-panel>
|
<a-col :lg="24" :xl="12">
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}'>
|
<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-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" style="text-align: center;">
|
<a-alert type="warning" style="text-align: center;">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
{{ i18n "pages.xray.directCountryConfigsDesc" }}
|
{{ i18n "pages.xray.directConnectionsConfigsDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</a-row>
|
</a-row>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRIp"}}' desc='{{ i18n "pages.xray.DirectIRIpDesc"}}' v-model="IRIpDirectSettings"></setting-list-item>
|
<a-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectIRDomain"}}' desc='{{ i18n "pages.xray.DirectIRDomainDesc"}}' v-model="IRDomainDirectSettings"></setting-list-item>
|
<a-row style="padding: 0 20px">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaIp"}}' desc='{{ i18n "pages.xray.DirectChinaIpDesc"}}' v-model="ChinaIpDirectSettings"></setting-list-item>
|
<a-col :lg="24" :xl="12">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectChinaDomain"}}' desc='{{ i18n "pages.xray.DirectChinaDomainDesc"}}' v-model="ChinaDomainDirectSettings"></setting-list-item>
|
<a-list-item-meta
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaIp"}}' desc='{{ i18n "pages.xray.DirectRussiaIpDesc"}}' v-model="RussiaIpDirectSettings"></setting-list-item>
|
title='{{ i18n "pages.xray.directips" }}'/>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.DirectRussiaDomain"}}' desc='{{ i18n "pages.xray.DirectRussiaDomainDesc"}}' v-model="RussiaDomainDirectSettings"></setting-list-item>
|
</a-col>
|
||||||
</a-collapse-panel>
|
<a-col :lg="24" :xl="12">
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.ipv4Configs"}}'>
|
<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-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" style="text-align: center;">
|
<a-alert type="warning" style="text-align: center;">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
{{ i18n "pages.xray.ipv4ConfigsDesc" }}
|
{{ i18n "pages.xray.ipv4RoutingDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</a-row>
|
</a-row>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.GoogleIPv4"}}' desc='{{ i18n "pages.xray.GoogleIPv4Desc"}}' v-model="GoogleIPv4Settings"></setting-list-item>
|
<a-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixIPv4"}}' desc='{{ i18n "pages.xray.NetflixIPv4Desc"}}' v-model="NetflixIPv4Settings"></setting-list-item>
|
<a-row style="padding: 0 20px">
|
||||||
</a-collapse-panel>
|
<a-col :lg="24" :xl="12">
|
||||||
<a-collapse-panel header='{{ i18n "pages.xray.warpConfigs"}}'>
|
<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-row :xs="24" :sm="24" :lg="12">
|
||||||
<a-alert type="warning" style="text-align: center;">
|
<a-alert type="warning" style="text-align: center;">
|
||||||
<template slot="message">
|
<template slot="message">
|
||||||
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
<a-icon type="exclamation-circle" theme="filled" style="color: #FFA031"></a-icon>
|
||||||
{{ i18n "pages.xray.warpConfigsDesc" }}
|
{{ i18n "pages.xray.warpRoutingDesc" }}
|
||||||
</template>
|
</template>
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</a-row>
|
</a-row>
|
||||||
<template v-if="WarpExist">
|
<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>
|
<a-list-item>
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.OpenAIWARP"}}' desc='{{ i18n "pages.xray.OpenAIWARPDesc"}}' v-model="OpenAIWARPSettings"></setting-list-item>
|
<a-row style="padding: 0 20px">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.NetflixWARP"}}' desc='{{ i18n "pages.xray.NetflixWARPDesc"}}' v-model="NetflixWARPSettings"></setting-list-item>
|
<a-col :lg="24" :xl="12">
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.SpotifyWARP"}}' desc='{{ i18n "pages.xray.SpotifyWARPDesc"}}' v-model="SpotifyWARPSettings"></setting-list-item>
|
<a-list-item-meta
|
||||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.MetaWARP"}}' desc='{{ i18n "pages.xray.MetaWARPDesc"}}' v-model="MetaWARPSettings"></setting-list-item>
|
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>
|
</template>
|
||||||
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
<a-button v-else type="primary" icon="cloud" style="margin: 15px 20px;" @click="showWarp()">WARP</a-button>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
@@ -571,6 +685,9 @@
|
|||||||
<template slot="domain" slot-scope="dns,index">
|
<template slot="domain" slot-scope="dns,index">
|
||||||
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
<span v-if="typeof dns == 'object'">[[ dns.domains.join(",") ]]</span>
|
||||||
</template>
|
</template>
|
||||||
|
<template slot="expectIPs" slot-scope="dns,index">
|
||||||
|
<span v-if="typeof dns == 'object'">[[ dns.expectIPs.join(",") ]]</span>
|
||||||
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-divider>Fake DNS</a-divider>
|
<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>
|
<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: "#", align: 'center', width: 20, scopedSlots: { customRender: 'action' } },
|
||||||
{ title: '{{ i18n "pages.xray.outbound.address"}}', align: 'center', width: 50, scopedSlots: { customRender: 'address' } },
|
{ 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.domains"}}', align: 'center', width: 50, scopedSlots: { customRender: 'domain' } },
|
||||||
|
{ title: '{{ i18n "pages.xray.dns.expectIPs"}}', align: 'center', width: 50, scopedSlots: { customRender: 'expectIPs' } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const fakednsColumns = [
|
const fakednsColumns = [
|
||||||
@@ -738,41 +856,43 @@
|
|||||||
tag: "direct",
|
tag: "direct",
|
||||||
protocol: "freedom"
|
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: {
|
settingsData: {
|
||||||
protocols: {
|
protocols: {
|
||||||
bittorrent: ["bittorrent"],
|
bittorrent: ["bittorrent"],
|
||||||
},
|
},
|
||||||
ips: {
|
IPsOptions: [
|
||||||
local: ["geoip:private"],
|
{ label: 'Private IP', value: 'geoip:private' },
|
||||||
cn: ["geoip:cn"],
|
{ label: '🇮🇷 Iran', value: 'ext:geoip_IR.dat:ir' },
|
||||||
ir: ["ext:geoip_IR.dat:ir"],
|
{ label: '🇨🇳 China', value: 'geoip:cn' },
|
||||||
ru: ["geoip:ru"],
|
{ label: '🇷🇺 Russia', value: 'geoip:ru' },
|
||||||
},
|
],
|
||||||
domains: {
|
DomainsOptions: [
|
||||||
ads: [
|
{ label: 'Ads All', value: 'geosite:category-ads-all' },
|
||||||
"geosite:category-ads-all",
|
{ label: 'Ads IR', value: 'ext:geosite_IR.dat:category-ads-all' },
|
||||||
"ext:geosite_IR.dat:category-ads-all"
|
{ label: '🇮🇷 Iran', value: 'ext:geosite_IR.dat:ir' },
|
||||||
],
|
{ label: '🇮🇷 .ir', value: 'regexp:.*\\.ir$' },
|
||||||
openai: ["geosite:openai"],
|
{ label: '🇮🇷 .ایران', value: 'regexp:.*\\.xn--mgba3a4f16a$' },
|
||||||
google: ["geosite:google"],
|
{ label: '🇨🇳 China', value: 'geosite:cn' },
|
||||||
spotify: ["geosite:spotify"],
|
{ label: '🇨🇳 .cn', value: 'regexp:.*\\.cn$' },
|
||||||
netflix: ["geosite:netflix"],
|
{ label: '🇷🇺 Russia', value: 'geosite:category-ru' },
|
||||||
meta: ["geosite:meta"],
|
{ label: '🇷🇺 .ru', value: 'regexp:.*\\.ru' },
|
||||||
cn: [
|
],
|
||||||
"geosite:cn",
|
ServicesOptions: [
|
||||||
"regexp:.*\\.cn$"
|
{ label: 'Apple', value: 'geosite:apple' },
|
||||||
],
|
{ label: 'Meta', value: 'geosite:meta' },
|
||||||
ru: [
|
{ label: 'Google', value: 'geosite:google' },
|
||||||
"geosite:category-gov-ru",
|
{ label: 'OpenAI', value: 'geosite:openai' },
|
||||||
"regexp:.*\\.ru$"
|
{ label: 'Spotify', value: 'geosite:spotify' },
|
||||||
],
|
{ label: 'Netflix', value: 'geosite:netflix' },
|
||||||
ir: [
|
],
|
||||||
"regexp:.*\\.ir$",
|
|
||||||
"regexp:.*\\.xn--mgba3a4f16a$", // .ایران
|
|
||||||
"ext:geosite_IR.dat:ir"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
familyProtectDNS: {
|
familyProtectDNS: {
|
||||||
"servers": [
|
"servers": [
|
||||||
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
"1.1.1.3", // https://developers.cloudflare.com/1.1.1.1/setup/
|
||||||
@@ -937,12 +1057,13 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeObsCode() {
|
changeObsCode() {
|
||||||
if (this.obsSettings == ''){
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if(this.cm != null) {
|
if(this.cm != null) {
|
||||||
this.cm.toTextArea();
|
this.cm.toTextArea();
|
||||||
}
|
}
|
||||||
|
if (this.obsSettings == ''){
|
||||||
|
this.cm = null;
|
||||||
|
return
|
||||||
|
}
|
||||||
textAreaObj = document.getElementById('obsSetting');
|
textAreaObj = document.getElementById('obsSetting');
|
||||||
textAreaObj.value = this[this.obsSettings];
|
textAreaObj.value = this[this.obsSettings];
|
||||||
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
this.cm = CodeMirror.fromTextArea(textAreaObj, this.cmOptions);
|
||||||
@@ -966,7 +1087,9 @@
|
|||||||
switch(o.protocol){
|
switch(o.protocol){
|
||||||
case Protocols.VMess:
|
case Protocols.VMess:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
serverObj = o.settings.vnext;
|
if (o.settings && o.settings.address && o.settings.port) {
|
||||||
|
return [o.settings.address + ':' + o.settings.port];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Protocols.HTTP:
|
case Protocols.HTTP:
|
||||||
case Protocols.Socks:
|
case Protocols.Socks:
|
||||||
@@ -1116,7 +1239,8 @@
|
|||||||
balancer: {
|
balancer: {
|
||||||
tag: '',
|
tag: '',
|
||||||
strategy: 'random',
|
strategy: 'random',
|
||||||
selector: []
|
selector: [],
|
||||||
|
fallbackTag: ''
|
||||||
},
|
},
|
||||||
confirm: (balancer) => {
|
confirm: (balancer) => {
|
||||||
balancerModal.loading();
|
balancerModal.loading();
|
||||||
@@ -1126,27 +1250,17 @@
|
|||||||
}
|
}
|
||||||
let tmpBalancer = {
|
let tmpBalancer = {
|
||||||
'tag': balancer.tag,
|
'tag': balancer.tag,
|
||||||
'selector': balancer.selector
|
'selector': balancer.selector,
|
||||||
|
'fallbackTag': balancer.fallbackTag
|
||||||
};
|
};
|
||||||
if (balancer.strategy && balancer.strategy != 'random') {
|
if (balancer.strategy && balancer.strategy != 'random') {
|
||||||
tmpBalancer.strategy = {
|
tmpBalancer.strategy = {
|
||||||
'type': balancer.strategy
|
'type': balancer.strategy
|
||||||
};
|
};
|
||||||
if (balancer.strategy == 'leastPing'){
|
|
||||||
if (!newTemplateSettings.observatory)
|
|
||||||
newTemplateSettings.observatory = this.defaultObservatory;
|
|
||||||
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
|
|
||||||
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
|
|
||||||
}
|
|
||||||
if (balancer.strategy == 'leastLoad'){
|
|
||||||
if (!newTemplateSettings.burstObservatory)
|
|
||||||
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
|
||||||
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
|
|
||||||
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
newTemplateSettings.routing.balancers.push(tmpBalancer);
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
|
this.updateObservatorySelectors();
|
||||||
balancerModal.close();
|
balancerModal.close();
|
||||||
this.changeObsCode();
|
this.changeObsCode();
|
||||||
},
|
},
|
||||||
@@ -1166,7 +1280,8 @@
|
|||||||
|
|
||||||
let tmpBalancer = {
|
let tmpBalancer = {
|
||||||
'tag': balancer.tag,
|
'tag': balancer.tag,
|
||||||
'selector': balancer.selector
|
'selector': balancer.selector,
|
||||||
|
'fallbackTag': balancer.fallbackTag
|
||||||
};
|
};
|
||||||
|
|
||||||
// Remove old tag
|
// Remove old tag
|
||||||
@@ -1181,18 +1296,6 @@
|
|||||||
tmpBalancer.strategy = {
|
tmpBalancer.strategy = {
|
||||||
'type': balancer.strategy
|
'type': balancer.strategy
|
||||||
};
|
};
|
||||||
if (balancer.strategy == 'leastPing'){
|
|
||||||
if (!newTemplateSettings.observatory)
|
|
||||||
newTemplateSettings.observatory = this.defaultObservatory;
|
|
||||||
if (!newTemplateSettings.observatory.subjectSelector.includes(balancer.tag))
|
|
||||||
newTemplateSettings.observatory.subjectSelector.push(balancer.tag);
|
|
||||||
}
|
|
||||||
if (balancer.strategy == 'leastLoad'){
|
|
||||||
if (!newTemplateSettings.burstObservatory)
|
|
||||||
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
|
||||||
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(balancer.tag))
|
|
||||||
newTemplateSettings.burstObservatory.subjectSelector.push(balancer.tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
newTemplateSettings.routing.balancers[index] = tmpBalancer;
|
||||||
@@ -1205,12 +1308,50 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
|
this.updateObservatorySelectors();
|
||||||
balancerModal.close();
|
balancerModal.close();
|
||||||
this.changeObsCode();
|
this.changeObsCode();
|
||||||
},
|
},
|
||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateObservatorySelectors(){
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
const leastPings = this.balancersData.filter((b) => b.strategy == 'leastPing');
|
||||||
|
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;
|
||||||
|
newTemplateSettings.observatory.subjectSelector = [];
|
||||||
|
leastPings.forEach((b) => {
|
||||||
|
b.selector.forEach((s) => {
|
||||||
|
if (!newTemplateSettings.observatory.subjectSelector.includes(s))
|
||||||
|
newTemplateSettings.observatory.subjectSelector.push(s);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.observatory
|
||||||
|
}
|
||||||
|
if (leastLoads.length>0){
|
||||||
|
if (!newTemplateSettings.burstObservatory)
|
||||||
|
newTemplateSettings.burstObservatory = this.defaultBurstObservatory;
|
||||||
|
newTemplateSettings.burstObservatory.subjectSelector = [];
|
||||||
|
leastLoads.forEach((b) => {
|
||||||
|
b.selector.forEach((s) => {
|
||||||
|
if (!newTemplateSettings.burstObservatory.subjectSelector.includes(s))
|
||||||
|
newTemplateSettings.burstObservatory.subjectSelector.push(s);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
delete newTemplateSettings.burstObservatory
|
||||||
|
}
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
this.changeObsCode();
|
||||||
|
},
|
||||||
deleteBalancer(index) {
|
deleteBalancer(index) {
|
||||||
newTemplateSettings = this.templateSettings;
|
newTemplateSettings = this.templateSettings;
|
||||||
|
|
||||||
@@ -1221,19 +1362,13 @@
|
|||||||
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
let realIndex = newTemplateSettings.routing.balancers.findIndex((b) => b.tag === removedBalancer.tag);
|
||||||
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
newTemplateSettings.routing.balancers.splice(realIndex, 1);
|
||||||
|
|
||||||
// Remove tag from observatory
|
|
||||||
if (newTemplateSettings.observatory){
|
|
||||||
newTemplateSettings.observatory.subjectSelector = newTemplateSettings.observatory.subjectSelector.filter(s => s != removedBalancer.tag);
|
|
||||||
}
|
|
||||||
if (newTemplateSettings.burstObservatory){
|
|
||||||
newTemplateSettings.burstObservatory.subjectSelector = newTemplateSettings.burstObservatory.subjectSelector.filter(s => s != removedBalancer.tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update balancers property to an empty array if there are no more balancers
|
// Update balancers property to an empty array if there are no more balancers
|
||||||
if (newTemplateSettings.routing.balancers.length === 0) {
|
if (newTemplateSettings.routing.balancers.length === 0) {
|
||||||
delete newTemplateSettings.routing.balancers;
|
delete newTemplateSettings.routing.balancers;
|
||||||
}
|
}
|
||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
|
this.updateObservatorySelectors();
|
||||||
|
this.obsSettings = '';
|
||||||
this.changeObsCode()
|
this.changeObsCode()
|
||||||
},
|
},
|
||||||
addDNSServer(){
|
addDNSServer(){
|
||||||
@@ -1366,28 +1501,59 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
logLevel: {
|
logLevel: {
|
||||||
get: function () { return this.logSettings?.loglevel?? 'none'; },
|
get: function () {
|
||||||
set: function (newValue) {
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.loglevel) return "warning";
|
||||||
newLogSettings = this.logSettings;
|
return this.templateSettings.log.loglevel;
|
||||||
newLogSettings.loglevel = newValue;
|
|
||||||
this.logSettings = newLogSettings;
|
|
||||||
},
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.loglevel = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
logAccess: {
|
accessLog: {
|
||||||
get: function () { return this.logSettings?.access?? ''; },
|
get: function () {
|
||||||
set: function (newValue) {
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.access) return "";
|
||||||
newLogSettings = this.logSettings;
|
return this.templateSettings.log.access;
|
||||||
newValue == "" ? delete newLogSettings.access : newLogSettings.access = newValue;
|
|
||||||
this.logSettings = newLogSettings;
|
|
||||||
},
|
},
|
||||||
|
set: function (newValue) {
|
||||||
|
newTemplateSettings = this.templateSettings;
|
||||||
|
newTemplateSettings.log.access = newValue;
|
||||||
|
this.templateSettings = newTemplateSettings;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
logError: {
|
errorLog: {
|
||||||
get: function () { return this.logSettings?.error?? ''; },
|
get: function () {
|
||||||
set: function (newValue) {
|
if (!this.templateSettings || !this.templateSettings.log || !this.templateSettings.log.error) return "";
|
||||||
newLogSettings = this.logSettings;
|
return this.templateSettings.log.error;
|
||||||
newValue == "" ? delete newLogSettings.error : newLogSettings.error = newValue;
|
|
||||||
this.logSettings = newLogSettings;
|
|
||||||
},
|
},
|
||||||
|
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: {
|
inboundSettings: {
|
||||||
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
get: function () { return this.templateSettings ? JSON.stringify(this.templateSettings.inbounds, null, 2) : null; },
|
||||||
@@ -1472,7 +1638,8 @@
|
|||||||
'key': index,
|
'key': index,
|
||||||
'tag': o.tag ? o.tag : "",
|
'tag': o.tag ? o.tag : "",
|
||||||
'strategy': o.strategy?.type ?? "random",
|
'strategy': o.strategy?.type ?? "random",
|
||||||
'selector': o.selector ? o.selector : []
|
'selector': o.selector ? o.selector : [],
|
||||||
|
'fallbackTag': o.fallbackTag?? '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1499,22 +1666,8 @@
|
|||||||
this.templateSettings = newTemplateSettings;
|
this.templateSettings = newTemplateSettings;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
observatoryEnable: {
|
observatoryEnable: function () { return this.templateSettings != null && this.templateSettings.observatory != undefined },
|
||||||
get: function () { return this.templateSettings != null && this.templateSettings.observatory },
|
burstObservatoryEnable: function () { return this.templateSettings != null && this.templateSettings.burstObservatory != undefined },
|
||||||
set: function (v) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.observatory = v ? this.defaultObservatory : undefined;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
burstObservatoryEnable: {
|
|
||||||
get: function () { return this.templateSettings != null && this.templateSettings.burstObservatory },
|
|
||||||
set: function (v) {
|
|
||||||
newTemplateSettings = this.templateSettings;
|
|
||||||
newTemplateSettings.burstObservatory = v ? this.defaultBurstObservatory : undefined;
|
|
||||||
this.templateSettings = newTemplateSettings;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
freedomStrategy: {
|
freedomStrategy: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings) return "AsIs";
|
if (!this.templateSettings) return "AsIs";
|
||||||
@@ -1618,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: {
|
familyProtectSettings: {
|
||||||
get: function () {
|
get: function () {
|
||||||
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
if (!this.templateSettings || !this.templateSettings.dns || !this.templateSettings.dns.servers) return false;
|
||||||
@@ -1657,239 +1786,11 @@
|
|||||||
this.templateSettings = newTemplateSettings;
|
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: {
|
WarpExist: {
|
||||||
get: function() {
|
get: function() {
|
||||||
return this.templateSettings ? this.templateSettings.outbounds.findIndex((o) => o.tag == "warp")>=0 : false;
|
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: {
|
enableDNS: {
|
||||||
get: function () {
|
get: function () {
|
||||||
return this.templateSettings ? this.templateSettings.dns != null : false;
|
return this.templateSettings ? this.templateSettings.dns != null : false;
|
||||||
|
|||||||
@@ -31,6 +31,12 @@
|
|||||||
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.xray.balancer.fallback" }}'>
|
||||||
|
<a-select v-model="balancerModal.balancer.fallbackTag" clearable
|
||||||
|
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||||
|
<a-select-option v-for="tag in [ '', ...balancerModal.outboundTags]" :value="tag">[[ tag ]]</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
</table>
|
</table>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
@@ -47,7 +53,8 @@
|
|||||||
balancer: {
|
balancer: {
|
||||||
tag: '',
|
tag: '',
|
||||||
strategy: 'random',
|
strategy: 'random',
|
||||||
selector: []
|
selector: [],
|
||||||
|
fallbackTag: ''
|
||||||
},
|
},
|
||||||
outboundTags: [],
|
outboundTags: [],
|
||||||
balancerTags:[],
|
balancerTags:[],
|
||||||
@@ -70,7 +77,8 @@
|
|||||||
balancerModal.balancer = {
|
balancerModal.balancer = {
|
||||||
tag: '',
|
tag: '',
|
||||||
strategy: 'random',
|
strategy: 'random',
|
||||||
selector: []
|
selector: [],
|
||||||
|
fallbackTag: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
)
|
)
|
||||||
@@ -20,7 +20,11 @@ func NewCheckCpuJob() *CheckCpuJob {
|
|||||||
|
|
||||||
// Here run is a interface method of Job interface
|
// Here run is a interface method of Job interface
|
||||||
func (j *CheckCpuJob) Run() {
|
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
|
// get latest status of server
|
||||||
percent, err := cpu.Percent(1*time.Second, false)
|
percent, err := cpu.Percent(1*time.Second, false)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import "x-ui/web/service"
|
import "github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
type CheckXrayRunningJob struct {
|
type CheckXrayRunningJob struct {
|
||||||
xrayService service.XrayService
|
xrayService service.XrayService
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginStatus byte
|
type LoginStatus byte
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type XrayTrafficJob struct {
|
type XrayTrafficJob struct {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
|||||||
@@ -3,13 +3,17 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
|
func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
host, _, _ := net.SplitHostPort(c.Request.Host)
|
host := c.Request.Host
|
||||||
|
if colonIndex := strings.LastIndex(host, ":"); colonIndex != -1 {
|
||||||
|
host, _, _ = net.SplitHostPort(c.Request.Host)
|
||||||
|
}
|
||||||
|
|
||||||
if host != domain {
|
if host != domain {
|
||||||
c.AbortWithStatus(http.StatusForbidden)
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
|||||||
@@ -1,34 +1,44 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"services": [
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
],
|
||||||
|
"tag": "api"
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
"tag": "api",
|
|
||||||
"listen": "127.0.0.1",
|
"listen": "127.0.0.1",
|
||||||
"port": 62789,
|
"port": 62789,
|
||||||
"protocol": "dokodemo-door",
|
"protocol": "dokodemo-door",
|
||||||
"settings": {
|
"settings": {
|
||||||
"address": "127.0.0.1"
|
"address": "127.0.0.1"
|
||||||
}
|
},
|
||||||
|
"tag": "api"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"log": {
|
||||||
|
"access": "none",
|
||||||
|
"dnsLog": false,
|
||||||
|
"error": "",
|
||||||
|
"loglevel": "warning",
|
||||||
|
"maskAddress": ""
|
||||||
|
},
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
{
|
{
|
||||||
"tag": "direct",
|
|
||||||
"protocol": "freedom",
|
"protocol": "freedom",
|
||||||
"settings": {
|
"settings": {
|
||||||
"domainStrategy": "UseIP"
|
"domainStrategy": "UseIP",
|
||||||
}
|
"noises": [],
|
||||||
|
"redirect": ""
|
||||||
|
},
|
||||||
|
"tag": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "blocked",
|
|
||||||
"protocol": "blackhole",
|
"protocol": "blackhole",
|
||||||
"settings": {}
|
"settings": {},
|
||||||
|
"tag": "blocked"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"policy": {
|
"policy": {
|
||||||
@@ -47,19 +57,25 @@
|
|||||||
"domainStrategy": "AsIs",
|
"domainStrategy": "AsIs",
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"inboundTag": [
|
||||||
"inboundTag": ["api"],
|
"api"
|
||||||
"outboundTag": "api"
|
],
|
||||||
|
"outboundTag": "api",
|
||||||
|
"type": "field"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
],
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"type": "field"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -98,8 +98,9 @@ func (s *InboundService) getAllEmails() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) contains(slice []string, str string) bool {
|
func (s *InboundService) contains(slice []string, str string) bool {
|
||||||
|
lowerStr := strings.ToLower(str)
|
||||||
for _, s := range slice {
|
for _, s := range slice {
|
||||||
if s == str {
|
if strings.ToLower(s) == lowerStr {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,11 +519,13 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
|||||||
|
|
||||||
interfaceClients := settings["clients"].([]interface{})
|
interfaceClients := settings["clients"].([]interface{})
|
||||||
var newClients []interface{}
|
var newClients []interface{}
|
||||||
|
needApiDel := false
|
||||||
for _, client := range interfaceClients {
|
for _, client := range interfaceClients {
|
||||||
c := client.(map[string]interface{})
|
c := client.(map[string]interface{})
|
||||||
c_id := c[client_key].(string)
|
c_id := c[client_key].(string)
|
||||||
if c_id == clientId {
|
if c_id == clientId {
|
||||||
email = c["email"].(string)
|
email, _ = c["email"].(string)
|
||||||
|
needApiDel, _ = c["enable"].(bool)
|
||||||
} else {
|
} else {
|
||||||
newClients = append(newClients, client)
|
newClients = append(newClients, client)
|
||||||
}
|
}
|
||||||
@@ -541,23 +544,36 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
|||||||
oldInbound.Settings = string(newSettings)
|
oldInbound.Settings = string(newSettings)
|
||||||
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = s.DelClientStat(db, email)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Delete stats Data Error")
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
needRestart := false
|
needRestart := false
|
||||||
|
|
||||||
if len(email) > 0 {
|
if len(email) > 0 {
|
||||||
s.xrayApi.Init(p.GetAPIPort())
|
notDepleted := true
|
||||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, email)
|
err = db.Model(xray.ClientTraffic{}).Select("enable").Where("email = ?", email).First(¬Depleted).Error
|
||||||
if err1 == nil {
|
if err != nil {
|
||||||
logger.Debug("Client deleted by api:", email)
|
logger.Error("Get stats error")
|
||||||
needRestart = false
|
return false, err
|
||||||
} else {
|
}
|
||||||
logger.Debug("Unable to del client by api:", err1)
|
err = s.DelClientStat(db, email)
|
||||||
needRestart = true
|
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
|
return needRestart, db.Save(oldInbound).Error
|
||||||
}
|
}
|
||||||
@@ -574,7 +590,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
inerfaceClients := settings["clients"].([]interface{})
|
interfaceClients := settings["clients"].([]interface{})
|
||||||
|
|
||||||
oldInbound, err := s.GetInbound(data.Id)
|
oldInbound, err := s.GetInbound(data.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -629,7 +645,7 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
settingsClients := oldSettings["clients"].([]interface{})
|
settingsClients := oldSettings["clients"].([]interface{})
|
||||||
settingsClients[clientIndex] = inerfaceClients[0]
|
settingsClients[clientIndex] = interfaceClients[0]
|
||||||
oldSettings["clients"] = settingsClients
|
oldSettings["clients"] = settingsClients
|
||||||
|
|
||||||
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
newSettings, err := json.MarshalIndent(oldSettings, "", " ")
|
||||||
@@ -667,12 +683,18 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||||||
needRestart := false
|
needRestart := false
|
||||||
if len(oldEmail) > 0 {
|
if len(oldEmail) > 0 {
|
||||||
s.xrayApi.Init(p.GetAPIPort())
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
if oldClients[clientIndex].Enable {
|
||||||
if err1 == nil {
|
err1 := s.xrayApi.RemoveUser(oldInbound.Tag, oldEmail)
|
||||||
logger.Debug("Old client deleted by api:", clients[0].Email)
|
if err1 == nil {
|
||||||
} else {
|
logger.Debug("Old client deleted by api:", oldEmail)
|
||||||
logger.Debug("Error in deleting client by api:", err1)
|
} else {
|
||||||
needRestart = true
|
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 {
|
if clients[0].Enable {
|
||||||
cipher := ""
|
cipher := ""
|
||||||
@@ -1030,8 +1052,12 @@ func (s *InboundService) disableInvalidClients(tx *gorm.DB) (bool, int64, error)
|
|||||||
if err1 == nil {
|
if err1 == nil {
|
||||||
logger.Debug("Client disabled by api:", result.Email)
|
logger.Debug("Client disabled by api:", result.Email)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Error in disabling client by api:", err1)
|
if strings.Contains(err1.Error(), fmt.Sprintf("User %s not found.", result.Email)) {
|
||||||
needRestart = true
|
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()
|
s.xrayApi.Close()
|
||||||
@@ -1062,7 +1088,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
|||||||
clientTraffic.Email = client.Email
|
clientTraffic.Email = client.Email
|
||||||
clientTraffic.Total = client.TotalGB
|
clientTraffic.Total = client.TotalGB
|
||||||
clientTraffic.ExpiryTime = client.ExpiryTime
|
clientTraffic.ExpiryTime = client.ExpiryTime
|
||||||
clientTraffic.Enable = true
|
clientTraffic.Enable = client.Enable
|
||||||
clientTraffic.Up = 0
|
clientTraffic.Up = 0
|
||||||
clientTraffic.Down = 0
|
clientTraffic.Down = 0
|
||||||
clientTraffic.Reset = client.Reset
|
clientTraffic.Reset = client.Reset
|
||||||
@@ -1075,7 +1101,7 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
|
|||||||
result := tx.Model(xray.ClientTraffic{}).
|
result := tx.Model(xray.ClientTraffic{}).
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
Updates(map[string]interface{}{
|
Updates(map[string]interface{}{
|
||||||
"enable": true,
|
"enable": client.Enable,
|
||||||
"email": client.Email,
|
"email": client.Email,
|
||||||
"total": client.TotalGB,
|
"total": client.TotalGB,
|
||||||
"expiry_time": client.ExpiryTime,
|
"expiry_time": client.ExpiryTime,
|
||||||
@@ -1107,7 +1133,7 @@ func (s *InboundService) ResetClientTraffic(id int, clientEmail string) (bool, e
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email == clientEmail {
|
if client.Email == clientEmail && client.Enable {
|
||||||
s.xrayApi.Init(p.GetAPIPort())
|
s.xrayApi.Init(p.GetAPIPort())
|
||||||
cipher := ""
|
cipher := ""
|
||||||
if string(inbound.Protocol) == "shadowsocks" {
|
if string(inbound.Protocol) == "shadowsocks" {
|
||||||
@@ -1292,6 +1318,25 @@ func (s *InboundService) GetClientTrafficByEmail(email string) (traffic *xray.Cl
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *InboundService) GetClientTrafficByID(id string) ([]xray.ClientTraffic, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var traffics []xray.ClientTraffic
|
||||||
|
|
||||||
|
err := db.Model(xray.ClientTraffic{}).Where(`email IN(
|
||||||
|
SELECT JSON_EXTRACT(client.value, '$.email') as email
|
||||||
|
FROM inbounds,
|
||||||
|
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||||
|
WHERE
|
||||||
|
JSON_EXTRACT(client.value, '$.id') in (?)
|
||||||
|
)`, id).Find(&traffics).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return traffics, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
func (s *InboundService) SearchClientTraffic(query string) (traffic *xray.ClientTraffic, err error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
@@ -1359,6 +1404,9 @@ func (s *InboundService) MigrationRequirements() {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
|
if dbErr := db.Exec(`VACUUM "main"`).Error; dbErr != nil {
|
||||||
|
logger.Warningf("VACUUM failed: %v", dbErr)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
}
|
}
|
||||||
@@ -1468,6 +1516,6 @@ func (s *InboundService) MigrateDB() {
|
|||||||
s.MigrationRemoveOrphanedTraffics()
|
s.MigrationRemoveOrphanedTraffics()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InboundService) GetOnlineClinets() []string {
|
func (s *InboundService) GetOnlineClients() []string {
|
||||||
return p.GetOnlineClients()
|
return p.GetOnlineClients()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PanelService struct{}
|
type PanelService struct{}
|
||||||
|
|||||||
@@ -11,17 +11,18 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/util/sys"
|
"github.com/alireza0/x-ui/util/sys"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
"github.com/shirou/gopsutil/v4/disk"
|
"github.com/shirou/gopsutil/v4/disk"
|
||||||
@@ -243,7 +244,7 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
|||||||
}
|
}
|
||||||
var versions []string
|
var versions []string
|
||||||
for _, release := range releases {
|
for _, release := range releases {
|
||||||
if release.TagName >= "v1.8.0" {
|
if release.TagName >= "v26.1.23" {
|
||||||
versions = append(versions, release.TagName)
|
versions = append(versions, release.TagName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,6 +280,8 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
switch osName {
|
switch osName {
|
||||||
case "darwin":
|
case "darwin":
|
||||||
osName = "macos"
|
osName = "macos"
|
||||||
|
case "windows":
|
||||||
|
osName = "windows"
|
||||||
}
|
}
|
||||||
|
|
||||||
switch arch {
|
switch arch {
|
||||||
@@ -286,6 +289,16 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
arch = "64"
|
arch = "64"
|
||||||
case "arm64":
|
case "arm64":
|
||||||
arch = "arm64-v8a"
|
arch = "arm64-v8a"
|
||||||
|
case "armv7":
|
||||||
|
arch = "arm32-v7a"
|
||||||
|
case "armv6":
|
||||||
|
arch = "arm32-v6"
|
||||||
|
case "armv5":
|
||||||
|
arch = "arm32-v5"
|
||||||
|
case "386":
|
||||||
|
arch = "32"
|
||||||
|
case "s390x":
|
||||||
|
arch = "s390x"
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||||
@@ -312,19 +325,23 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) UpdateXray(version 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)
|
zipFileName, err := s.downloadXRay(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer os.Remove(zipFileName)
|
||||||
|
|
||||||
zipFile, err := os.Open(zipFileName)
|
zipFile, err := os.Open(zipFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer zipFile.Close()
|
||||||
zipFile.Close()
|
|
||||||
os.Remove(zipFileName)
|
|
||||||
}()
|
|
||||||
|
|
||||||
stat, err := zipFile.Stat()
|
stat, err := zipFile.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -335,19 +352,14 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.xrayService.StopXray()
|
// 3. Helper to extract files
|
||||||
defer func() {
|
|
||||||
err := s.xrayService.RestartXray(true)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("start xray failed:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
copyZipFile := func(zipName string, fileName string) error {
|
copyZipFile := func(zipName string, fileName string) error {
|
||||||
zipFile, err := reader.Open(zipName)
|
zipFile, err := reader.Open(zipName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer zipFile.Close()
|
||||||
|
os.MkdirAll(filepath.Dir(fileName), 0755)
|
||||||
os.Remove(fileName)
|
os.Remove(fileName)
|
||||||
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -358,11 +370,23 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Restart xray
|
||||||
|
if err := s.xrayService.RestartXray(true); err != nil {
|
||||||
|
logger.Error("start xray failed:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,14 +395,39 @@ func (s *ServerService) GetLogs(count string, level string, syslog string) []str
|
|||||||
var lines []string
|
var lines []string
|
||||||
|
|
||||||
if syslog == "true" {
|
if syslog == "true" {
|
||||||
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
|
// Check if running on Windows - journalctl is not available
|
||||||
// Run the command
|
if runtime.GOOS == "windows" {
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
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
|
var out bytes.Buffer
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
err := cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
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")
|
lines = strings.Split(out.String(), "\n")
|
||||||
} else {
|
} else {
|
||||||
@@ -471,14 +520,26 @@ func (s *ServerService) ImportDB(file multipart.File) error {
|
|||||||
return common.NewErrorf("Error saving db: %v", err)
|
return common.NewErrorf("Error saving db: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we can init db or not
|
// Close temp file before opening via sqlite
|
||||||
err = database.InitDB(tempPath)
|
if err = tempFile.Close(); err != nil {
|
||||||
if err != nil {
|
return common.NewErrorf("Error closing temporary db file: %v", err)
|
||||||
return common.NewErrorf("Error checking db: %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
|
// Stop Xray (ignore error but log)
|
||||||
s.StopXrayService()
|
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
|
// Backup the current database for fallback
|
||||||
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
fallbackPath := fmt.Sprintf("%s.backup", config.GetDBPath())
|
||||||
@@ -554,3 +615,93 @@ func (s *ServerService) GetNewX25519Cert() (interface{}, error) {
|
|||||||
|
|
||||||
return keyPair, nil
|
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"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/util/random"
|
"github.com/alireza0/x-ui/util/random"
|
||||||
"x-ui/util/reflect_util"
|
"github.com/alireza0/x-ui/util/reflect_util"
|
||||||
"x-ui/web/entity"
|
"github.com/alireza0/x-ui/web/entity"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed config.json
|
//go:embed config.json
|
||||||
@@ -59,6 +59,7 @@ var defaultValueMap = map[string]string{
|
|||||||
"subJsonPath": "/json/",
|
"subJsonPath": "/json/",
|
||||||
"subJsonURI": "",
|
"subJsonURI": "",
|
||||||
"subJsonFragment": "",
|
"subJsonFragment": "",
|
||||||
|
"subJsonNoises": "",
|
||||||
"subJsonMux": "",
|
"subJsonMux": "",
|
||||||
"subJsonRules": "",
|
"subJsonRules": "",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
@@ -282,10 +283,18 @@ func (s *SettingService) SetPort(port int) error {
|
|||||||
return s.setInt("webPort", port)
|
return s.setInt("webPort", port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetCertFile(webCertFile string) error {
|
||||||
|
return s.setString("webCertFile", webCertFile)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetCertFile() (string, error) {
|
func (s *SettingService) GetCertFile() (string, error) {
|
||||||
return s.getString("webCertFile")
|
return s.getString("webCertFile")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) SetKeyFile(webKeyFile string) error {
|
||||||
|
return s.setString("webKeyFile", webKeyFile)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetKeyFile() (string, error) {
|
func (s *SettingService) GetKeyFile() (string, error) {
|
||||||
return s.getString("webKeyFile")
|
return s.getString("webKeyFile")
|
||||||
}
|
}
|
||||||
@@ -317,6 +326,16 @@ func (s *SettingService) GetSecret() ([]byte, error) {
|
|||||||
return []byte(secret), err
|
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) {
|
func (s *SettingService) GetBasePath() (string, error) {
|
||||||
basePath, err := s.getString("webBasePath")
|
basePath, err := s.getString("webBasePath")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -405,6 +424,10 @@ func (s *SettingService) GetSubJsonFragment() (string, error) {
|
|||||||
return s.getString("subJsonFragment")
|
return s.getString("subJsonFragment")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubJsonNoises() (string, error) {
|
||||||
|
return s.getString("subJsonNoises")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubJsonMux() (string, error) {
|
func (s *SettingService) GetSubJsonMux() (string, error) {
|
||||||
return s.getString("subJsonMux")
|
return s.getString("subJsonMux")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/web/locale"
|
"github.com/alireza0/x-ui/web/locale"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||||
)
|
)
|
||||||
@@ -135,7 +135,7 @@ func (t *Tgbot) OnReceive() {
|
|||||||
isAdmin := checkAdmin(tgId)
|
isAdmin := checkAdmin(tgId)
|
||||||
if update.Message == nil {
|
if update.Message == nil {
|
||||||
if update.CallbackQuery != nil {
|
if update.CallbackQuery != nil {
|
||||||
t.asnwerCallback(update.CallbackQuery)
|
t.answerCallback(update.CallbackQuery)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if update.Message.IsCommand() {
|
if update.Message.IsCommand() {
|
||||||
@@ -196,7 +196,7 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
|||||||
t.SendAnswer(chatId, msg, isAdmin)
|
t.SendAnswer(chatId, msg, isAdmin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery) {
|
func (t *Tgbot) answerCallback(callbackQuery *tgbotapi.CallbackQuery) {
|
||||||
// Respond to the callback query, telling Telegram to show the user
|
// Respond to the callback query, telling Telegram to show the user
|
||||||
// a message with the data received.
|
// a message with the data received.
|
||||||
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
|
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
|
||||||
@@ -424,14 +424,14 @@ func (t *Tgbot) UserLoginNotify(username string, ip string, time string, status
|
|||||||
func (t *Tgbot) getInboundUsages() string {
|
func (t *Tgbot) getInboundUsages() string {
|
||||||
info := ""
|
info := ""
|
||||||
// get traffic
|
// get traffic
|
||||||
inbouds, err := t.inboundService.GetAllInbounds()
|
inbounds, err := t.inboundService.GetAllInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("GetAllInbounds run failed:", err)
|
logger.Warning("GetAllInbounds run failed:", err)
|
||||||
info += t.I18nBot("tgbot.answers.getInboundsFailed")
|
info += t.I18nBot("tgbot.answers.getInboundsFailed")
|
||||||
} else {
|
} else {
|
||||||
// NOTE:If there no any sessions here,need to notify here
|
// NOTE:If there no any sessions here,need to notify here
|
||||||
// TODO:Sub-node push, automatic conversion format
|
// TODO:Sub-node push, automatic conversion format
|
||||||
for _, inbound := range inbouds {
|
for _, inbound := range inbounds {
|
||||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||||
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
||||||
@@ -539,7 +539,7 @@ func (t *Tgbot) searchClient(chatId int64, email string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
||||||
inbouds, err := t.inboundService.SearchInbounds(remark)
|
inbounds, err := t.inboundService.SearchInbounds(remark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning(err)
|
logger.Warning(err)
|
||||||
msg := t.I18nBot("tgbot.wentWrong")
|
msg := t.I18nBot("tgbot.wentWrong")
|
||||||
@@ -547,13 +547,13 @@ func (t *Tgbot) searchInbound(chatId int64, remark string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(inbouds) == 0 {
|
if len(inbounds) == 0 {
|
||||||
msg := t.I18nBot("tgbot.noInbounds")
|
msg := t.I18nBot("tgbot.noInbounds")
|
||||||
t.SendMsgToTgbot(chatId, msg)
|
t.SendMsgToTgbot(chatId, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, inbound := range inbouds {
|
for _, inbound := range inbounds {
|
||||||
info := ""
|
info := ""
|
||||||
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
info += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
||||||
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
info += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package service
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"x-ui/database"
|
"github.com/alireza0/x-ui/database"
|
||||||
"x-ui/database/model"
|
"github.com/alireza0/x-ui/database/model"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|||||||
171
web/service/warp.go
Normal file
171
web/service/warp.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alireza0/x-ui/logger"
|
||||||
|
"github.com/alireza0/x-ui/util/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WarpService struct {
|
||||||
|
SettingService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WarpService) GetWarpData() (string, error) {
|
||||||
|
warp, err := s.SettingService.GetWarp()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return warp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WarpService) DelWarpData() error {
|
||||||
|
err := s.SettingService.SetWarp("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WarpService) GetWarpConfig() (string, error) {
|
||||||
|
var warpData map[string]string
|
||||||
|
warp, err := s.SettingService.GetWarp()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(warp), &warpData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
_, err = buffer.ReadFrom(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WarpService) RegWarp(secretKey string, publicKey string) (string, error) {
|
||||||
|
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
||||||
|
hostName, _ := os.Hostname()
|
||||||
|
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
|
||||||
|
|
||||||
|
url := "https://api.cloudflareclient.com/v0a2158/reg"
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("CF-Client-Version", "a-7.21-0721")
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
_, err = buffer.ReadFrom(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rspData map[string]interface{}
|
||||||
|
err = json.Unmarshal(buffer.Bytes(), &rspData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceId := rspData["id"].(string)
|
||||||
|
token := rspData["token"].(string)
|
||||||
|
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
|
||||||
|
if !ok {
|
||||||
|
logger.Debug("Error accessing license value.")
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
|
||||||
|
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
|
||||||
|
|
||||||
|
s.SettingService.SetWarp(warpData)
|
||||||
|
|
||||||
|
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WarpService) SetWarpLicense(license string) (string, error) {
|
||||||
|
var warpData map[string]string
|
||||||
|
warp, err := s.SettingService.GetWarp()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = json.Unmarshal([]byte(warp), &warpData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
|
||||||
|
data := fmt.Sprintf(`{"license": "%s"}`, license)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
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, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
s.SettingService.SetWarp(string(newWarpData))
|
||||||
|
|
||||||
|
return string(newWarpData), nil
|
||||||
|
}
|
||||||
@@ -3,10 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
@@ -32,7 +33,19 @@ func (s *XrayService) GetXrayErr() error {
|
|||||||
if p == nil {
|
if p == nil {
|
||||||
return 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 {
|
func (s *XrayService) GetXrayResult() string {
|
||||||
@@ -45,7 +58,15 @@ func (s *XrayService) GetXrayResult() string {
|
|||||||
if p == nil {
|
if p == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
result = p.GetResult()
|
result = p.GetResult()
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" && result == "exit status 1" {
|
||||||
|
// exit status 1 on Windows means that Xray process was killed
|
||||||
|
// as we kill process to stop in on Windows, this is not an error
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/xray"
|
"github.com/alireza0/x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type XraySettingService struct {
|
type XraySettingService struct {
|
||||||
@@ -32,142 +27,3 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XraySettingService) GetWarpData() (string, error) {
|
|
||||||
warp, err := s.SettingService.GetWarp()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return warp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *XraySettingService) GetWarpConfig() (string, error) {
|
|
||||||
var warpData map[string]string
|
|
||||||
warp, err := s.SettingService.GetWarp()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(warp), &warpData)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s", warpData["device_id"])
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
|
||||||
buffer.Reset()
|
|
||||||
_, err = buffer.ReadFrom(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *XraySettingService) RegWarp(secretKey string, publicKey string) (string, error) {
|
|
||||||
tos := time.Now().UTC().Format("2006-01-02T15:04:05.000Z")
|
|
||||||
hostName, _ := os.Hostname()
|
|
||||||
data := fmt.Sprintf(`{"key":"%s","tos":"%s","type": "PC","model": "x-ui", "name": "%s"}`, publicKey, tos, hostName)
|
|
||||||
|
|
||||||
url := "https://api.cloudflareclient.com/v0a2158/reg"
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(data)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("CF-Client-Version", "a-7.21-0721")
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
|
||||||
buffer.Reset()
|
|
||||||
_, err = buffer.ReadFrom(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var rspData map[string]interface{}
|
|
||||||
err = json.Unmarshal(buffer.Bytes(), &rspData)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceId := rspData["id"].(string)
|
|
||||||
token := rspData["token"].(string)
|
|
||||||
license, ok := rspData["account"].(map[string]interface{})["license"].(string)
|
|
||||||
if !ok {
|
|
||||||
fmt.Println("Error accessing license value.")
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
warpData := fmt.Sprintf("{\n \"access_token\": \"%s\",\n \"device_id\": \"%s\",", token, deviceId)
|
|
||||||
warpData += fmt.Sprintf("\n \"license_key\": \"%s\",\n \"private_key\": \"%s\"\n}", license, secretKey)
|
|
||||||
|
|
||||||
s.SettingService.SetWarp(warpData)
|
|
||||||
|
|
||||||
result := fmt.Sprintf("{\n \"data\": %s,\n \"config\": %s\n}", warpData, buffer.String())
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *XraySettingService) SetWarpLicence(license string) (string, error) {
|
|
||||||
var warpData map[string]string
|
|
||||||
warp, err := s.SettingService.GetWarp()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal([]byte(warp), &warpData)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf("https://api.cloudflareclient.com/v0a2158/reg/%s/account", warpData["device_id"])
|
|
||||||
data := fmt.Sprintf(`{"license": "%s"}`, license)
|
|
||||||
|
|
||||||
req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(data)))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
req.Header.Set("Authorization", "Bearer "+warpData["access_token"])
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
buffer := bytes.NewBuffer(make([]byte, 8192))
|
|
||||||
buffer.Reset()
|
|
||||||
_, err = buffer.ReadFrom(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
warpData["license_key"] = license
|
|
||||||
newWarpData, err := json.MarshalIndent(warpData, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
s.SettingService.SetWarp(string(newWarpData))
|
|
||||||
println(string(newWarpData))
|
|
||||||
|
|
||||||
return string(newWarpData), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package session
|
|||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"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"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,6 +19,10 @@ func init() {
|
|||||||
|
|
||||||
func SetLoginUser(c *gin.Context, user *model.User) error {
|
func SetLoginUser(c *gin.Context, user *model.User) error {
|
||||||
s := sessions.Default(c)
|
s := sessions.Default(c)
|
||||||
|
s.Options(sessions.Options{
|
||||||
|
Path: "/",
|
||||||
|
HttpOnly: true,
|
||||||
|
})
|
||||||
s.Set(loginUser, user)
|
s.Set(loginUser, user)
|
||||||
return s.Save()
|
return s.Save()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
"monitor" = "Listen IP"
|
"monitor" = "Listen IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Certificate"
|
||||||
"fail" = " Failed"
|
"fail" = " Failed"
|
||||||
"success" = " Successful"
|
"success" = " Successfully"
|
||||||
"getVersion" = "Get Version"
|
"getVersion" = "Get Version"
|
||||||
"install" = "Install"
|
"install" = "Install"
|
||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "Remark"
|
"remark" = "Remark"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"port" = "Port"
|
"port" = "Port"
|
||||||
|
"portMap" = "Port Mapping"
|
||||||
"traffic" = "Traffic"
|
"traffic" = "Traffic"
|
||||||
"details" = "Details"
|
"details" = "Details"
|
||||||
"transportConfig" = "Transport Config"
|
"transportConfig" = "Transport Config"
|
||||||
@@ -221,9 +222,6 @@
|
|||||||
"requestHeader" = "Request Header"
|
"requestHeader" = "Request Header"
|
||||||
"responseHeader" = "Response Header"
|
"responseHeader" = "Response Header"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "Encryption"
|
|
||||||
|
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "Panel Settings"
|
"title" = "Panel Settings"
|
||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
@@ -301,6 +299,14 @@
|
|||||||
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies."
|
"subURIDesc" = "The subscription service will use the URI that has been set up behind reverse proxies."
|
||||||
"fragment" = "Fragmentation"
|
"fragment" = "Fragmentation"
|
||||||
"fragmentDesc" = "Enable fragmentation for TLS hello packet"
|
"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]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "Modify Settings"
|
"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."
|
"logConfigsDesc" = "Enabling logs may affect your server's efficiency. It is recommended to enable it wisely only when necessary."
|
||||||
"blockConfigs" = "Protection Shield"
|
"blockConfigs" = "Protection Shield"
|
||||||
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
"blockConfigsDesc" = "These options will block traffic based on specific requested protocols and websites."
|
||||||
"blockCountryConfigs" = "Block Country"
|
"basicRouting" = "Basic Routing"
|
||||||
"blockCountryConfigsDesc" = "These options will block traffic based on the specific requested country."
|
"blockConnectionsConfigsDesc" = "These options will block traffic based on the specific requested country."
|
||||||
"directCountryConfigs" = "Direct Country"
|
"directConnectionsConfigsDesc" = "A direct connection ensures that specific traffic is not routed through another server."
|
||||||
"directCountryConfigsDesc" = "These options will directly forward traffic based on the specific requested country."
|
"blockips" = "Block IPs"
|
||||||
"ipv4Configs" = "IPv4 Routing"
|
"blockdomains" = "Block Domains"
|
||||||
"ipv4ConfigsDesc" = "These options will route traffic based on specific requested destination via server's IPv4."
|
"directips" = "Direct IPs"
|
||||||
"warpConfigs" = "WARP Config"
|
"directdomains" = "Direct Domains"
|
||||||
"warpConfigsDesc" = "These options will route traffic based on specific requested destination via WARP."
|
"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"
|
"Template" = "Advanced Xray Configuration Template"
|
||||||
"TemplateDesc" = "The final Xray config file will be generated based on this template."
|
"TemplateDesc" = "The final Xray config file will be generated based on this template."
|
||||||
"FreedomStrategy" = "Freedom Protocol Strategy"
|
"FreedomStrategy" = "Freedom Protocol Strategy"
|
||||||
@@ -337,50 +346,8 @@
|
|||||||
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
|
"RoutingStrategyDesc" = "Set the overall traffic routing strategy for resolving all requests."
|
||||||
"Torrent" = "Block BitTorrent Protocol"
|
"Torrent" = "Block BitTorrent Protocol"
|
||||||
"TorrentDesc" = "Blocks 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"
|
"Family" = "Family Protection"
|
||||||
"FamilyDesc" = "Blocks adult content, and malware websites."
|
"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"
|
"completeTemplate" = "All"
|
||||||
"Inbounds" = "Inbounds"
|
"Inbounds" = "Inbounds"
|
||||||
"Outbounds" = "Outbounds"
|
"Outbounds" = "Outbounds"
|
||||||
@@ -393,6 +360,10 @@
|
|||||||
"accessLogDesc" = "The file path for the access log."
|
"accessLogDesc" = "The file path for the access log."
|
||||||
"errorLog" = "Error Log"
|
"errorLog" = "Error Log"
|
||||||
"errorLogDesc" = "The file path for the 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]
|
[pages.xray.rules]
|
||||||
"first" = "First"
|
"first" = "First"
|
||||||
@@ -433,6 +404,7 @@
|
|||||||
"editBalancer" = "Edit Balancer"
|
"editBalancer" = "Edit Balancer"
|
||||||
"balancerStrategy" = "Strategy"
|
"balancerStrategy" = "Strategy"
|
||||||
"balancerSelectors" = "Selectors"
|
"balancerSelectors" = "Selectors"
|
||||||
|
"fallback" = "Fallback"
|
||||||
"tag" = "Tag"
|
"tag" = "Tag"
|
||||||
"tagDesc" = "Unique Tag"
|
"tagDesc" = "Unique Tag"
|
||||||
"balancerDesc" = "It is not possible to use both balancerTag and outboundTag simultaneously. If both are used together, only outboundTag will function."
|
"balancerDesc" = "It is not possible to use both balancerTag and outboundTag simultaneously. If both are used together, only outboundTag will function."
|
||||||
@@ -445,6 +417,12 @@
|
|||||||
"psk" = "PreShared Key"
|
"psk" = "PreShared Key"
|
||||||
"domainStrategy" = "Domain Strategy"
|
"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]
|
[pages.xray.dns]
|
||||||
"enable" = "Enable DNS"
|
"enable" = "Enable DNS"
|
||||||
"enableDesc" = "Enables built-in DNS server."
|
"enableDesc" = "Enables built-in DNS server."
|
||||||
@@ -455,6 +433,7 @@
|
|||||||
"add" = "Add Server"
|
"add" = "Add Server"
|
||||||
"edit" = "Edit Server"
|
"edit" = "Edit Server"
|
||||||
"domains" = "Domains"
|
"domains" = "Domains"
|
||||||
|
"expectIPs" = "Expect IPs"
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
[pages.xray.fakedns]
|
||||||
"add" = "Add Fake DNS"
|
"add" = "Add Fake DNS"
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "نام"
|
"remark" = "نام"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"port" = "پورت"
|
"port" = "پورت"
|
||||||
|
"portMap" = "پورتهای نظیر"
|
||||||
"traffic" = "ترافیک"
|
"traffic" = "ترافیک"
|
||||||
"details" = "جزئیات"
|
"details" = "جزئیات"
|
||||||
"transportConfig" = "نحوه اتصال"
|
"transportConfig" = "نحوه اتصال"
|
||||||
@@ -220,9 +221,6 @@
|
|||||||
"requestHeader" = "سربرگ درخواست"
|
"requestHeader" = "سربرگ درخواست"
|
||||||
"responseHeader" = "سربرگ پاسخ"
|
"responseHeader" = "سربرگ پاسخ"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "رمزنگاری"
|
|
||||||
|
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "تنظیمات پنل"
|
"title" = "تنظیمات پنل"
|
||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
@@ -300,6 +298,14 @@
|
|||||||
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسیهای معکوس تنظیم شده، استفاده خواهدکرد"
|
"subURIDesc" = "سابسکریپشن از لینکی که در پشت پراکسیهای معکوس تنظیم شده، استفاده خواهدکرد"
|
||||||
"fragment" = "تکهتکه شدن"
|
"fragment" = "تکهتکه شدن"
|
||||||
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
"fragmentDesc" = "فعال کردن تکه تکه شدن برای بسته نخست تیالاس"
|
||||||
|
"fragmentSett" = "تنظیمات فرگمنت"
|
||||||
|
"noisesDesc" = "فعال کردن Noises."
|
||||||
|
"noisesSett" = "تنظیمات Noises"
|
||||||
|
"mux" = "ماکس"
|
||||||
|
"muxDesc" = "چندین جریان داده مستقل را در یک جریان داده ثابت منتقل می کند"
|
||||||
|
"muxSett" = "تنظیمات ماکس"
|
||||||
|
"direct" = "اتصال مستقیم"
|
||||||
|
"directDesc" = "به طور مستقیم با دامنه ها یا محدوده آیپی یک کشور خاص ارتباط برقرار می کند"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "ویرایش تنظیمات"
|
"modifySettings" = "ویرایش تنظیمات"
|
||||||
@@ -320,14 +326,17 @@
|
|||||||
"logConfigsDesc" = "فعال کردن گزارش ممکن است بر عملکرد سرور شما تأثیر بگذارد. توصیه میشود فقط در صورت لزوم آن را با دقت فعال کنید"
|
"logConfigsDesc" = "فعال کردن گزارش ممکن است بر عملکرد سرور شما تأثیر بگذارد. توصیه میشود فقط در صورت لزوم آن را با دقت فعال کنید"
|
||||||
"blockConfigs" = "سپر محافظ"
|
"blockConfigs" = "سپر محافظ"
|
||||||
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
"blockConfigsDesc" = "این گزینهها ترافیک را بر اساس پروتکلهای درخواستی خاص، و وب سایتها مسدود میکند"
|
||||||
"blockCountryConfigs" = "مسدودسازی کشور"
|
"basicRouting" = "مسیریابی پایه"
|
||||||
"blockCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص مسدود میکند"
|
"blockConnectionsConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستشده خاص مسدود میکنند."
|
||||||
"directCountryConfigs" = "اتصال مستقیم کشور"
|
"directConnectionsConfigsDesc" = "یک اتصال مستقیم تضمین میکند که ترافیک خاص از طریق سرور دیگری مسیریابی نشود."
|
||||||
"directCountryConfigsDesc" = "این گزینهها ترافیک را بر اساس کشور درخواستی خاص بصورت مستقیم ارسال میکند"
|
"blockips" = "مسدود کردن آیپیها"
|
||||||
"ipv4Configs" = "IPv4 مسیریابی"
|
"blockdomains" = "مسدود کردن دامنهها"
|
||||||
"ipv4ConfigsDesc" = "این گزینهها ترافیک را از طریق آیپی نسخه4 سرور، به مقصد هدایت میکند"
|
"directips" = "آیپیهای مستقیم"
|
||||||
"warpConfigs" = "WARP تنظمیات"
|
"directdomains" = "دامنههای مستقیم"
|
||||||
"warpConfigsDesc" = "این گزینهها ترافیک را از طریق وارپ کلادفلر به مقصد هدایت میکند"
|
"ipv4Routing" = "IPv4 مسیریابی"
|
||||||
|
"ipv4RoutingDesc" = "این گزینهها ترافیک را از طریق آیپی نسخه4 سرور، به مقصد هدایت میکند"
|
||||||
|
"warpRouting" = "WARP مسیریابی"
|
||||||
|
"warpRoutingDesc" = "این گزینهها ترافیک را از طریق وارپ کلادفلر به مقصد هدایت میکند"
|
||||||
"Template" = "پیکربندی پیشرفته الگو ایکسری"
|
"Template" = "پیکربندی پیشرفته الگو ایکسری"
|
||||||
"TemplateDesc" = "فایل پیکربندی نهایی ایکسری بر اساس این الگو ایجاد میشود"
|
"TemplateDesc" = "فایل پیکربندی نهایی ایکسری بر اساس این الگو ایجاد میشود"
|
||||||
"FreedomStrategy" = "Freedom استراتژی پروتکل"
|
"FreedomStrategy" = "Freedom استراتژی پروتکل"
|
||||||
@@ -336,51 +345,8 @@
|
|||||||
"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواستها را تعیین میکند"
|
"RoutingStrategyDesc" = "استراتژی کلی مسیریابی برای حل تمام درخواستها را تعیین میکند"
|
||||||
"Torrent" = "مسدودسازی پروتکل بیتتورنت"
|
"Torrent" = "مسدودسازی پروتکل بیتتورنت"
|
||||||
"TorrentDesc" = "پروتکل بیت تورنت را مسدود میکند"
|
"TorrentDesc" = "پروتکل بیت تورنت را مسدود میکند"
|
||||||
"PrivateIp" = "مسدودسازی اتصال آیپیهای خصوصی"
|
|
||||||
"PrivateIpDesc" = "اتصال به آیپیهای رنج خصوصی را مسدود میکند"
|
|
||||||
"Ads" = "مسدودسازی تبلیغات"
|
|
||||||
"AdsDesc" = "وبسایتهای تبلیغاتی را مسدود میکند"
|
|
||||||
"Family" = "محافظ خانواده"
|
"Family" = "محافظ خانواده"
|
||||||
"FamilyDesc" = "محتوای مخصوص بزرگسالان، و وبسایتهای ناامن را مسدود میکند"
|
"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" = "ورودیها"
|
"Inbounds" = "ورودیها"
|
||||||
"Outbounds" = "خروجیها"
|
"Outbounds" = "خروجیها"
|
||||||
"Routings" = "قوانین مسیریابی"
|
"Routings" = "قوانین مسیریابی"
|
||||||
@@ -392,6 +358,10 @@
|
|||||||
"accessLogDesc" = "مسیر فایل گزارش دسترسی"
|
"accessLogDesc" = "مسیر فایل گزارش دسترسی"
|
||||||
"errorLog" = "گزارش خطا"
|
"errorLog" = "گزارش خطا"
|
||||||
"errorLogDesc" = "مسیر فایل گزارش خطا"
|
"errorLogDesc" = "مسیر فایل گزارش خطا"
|
||||||
|
"dnsLog" = "گزارش DNS"
|
||||||
|
"dnsLogDesc" = "آیا ثبتهای درخواست DNS را فعال کنید"
|
||||||
|
"maskAddress" = "پنهان کردن آدرس"
|
||||||
|
"maskAddressDesc" = "پوشش آدرس IP، هنگامی که فعال میشود، به طور خودکار آدرس IP که در لاگ ظاهر میشود را جایگزین میکند."
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "اولین"
|
"first" = "اولین"
|
||||||
@@ -432,6 +402,7 @@
|
|||||||
"editBalancer" = "ویرایش بالانسر"
|
"editBalancer" = "ویرایش بالانسر"
|
||||||
"balancerStrategy" = "استراتژی"
|
"balancerStrategy" = "استراتژی"
|
||||||
"balancerSelectors" = "انتخابگرها"
|
"balancerSelectors" = "انتخابگرها"
|
||||||
|
"fallback" = "جایگزین"
|
||||||
"tag" = "برچسب"
|
"tag" = "برچسب"
|
||||||
"tagDesc" = "برچسب یگانه"
|
"tagDesc" = "برچسب یگانه"
|
||||||
"balancerDesc" = "امکان استفاده همزمان برچسب خروجی و برچسب بالانسر باهم وجود ندارد. درصورت استفاده همزمان فقط برچسب خروجی عمل خواهد کرد"
|
"balancerDesc" = "امکان استفاده همزمان برچسب خروجی و برچسب بالانسر باهم وجود ندارد. درصورت استفاده همزمان فقط برچسب خروجی عمل خواهد کرد"
|
||||||
@@ -444,6 +415,12 @@
|
|||||||
"psk" = "کلید مشترک"
|
"psk" = "کلید مشترک"
|
||||||
"domainStrategy" = "استراتژی حل دامنه"
|
"domainStrategy" = "استراتژی حل دامنه"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "نام رابط TUN. مقدار پیشفرض 'xrayN', N یک عدد است"
|
||||||
|
"mtuDesc" = "واحد انتقال حداکثر. بیشترین اندازه بستههای داده. مقدار پیشفرض 1500 است"
|
||||||
|
"userLevel" = "سطح کاربر"
|
||||||
|
"userLevelDesc" = "تمام اتصالات انجامشده از طریق این ورودی از این سطح کاربری استفاده خواهند کرد. مقدار پیشفرض 0 است"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "فعال کردن حل دامنه"
|
"enable" = "فعال کردن حل دامنه"
|
||||||
"enableDesc" = "سرور حل دامنه داخلی را فعال میکند"
|
"enableDesc" = "سرور حل دامنه داخلی را فعال میکند"
|
||||||
@@ -454,6 +431,7 @@
|
|||||||
"add" = "افزودن سرور"
|
"add" = "افزودن سرور"
|
||||||
"edit" = "ویرایش سرور"
|
"edit" = "ویرایش سرور"
|
||||||
"domains" = "دامنهها"
|
"domains" = "دامنهها"
|
||||||
|
"expectIPs" = "آیپیهای مورد انتظار"
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
[pages.xray.fakedns]
|
||||||
"add" = "افزودن دیاناس جعلی"
|
"add" = "افزودن دیاناس جعلی"
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "Примечание"
|
"remark" = "Примечание"
|
||||||
"protocol" = "Протокол"
|
"protocol" = "Протокол"
|
||||||
"port" = "Порт"
|
"port" = "Порт"
|
||||||
|
"portMap" = "Порт-перенаправление"
|
||||||
"traffic" = "Трафик"
|
"traffic" = "Трафик"
|
||||||
"details" = "Подробнее"
|
"details" = "Подробнее"
|
||||||
"transportConfig" = "Перенести"
|
"transportConfig" = "Перенести"
|
||||||
@@ -221,9 +222,6 @@
|
|||||||
"requestHeader" = "Заголовок запроса"
|
"requestHeader" = "Заголовок запроса"
|
||||||
"responseHeader" = "Заголовок ответа"
|
"responseHeader" = "Заголовок ответа"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "Шифрование"
|
|
||||||
|
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "Настройки"
|
"title" = "Настройки"
|
||||||
"save" = "Сохранить"
|
"save" = "Сохранить"
|
||||||
@@ -301,6 +299,14 @@
|
|||||||
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
"subURIDesc" = "Изменить базовый URI URL-адреса подписки для использования за прокси-серверами"
|
||||||
"fragment" = "Фрагментация"
|
"fragment" = "Фрагментация"
|
||||||
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
"fragmentDesc" = "Включить фрагментацию для пакета приветствия TLS"
|
||||||
|
"fragmentSett" = "Настройки фрагментации"
|
||||||
|
"noisesDesc" = "Включить Noises."
|
||||||
|
"noisesSett" = "Настройки Noises"
|
||||||
|
"mux" = "Mux"
|
||||||
|
"muxDesc" = "Передача нескольких независимых потоков данных в рамках установленного потока данных."
|
||||||
|
"muxSett" = "Mux Настройки"
|
||||||
|
"direct" = "Прямая связь"
|
||||||
|
"directDesc" = "Напрямую устанавливает соединения с доменами или диапазонами IP конкретной страны."
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "Изменение настроек"
|
"modifySettings" = "Изменение настроек"
|
||||||
@@ -321,14 +327,17 @@
|
|||||||
"logConfigsDesc" = "Включение журнала может повлиять на эффективность вашего сервера. Рекомендуется включать его разумно, только когда это необходимо."
|
"logConfigsDesc" = "Включение журнала может повлиять на эффективность вашего сервера. Рекомендуется включать его разумно, только когда это необходимо."
|
||||||
"blockConfigs" = "Блокирующие конфигурации"
|
"blockConfigs" = "Блокирующие конфигурации"
|
||||||
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
"blockConfigsDesc" = "Эти параметры не позволят пользователям подключаться к определенным протоколам и веб-сайтам."
|
||||||
"blockCountryConfigs" = "Конфигурация блокировки стран"
|
"basicRouting" = "Базовые соединения"
|
||||||
"blockCountryConfigsDesc" = "Эти параметры не позволят пользователям подключаться к доменам определенной страны."
|
"blockConnectionsConfigsDesc" = "Эти параметры будут блокировать трафик в зависимости от запрашиваемой страны."
|
||||||
"directCountryConfigs" = "Прямые настройки стран"
|
"directConnectionsConfigsDesc" = "Прямое соединение гарантирует, что определенный трафик не будет перенаправлен через другой сервер."
|
||||||
"directCountryConfigsDesc" = "Эти параметры будут подключать пользователей напрямую к доменам определенной страны."
|
"blockips" = "Блокировать IP"
|
||||||
"ipv4Configs" = "Настройки IPv4"
|
"blockdomains" = "Блокировать домены"
|
||||||
"ipv4ConfigsDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
"directips" = "Прямые IP"
|
||||||
"warpConfigs" = "Настройки WARP"
|
"directdomains" = "Прямые домены"
|
||||||
"warpConfigsDesc" = "WARP будет направлять трафик на веб-сайты через серверы Cloudflare"
|
"ipv4Routing" = "Правила IPv4"
|
||||||
|
"ipv4RoutingDesc" = "Эти параметры будут маршрутизироваться к целевым доменам только через IPv4"
|
||||||
|
"warpRouting" = "Правила WARP"
|
||||||
|
"warpRoutingDesc" = "WARP будет направлять трафик на веб-сайты через серверы Cloudflare"
|
||||||
"Template" = "Шаблон конфигурации Xray"
|
"Template" = "Шаблон конфигурации Xray"
|
||||||
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
"TemplateDesc" = "Создание файла конфигурации Xray на основе этого шаблона."
|
||||||
"FreedomStrategy" = "Настроить стратегию протокола Freedom"
|
"FreedomStrategy" = "Настроить стратегию протокола Freedom"
|
||||||
@@ -337,50 +346,8 @@
|
|||||||
"RoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
"RoutingStrategyDesc" = "Установить общую стратегию маршрутизации разрешения DNS"
|
||||||
"Torrent" = "Запретить использование BitTorrent"
|
"Torrent" = "Запретить использование BitTorrent"
|
||||||
"TorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
"TorrentDesc" = "Измените конфигурацию, чтобы пользователи не использовали BitTorrent."
|
||||||
"PrivateIp" = "Запрет частных диапазонов IP-адресов для подключения"
|
|
||||||
"PrivateIpDesc" = "Измените конфигурацию, чтобы избежать подключения к диапазонам частных IP-адресов."
|
|
||||||
"Ads" = "Блокировка рекламы"
|
|
||||||
"AdsDesc" = "Измените конфигурацию, чтобы заблокировать рекламу."
|
|
||||||
"Family" = "Включить семейную конфигурацию"
|
"Family" = "Включить семейную конфигурацию"
|
||||||
"FamilyDesc" = "Избегать подключения к небезопасным веб-сайтам для всей семьи"
|
"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" = "Все"
|
"completeTemplate" = "Все"
|
||||||
"Inbounds" = "Входящие"
|
"Inbounds" = "Входящие"
|
||||||
"Outbounds" = "Исходящие"
|
"Outbounds" = "Исходящие"
|
||||||
@@ -393,6 +360,10 @@
|
|||||||
"accessLogDesc" = "Путь к файлу журнала доступа."
|
"accessLogDesc" = "Путь к файлу журнала доступа."
|
||||||
"errorLog" = "Журнал ошибок"
|
"errorLog" = "Журнал ошибок"
|
||||||
"errorLogDesc" = "Путь к файлу журнала ошибок."
|
"errorLogDesc" = "Путь к файлу журнала ошибок."
|
||||||
|
"dnsLog" = "DNS Журнал"
|
||||||
|
"dnsLogDesc" = "Включить логи запросов DNS"
|
||||||
|
"maskAddress" = "Маскировать Адрес"
|
||||||
|
"maskAddressDesc" = "Маска IP-адреса, при активации автоматически заменяет IP-адрес, который появляется в логе."
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "Первый"
|
"first" = "Первый"
|
||||||
@@ -433,6 +404,7 @@
|
|||||||
"editBalancer" = "Редактировать балансир"
|
"editBalancer" = "Редактировать балансир"
|
||||||
"balancerStrategy" = "Стратегия"
|
"balancerStrategy" = "Стратегия"
|
||||||
"balancerSelectors" = "Селекторы"
|
"balancerSelectors" = "Селекторы"
|
||||||
|
"fallback" = "Отступать"
|
||||||
"tag" = "Тег"
|
"tag" = "Тег"
|
||||||
"tagDesc" = "уникальный тег"
|
"tagDesc" = "уникальный тег"
|
||||||
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
|
"balancerDesc" = "Невозможно одновременно использовать balancerTag и outboundTag. При одновременном использовании будет работать только outboundTag."
|
||||||
@@ -445,6 +417,12 @@
|
|||||||
"psk" = "Общий ключ"
|
"psk" = "Общий ключ"
|
||||||
"domainStrategy" = "Стратегия домена"
|
"domainStrategy" = "Стратегия домена"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "Имя интерфейса TUN. Значение по умолчанию - 'xrayN', где N - номер интерфейса."
|
||||||
|
"mtuDesc" = "Максимальная единица передачи. Максимальный размер пакетов данных. Значение по умолчанию - 1500"
|
||||||
|
"userLevel" = "Уровень пользователя"
|
||||||
|
"userLevelDesc" = "Все соединения, установленные через этот входящий поток, будут использовать этот уровень пользователя. Значение по умолчанию - 0"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "Включить DNS"
|
"enable" = "Включить DNS"
|
||||||
"enableDesc" = "Включить встроенный DNS-сервер"
|
"enableDesc" = "Включить встроенный DNS-сервер"
|
||||||
@@ -455,6 +433,7 @@
|
|||||||
"add" = "Добавить сервер"
|
"add" = "Добавить сервер"
|
||||||
"edit" = "Редактировать сервер"
|
"edit" = "Редактировать сервер"
|
||||||
"domains" = "Домены"
|
"domains" = "Домены"
|
||||||
|
"expectIPs" = "Ожидаемые IP"
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
[pages.xray.fakedns]
|
||||||
"add" = "Добавить поддельный DNS"
|
"add" = "Добавить поддельный DNS"
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "Nhận xét"
|
"remark" = "Nhận xét"
|
||||||
"protocol" = "Giao thức"
|
"protocol" = "Giao thức"
|
||||||
"port" = "Cổng"
|
"port" = "Cổng"
|
||||||
|
"portMap" = "Cổng điều chính"
|
||||||
"traffic" = "Lưu lượng"
|
"traffic" = "Lưu lượng"
|
||||||
"details" = "Chi tiết"
|
"details" = "Chi tiết"
|
||||||
"transportConfig" = "Cấu hình vận chuyển"
|
"transportConfig" = "Cấu hình vận chuyển"
|
||||||
@@ -221,9 +222,6 @@
|
|||||||
"requestHeader" = "Tiêu đề yêu cầu"
|
"requestHeader" = "Tiêu đề yêu cầu"
|
||||||
"responseHeader" = "Tiêu đề phản hồi"
|
"responseHeader" = "Tiêu đề phản hồi"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "Mã hóa"
|
|
||||||
|
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "Cài đặt"
|
"title" = "Cài đặt"
|
||||||
"save" = "Lưu"
|
"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"
|
"subURIDesc" = "Thay đổi URI cơ sở của URL đăng ký để sử dụng ở phía sau proxy"
|
||||||
"fragment" = "Sự phân mảnh"
|
"fragment" = "Sự phân mảnh"
|
||||||
"fragmentDesc" = "Kích hoạt phân mảnh cho gói TLS hello"
|
"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]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "Sửa đổi cài đặt"
|
"modifySettings" = "Sửa đổi cài đặt"
|
||||||
@@ -318,17 +324,20 @@
|
|||||||
"generalConfigs" = "Cấu hình Chung"
|
"generalConfigs" = "Cấu hình Chung"
|
||||||
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
|
"generalConfigsDesc" = "Những tùy chọn này sẽ cung cấp điều chỉnh tổng quát."
|
||||||
"logConfigs" = "Cài đặt nhật ký"
|
"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"
|
"blockConfigs" = "Cấu hình Chặn"
|
||||||
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
|
"blockConfigsDesc" = "Những tùy chọn này sẽ ngăn người dùng kết nối đến các giao thức và trang web cụ thể."
|
||||||
"blockCountryConfigs" = "Cấu hình Chặn Quốc gia"
|
"basicRouting" = "Định tuyến Cơ bản"
|
||||||
"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ể."
|
"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ể."
|
||||||
"directCountryConfigs" = "Cấu hình Kết nối Trực tiếp Quốc gia"
|
"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."
|
||||||
"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ể."
|
"blockips" = "Chặn IP"
|
||||||
"ipv4Configs" = "Cấu hình IPv4"
|
"blockdomains" = "Chặn Tên Miền"
|
||||||
"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."
|
"directips" = "IP Trực Tiếp"
|
||||||
"warpConfigs" = "Cấu hình WARP"
|
"directdomains" = "Tên Miền Trực Tiếp"
|
||||||
"warpConfigsDesc" = "WARP sẽ định tuyến lưu lượng đến các trang web qua máy chủ Cloudflare."
|
"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"
|
"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."
|
"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"
|
"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."
|
"RoutingStrategyDesc" = "Đặt chiến lược định tuyến tổng thể để phân giải DNS."
|
||||||
"Torrent" = "Cấm sử dụng BitTorrent"
|
"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."
|
"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"
|
"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."
|
"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ả"
|
"completeTemplate" = "Tất cả"
|
||||||
"Inbounds" = "Đầu vào"
|
"Inbounds" = "Đầu vào"
|
||||||
"Outbounds" = "Đầu ra"
|
"Outbounds" = "Đầu ra"
|
||||||
@@ -393,6 +360,10 @@
|
|||||||
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập."
|
"accessLogDesc" = "Đường dẫn tệp cho nhật ký truy cập."
|
||||||
"errorLog" = "Nhật ký lỗi"
|
"errorLog" = "Nhật ký lỗi"
|
||||||
"errorLogDesc" = "Đường dẫn tệp cho 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]
|
[pages.xray.rules]
|
||||||
"first" = "Đầu tiên"
|
"first" = "Đầu tiên"
|
||||||
@@ -433,6 +404,7 @@
|
|||||||
"editBalancer" = "Chỉnh sửa cân bằng"
|
"editBalancer" = "Chỉnh sửa cân bằng"
|
||||||
"balancerStrategy" = "Chiến lược"
|
"balancerStrategy" = "Chiến lược"
|
||||||
"balancerSelectors" = "Bộ chọn"
|
"balancerSelectors" = "Bộ chọn"
|
||||||
|
"fallback" = "Dự phòng"
|
||||||
"tag" = "Thẻ"
|
"tag" = "Thẻ"
|
||||||
"tagDesc" = "thẻ duy nhất"
|
"tagDesc" = "thẻ duy nhất"
|
||||||
"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động."
|
"balancerDesc" = "Không thể sử dụng balancerTag và outboundTag cùng một lúc. Nếu sử dụng cùng lúc thì chỉ outboundTag mới hoạt động."
|
||||||
@@ -445,6 +417,12 @@
|
|||||||
"psk" = "Khóa chia sẻ"
|
"psk" = "Khóa chia sẻ"
|
||||||
"domainStrategy" = "Chiến lược tên miền"
|
"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]
|
[pages.xray.dns]
|
||||||
"enable" = "Kích hoạt DNS"
|
"enable" = "Kích hoạt DNS"
|
||||||
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
"enableDesc" = "Kích hoạt máy chủ DNS tích hợp"
|
||||||
@@ -455,6 +433,7 @@
|
|||||||
"add" = "Thêm máy chủ"
|
"add" = "Thêm máy chủ"
|
||||||
"edit" = "Chỉnh sửa máy chủ"
|
"edit" = "Chỉnh sửa máy chủ"
|
||||||
"domains" = "Tên miền"
|
"domains" = "Tên miền"
|
||||||
|
"expectIPs" = "Các IP Dự Kiến"
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
[pages.xray.fakedns]
|
||||||
"add" = "Thêm DNS giả"
|
"add" = "Thêm DNS giả"
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
"emptyUsername" = "请输入用户名"
|
"emptyUsername" = "请输入用户名"
|
||||||
"emptyPassword" = "请输入密码"
|
"emptyPassword" = "请输入密码"
|
||||||
"wrongUsernameOrPassword" = "用户名或密码错误"
|
"wrongUsernameOrPassword" = "用户名或密码错误"
|
||||||
"successLogin" = "登录成功"
|
"successLogin" = "登录"
|
||||||
|
|
||||||
[pages.index]
|
[pages.index]
|
||||||
"title" = "系统状态"
|
"title" = "系统状态"
|
||||||
@@ -123,6 +123,7 @@
|
|||||||
"remark" = "备注"
|
"remark" = "备注"
|
||||||
"protocol" = "协议"
|
"protocol" = "协议"
|
||||||
"port" = "端口"
|
"port" = "端口"
|
||||||
|
"portMap" = "端口映射"
|
||||||
"traffic" = "流量"
|
"traffic" = "流量"
|
||||||
"details" = "详细信息"
|
"details" = "详细信息"
|
||||||
"transportConfig" = "传输配置"
|
"transportConfig" = "传输配置"
|
||||||
@@ -221,9 +222,6 @@
|
|||||||
"requestHeader" = "请求头"
|
"requestHeader" = "请求头"
|
||||||
"responseHeader" = "响应头"
|
"responseHeader" = "响应头"
|
||||||
|
|
||||||
[pages.inbounds.stream.quic]
|
|
||||||
"encryption" = "加密"
|
|
||||||
|
|
||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "设置"
|
"title" = "设置"
|
||||||
"save" = "保存配置"
|
"save" = "保存配置"
|
||||||
@@ -301,6 +299,14 @@
|
|||||||
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
"subURIDesc" = "更改订阅 URL 的基本 URI 以在代理后面使用"
|
||||||
"fragment" = "碎片"
|
"fragment" = "碎片"
|
||||||
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
"fragmentDesc" = "启用 TLS hello 数据包分段"
|
||||||
|
"fragmentSett" = "设置"
|
||||||
|
"noisesDesc" = "启用 Noises."
|
||||||
|
"noisesSett" = "Noises 设置"
|
||||||
|
"mux" = "多路复用器"
|
||||||
|
"muxDesc" = "在已建立的数据流内传输多个独立的数据流"
|
||||||
|
"muxSett" = "复用器设置"
|
||||||
|
"direct" = "直接连接"
|
||||||
|
"directDesc" = "直接与特定国家的域或IP范围建立连接"
|
||||||
|
|
||||||
[pages.settings.toasts]
|
[pages.settings.toasts]
|
||||||
"modifySettings" = "修改设置"
|
"modifySettings" = "修改设置"
|
||||||
@@ -321,14 +327,17 @@
|
|||||||
"logConfigsDesc" = "启用日志可能会影响服务器的效率。建议仅在必要时明智地启用它。"
|
"logConfigsDesc" = "启用日志可能会影响服务器的效率。建议仅在必要时明智地启用它。"
|
||||||
"blockConfigs" = "阻塞配置"
|
"blockConfigs" = "阻塞配置"
|
||||||
"blockConfigsDesc" = "这些选项将禁止用户连接到特定协议和网站"
|
"blockConfigsDesc" = "这些选项将禁止用户连接到特定协议和网站"
|
||||||
"blockCountryConfigs" = "禁连国家配置"
|
"basicRouting" = "基本路由"
|
||||||
"blockCountryConfigsDesc" = "这些选项将禁止用户连接到特定国家/地区的域。"
|
"blockConnectionsConfigsDesc" = "这些选项将根据特定的请求国家阻止流量。"
|
||||||
"directCountryConfigs" = "直连国家配置"
|
"directConnectionsConfigsDesc" = "直接连接确保特定的流量不会通过其他服务器路由。"
|
||||||
"directCountryConfigsDesc" = "这些选项会将用户直接连接到特定国家/地区的域。"
|
"blockips" = "阻止IP"
|
||||||
"ipv4Configs" = "IPv4 配置"
|
"blockdomains" = "阻止域名"
|
||||||
"ipv4ConfigsDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
"directips" = "直接IP"
|
||||||
"warpConfigs" = "WARP 配置"
|
"directdomains" = "直接域名"
|
||||||
"warpConfigsDesc" = "WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
"ipv4Routing" = "IPv4 路由"
|
||||||
|
"ipv4RoutingDesc" = "此选项将仅通过 IPv4 路由到目标域"
|
||||||
|
"warpRouting" = "WARP 路由"
|
||||||
|
"warpRoutingDesc" = "WARP 将通过 Cloudflare 服务器将流量路由到网站。"
|
||||||
"Template" = "Xray 配置模板"
|
"Template" = "Xray 配置模板"
|
||||||
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件,重新启动面板生成效率"
|
"TemplateDesc" = "以该模型为基础生成最终的Xray配置文件,重新启动面板生成效率"
|
||||||
"FreedomStrategy" = "配置自由协议的策略"
|
"FreedomStrategy" = "配置自由协议的策略"
|
||||||
@@ -337,50 +346,8 @@
|
|||||||
"RoutingStrategyDesc" = "设置DNS解析的整体路由策略"
|
"RoutingStrategyDesc" = "设置DNS解析的整体路由策略"
|
||||||
"Torrent" = "禁止使用 bitTorrent"
|
"Torrent" = "禁止使用 bitTorrent"
|
||||||
"TorrentDesc" = "更改配置模板避免用户使用 bitTorrent"
|
"TorrentDesc" = "更改配置模板避免用户使用 bitTorrent"
|
||||||
"PrivateIp" = "禁止私人 IP 范围连接"
|
|
||||||
"PrivateIpDesc" = "更改配置模板以避免连接私有 IP 范围"
|
|
||||||
"Ads" = "屏蔽广告"
|
|
||||||
"AdsDesc" = "修改配置模板屏蔽广告"
|
|
||||||
"Family" = "启用家庭友好配置"
|
"Family" = "启用家庭友好配置"
|
||||||
"FamilyDesc" = "避免为家人连接到不安全的网站"
|
"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" = "全部"
|
"completeTemplate" = "全部"
|
||||||
"Inbounds" = "界内"
|
"Inbounds" = "界内"
|
||||||
"Outbounds" = "出站"
|
"Outbounds" = "出站"
|
||||||
@@ -393,6 +360,10 @@
|
|||||||
"accessLogDesc" = "访问日志的文件路径。"
|
"accessLogDesc" = "访问日志的文件路径。"
|
||||||
"errorLog" = "错误日志"
|
"errorLog" = "错误日志"
|
||||||
"errorLogDesc" = "错误日志的文件路径。"
|
"errorLogDesc" = "错误日志的文件路径。"
|
||||||
|
"dnsLog" = "DNS 日志"
|
||||||
|
"dnsLogDesc" = "是否启用 DNS 查询日志"
|
||||||
|
"maskAddress" = "隐藏地址"
|
||||||
|
"maskAddressDesc" = "IP 地址掩码,启用时会自动替换日志中出现的 IP 地址。"
|
||||||
|
|
||||||
[pages.xray.rules]
|
[pages.xray.rules]
|
||||||
"first" = "第一个"
|
"first" = "第一个"
|
||||||
@@ -433,6 +404,7 @@
|
|||||||
"editBalancer" = "编辑平衡器"
|
"editBalancer" = "编辑平衡器"
|
||||||
"balancerStrategy" = "战略"
|
"balancerStrategy" = "战略"
|
||||||
"balancerSelectors" = "选择器"
|
"balancerSelectors" = "选择器"
|
||||||
|
"fallback" = "倒退"
|
||||||
"tag" = "标签"
|
"tag" = "标签"
|
||||||
"tagDesc" = "唯一标记"
|
"tagDesc" = "唯一标记"
|
||||||
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用,则只有outboundTag起作用。"
|
"balancerDesc" = "不能同时使用balancerTag和outboundTag。 如果同时使用,则只有outboundTag起作用。"
|
||||||
@@ -445,6 +417,12 @@
|
|||||||
"psk" = "共享密钥"
|
"psk" = "共享密钥"
|
||||||
"domainStrategy" = "域策略"
|
"domainStrategy" = "域策略"
|
||||||
|
|
||||||
|
[pages.xray.tun]
|
||||||
|
"nameDesc" = "TUN 接口的名称。默认值为 'xrayN', 其中 N 是接口的编号。"
|
||||||
|
"mtuDesc" = "最大传输单元。数据包的最大大小。默认值为 1500"
|
||||||
|
"userLevel" = "用户级别"
|
||||||
|
"userLevelDesc" = "通过此入站的所有连接都将使用此用户级别。默认值为 0"
|
||||||
|
|
||||||
[pages.xray.dns]
|
[pages.xray.dns]
|
||||||
"enable" = "启用 DNS"
|
"enable" = "启用 DNS"
|
||||||
"enableDesc" = "启用内置 DNS 服务器"
|
"enableDesc" = "启用内置 DNS 服务器"
|
||||||
@@ -455,6 +433,7 @@
|
|||||||
"add" = "添加服务器"
|
"add" = "添加服务器"
|
||||||
"edit" = "编辑服务器"
|
"edit" = "编辑服务器"
|
||||||
"domains" = "域"
|
"domains" = "域"
|
||||||
|
"expectIPs" = "预期 IP"
|
||||||
|
|
||||||
[pages.xray.fakedns]
|
[pages.xray.fakedns]
|
||||||
"add" = "添加假 DNS"
|
"add" = "添加假 DNS"
|
||||||
|
|||||||
36
web/web.go
36
web/web.go
@@ -14,19 +14,19 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"github.com/alireza0/x-ui/config"
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
"x-ui/web/controller"
|
"github.com/alireza0/x-ui/web/controller"
|
||||||
"x-ui/web/job"
|
"github.com/alireza0/x-ui/web/job"
|
||||||
"x-ui/web/locale"
|
"github.com/alireza0/x-ui/web/locale"
|
||||||
"x-ui/web/middleware"
|
"github.com/alireza0/x-ui/web/middleware"
|
||||||
"x-ui/web/network"
|
"github.com/alireza0/x-ui/web/network"
|
||||||
"x-ui/web/service"
|
"github.com/alireza0/x-ui/web/service"
|
||||||
|
|
||||||
sessions "github.com/Calidity/gin-sessions"
|
|
||||||
"github.com/Calidity/gin-sessions/cookie"
|
|
||||||
"github.com/gin-contrib/gzip"
|
"github.com/gin-contrib/gzip"
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
)
|
)
|
||||||
@@ -228,7 +228,11 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
|||||||
s.index = controller.NewIndexController(g)
|
s.index = controller.NewIndexController(g)
|
||||||
s.server = controller.NewServerController(g)
|
s.server = controller.NewServerController(g)
|
||||||
s.xui = controller.NewXUIController(g)
|
s.xui = controller.NewXUIController(g)
|
||||||
s.api = controller.NewAPIController(g)
|
s.api = controller.NewAPIController(g, s.server)
|
||||||
|
|
||||||
|
engine.NoRoute(func(c *gin.Context) {
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
return engine, nil
|
return engine, nil
|
||||||
}
|
}
|
||||||
@@ -332,13 +336,13 @@ func (s *Server) Start() (err error) {
|
|||||||
}
|
}
|
||||||
listener = network.NewAutoHttpsListener(listener)
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
listener = tls.NewListener(listener, c)
|
listener = tls.NewListener(listener, c)
|
||||||
logger.Info("web server run https on", listener.Addr())
|
logger.Info("Web server running HTTPS on", listener.Addr())
|
||||||
} else {
|
} else {
|
||||||
logger.Error("error in loading certificates: ", err)
|
logger.Error("Error loading certificates:", err)
|
||||||
logger.Info("web server run http on", listener.Addr())
|
logger.Info("Web server running HTTP on", listener.Addr())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Info("web server run http on", listener.Addr())
|
logger.Info("Web server running HTTP on", listener.Addr())
|
||||||
}
|
}
|
||||||
s.listener = listener
|
s.listener = listener
|
||||||
|
|
||||||
|
|||||||
BIN
windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
Normal file
BIN
windows_files/SSL/Win64OpenSSL_Light-3_5_2.exe
Normal file
Binary file not shown.
12
windows_files/readme.txt
Normal file
12
windows_files/readme.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
we don't have bash menu for windows
|
||||||
|
if you forgot your password you need to check your database with https://sqlitebrowser.org/
|
||||||
|
the app need to be open all the time
|
||||||
|
|
||||||
|
default setting:
|
||||||
|
http://localhost:54321/
|
||||||
|
user: admin
|
||||||
|
pass: admin
|
||||||
|
port: 54321
|
||||||
|
|
||||||
|
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt
|
||||||
485
x-ui.sh
485
x-ui.sh
@@ -34,33 +34,9 @@ fi
|
|||||||
|
|
||||||
echo "The OS release is: $release"
|
echo "The OS release is: $release"
|
||||||
|
|
||||||
|
|
||||||
os_version=""
|
|
||||||
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
|
||||||
|
|
||||||
if [[ "${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 10 ]]; then
|
|
||||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
confirm() {
|
confirm() {
|
||||||
if [[ $# > 1 ]]; then
|
if [[ $# > 1 ]]; then
|
||||||
echo && read -p "$1 [Default$2]: " temp
|
echo && read -p "$1 [Default $2]: " temp
|
||||||
if [[ x"${temp}" == x"" ]]; then
|
if [[ x"${temp}" == x"" ]]; then
|
||||||
temp=$2
|
temp=$2
|
||||||
fi
|
fi
|
||||||
@@ -115,13 +91,13 @@ update() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_version() {
|
legacy_version() {
|
||||||
echo "Enter the panel version (like 1.6.0):"
|
echo "Enter the panel version (like 1.6.0):"
|
||||||
read panel_version
|
read panel_version
|
||||||
|
|
||||||
if [ -z "$panel_version" ]; then
|
if [ -z "$panel_version" ]; then
|
||||||
echo "Panel version cannot be empty. Exiting."
|
echo "Panel version cannot be empty. Exiting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
download_link="https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh"
|
download_link="https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh"
|
||||||
@@ -135,7 +111,7 @@ custom_version() {
|
|||||||
|
|
||||||
# Function to handle the deletion of the script file
|
# Function to handle the deletion of the script file
|
||||||
delete_script() {
|
delete_script() {
|
||||||
rm "$0" # Remove the script file itself
|
rm "$0" # Remove the script file itself
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,13 +148,46 @@ reset_user() {
|
|||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
/usr/local/x-ui/x-ui setting -username admin -password admin
|
|
||||||
echo -e "Username and password have been reset to ${green}admin${plain},Please restart the panel now."
|
read -rp "Please set the login username [default is a random username]: " config_account
|
||||||
|
[[ -z $config_account ]] && config_account=$(gen_random_string 10)
|
||||||
|
read -rp "Please set the login password [default is a random password]: " config_password
|
||||||
|
[[ -z $config_password ]] && config_password=$(gen_random_string 18)
|
||||||
|
|
||||||
|
echo -e "Panel login username has been reset to: ${green} ${config_account} ${plain}"
|
||||||
|
echo -e "Panel login password has been reset to: ${green} ${config_password} ${plain}"
|
||||||
|
echo -e "${green} Please use the new login username and password to access the X-UI panel. Also remember them! ${plain}"
|
||||||
|
|
||||||
confirm_restart
|
confirm_restart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_webbasepath() {
|
||||||
|
echo -e "${yellow}Resetting Web Base Path${plain}"
|
||||||
|
|
||||||
|
read -rp "Are you sure you want to reset the web base path? (y/n): " confirm
|
||||||
|
if [[ $confirm != "y" && $confirm != "Y" ]]; then
|
||||||
|
echo -e "${yellow}Operation canceled.${plain}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
config_webBasePath=$(gen_random_string 10)
|
||||||
|
|
||||||
|
# Apply the new web base path setting
|
||||||
|
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1
|
||||||
|
|
||||||
|
echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}"
|
||||||
|
echo -e "${green}Please use the new web base path to access the panel.${plain}"
|
||||||
|
restart
|
||||||
|
}
|
||||||
|
|
||||||
reset_config() {
|
reset_config() {
|
||||||
confirm "Are you sure you want to reset all panel settings,Account data will not be lost,Username and password will not change" "n"
|
confirm "Are you sure you want to reset all panel settings? Account data will not be lost, Username and password will not change" "n"
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
show_menu
|
show_menu
|
||||||
@@ -186,19 +195,29 @@ reset_config() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
/usr/local/x-ui/x-ui setting -reset
|
/usr/local/x-ui/x-ui setting -reset
|
||||||
echo -e "All panel settings have been reset to default,Please restart the panel now,and use the default ${green}54321${plain} Port to Access the web Panel"
|
echo -e "All panel settings have been reset to default. Please restart the panel now, and use the default ${green}54321${plain} Port to Access the web Panel"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
}
|
}
|
||||||
|
|
||||||
check_config() {
|
check_config() {
|
||||||
info=$(/usr/local/x-ui/x-ui setting -show true)
|
info=$(/usr/local/x-ui/x-ui setting -show true)
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
LOGE "get current settings error,please check logs"
|
LOGE "Get current settings error, please check logs"
|
||||||
show_menu
|
show_menu
|
||||||
fi
|
fi
|
||||||
LOGI "${info}"
|
LOGI "${info}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_uri() {
|
||||||
|
info=$(/usr/local/x-ui/x-ui uri)
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
LOGE "Get current uri error"
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
LOGI "You may access the Panel with following URL(s):"
|
||||||
|
echo -e "${yellow}${info}${plain}"
|
||||||
|
}
|
||||||
|
|
||||||
set_port() {
|
set_port() {
|
||||||
echo && echo -n -e "Enter port number[1-65535]: " && read port
|
echo && echo -n -e "Enter port number[1-65535]: " && read port
|
||||||
if [[ -z "${port}" ]]; then
|
if [[ -z "${port}" ]]; then
|
||||||
@@ -206,7 +225,7 @@ set_port() {
|
|||||||
before_show_menu
|
before_show_menu
|
||||||
else
|
else
|
||||||
/usr/local/x-ui/x-ui setting -port ${port}
|
/usr/local/x-ui/x-ui setting -port ${port}
|
||||||
echo -e "The port is set,Please restart the panel now,and use the new port ${green}${port}${plain} to access web panel"
|
echo -e "The port is set, Please restart the panel now, and use the new port ${green}${port}${plain} to access web panel"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -215,7 +234,7 @@ start() {
|
|||||||
check_status
|
check_status
|
||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGI "Panel is running,No need to start again,If you need to restart, please select restart"
|
LOGI "Panel is running, No need to start again, If you need to restart, please select restart"
|
||||||
else
|
else
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -223,7 +242,7 @@ start() {
|
|||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "x-ui Started Successfully"
|
LOGI "x-ui Started Successfully"
|
||||||
else
|
else
|
||||||
LOGE "panel Failed to start,Probably because it takes longer than two seconds to start,Please check the log information later"
|
LOGE "panel Failed to start, Probably because it takes longer than two seconds to start, Please check the log information later"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -236,7 +255,7 @@ stop() {
|
|||||||
check_status
|
check_status
|
||||||
if [[ $? == 1 ]]; then
|
if [[ $? == 1 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGI "Panel stopped,No need to stop again!"
|
LOGI "Panel stopped, No need to stop again!"
|
||||||
else
|
else
|
||||||
systemctl stop x-ui
|
systemctl stop x-ui
|
||||||
sleep 2
|
sleep 2
|
||||||
@@ -244,7 +263,7 @@ stop() {
|
|||||||
if [[ $? == 1 ]]; then
|
if [[ $? == 1 ]]; then
|
||||||
LOGI "x-ui and xray stopped successfully"
|
LOGI "x-ui and xray stopped successfully"
|
||||||
else
|
else
|
||||||
LOGE "Panel stop failed,Probably because the stop time exceeds two seconds,Please check the log information later"
|
LOGE "Panel stop failed, Probably because the stop time exceeds two seconds, Please check the log information later"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -260,7 +279,7 @@ restart() {
|
|||||||
if [[ $? == 0 ]]; then
|
if [[ $? == 0 ]]; then
|
||||||
LOGI "x-ui and xray Restarted successfully"
|
LOGI "x-ui and xray Restarted successfully"
|
||||||
else
|
else
|
||||||
LOGE "Panel restart failed,Probably because it takes longer than two seconds to start,Please check the log information later"
|
LOGE "Panel restart failed, Probably because it takes longer than two seconds to start, Please check the log information later"
|
||||||
fi
|
fi
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
@@ -301,21 +320,42 @@ disable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show_log() {
|
show_log() {
|
||||||
journalctl -u x-ui.service -e --no-pager -f
|
echo -e "${green}\t1.${plain} Debug Log"
|
||||||
if [[ $# == 0 ]]; then
|
echo -e "${green}\t2.${plain} Clear All logs"
|
||||||
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||||
|
read -p "Choose an option: " choice
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
0)
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
journalctl -u x-ui -e --no-pager -f -p debug
|
||||||
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
fi
|
fi
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
sudo journalctl --rotate
|
||||||
|
sudo journalctl --vacuum-time=1s
|
||||||
|
echo "All Logs cleared."
|
||||||
|
restart
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid choice"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
update_shell() {
|
update_shell() {
|
||||||
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/alireza0/x-ui/raw/main/x-ui.sh
|
wget -O /usr/bin/x-ui -N --no-check-certificate https://github.com/alireza0/x-ui/raw/main/x-ui.sh
|
||||||
if [[ $? != 0 ]]; then
|
if [[ $? != 0 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGE "Failed to download script,Please check whether the machine can connect Github"
|
LOGE "Failed to download script, Please check whether the machine can connect Github"
|
||||||
before_show_menu
|
before_show_menu
|
||||||
else
|
else
|
||||||
chmod +x /usr/bin/x-ui
|
chmod +x /usr/bin/x-ui
|
||||||
LOGI "Upgrade script succeeded,Please rerun the script" && exit 0
|
LOGI "Upgrade script succeeded, Please rerun the script" && exit 0
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +385,7 @@ check_uninstall() {
|
|||||||
check_status
|
check_status
|
||||||
if [[ $? != 2 ]]; then
|
if [[ $? != 2 ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
LOGE "Panel installed,Please do not reinstall"
|
LOGE "Panel installed, Please do not reinstall"
|
||||||
if [[ $# == 0 ]]; then
|
if [[ $# == 0 ]]; then
|
||||||
before_show_menu
|
before_show_menu
|
||||||
fi
|
fi
|
||||||
@@ -415,15 +455,23 @@ show_xray_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_acme() {
|
install_acme() {
|
||||||
cd ~
|
# Check if acme.sh is already installed
|
||||||
LOGI "install acme..."
|
if command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||||||
curl https://get.acme.sh | sh
|
LOGI "acme.sh is already installed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOGI "Installing acme.sh..."
|
||||||
|
cd ~ || return 1 # Ensure you can change to the home directory
|
||||||
|
|
||||||
|
curl -s https://get.acme.sh | sh
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "install acme failed"
|
LOGE "Installation of acme.sh failed."
|
||||||
return 1
|
return 1
|
||||||
else
|
else
|
||||||
LOGI "install acme succeed"
|
LOGI "Installation of acme.sh succeeded."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,20 +479,100 @@ ssl_cert_issue_main() {
|
|||||||
echo -e "${green}\t1.${plain} Get SSL"
|
echo -e "${green}\t1.${plain} Get SSL"
|
||||||
echo -e "${green}\t2.${plain} Revoke"
|
echo -e "${green}\t2.${plain} Revoke"
|
||||||
echo -e "${green}\t3.${plain} Force Renew"
|
echo -e "${green}\t3.${plain} Force Renew"
|
||||||
|
echo -e "${green}\t4.${plain} Show Existing Domains"
|
||||||
|
echo -e "${green}\t5.${plain} Set Cert paths for the panel"
|
||||||
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||||||
|
|
||||||
read -p "Choose an option: " choice
|
read -p "Choose an option: " choice
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) ssl_cert_issue ;;
|
0)
|
||||||
2)
|
show_menu
|
||||||
local domain=""
|
;;
|
||||||
read -p "Please enter your domain name to revoke the certificate: " domain
|
1)
|
||||||
~/.acme.sh/acme.sh --revoke -d ${domain}
|
ssl_cert_issue
|
||||||
LOGI "Certificate revoked"
|
;;
|
||||||
;;
|
2)
|
||||||
3)
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||||||
local domain=""
|
if [ -z "$domains" ]; then
|
||||||
read -p "Please enter your domain name to forcefully renew an SSL certificate: " domain
|
echo "No certificates found to revoke."
|
||||||
~/.acme.sh/acme.sh --renew -d ${domain} --force ;;
|
else
|
||||||
*) echo "Invalid choice" ;;
|
echo "Existing domains:"
|
||||||
|
echo "$domains"
|
||||||
|
read -p "Please enter a domain from the list to revoke the certificate: " domain
|
||||||
|
if echo "$domains" | grep -qw "$domain"; then
|
||||||
|
~/.acme.sh/acme.sh --revoke -d ${domain}
|
||||||
|
LOGI "Certificate revoked for domain: $domain"
|
||||||
|
else
|
||||||
|
echo "Invalid domain entered."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||||||
|
if [ -z "$domains" ]; then
|
||||||
|
echo "No certificates found to renew."
|
||||||
|
else
|
||||||
|
echo "Existing domains:"
|
||||||
|
echo "$domains"
|
||||||
|
read -p "Please enter a domain from the list to renew the SSL certificate: " domain
|
||||||
|
if echo "$domains" | grep -qw "$domain"; then
|
||||||
|
~/.acme.sh/acme.sh --renew -d ${domain} --force
|
||||||
|
LOGI "Certificate forcefully renewed for domain: $domain"
|
||||||
|
else
|
||||||
|
echo "Invalid domain entered."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||||||
|
if [ -z "$domains" ]; then
|
||||||
|
echo "No certificates found."
|
||||||
|
else
|
||||||
|
echo "Existing domains and their paths:"
|
||||||
|
for domain in $domains; do
|
||||||
|
local cert_path="/root/cert/${domain}/fullchain.pem"
|
||||||
|
local key_path="/root/cert/${domain}/privkey.pem"
|
||||||
|
if [[ -f "${cert_path}" && -f "${key_path}" ]]; then
|
||||||
|
echo -e "Domain: ${domain}"
|
||||||
|
echo -e "\tCertificate Path: ${cert_path}"
|
||||||
|
echo -e "\tPrivate Key Path: ${key_path}"
|
||||||
|
else
|
||||||
|
echo -e "Domain: ${domain} - Certificate or Key missing."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
5)
|
||||||
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||||||
|
if [ -z "$domains" ]; then
|
||||||
|
echo "No certificates found."
|
||||||
|
else
|
||||||
|
echo "Available domains:"
|
||||||
|
echo "$domains"
|
||||||
|
read -p "Please choose a domain to set the panel paths: " domain
|
||||||
|
|
||||||
|
if echo "$domains" | grep -qw "$domain"; then
|
||||||
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
||||||
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
||||||
|
|
||||||
|
if [[ -f "${webCertFile}" && -f "${webKeyFile}" ]]; then
|
||||||
|
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||||||
|
echo "Panel paths set for domain: $domain"
|
||||||
|
echo " - Certificate File: $webCertFile"
|
||||||
|
echo " - Private Key File: $webKeyFile"
|
||||||
|
restart
|
||||||
|
else
|
||||||
|
echo "Certificate or private key not found for domain: $domain."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Invalid domain entered."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Invalid choice"
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,17 +586,25 @@ ssl_cert_issue() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# install socat second
|
# install socat second
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu|debian)
|
ubuntu | debian | armbian)
|
||||||
apt update && apt install socat -y ;;
|
apt update && apt install socat -y
|
||||||
centos)
|
;;
|
||||||
yum -y update && yum -y install socat ;;
|
centos | almalinux | rocky | ol)
|
||||||
fedora)
|
yum -y update && yum -y install socat
|
||||||
dnf -y update && dnf -y install socat ;;
|
;;
|
||||||
*)
|
fedora | amzn)
|
||||||
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
dnf -y update && dnf -y install socat
|
||||||
exit 1 ;;
|
;;
|
||||||
|
arch | manjaro | parch)
|
||||||
|
pacman -Sy --noconfirm socat
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "install socat failed, please check logs"
|
LOGE "install socat failed, please check logs"
|
||||||
@@ -477,23 +613,23 @@ ssl_cert_issue() {
|
|||||||
LOGI "install socat succeed..."
|
LOGI "install socat succeed..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# get the domain here,and we need verify it
|
# get the domain here, and we need to verify it
|
||||||
local domain=""
|
local domain=""
|
||||||
read -p "Please enter your domain name:" domain
|
read -p "Please enter your domain name: " domain
|
||||||
LOGD "your domain is:${domain},check it..."
|
LOGD "Your domain is: ${domain}, checking it..."
|
||||||
# here we need to judge whether there exists cert already
|
|
||||||
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
|
|
||||||
|
|
||||||
if [ ${currentCert} == ${domain} ]; then
|
# check if there already exists a certificate
|
||||||
|
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
|
||||||
|
if [ "${currentCert}" == "${domain}" ]; then
|
||||||
local certInfo=$(~/.acme.sh/acme.sh --list)
|
local certInfo=$(~/.acme.sh/acme.sh --list)
|
||||||
LOGE "system already has certs here,can not issue again,current certs details:"
|
LOGE "System already has certificates for this domain. Cannot issue again. Current certificate details:"
|
||||||
LOGI "$certInfo"
|
LOGI "$certInfo"
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
LOGI "your domain is ready for issuing cert now..."
|
LOGI "Your domain is ready for issuing certificates now..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# create a directory for install cert
|
# create a directory for the certificate
|
||||||
certPath="/root/cert/${domain}"
|
certPath="/root/cert/${domain}"
|
||||||
if [ ! -d "$certPath" ]; then
|
if [ ! -d "$certPath" ]; then
|
||||||
mkdir -p "$certPath"
|
mkdir -p "$certPath"
|
||||||
@@ -502,48 +638,70 @@ ssl_cert_issue() {
|
|||||||
mkdir -p "$certPath"
|
mkdir -p "$certPath"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# get needed port here
|
# get the port number for the standalone server
|
||||||
local WebPort=80
|
local WebPort=80
|
||||||
read -p "please choose which port do you use,default will be 80 port:" WebPort
|
read -p "Please choose which port to use (default is 80): " WebPort
|
||||||
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
|
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
|
||||||
LOGE "your input ${WebPort} is invalid,will use default port"
|
LOGE "Your input ${WebPort} is invalid, will use default port 80."
|
||||||
|
WebPort=80
|
||||||
fi
|
fi
|
||||||
LOGI "will use port:${WebPort} to issue certs,please make sure this port is open..."
|
LOGI "Will use port: ${WebPort} to issue certificates. Please make sure this port is open."
|
||||||
# NOTE:This should be handled by user
|
|
||||||
# open the port and kill the occupied progress
|
# issue the certificate
|
||||||
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||||||
~/.acme.sh/acme.sh --issue -d ${domain} --standalone --httpport ${WebPort}
|
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort}
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "issue certs failed,please check logs"
|
LOGE "Issuing certificate failed, please check logs."
|
||||||
rm -rf ~/.acme.sh/${domain}
|
rm -rf ~/.acme.sh/${domain}
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
LOGE "issue certs succeed,installing certs..."
|
LOGE "Issuing certificate succeeded, installing certificates..."
|
||||||
fi
|
fi
|
||||||
# install cert
|
|
||||||
|
# install the certificate
|
||||||
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||||||
--key-file /root/cert/${domain}/privkey.pem \
|
--key-file /root/cert/${domain}/privkey.pem \
|
||||||
--fullchain-file /root/cert/${domain}/fullchain.pem
|
--fullchain-file /root/cert/${domain}/fullchain.pem
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "install certs failed,exit"
|
LOGE "Installing certificate failed, exiting."
|
||||||
rm -rf ~/.acme.sh/${domain}
|
rm -rf ~/.acme.sh/${domain}
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
LOGI "install certs succeed,enable auto renew..."
|
LOGI "Installing certificate succeeded, enabling auto renew..."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# enable auto-renew
|
||||||
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "auto renew failed, certs details:"
|
LOGE "Auto renew failed, certificate details:"
|
||||||
ls -lah cert/*
|
ls -lah cert/*
|
||||||
chmod 755 $certPath/*
|
chmod 755 $certPath/*
|
||||||
exit 1
|
exit 1
|
||||||
else
|
else
|
||||||
LOGI "auto renew succeed, certs details:"
|
LOGI "Auto renew succeeded, certificate details:"
|
||||||
ls -lah cert/*
|
ls -lah cert/*
|
||||||
chmod 755 $certPath/*
|
chmod 755 $certPath/*
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Prompt user to set panel paths after successful certificate installation
|
||||||
|
read -p "Would you like to set this certificate for the panel? (y/n): " setPanel
|
||||||
|
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
|
||||||
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
||||||
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
||||||
|
|
||||||
|
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||||||
|
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||||||
|
LOGI "Panel paths set for domain: $domain"
|
||||||
|
LOGI " - Certificate File: $webCertFile"
|
||||||
|
LOGI " - Private Key File: $webKeyFile"
|
||||||
|
restart
|
||||||
|
else
|
||||||
|
LOGE "Error: Certificate or private key file not found for domain: $domain."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
LOGI "Skipping panel path setting."
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
ssl_cert_issue_CF() {
|
ssl_cert_issue_CF() {
|
||||||
@@ -599,8 +757,8 @@ ssl_cert_issue_CF() {
|
|||||||
LOGI "Certificate issued Successfully, Installing..."
|
LOGI "Certificate issued Successfully, Installing..."
|
||||||
fi
|
fi
|
||||||
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
|
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} --ca-file /root/cert/ca.cer \
|
||||||
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
|
--cert-file /root/cert/${CF_Domain}.cer --key-file /root/cert/${CF_Domain}.key \
|
||||||
--fullchain-file /root/cert/fullchain.cer
|
--fullchain-file /root/cert/fullchain.cer
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
LOGE "Certificate installation failed, script exiting..."
|
LOGE "Certificate installation failed, script exiting..."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -782,15 +940,18 @@ enable_bbr() {
|
|||||||
|
|
||||||
# Check the OS and install necessary packages
|
# Check the OS and install necessary packages
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
ubuntu | debian)
|
ubuntu | debian | armbian)
|
||||||
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
|
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
|
||||||
;;
|
;;
|
||||||
centos | almalinux | rocky)
|
centos | almalinux | rocky | ol)
|
||||||
yum -y update && yum -y install ca-certificates
|
yum -y update && yum -y install ca-certificates
|
||||||
;;
|
;;
|
||||||
fedora)
|
fedora | amzn)
|
||||||
dnf -y update && dnf -y install ca-certificates
|
dnf -y update && dnf -y install ca-certificates
|
||||||
;;
|
;;
|
||||||
|
arch | manjaro | parch)
|
||||||
|
pacman -Sy --noconfirm ca-certificates
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -820,32 +981,32 @@ update_geo() {
|
|||||||
read -p "Select: " select
|
read -p "Select: " select
|
||||||
|
|
||||||
case "$select" in
|
case "$select" in
|
||||||
0)
|
0)
|
||||||
show_menu
|
show_menu
|
||||||
;;
|
;;
|
||||||
|
|
||||||
1)
|
1)
|
||||||
wget -N "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget -N "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
wget -N "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget -N "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
wget "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -O /tmp/wget && mv /tmp/wget geoip_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat" -O /tmp/wget && mv /tmp/wget geoip_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
wget "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -O /tmp/wget && mv /tmp/wget geosite_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget "https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat" -O /tmp/wget && mv /tmp/wget geosite_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
echo -e "${green}Files are updated.${plain}"
|
echo -e "${green}Files are updated.${plain}"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
;;
|
;;
|
||||||
|
|
||||||
2)
|
2)
|
||||||
wget -N "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget -N "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
wget -N "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget -N "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
wget "https://cdn.jsdelivr.net/gh/chocolate4u/Iran-v2ray-rules@release/geoip.dat" -O /tmp/wget && mv /tmp/wget geoip_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget "https://cdn.jsdelivr.net/gh/chocolate4u/Iran-v2ray-rules@release/geoip.dat" -O /tmp/wget && mv /tmp/wget geoip_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
wget "https://cdn.jsdelivr.net/gh/chocolate4u/Iran-v2ray-rules@release/geosite.dat" -O /tmp/wget && mv /tmp/wget geosite_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
wget "https://cdn.jsdelivr.net/gh/chocolate4u/Iran-v2ray-rules@release/geosite.dat" -O /tmp/wget && mv /tmp/wget geosite_IR.dat && echo -e "${green}Success${plain}\n" || echo -e "${red}Failure${plain}\n"
|
||||||
echo -e "${green}Files are updated.${plain}"
|
echo -e "${green}Files are updated.${plain}"
|
||||||
confirm_restart
|
confirm_restart
|
||||||
;;
|
;;
|
||||||
|
|
||||||
*)
|
*)
|
||||||
LOGE "Please enter a correct number [0-2]\n"
|
LOGE "Please enter a correct number [0-2]\n"
|
||||||
update_geo
|
update_geo
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,12 +1047,13 @@ run_speedtest() {
|
|||||||
show_usage() {
|
show_usage() {
|
||||||
echo "X-UI Control Menu Usage"
|
echo "X-UI Control Menu Usage"
|
||||||
echo "------------------------------------------"
|
echo "------------------------------------------"
|
||||||
echo "SUBCOMMANDS:"
|
echo "SUBCOMMANDS:"
|
||||||
echo "x-ui - Admin Management Script"
|
echo "x-ui - Admin Management Script"
|
||||||
echo "x-ui start - Start"
|
echo "x-ui start - Start"
|
||||||
echo "x-ui stop - Stop"
|
echo "x-ui stop - Stop"
|
||||||
echo "x-ui restart - Restart"
|
echo "x-ui restart - Restart"
|
||||||
echo "x-ui status - Current Status"
|
echo "x-ui status - Current Status"
|
||||||
|
echo "x-ui settings - Current Settings"
|
||||||
echo "x-ui enable - Enable Autostart on OS Startup"
|
echo "x-ui enable - Enable Autostart on OS Startup"
|
||||||
echo "x-ui disable - Disable Autostart on OS Startup"
|
echo "x-ui disable - Disable Autostart on OS Startup"
|
||||||
echo "x-ui log - Check Logs"
|
echo "x-ui log - Check Logs"
|
||||||
@@ -910,33 +1072,34 @@ show_menu() {
|
|||||||
————————————————
|
————————————————
|
||||||
${green}1.${plain} Install
|
${green}1.${plain} Install
|
||||||
${green}2.${plain} Update
|
${green}2.${plain} Update
|
||||||
${green}3.${plain} Custom Version
|
${green}3.${plain} Legacy Version
|
||||||
${green}4.${plain} Uninstall
|
${green}4.${plain} Uninstall
|
||||||
————————————————
|
————————————————
|
||||||
${green}5.${plain} Reset Username and Password
|
${green}5.${plain} Reset Username and Password
|
||||||
${green}6.${plain} Reset Panel Settings
|
${green}6.${plain} Reset Web Base Path
|
||||||
${green}7.${plain} Set Panel Port
|
${green}7.${plain} Reset Panel Settings
|
||||||
${green}8.${plain} View Panel Settings
|
${green}8.${plain} Set Panel Port
|
||||||
|
${green}9.${plain} View Panel Settings
|
||||||
————————————————
|
————————————————
|
||||||
${green}9.${plain} Start
|
${green}10.${plain} Start
|
||||||
${green}10.${plain} Stop
|
${green}11.${plain} Stop
|
||||||
${green}11.${plain} Restart
|
${green}12.${plain} Restart
|
||||||
${green}12.${plain} Check State
|
${green}13.${plain} Check State
|
||||||
${green}13.${plain} Check Logs
|
${green}14.${plain} Check Logs
|
||||||
————————————————
|
————————————————
|
||||||
${green}14.${plain} Enable Autostart
|
${green}15.${plain} Enable Autostart
|
||||||
${green}15.${plain} Disable Autostart
|
${green}16.${plain} Disable Autostart
|
||||||
————————————————
|
————————————————
|
||||||
${green}16.${plain} SSL Certificate Management
|
${green}17.${plain} SSL Certificate Management
|
||||||
${green}17.${plain} Cloudflare SSL Certificate
|
${green}18.${plain} Cloudflare SSL Certificate
|
||||||
${green}18.${plain} Firewall Management
|
${green}19.${plain} Firewall Management
|
||||||
————————————————
|
————————————————
|
||||||
${green}19.${plain} Enable or Disable BBR
|
${green}20.${plain} Enable or Disable BBR
|
||||||
${green}20.${plain} Update Geo Files
|
${green}21.${plain} Update Geo Files
|
||||||
${green}21.${plain} Speedtest by Ookla
|
${green}22.${plain} Speedtest by Ookla
|
||||||
"
|
"
|
||||||
show_status
|
show_status
|
||||||
echo && read -p "Please enter your selection [0-21]: " num
|
echo && read -p "Please enter your selection [0-22]: " num
|
||||||
|
|
||||||
case "${num}" in
|
case "${num}" in
|
||||||
0)
|
0)
|
||||||
@@ -949,7 +1112,7 @@ show_menu() {
|
|||||||
check_install && update
|
check_install && update
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
check_install && custom_version
|
check_install && legacy_version
|
||||||
;;
|
;;
|
||||||
4)
|
4)
|
||||||
check_install && uninstall
|
check_install && uninstall
|
||||||
@@ -958,55 +1121,58 @@ show_menu() {
|
|||||||
check_install && reset_user
|
check_install && reset_user
|
||||||
;;
|
;;
|
||||||
6)
|
6)
|
||||||
check_install && reset_config
|
check_install && reset_webbasepath
|
||||||
;;
|
;;
|
||||||
7)
|
7)
|
||||||
check_install && set_port
|
check_install && reset_config
|
||||||
;;
|
;;
|
||||||
8)
|
8)
|
||||||
check_install && check_config
|
check_install && set_port
|
||||||
;;
|
;;
|
||||||
9)
|
9)
|
||||||
check_install && start
|
check_install && check_config && get_uri
|
||||||
;;
|
;;
|
||||||
10)
|
10)
|
||||||
check_install && stop
|
check_install && start
|
||||||
;;
|
;;
|
||||||
11)
|
11)
|
||||||
check_install && restart
|
check_install && stop
|
||||||
;;
|
;;
|
||||||
12)
|
12)
|
||||||
check_install && status
|
check_install && restart
|
||||||
;;
|
;;
|
||||||
13)
|
13)
|
||||||
check_install && show_log
|
check_install && status
|
||||||
;;
|
;;
|
||||||
14)
|
14)
|
||||||
check_install && enable
|
check_install && show_log
|
||||||
;;
|
;;
|
||||||
15)
|
15)
|
||||||
check_install && disable
|
check_install && enable
|
||||||
;;
|
;;
|
||||||
16)
|
16)
|
||||||
ssl_cert_issue_main
|
check_install && disable
|
||||||
;;
|
;;
|
||||||
17)
|
17)
|
||||||
ssl_cert_issue_CF
|
ssl_cert_issue_main
|
||||||
;;
|
;;
|
||||||
18)
|
18)
|
||||||
firewall_menu
|
ssl_cert_issue_CF
|
||||||
;;
|
;;
|
||||||
19)
|
19)
|
||||||
bbr_menu
|
firewall_menu
|
||||||
;;
|
;;
|
||||||
20)
|
20)
|
||||||
update_geo
|
bbr_menu
|
||||||
;;
|
;;
|
||||||
21)
|
21)
|
||||||
|
update_geo
|
||||||
|
;;
|
||||||
|
22)
|
||||||
run_speedtest
|
run_speedtest
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
LOGE "Please enter the correct number [0-21]"
|
LOGE "Please enter the correct number [0-22]"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -1025,6 +1191,9 @@ if [[ $# > 0 ]]; then
|
|||||||
"status")
|
"status")
|
||||||
check_install 0 && status 0
|
check_install 0 && status 0
|
||||||
;;
|
;;
|
||||||
|
"settings")
|
||||||
|
check_install 0 && check_config 0 && get_uri 0
|
||||||
|
;;
|
||||||
"enable")
|
"enable")
|
||||||
check_install 0 && enable 0
|
check_install 0 && enable 0
|
||||||
;;
|
;;
|
||||||
|
|||||||
36
xray/api.go
36
xray/api.go
@@ -7,8 +7,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/logger"
|
"github.com/alireza0/x-ui/logger"
|
||||||
"x-ui/util/common"
|
"github.com/alireza0/x-ui/util/common"
|
||||||
|
|
||||||
"github.com/xtls/xray-core/app/proxyman/command"
|
"github.com/xtls/xray-core/app/proxyman/command"
|
||||||
statsService "github.com/xtls/xray-core/app/stats/command"
|
statsService "github.com/xtls/xray-core/app/stats/command"
|
||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/xtls/xray-core/proxy/vless"
|
"github.com/xtls/xray-core/proxy/vless"
|
||||||
"github.com/xtls/xray-core/proxy/vmess"
|
"github.com/xtls/xray-core/proxy/vmess"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
)
|
)
|
||||||
|
|
||||||
type XrayAPI struct {
|
type XrayAPI struct {
|
||||||
@@ -34,7 +35,7 @@ func (x *XrayAPI) Init(apiPort int) (err error) {
|
|||||||
if apiPort == 0 {
|
if apiPort == 0 {
|
||||||
return common.NewError("xray api port wrong:", apiPort)
|
return common.NewError("xray api port wrong:", apiPort)
|
||||||
}
|
}
|
||||||
x.grpcClient, err = grpc.Dial(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithInsecure())
|
x.grpcClient, err = grpc.NewClient(fmt.Sprintf("127.0.0.1:%v", apiPort), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -93,10 +94,33 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
|
|||||||
Id: user["id"].(string),
|
Id: user["id"].(string),
|
||||||
})
|
})
|
||||||
case "vless":
|
case "vless":
|
||||||
account = serial.ToTypedMessage(&vless.Account{
|
vlessAccount := &vless.Account{
|
||||||
Id: user["id"].(string),
|
Id: user["id"].(string),
|
||||||
Flow: user["flow"].(string),
|
Flow: user["flow"].(string),
|
||||||
})
|
}
|
||||||
|
// Add testseed if provided
|
||||||
|
if testseedVal, ok := user["testseed"]; ok {
|
||||||
|
if testseedArr, ok := testseedVal.([]interface{}); ok && len(testseedArr) >= 4 {
|
||||||
|
testseed := make([]uint32, len(testseedArr))
|
||||||
|
for i, v := range testseedArr {
|
||||||
|
if num, ok := v.(float64); ok {
|
||||||
|
testseed[i] = uint32(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vlessAccount.Testseed = testseed
|
||||||
|
} else if testseedArr, ok := testseedVal.([]uint32); ok && len(testseedArr) >= 4 {
|
||||||
|
vlessAccount.Testseed = testseedArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add testpre if provided (for outbound, but can be in user for compatibility)
|
||||||
|
if testpreVal, ok := user["testpre"]; ok {
|
||||||
|
if testpre, ok := testpreVal.(float64); ok && testpre > 0 {
|
||||||
|
vlessAccount.Testpre = uint32(testpre)
|
||||||
|
} else if testpre, ok := testpreVal.(uint32); ok && testpre > 0 {
|
||||||
|
vlessAccount.Testpre = testpre
|
||||||
|
}
|
||||||
|
}
|
||||||
|
account = serial.ToTypedMessage(vlessAccount)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
account = serial.ToTypedMessage(&trojan.Account{
|
account = serial.ToTypedMessage(&trojan.Account{
|
||||||
Password: user["password"].(string),
|
Password: user["password"].(string),
|
||||||
@@ -122,7 +146,7 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in
|
|||||||
CipherType: ssCipherType,
|
CipherType: ssCipherType,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
account = serial.ToTypedMessage(&shadowsocks_2022.User{
|
account = serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{
|
||||||
Key: user["password"].(string),
|
Key: user["password"].(string),
|
||||||
Email: user["email"].(string),
|
Email: user["email"].(string),
|
||||||
})
|
})
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user