Compare commits
241 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
045c549937 | ||
|
|
25430b7818 | ||
|
|
6aeedac051 | ||
|
|
9c231a41d1 | ||
|
|
ba673778ea | ||
|
|
8698024b61 | ||
|
|
e8b576fa65 | ||
|
|
c49147c5db | ||
|
|
149bd0ec51 | ||
|
|
f3280b46fe | ||
|
|
0f91e4d5f6 | ||
|
|
18617afd43 | ||
|
|
731e83a7da | ||
|
|
1c1f53267a | ||
|
|
8489f5f528 | ||
|
|
872974910c | ||
|
|
147999dd88 | ||
|
|
fe22cbd0e5 | ||
|
|
1277285d08 | ||
|
|
75df8a05f1 | ||
|
|
38e1d0f94e | ||
|
|
71c1a05386 | ||
|
|
4a6bd23873 | ||
|
|
8a1d647547 | ||
|
|
fb5180a890 | ||
|
|
d2c75b94cf | ||
|
|
edfc2d8d93 | ||
|
|
dd177b19f7 | ||
|
|
ae7283fc73 | ||
|
|
51a7c56cae | ||
|
|
c812168d6e | ||
|
|
fa10ae14f7 | ||
|
|
ed53df5ef3 | ||
|
|
b541290ded | ||
|
|
792fe6d9ef | ||
|
|
eb0c1dabf1 | ||
|
|
e00c3f1823 | ||
|
|
05bc655e16 | ||
|
|
7cddada8b4 | ||
|
|
24eb36715a | ||
|
|
22cf278ce2 | ||
|
|
5ab5986bd0 | ||
|
|
f0775abc67 | ||
|
|
d424b4bc32 | ||
|
|
329889ec00 | ||
|
|
95268dbc61 | ||
|
|
9a3200c9b5 | ||
|
|
c213fb6216 | ||
|
|
dd0217b46b | ||
|
|
b805bf6222 | ||
|
|
07b9474212 | ||
|
|
bf971911d5 | ||
|
|
c46ced0c4c | ||
|
|
dd3bbbc4af | ||
|
|
a97f90d225 | ||
|
|
6066edd510 | ||
|
|
eaec9e54ad | ||
|
|
fafcb2e8e7 | ||
|
|
145ea1e6f1 | ||
|
|
4cfed17650 | ||
|
|
c2b1fb4855 | ||
|
|
09807b39aa | ||
|
|
a0ec2f3972 | ||
|
|
e4b1dc20c3 | ||
|
|
2f05d4960e | ||
|
|
e63d2644bd | ||
|
|
56e4d13179 | ||
|
|
0dd0ba717f | ||
|
|
8050330e2e | ||
|
|
65e35c1711 | ||
|
|
0311ae4d05 | ||
|
|
6f09fae28b | ||
|
|
c2e9ee3665 | ||
|
|
1f78842b70 | ||
|
|
81a057d638 | ||
|
|
64d17bee70 | ||
|
|
6beb19b945 | ||
|
|
6b84d39700 | ||
|
|
5f3b91f3f1 | ||
|
|
9e433ea4c4 | ||
|
|
39537f6f44 | ||
|
|
44b34c437e | ||
|
|
467c5f807e | ||
|
|
1028319386 | ||
|
|
f726474a5d | ||
|
|
cd7a790637 | ||
|
|
4f74f5154b | ||
|
|
6e22aa59e7 | ||
|
|
85df1301dc | ||
|
|
e92aba0179 | ||
|
|
85fb2fda5e | ||
|
|
f57e693023 | ||
|
|
83f6f13b50 | ||
|
|
b833ed7992 | ||
|
|
5188d516e3 | ||
|
|
97925eeebe | ||
|
|
1328bb5aba | ||
|
|
4cc755c883 | ||
|
|
4e89c71095 | ||
|
|
d40e61fc45 | ||
|
|
c0f1a926e5 | ||
|
|
970dd0915e | ||
|
|
526e2578b0 | ||
|
|
ad9134bc1a | ||
|
|
b5657ab87d | ||
|
|
2c7ec3dc8a | ||
|
|
0fe35fde49 | ||
|
|
c7658973c3 | ||
|
|
dea61b839c | ||
|
|
9063336778 | ||
|
|
7fc3d37851 | ||
|
|
81aa3ed10e | ||
|
|
ca10623e40 | ||
|
|
547e38079f | ||
|
|
29e40a0bce | ||
|
|
4c1fa59453 | ||
|
|
84b0471ca0 | ||
|
|
1c785b930f | ||
|
|
c8ea9e43ec | ||
|
|
594d682e20 | ||
|
|
70f250dfe1 | ||
|
|
1030bcf321 | ||
|
|
fdc1124ea4 | ||
|
|
33a598366b | ||
|
|
459c19eee2 | ||
|
|
d73a995257 | ||
|
|
e4e0deeed4 | ||
|
|
48dce38e14 | ||
|
|
3eebd7c3e5 | ||
|
|
2fa151d52e | ||
|
|
c182f48079 | ||
|
|
94fad02737 | ||
|
|
d694e6eafc | ||
|
|
2a9cb6d29e | ||
|
|
c565a429af | ||
|
|
572d912858 | ||
|
|
6ae80fc992 | ||
|
|
ef7b979d53 | ||
|
|
b203067dfd | ||
|
|
1c9fc9422e | ||
|
|
6c26e40aea | ||
|
|
fe963d91ef | ||
|
|
c9461f1647 | ||
|
|
ea7fe09c27 | ||
|
|
8170b65db4 | ||
|
|
a2d8c98b0d | ||
|
|
8442836512 | ||
|
|
331f2adb1c | ||
|
|
45a8d6c95e | ||
|
|
1f2eb2ca1a | ||
|
|
de571a2da4 | ||
|
|
2c233dffa5 | ||
|
|
31339d6bf8 | ||
|
|
8865443438 | ||
|
|
835deb77f4 | ||
|
|
6a7c3716ac | ||
|
|
15211f81b1 | ||
|
|
b3f7a6572e | ||
|
|
6f28a3a2fe | ||
|
|
896cc5386c | ||
|
|
76f70ce1e9 | ||
|
|
6aa3c8d4a2 | ||
|
|
aff9d0ea15 | ||
|
|
526426e2dd | ||
|
|
2223a21cfc | ||
|
|
47ccc7b501 | ||
|
|
c38e1e0cfe | ||
|
|
f36034541e | ||
|
|
783fa856c3 | ||
|
|
6139effb8a | ||
|
|
8ae7f4a564 | ||
|
|
6f46f3e636 | ||
|
|
ba278a4269 | ||
|
|
3825e36ef7 | ||
|
|
f6e0e1b3cf | ||
|
|
769590d779 | ||
|
|
1fa9101b40 | ||
|
|
3f2e1aede9 | ||
|
|
235e6880c1 | ||
|
|
ffa23a43c6 | ||
|
|
66e3c505e3 | ||
|
|
0938519f49 | ||
|
|
5f489c3d08 | ||
|
|
f82d0051b2 | ||
|
|
76267b23a0 | ||
|
|
40a926a54a | ||
|
|
3a835fbeb8 | ||
|
|
f6461b8386 | ||
|
|
586663c840 | ||
|
|
82ead05093 | ||
|
|
d9b1b200ce | ||
|
|
786a3ac992 | ||
|
|
8c5648eb09 | ||
|
|
4dfe527f20 | ||
|
|
980ebd99ca | ||
|
|
9b7cddbec7 | ||
|
|
2bd706aa92 | ||
|
|
95e0d9a468 | ||
|
|
4865754b3d | ||
|
|
92eaff9608 | ||
|
|
0b7aa8a9e0 | ||
|
|
678962d4ca | ||
|
|
795835c54f | ||
|
|
91360a3f49 | ||
|
|
4831c2f1b2 | ||
|
|
3166d497f9 | ||
|
|
f50ccce9ec | ||
|
|
c7e300f14d | ||
|
|
419a1938ee | ||
|
|
a48745cb3e | ||
|
|
ac9408c37f | ||
|
|
3d71289075 | ||
|
|
fbd3772788 | ||
|
|
334b28cddc | ||
|
|
2fbfc88bc1 | ||
|
|
f781979d38 | ||
|
|
aba37be6eb | ||
|
|
30042bc047 | ||
|
|
49f60f7775 | ||
|
|
5f44c80cd5 | ||
|
|
85d42ce94f | ||
|
|
2f3c3d0ed2 | ||
|
|
7debf96610 | ||
|
|
d8b60c3cd5 | ||
|
|
f8eb548376 | ||
|
|
88fc4f81d4 | ||
|
|
911f2b0bb5 | ||
|
|
2d16eabc6e | ||
|
|
be50be75fe | ||
|
|
70e7987df5 | ||
|
|
8ceeb454ee | ||
|
|
f311bf1dbf | ||
|
|
837d7f77a1 | ||
|
|
d0213ce50b | ||
|
|
622e440366 | ||
|
|
1dc5452f1d | ||
|
|
a0daf2fae2 | ||
|
|
a21bdc9396 | ||
|
|
4cf7f75749 | ||
|
|
bcdac5aad6 | ||
|
|
3d5f851fce |
14
.github/workflows/docker.yml
vendored
@@ -11,15 +11,15 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the code
|
- name: Check out the code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3.6.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2.10.0
|
||||||
|
|
||||||
- name: Log in to GitHub Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -27,15 +27,15 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4.6.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4.1.1
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
platforms: linux/amd64, linux/arm64
|
platforms: linux/amd64, linux/arm64/v8
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
124
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release X-ui
|
name: Release 3X-ui
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -6,85 +7,70 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linuxamd64build:
|
build:
|
||||||
name: build x-ui amd64 version
|
strategy:
|
||||||
|
matrix:
|
||||||
|
platform: [amd64, arm64]
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.5.2
|
- name: Checkout repository
|
||||||
- name: Set up Go
|
uses: actions/checkout@v3.6.0
|
||||||
uses: actions/setup-go@v4.0.0
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v4.1.0
|
||||||
with:
|
with:
|
||||||
go-version: "stable"
|
go-version: '1.21'
|
||||||
- name: build linux amd64 version
|
|
||||||
run: |
|
- name: Install dependencies for arm64
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
if: matrix.platform == 'arm64'
|
||||||
mkdir x-ui
|
|
||||||
cp xui-release x-ui/xui-release
|
|
||||||
cp x-ui.service x-ui/x-ui.service
|
|
||||||
cp x-ui.sh x-ui/x-ui.sh
|
|
||||||
cd x-ui
|
|
||||||
mv xui-release x-ui
|
|
||||||
mkdir bin
|
|
||||||
cd bin
|
|
||||||
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
|
||||||
unzip Xray-linux-64.zip
|
|
||||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat iran.dat
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
|
||||||
mv xray xray-linux-amd64
|
|
||||||
cd ..
|
|
||||||
cd ..
|
|
||||||
- name: package
|
|
||||||
run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
|
|
||||||
- name: upload
|
|
||||||
uses: svenstaro/upload-release-action@2.5.0
|
|
||||||
with:
|
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
tag: ${{ github.ref }}
|
|
||||||
file: x-ui-linux-amd64.tar.gz
|
|
||||||
asset_name: x-ui-linux-amd64.tar.gz
|
|
||||||
prerelease: true
|
|
||||||
overwrite: true
|
|
||||||
linuxarm64build:
|
|
||||||
name: build x-ui arm64 version
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v4.0.0
|
|
||||||
with:
|
|
||||||
go-version: "stable"
|
|
||||||
- name: build linux arm64 version
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt install gcc-aarch64-linux-gnu
|
sudo apt install gcc-aarch64-linux-gnu
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc go build -o xui-release -v main.go
|
|
||||||
|
- name: Build x-ui
|
||||||
|
run: |
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export GOOS=linux
|
||||||
|
export GOARCH=${{ matrix.platform }}
|
||||||
|
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||||
|
export CC=aarch64-linux-gnu-gcc
|
||||||
|
fi
|
||||||
|
go build -o xui-release -v main.go
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/xui-release
|
cp xui-release x-ui/
|
||||||
cp x-ui.service x-ui/x-ui.service
|
cp x-ui.service x-ui/
|
||||||
cp x-ui.sh x-ui/x-ui.sh
|
cp x-ui.sh x-ui/
|
||||||
cd x-ui
|
mv x-ui/xui-release x-ui/x-ui
|
||||||
mv xui-release x-ui
|
mkdir x-ui/bin
|
||||||
mkdir bin
|
cd x-ui/bin
|
||||||
cd bin
|
|
||||||
wget https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-arm64-v8a.zip
|
# Download dependencies
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat iran.dat
|
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-64.zip
|
||||||
|
unzip Xray-linux-64.zip
|
||||||
|
rm -f Xray-linux-64.zip
|
||||||
|
else
|
||||||
|
wget https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-arm64-v8a.zip
|
||||||
|
unzip Xray-linux-arm64-v8a.zip
|
||||||
|
rm -f Xray-linux-arm64-v8a.zip
|
||||||
|
fi
|
||||||
|
rm -f geoip.dat geosite.dat iran.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
wget https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat
|
wget https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat
|
||||||
mv xray xray-linux-arm64
|
mv xray xray-linux-${{ matrix.platform }}
|
||||||
cd ..
|
cd ../..
|
||||||
cd ..
|
|
||||||
- name: package
|
- name: Package
|
||||||
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
|
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||||
- name: upload
|
|
||||||
uses: svenstaro/upload-release-action@2.5.0
|
- name: Upload
|
||||||
|
uses: svenstaro/upload-release-action@2.7.0
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-arm64.tar.gz
|
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
asset_name: x-ui-linux-arm64.tar.gz
|
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
16
.gitignore
vendored
@@ -1,15 +1,15 @@
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.cache
|
||||||
|
.sync*
|
||||||
|
*.tar.gz
|
||||||
|
access.log
|
||||||
|
error.log
|
||||||
tmp
|
tmp
|
||||||
|
main
|
||||||
backup/
|
backup/
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
x-ui-*.tar.gz
|
|
||||||
/x-ui
|
|
||||||
/release.sh
|
|
||||||
.sync*
|
|
||||||
main
|
|
||||||
release/
|
release/
|
||||||
access.log
|
/release.sh
|
||||||
error.log
|
/x-ui
|
||||||
.cache
|
|
||||||
|
|||||||
7
DockerEntrypoint.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Start fail2ban
|
||||||
|
fail2ban-client -x start
|
||||||
|
|
||||||
|
# Run x-ui
|
||||||
|
exec /app/x-ui
|
||||||
@@ -1,22 +1,28 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
if [ $1 == "amd64" ]; then
|
|
||||||
ARCH="64";
|
case $1 in
|
||||||
FNAME="amd64";
|
amd64)
|
||||||
elif [ $1 == "arm64" ]; then
|
ARCH="64"
|
||||||
ARCH="arm64-v8a"
|
FNAME="amd64"
|
||||||
FNAME="arm64";
|
;;
|
||||||
else
|
arm64)
|
||||||
ARCH="64";
|
ARCH="arm64-v8a"
|
||||||
FNAME="amd64";
|
FNAME="arm64"
|
||||||
fi
|
;;
|
||||||
|
*)
|
||||||
|
ARCH="64"
|
||||||
|
FNAME="amd64"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
mkdir -p build/bin
|
mkdir -p build/bin
|
||||||
cd build/bin
|
cd build/bin
|
||||||
wget "https://github.com/mhsanaei/xray-core/releases/latest/download/Xray-linux-${ARCH}.zip"
|
|
||||||
|
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.4/Xray-linux-${ARCH}.zip"
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
unzip "Xray-linux-${ARCH}.zip"
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat iran.dat
|
||||||
mv xray "xray-linux-${FNAME}"
|
mv xray "xray-linux-${FNAME}"
|
||||||
|
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat"
|
||||||
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
wget "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||||
wget "https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran.dat"
|
wget "https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat"
|
||||||
|
|
||||||
cd ../../
|
|
||||||
|
|||||||
47
Dockerfile
@@ -1,20 +1,49 @@
|
|||||||
#Build latest x-ui from source
|
# ========================================================
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
|
# Stage: Builder
|
||||||
|
# ========================================================
|
||||||
|
FROM --platform=$BUILDPLATFORM golang:alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
ENV CGO_ENABLED=1
|
||||||
|
|
||||||
|
RUN apk --no-cache --update add \
|
||||||
|
build-base \
|
||||||
|
gcc \
|
||||||
|
wget \
|
||||||
|
unzip
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN env CGO_ENABLED=1 go build -o build/x-ui main.go
|
|
||||||
|
RUN go build -o build/x-ui main.go
|
||||||
RUN ./DockerInit.sh "$TARGETARCH"
|
RUN ./DockerInit.sh "$TARGETARCH"
|
||||||
|
|
||||||
|
# ========================================================
|
||||||
#Build app image using latest x-ui
|
# Stage: Final Image of 3x-ui
|
||||||
|
# ========================================================
|
||||||
FROM alpine
|
FROM alpine
|
||||||
ENV TZ=Asia/Tehran
|
ENV TZ=Asia/Tehran
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN apk add ca-certificates tzdata
|
RUN apk add --no-cache --update \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
fail2ban
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
COPY --from=builder /app/build/ /app/
|
||||||
|
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
||||||
|
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
||||||
|
|
||||||
|
# Configure fail2ban
|
||||||
|
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
||||||
|
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
||||||
|
&& sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
||||||
|
&& sed -i "s/^\[sshd\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
||||||
|
&& sed -i "s/#allowipv6 = auto/allowipv6 = auto/g" /etc/fail2ban/fail2ban.conf
|
||||||
|
|
||||||
|
RUN chmod +x \
|
||||||
|
/app/DockerEntrypoint.sh \
|
||||||
|
/app/x-ui \
|
||||||
|
/usr/bin/x-ui
|
||||||
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
VOLUME [ "/etc/x-ui" ]
|
||||||
ENTRYPOINT [ "/app/x-ui" ]
|
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
||||||
|
|||||||
323
README.md
@@ -8,8 +8,7 @@
|
|||||||
[](#)
|
[](#)
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian)**
|
3x-ui panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese,Russian,Vietnamese)**
|
||||||
|
|
||||||
**If you think this project is helpful to you, you may wish to give a** :star2:
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
**Buy Me a Coffee :**
|
**Buy Me a Coffee :**
|
||||||
@@ -22,12 +21,12 @@
|
|||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Install custom version
|
# Install custom version
|
||||||
|
|
||||||
To install your desired version you can add the version to the end of install command. Example for ver `v1.4.6`:
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.7.7`:
|
||||||
|
|
||||||
```
|
```
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.4.6
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.7.7
|
||||||
```
|
```
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
@@ -38,97 +37,7 @@ certbot certonly --standalone --agree-tos --register-unsafely-without-email -d y
|
|||||||
certbot renew --dry-run
|
certbot renew --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)
|
You also can use `x-ui` menu then select `SSL Certificate Management`
|
||||||
|
|
||||||
# Default settings
|
|
||||||
|
|
||||||
- Port: 2053
|
|
||||||
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
|
||||||
- database path: /etc/x-ui/x-ui.db
|
|
||||||
- xray config path: /usr/local/x-ui/bin/config.json
|
|
||||||
|
|
||||||
Before you set ssl on settings
|
|
||||||
|
|
||||||
- http://ip:2053/panel
|
|
||||||
- http://domain:2053/panel
|
|
||||||
|
|
||||||
After you set ssl on settings
|
|
||||||
|
|
||||||
- https://yourdomain:2053/panel
|
|
||||||
|
|
||||||
# Environment Variables
|
|
||||||
|
|
||||||
| Variable | Type | Default |
|
|
||||||
| -------------- | :--------------------------------------------: | :------------ |
|
|
||||||
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
|
||||||
| XUI_DEBUG | `boolean` | `false` |
|
|
||||||
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
|
||||||
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
# Install with Docker
|
|
||||||
|
|
||||||
1. Install Docker:
|
|
||||||
```sh
|
|
||||||
bash <(curl -sSL https://get.docker.com)
|
|
||||||
```
|
|
||||||
2. Run 3x-ui:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
OR
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker run -itd \
|
|
||||||
-e XRAY_VMESS_AEAD_FORCED=false \
|
|
||||||
-v $PWD/db/:/etc/x-ui/ \
|
|
||||||
-v $PWD/cert/:/root/cert/ \
|
|
||||||
--network=host \
|
|
||||||
--restart=unless-stopped \
|
|
||||||
--name 3x-ui \
|
|
||||||
ghcr.io/mhsanaei/3x-ui:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
# Xray Configurations:
|
|
||||||
|
|
||||||
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
|
||||||
|
|
||||||
- [traffic](./media/configs/traffic.json)
|
|
||||||
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
|
||||||
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
|
||||||
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
|
||||||
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
|
||||||
|
|
||||||
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
|
||||||
|
|
||||||
If you want to use routing to WARP follow steps as below:
|
|
||||||
|
|
||||||
1. If you already installed warp, you can uninstall using below command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
warp u
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install WARP on **socks proxy mode**:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl -fsSL https://gist.githubusercontent.com/hamid-gh98/dc5dd9b0cc5b0412af927b1ccdb294c7/raw/install_warp_proxy.sh | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
|
||||||
|
|
||||||
Config Features:
|
|
||||||
|
|
||||||
- Block Ads
|
|
||||||
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
|
||||||
- Fix Google 403 error
|
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
@@ -147,7 +56,166 @@ If you want to use routing to WARP follow steps as below:
|
|||||||
- Support to change configs by different items provided in panel
|
- Support to change configs by different items provided in panel
|
||||||
- Support export/import database from panel
|
- Support export/import database from panel
|
||||||
|
|
||||||
# Tg robot use
|
# Manual Install & Upgrade
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Manual Install details</summary>
|
||||||
|
|
||||||
|
1. To download the latest version of the compressed package directly to your server, run the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
|
wget https://github.com/MHSanaei/3x-ui/releases/latest/download/x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Once the compressed package is downloaded, execute the following commands to install or upgrade x-ui:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
[[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||||
|
cd /root/
|
||||||
|
rm -rf x-ui/ /usr/local/x-ui/ /usr/bin/x-ui
|
||||||
|
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||||
|
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
|
||||||
|
cp x-ui/x-ui.sh /usr/bin/x-ui
|
||||||
|
cp -f x-ui/x-ui.service /etc/systemd/system/
|
||||||
|
mv x-ui/ /usr/local/
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable x-ui
|
||||||
|
systemctl restart x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Install with Docker
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Docker details</summary>
|
||||||
|
|
||||||
|
1. Install Docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://get.docker.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Clone the Project Repository:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/MHSanaei/3x-ui.git
|
||||||
|
cd 3x-ui
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the Service
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -itd \
|
||||||
|
-e XRAY_VMESS_AEAD_FORCED=false \
|
||||||
|
-v $PWD/db/:/etc/x-ui/ \
|
||||||
|
-v $PWD/cert/:/root/cert/ \
|
||||||
|
--network=host \
|
||||||
|
--restart=unless-stopped \
|
||||||
|
--name 3x-ui \
|
||||||
|
ghcr.io/mhsanaei/3x-ui:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Default settings
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Default settings details</summary>
|
||||||
|
|
||||||
|
- Port: 2053
|
||||||
|
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
||||||
|
- database path: /etc/x-ui/x-ui.db
|
||||||
|
- xray config path: /usr/local/x-ui/bin/config.json
|
||||||
|
|
||||||
|
Before you set ssl on settings
|
||||||
|
|
||||||
|
- http://ip:2053/panel
|
||||||
|
- http://domain:2053/panel
|
||||||
|
|
||||||
|
After you set ssl on settings
|
||||||
|
|
||||||
|
- https://yourdomain:2053/panel
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Xray Configurations:
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Xray Configurations details</summary>
|
||||||
|
|
||||||
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
|
|
||||||
|
- [traffic](./media/configs/traffic.json)
|
||||||
|
- [traffic + Block all Iran IP address](./media/configs/traffic+block-iran-ip.json)
|
||||||
|
- [traffic + Block all Iran Domains](./media/configs/traffic+block-iran-domains.json)
|
||||||
|
- [traffic + Block Ads + Use IPv4 for Google](./media/configs/traffic+block-ads+ipv4-google.json)
|
||||||
|
- [traffic + Block Ads + Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# [WARP Configuration](https://github.com/fscarmen/warp) (Optional)
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for WARP Configuration details</summary>
|
||||||
|
|
||||||
|
If you want to use routing to WARP follow steps as below:
|
||||||
|
|
||||||
|
1. If you already installed warp, you can uninstall using below command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
warp u
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install WARP on **socks proxy mode**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash <(curl -sSL https://raw.githubusercontent.com/hamid-gh98/x-ui-scripts/main/install_warp_proxy.sh)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Turn on the config you need in panel or [Copy and paste this file to Xray Configuration](./media/configs/traffic+block-ads+warp.json)
|
||||||
|
|
||||||
|
Config Features:
|
||||||
|
|
||||||
|
- Block Ads
|
||||||
|
- Route Google + Netflix + Spotify + OpenAI (ChatGPT) to WARP
|
||||||
|
- Fix Google 403 error
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# IP Limit
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for IP Limit details</summary>
|
||||||
|
|
||||||
|
**Note: IP Limit won't work correctly when using IP Tunnel**
|
||||||
|
|
||||||
|
- For versions up to `v1.6.1`:
|
||||||
|
|
||||||
|
- IP limit is built-in into the panel.
|
||||||
|
|
||||||
|
- For versions `v1.7.0` and newer:
|
||||||
|
|
||||||
|
- To make IP Limit work properly, you need to install fail2ban and its required files by following these steps:
|
||||||
|
|
||||||
|
1. Use the `x-ui` command inside the shell.
|
||||||
|
2. Select `IP Limit Management`.
|
||||||
|
3. Choose the appropriate options based on your needs.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Telegram Bot
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Telegram Bot details</summary>
|
||||||
|
|
||||||
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
Set the robot-related parameters in the panel background, including:
|
Set the robot-related parameters in the panel background, including:
|
||||||
@@ -176,17 +244,43 @@ Reference syntax:
|
|||||||
- CPU threshold notification
|
- CPU threshold notification
|
||||||
- Threshold for Expiration time and Traffic to report in advance
|
- Threshold for Expiration time and Traffic to report in advance
|
||||||
- Support client report menu if client's telegram username added to the user's configurations
|
- Support client report menu if client's telegram username added to the user's configurations
|
||||||
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
- Menu based bot
|
- Menu based bot
|
||||||
- Search client by email ( only admin )
|
- Search client by email ( only admin )
|
||||||
- Check all inbounds
|
- Check all inbounds
|
||||||
- Check server status
|
- Check server status
|
||||||
- Check depleted users
|
- Check depleted users
|
||||||
- Receive backup by request and in periodic reports
|
- Receive backup by request and in periodic reports
|
||||||
|
- Multi language bot
|
||||||
|
</details>
|
||||||
|
|
||||||
## API routes
|
# Setting up Telegram bot
|
||||||
|
|
||||||
- `/login` with `PUSH` user data: `{username: '', password: ''}` for login
|
- Start [Botfather](https://t.me/BotFather) in your Telegram account:
|
||||||
|

|
||||||
|
|
||||||
|
- Create a new Bot using /newbot command: It will ask you 2 questions, A name and a username for your bot. Note that the username has to end with the word "bot".
|
||||||
|

|
||||||
|
|
||||||
|
- Start the bot you've just created. You can find the link to your bot here.
|
||||||
|

|
||||||
|
|
||||||
|
- Enter your panel and config Telegram bot settings like below:
|
||||||
|

|
||||||
|
|
||||||
|
Enter your bot token in input field number 3.
|
||||||
|
Enter the user ID in input field number 4. The Telegram accounts with this id will be the bot admin. (You can enter more than one, Just separate them with ,)
|
||||||
|
|
||||||
|
- How to get Telegram user ID? Use this [bot](https://t.me/useridinfobot), Start the bot and it will give you the Telegram user ID.
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
# API routes
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for API routes details</summary>
|
||||||
|
|
||||||
|
- `/login` with `POST` user data: `{username: '', password: ''}` for login
|
||||||
- `/panel/api/inbounds` base for following actions:
|
- `/panel/api/inbounds` base for following actions:
|
||||||
|
|
||||||
| Method | Path | Action |
|
| Method | Path | Action |
|
||||||
@@ -194,6 +288,7 @@ Reference syntax:
|
|||||||
| `GET` | `"/list"` | Get all inbounds |
|
| `GET` | `"/list"` | Get all inbounds |
|
||||||
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
| `GET` | `"/get/:id"` | Get inbound with inbound.id |
|
||||||
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
| `GET` | `"/getClientTraffics/:email"` | Get Client Traffics with email |
|
||||||
|
| `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 |
|
||||||
@@ -214,17 +309,45 @@ Reference syntax:
|
|||||||
- `client.email` for Shadowsocks
|
- `client.email` for Shadowsocks
|
||||||
|
|
||||||
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
- [Postman Collection](https://gist.github.com/mehdikhody/9a862801a2e41f6b5fb6bbc7e1326044)
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# Environment Variables
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click for Environment Variables details</summary>
|
||||||
|
|
||||||
|
| Variable | Type | Default |
|
||||||
|
| -------------- | :--------------------------------------------: | :------------ |
|
||||||
|
| XUI_LOG_LEVEL | `"debug"` \| `"info"` \| `"warn"` \| `"error"` | `"info"` |
|
||||||
|
| XUI_DEBUG | `boolean` | `false` |
|
||||||
|
| XUI_BIN_FOLDER | `string` | `"bin"` |
|
||||||
|
| XUI_DB_FOLDER | `string` | `"/etc/x-ui"` |
|
||||||
|
| XUI_LOG_FOLDER | `string` | `"/var/log"` |
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
XUI_BIN_FOLDER="bin" XUI_DB_FOLDER="/etc/x-ui" go build main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
# A Special Thanks To
|
# A Special Thanks To
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
- [alireza0](https://github.com/alireza0/)
|
||||||
|
|
||||||
|
# Acknowledgment
|
||||||
|
|
||||||
|
- [Iran Hosted Domains](https://github.com/bootmortis/iran-hosted-domains) (License: **MIT**): _A comprehensive list of Iranian domains and services that are hosted within the country._
|
||||||
|
- [PersianBlocker](https://github.com/MasterKia/PersianBlocker) (License: **AGPLv3**): _An optimal and extensive list to block ads and trackers on Persian websites._
|
||||||
|
|
||||||
# Suggestion System
|
# Suggestion System
|
||||||
|
|
||||||
- Ubuntu 20.04+
|
- Ubuntu 20.04+
|
||||||
- Debian 10+
|
- Debian 10+
|
||||||
- CentOS 8+
|
- CentOS 8+
|
||||||
- Fedora 36+
|
- Fedora 36+
|
||||||
|
- Arch Linux
|
||||||
|
|
||||||
# Pictures
|
# Pictures
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,11 @@ 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"
|
Notice LogLevel = "notice"
|
||||||
Error LogLevel = "error"
|
Warn LogLevel = "warn"
|
||||||
|
Error LogLevel = "error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
@@ -64,3 +65,11 @@ func GetDBFolderPath() string {
|
|||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLogFolder() string {
|
||||||
|
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
||||||
|
if logFolderPath == "" {
|
||||||
|
logFolderPath = "/var/log"
|
||||||
|
}
|
||||||
|
return logFolderPath
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.4.6
|
1.7.8
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
@@ -17,6 +18,14 @@ import (
|
|||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
|
|
||||||
|
var initializers = []func() error{
|
||||||
|
initUser,
|
||||||
|
initInbound,
|
||||||
|
initSetting,
|
||||||
|
initInboundClientIps,
|
||||||
|
initClientTraffic,
|
||||||
|
}
|
||||||
|
|
||||||
func initUser() error {
|
func initUser() error {
|
||||||
err := db.AutoMigrate(&model.User{})
|
err := db.AutoMigrate(&model.User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -45,16 +54,18 @@ func initInbound() error {
|
|||||||
func initSetting() error {
|
func initSetting() error {
|
||||||
return db.AutoMigrate(&model.Setting{})
|
return db.AutoMigrate(&model.Setting{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initInboundClientIps() error {
|
func initInboundClientIps() error {
|
||||||
return db.AutoMigrate(&model.InboundClientIps{})
|
return db.AutoMigrate(&model.InboundClientIps{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func initClientTraffic() error {
|
func initClientTraffic() error {
|
||||||
return db.AutoMigrate(&xray.ClientTraffic{})
|
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDB(dbPath string) error {
|
func InitDB(dbPath string) error {
|
||||||
dir := path.Dir(dbPath)
|
dir := path.Dir(dbPath)
|
||||||
err := os.MkdirAll(dir, fs.ModeDir)
|
err := os.MkdirAll(dir, fs.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -75,25 +86,10 @@ func InitDB(dbPath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = initUser()
|
for _, initialize := range initializers {
|
||||||
if err != nil {
|
if err := initialize(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = initInbound()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initSetting()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initInboundClientIps()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = initClientTraffic()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -107,10 +103,10 @@ func IsNotFound(err error) bool {
|
|||||||
return err == gorm.ErrRecordNotFound
|
return err == gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSQLiteDB(file io.Reader) (bool, error) {
|
func IsSQLiteDB(file io.ReaderAt) (bool, error) {
|
||||||
signature := []byte("SQLite format 3\x00")
|
signature := []byte("SQLite format 3\x00")
|
||||||
buf := make([]byte, len(signature))
|
buf := make([]byte, len(signature))
|
||||||
_, err := file.Read(buf)
|
_, err := file.ReadAt(buf, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ type Client struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
AlterIds uint16 `json:"alterId"`
|
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
LimitIP int `json:"limitIp"`
|
LimitIP int `json:"limitIp"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
|
|||||||
99
go.mod
@@ -1,63 +1,100 @@
|
|||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.20
|
go 1.21
|
||||||
|
|
||||||
|
toolchain go1.21.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Workiva/go-datastructures v1.0.53
|
github.com/Calidity/gin-sessions v1.3.1
|
||||||
github.com/gin-contrib/sessions v0.0.4
|
github.com/Workiva/go-datastructures v1.1.0
|
||||||
github.com/gin-gonic/gin v1.9.0
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-cmd/cmd v1.4.1
|
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
|
||||||
github.com/goccy/go-json v0.10.2
|
github.com/goccy/go-json v0.10.2
|
||||||
|
github.com/mymmrac/telego v0.26.1
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
github.com/nicksnyder/go-i18n/v2 v2.2.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.0.7
|
github.com/pelletier/go-toml/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v3 v3.23.4
|
github.com/shirou/gopsutil/v3 v3.23.7
|
||||||
github.com/xtls/xray-core v1.8.1
|
github.com/xtls/xray-core v1.8.4
|
||||||
go.uber.org/atomic v1.11.0
|
go.uber.org/atomic v1.11.0
|
||||||
golang.org/x/text v0.9.0
|
golang.org/x/text v0.12.0
|
||||||
google.golang.org/grpc v1.55.0
|
google.golang.org/grpc v1.57.0
|
||||||
gorm.io/driver/sqlite v1.5.0
|
gorm.io/driver/sqlite v1.5.3
|
||||||
gorm.io/gorm v1.25.1
|
gorm.io/gorm v1.25.4
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||||
github.com/bytedance/sonic v1.8.8 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/bytedance/sonic v1.10.0 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||||
|
github.com/fasthttp/router v1.4.20 // indirect
|
||||||
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // 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.13.0 // indirect
|
github.com/go-playground/validator/v10 v10.15.2 // indirect
|
||||||
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/google/btree v1.1.2 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
|
||||||
github.com/gorilla/context v1.1.1 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/gorilla/sessions v1.2.1 // indirect
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // 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/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // 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.12.0 // indirect
|
||||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.5 // indirect
|
github.com/quic-go/qtls-go1-20 v0.3.3 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
github.com/quic-go/quic-go v0.38.1 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.0 // indirect
|
github.com/refraction-networking/utls v1.4.3 // indirect
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||||
|
github.com/sagernet/sing v0.2.9 // indirect
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.4 // indirect
|
||||||
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c // indirect
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // 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.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.8.0 // indirect
|
github.com/valyala/fasthttp v1.48.0 // indirect
|
||||||
golang.org/x/net v0.9.0 // indirect
|
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 // indirect
|
||||||
golang.org/x/sys v0.7.0 // indirect
|
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
golang.org/x/arch v0.4.0 // indirect
|
||||||
|
golang.org/x/crypto v0.12.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||||
|
golang.org/x/mod v0.12.0 // indirect
|
||||||
|
golang.org/x/net v0.14.0 // indirect
|
||||||
|
golang.org/x/sys v0.11.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
golang.org/x/tools v0.12.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 // indirect
|
||||||
|
lukechampine.com/blake3 v1.2.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
422
go.sum
@@ -1,259 +1,457 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||||
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||||
|
github.com/Workiva/go-datastructures v1.1.0 h1:hu20UpgZneBhQ3ZvwiOGlqJSKIosin2Rd5wAKUHEO/k=
|
||||||
|
github.com/Workiva/go-datastructures v1.1.0/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
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.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.8.8 h1:Kj4AYbZSeENfyXicsYppYKO0K2YWab+i2UTSY7Ukz9Q=
|
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||||
github.com/bytedance/sonic v1.8.8/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.10.0 h1:qtNZduETEIWJVIyDl01BeNxur2rW9OwTQ/yBqFRkKEk=
|
||||||
|
github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||||
|
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
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/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/fasthttp/router v1.4.20 h1:yPeNxz5WxZGojzolKqiP15DTXnxZce9Drv577GBrDgU=
|
||||||
|
github.com/fasthttp/router v1.4.20/go.mod h1:um867yNQKtERxBm+C+yzgWxjspTiQoA8z86Ec3fK/tc=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk=
|
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.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
|
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||||
|
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
|
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/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc=
|
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
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-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
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.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
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.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.15.2 h1:Ra5cll2/eF8X0Ff2+8SMD7euo2nenQ8WEpgqfy4NhHU=
|
||||||
github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ=
|
github.com/go-playground/validator/v10 v10.15.2/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
|
||||||
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
|
||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
|
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/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/pprof v0.0.0-20230406165453-00490a63f317 h1:hFhpt7CTmR3DX+b4R19ydQFtofxT0Sv3QsKNMVQYTMQ=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
|
||||||
|
github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
github.com/googleapis/gax-go/v2 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.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
|
||||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
|
||||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/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.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
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/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||||
|
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
|
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
||||||
|
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/mymmrac/telego v0.26.1 h1:BzWWzDOkov0SMVnSF+mLJ5ChYcTqmhTIyBWBGyi51hw=
|
||||||
|
github.com/mymmrac/telego v0.26.1/go.mod h1:kizipjY3MhxmkcGvyz8jiw/26vEKAhR2V7YTE69iqvw=
|
||||||
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
|
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
|
||||||
|
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||||
|
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||||
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/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
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-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/refraction-networking/utls v1.3.2 h1:o+AkWB57mkcoW36ET7uJ002CpBWHu0KPxi6vzxvPnv8=
|
github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM=
|
||||||
|
github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||||
|
github.com/quic-go/quic-go v0.38.1 h1:M36YWA5dEhEeT+slOu/SwMEucbYd0YFidxG3KlGPZaE=
|
||||||
|
github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdyhGmg+SN4=
|
||||||
|
github.com/refraction-networking/utls v1.4.3 h1:BdWS3BSzCwWCFfMIXP3mjLAyQkdmog7diaD/OqFbAzM=
|
||||||
|
github.com/refraction-networking/utls v1.4.3/go.mod h1:4u9V/awOSBrRw6+federGmVJQfPtemEqLBXkML1b0bo=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
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/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.1 h1:FvdLQOqpvxHBJUcUe4fvgiYP2XLLwH5i1DtXQviVEPw=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo=
|
||||||
|
github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.4 h1:s/CqXlvFAZhlIoHWUwPw5CoNnQ9Ibki9pckjuugtVfY=
|
||||||
|
github.com/sagernet/sing-shadowsocks v0.2.4/go.mod h1:80fNKP0wnqlu85GZXV1H1vDPC/2t+dQbFggOw4XuFUM=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.4 h1:hZwmDxZs7Ewt75DV81r4pFMqbq+di2cbt9FsQBqLD2o=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
github.com/shirou/gopsutil/v3 v3.23.4/go.mod h1:ZcGxyfzAMRevhUR2+cfhXDH6gQdFYE/t8j1nsU4mPI8=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/shoenig/go-m1cpu v0.1.5 h1:LF57Z/Fpb/WdGLjt2HZilNnmZOxg/q2bSKTQhgbrLrQ=
|
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.5/go.mod h1:Wwvst4LR89UxjeFtLRMrpgRiyY4xPsejnVZym39dbAQ=
|
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||||
github.com/shoenig/test v0.6.3 h1:GVXWJFk9PiOjN0KoJ7VrJGH6uLPnqxR7/fe3HUPfE0c=
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
github.com/shoenig/test v0.6.3/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
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/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
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 v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/xtls/reality v0.0.0-20230331223127-176a94313eda h1:psRJD2RrZbnI0OWyHvXfgYCPqlRM5q5SPDcjDoDBWhE=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||||
github.com/xtls/xray-core v1.8.1 h1:iSTTqXj82ZdwC1ah+eV331X4JTcnrDz+WuKuB/EB3P4=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/xtls/xray-core v1.8.1/go.mod h1:AXxSso0MZwUE4NhRocCfHCg73BtJ+T2dSpQVo1Cg9VM=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasthttp v1.48.0 h1:oJWvHb9BIZToTQS3MuQ2R3bJZiNSa2KiNdeI8A+79Tc=
|
||||||
|
github.com/valyala/fasthttp v1.48.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||||
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
|
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6 h1:T+YCYGfFdzyaKTDCdZn/hEiKvsw6yUfd+e4hze0rCUw=
|
||||||
|
github.com/xtls/reality v0.0.0-20230828171259-e426190d57f6/go.mod h1:rkuAY1S9F8eI8gDiPDYvACE8e2uwkyg8qoOTuwWov7Y=
|
||||||
|
github.com/xtls/xray-core v1.8.4 h1:YEoY3iLx/5zoNbt5HORG5LtPyzwICInFfoS+oPkYDIw=
|
||||||
|
github.com/xtls/xray-core v1.8.4/go.mod h1:GGD9elFSHa4IqOArW8gzMsEksPIqK/jdNLo8RcSMfnI=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU=
|
||||||
|
go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM=
|
||||||
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||||
|
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
|
||||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||||
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
|
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
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.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
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.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||||
|
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||||
|
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.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||||
|
google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/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.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.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
|
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||||
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
|
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||||
gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64=
|
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744 h1:tE44CyJgxEGzoPtHs9GI7ddKdgEGCREQBP54AmaVM+I=
|
||||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
gvisor.dev/gvisor v0.0.0-20230822212503-5bf4e5f98744/go.mod h1:lYEMhXbxgudVhALYsMQrBaUAjM3NMinh8mKL1CJv7rc=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||||
|
lukechampine.com/blake3 v1.2.1/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=
|
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=
|
||||||
|
|||||||
29
install.sh
@@ -8,7 +8,7 @@ plain='\033[0m'
|
|||||||
cur_dir=$(pwd)
|
cur_dir=$(pwd)
|
||||||
|
|
||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error:${plain} Please run this script with root privilege \n " && exit 1
|
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
||||||
|
|
||||||
# Check OS and set release variable
|
# Check OS and set release variable
|
||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
@@ -41,34 +41,41 @@ if [[ "${release}" == "centos" ]]; then
|
|||||||
fi
|
fi
|
||||||
elif [[ "${release}" == "ubuntu" ]]; then
|
elif [[ "${release}" == "ubuntu" ]]; then
|
||||||
if [[ ${os_version} -lt 20 ]]; then
|
if [[ ${os_version} -lt 20 ]]; then
|
||||||
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "${release}" == "fedora" ]]; then
|
elif [[ "${release}" == "fedora" ]]; then
|
||||||
if [[ ${os_version} -lt 36 ]]; then
|
if [[ ${os_version} -lt 36 ]]; then
|
||||||
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
|
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [[ "${release}" == "debian" ]]; then
|
elif [[ "${release}" == "debian" ]]; then
|
||||||
if [[ ${os_version} -lt 10 ]]; then
|
if [[ ${os_version} -lt 10 ]]; then
|
||||||
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
||||||
fi
|
fi
|
||||||
|
elif [[ "${release}" == "arch" ]]; then
|
||||||
|
echo "OS is ArchLinux"
|
||||||
|
|
||||||
else
|
else
|
||||||
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
case "${release}" in
|
case "${release}" in
|
||||||
centos | fedora)
|
centos|fedora)
|
||||||
yum install -y -q wget curl tar
|
yum install -y -q wget curl tar
|
||||||
;;
|
;;
|
||||||
*)
|
arch)
|
||||||
apt install -y -q wget curl tar
|
pacman -Syu --noconfirm wget curl tar
|
||||||
;;
|
;;
|
||||||
|
*)
|
||||||
|
apt install -y -q wget curl tar
|
||||||
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
#This function will be called when user installed x-ui out of sercurity
|
|
||||||
|
# This function will be called when user installed x-ui out of sercurity
|
||||||
config_after_install() {
|
config_after_install() {
|
||||||
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||||
@@ -104,7 +111,6 @@ config_after_install() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_x-ui() {
|
install_x-ui() {
|
||||||
systemctl stop x-ui
|
|
||||||
cd /usr/local/
|
cd /usr/local/
|
||||||
|
|
||||||
if [ $# == 0 ]; then
|
if [ $# == 0 ]; then
|
||||||
@@ -131,6 +137,7 @@ install_x-ui() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -e /usr/local/x-ui/ ]]; then
|
if [[ -e /usr/local/x-ui/ ]]; then
|
||||||
|
systemctl stop x-ui
|
||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,47 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger *logging.Logger
|
var logger *logging.Logger
|
||||||
|
var logBuffer []struct {
|
||||||
|
time string
|
||||||
|
level logging.Level
|
||||||
|
log string
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitLogger(logging.INFO)
|
InitLogger(logging.INFO)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLogger(level logging.Level) {
|
func InitLogger(level logging.Level) {
|
||||||
format := logging.MustStringFormatter(
|
|
||||||
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
|
||||||
)
|
|
||||||
newLogger := logging.MustGetLogger("x-ui")
|
newLogger := logging.MustGetLogger("x-ui")
|
||||||
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
var err error
|
||||||
|
var backend logging.Backend
|
||||||
|
var format logging.Formatter
|
||||||
|
ppid := os.Getppid()
|
||||||
|
|
||||||
|
if ppid == 1 {
|
||||||
|
backend, err = logging.NewSyslogBackend("")
|
||||||
|
format = logging.MustStringFormatter(
|
||||||
|
`%{level} - %{message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err != nil || ppid != 1 {
|
||||||
|
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
|
format = logging.MustStringFormatter(
|
||||||
|
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||||
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
||||||
backendLeveled.SetLevel(level, "")
|
backendLeveled.SetLevel(level, "x-ui")
|
||||||
newLogger.SetBackend(backendLeveled)
|
newLogger.SetBackend(backendLeveled)
|
||||||
|
|
||||||
logger = newLogger
|
logger = newLogger
|
||||||
@@ -28,32 +49,70 @@ func InitLogger(level logging.Level) {
|
|||||||
|
|
||||||
func Debug(args ...interface{}) {
|
func Debug(args ...interface{}) {
|
||||||
logger.Debug(args...)
|
logger.Debug(args...)
|
||||||
|
addToBuffer("DEBUG", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, args ...interface{}) {
|
func Debugf(format string, args ...interface{}) {
|
||||||
logger.Debugf(format, args...)
|
logger.Debugf(format, args...)
|
||||||
|
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info(args ...interface{}) {
|
func Info(args ...interface{}) {
|
||||||
logger.Info(args...)
|
logger.Info(args...)
|
||||||
|
addToBuffer("INFO", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, args ...interface{}) {
|
func Infof(format string, args ...interface{}) {
|
||||||
logger.Infof(format, args...)
|
logger.Infof(format, args...)
|
||||||
|
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warning(args ...interface{}) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
|
addToBuffer("WARNING", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warningf(format string, args ...interface{}) {
|
func Warningf(format string, args ...interface{}) {
|
||||||
logger.Warningf(format, args...)
|
logger.Warningf(format, args...)
|
||||||
|
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(args ...interface{}) {
|
func Error(args ...interface{}) {
|
||||||
logger.Error(args...)
|
logger.Error(args...)
|
||||||
|
addToBuffer("ERROR", fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(format string, args ...interface{}) {
|
func Errorf(format string, args ...interface{}) {
|
||||||
logger.Errorf(format, args...)
|
logger.Errorf(format, args...)
|
||||||
|
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func addToBuffer(level string, newLog string) {
|
||||||
|
t := time.Now()
|
||||||
|
if len(logBuffer) >= 10240 {
|
||||||
|
logBuffer = logBuffer[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevel, _ := logging.LogLevel(level)
|
||||||
|
logBuffer = append(logBuffer, struct {
|
||||||
|
time string
|
||||||
|
level logging.Level
|
||||||
|
log string
|
||||||
|
}{
|
||||||
|
time: t.Format("2006/01/02 15:04:05"),
|
||||||
|
level: logLevel,
|
||||||
|
log: newLog,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLogs(c int, level string) []string {
|
||||||
|
var output []string
|
||||||
|
logLevel, _ := logging.LogLevel(level)
|
||||||
|
|
||||||
|
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
|
||||||
|
if logBuffer[i].level <= logLevel {
|
||||||
|
output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|||||||
49
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/v2ui"
|
"x-ui/sub"
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
@@ -27,6 +27,8 @@ 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.Notice:
|
||||||
|
logger.InitLogger(logging.NOTICE)
|
||||||
case config.Warn:
|
case config.Warn:
|
||||||
logger.InitLogger(logging.WARNING)
|
logger.InitLogger(logging.WARNING)
|
||||||
case config.Error:
|
case config.Error:
|
||||||
@@ -50,6 +52,16 @@ func runWebServer() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subServer *sub.Server
|
||||||
|
subServer = sub.NewServer()
|
||||||
|
global.SetSubServer(subServer)
|
||||||
|
|
||||||
|
err = subServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
// Trap shutdown signals
|
// Trap shutdown signals
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
||||||
@@ -62,6 +74,11 @@ func runWebServer() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("stop server err:", err)
|
logger.Warning("stop server err:", err)
|
||||||
}
|
}
|
||||||
|
err = subServer.Stop()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("stop server err:", err)
|
||||||
|
}
|
||||||
|
|
||||||
server = web.NewServer()
|
server = web.NewServer()
|
||||||
global.SetWebServer(server)
|
global.SetWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
@@ -69,8 +86,18 @@ func runWebServer() {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subServer = sub.NewServer()
|
||||||
|
global.SetSubServer(subServer)
|
||||||
|
|
||||||
|
err = subServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
server.Stop()
|
server.Stop()
|
||||||
|
subServer.Stop()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +160,6 @@ func updateTgbotEnableSts(status bool) {
|
|||||||
logger.Infof("SetTgbotenabled[%v] success", status)
|
logger.Infof("SetTgbotenabled[%v] success", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
||||||
@@ -245,10 +271,6 @@ func main() {
|
|||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
|
||||||
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
|
||||||
var dbPath string
|
|
||||||
v2uiCmd.StringVar(&dbPath, "db", fmt.Sprintf("%s/v2-ui.db", config.GetDBFolderPath()), "set v2-ui db file path")
|
|
||||||
|
|
||||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
@@ -276,7 +298,6 @@ func main() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run run web panel")
|
||||||
fmt.Println(" v2-ui migrate form v2-ui")
|
|
||||||
fmt.Println(" migrate migrate form other/old x-ui")
|
fmt.Println(" migrate migrate form other/old x-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
@@ -297,16 +318,6 @@ func main() {
|
|||||||
runWebServer()
|
runWebServer()
|
||||||
case "migrate":
|
case "migrate":
|
||||||
migrateDb()
|
migrateDb()
|
||||||
case "v2-ui":
|
|
||||||
err := v2uiCmd.Parse(os.Args[2:])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = v2ui.MigrateFromV2UI(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("migrate from v2-ui failed:", err)
|
|
||||||
}
|
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -331,12 +342,10 @@ func main() {
|
|||||||
updateTgbotEnableSts(enabletgbot)
|
updateTgbotEnableSts(enabletgbot)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
fmt.Println("except 'run' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
v2uiCmd.Usage()
|
|
||||||
fmt.Println()
|
|
||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
media/1.png
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 54 KiB |
BIN
media/2.png
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 26 KiB |
BIN
media/3.png
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 70 KiB |
BIN
media/4.png
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 36 KiB |
BIN
media/5.png
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 54 KiB |
BIN
media/botfather.png
Normal file
|
After Width: | Height: | Size: 544 KiB |
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -54,35 +58,41 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:category-ads-all",
|
"geosite:category-ads-all",
|
||||||
"geosite:category-ads",
|
"ext:iran.dat:ads"
|
||||||
"geosite:google-ads",
|
|
||||||
"geosite:spotify-ads"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "IPv4",
|
"outboundTag": "IPv4",
|
||||||
"domain": ["geosite:google"]
|
"domain": [
|
||||||
|
"geosite:google"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -59,40 +63,44 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:category-ads-all",
|
"geosite:category-ads-all",
|
||||||
"geosite:category-ads",
|
"ext:iran.dat:ads"
|
||||||
"geosite:google-ads",
|
|
||||||
"geosite:spotify-ads"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "WARP",
|
"outboundTag": "WARP",
|
||||||
"domain": [
|
"domain": [
|
||||||
"geosite:google",
|
|
||||||
"geosite:netflix",
|
|
||||||
"geosite:spotify",
|
"geosite:spotify",
|
||||||
"geosite:openai"
|
"geosite:netflix",
|
||||||
|
"geosite:openai",
|
||||||
|
"geosite:google"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -47,18 +51,24 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
@@ -73,4 +83,4 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -47,30 +51,27 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
},
|
"bittorrent"
|
||||||
{
|
]
|
||||||
"type": "field",
|
|
||||||
"outboundTag": "blocked",
|
|
||||||
"ip": ["geoip:private"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "field",
|
|
||||||
"outboundTag": "blocked",
|
|
||||||
"ip": ["geoip:ir"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
BIN
media/newbot.png
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
media/panel-bot-config.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
media/token.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
media/user-id.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
162
sub/sub.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package sub
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/middleware"
|
||||||
|
"x-ui/web/network"
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
httpServer *http.Server
|
||||||
|
listener net.Listener
|
||||||
|
|
||||||
|
sub *SUBController
|
||||||
|
settingService service.SettingService
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() *Server {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
return &Server{
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) initRouter() (*gin.Engine, error) {
|
||||||
|
if config.IsDebug() {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
} else {
|
||||||
|
gin.DefaultWriter = io.Discard
|
||||||
|
gin.DefaultErrorWriter = io.Discard
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
engine := gin.Default()
|
||||||
|
|
||||||
|
subPath, err := s.settingService.GetSubPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subDomain, err := s.settingService.GetSubDomain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if subDomain != "" {
|
||||||
|
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||||
|
}
|
||||||
|
|
||||||
|
g := engine.Group(subPath)
|
||||||
|
|
||||||
|
s.sub = NewSUBController(g)
|
||||||
|
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() (err error) {
|
||||||
|
//This is an anonymous function, no function name
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
s.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
subEnable, err := s.settingService.GetSubEnable()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !subEnable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
engine, err := s.initRouter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certFile, err := s.settingService.GetSubCertFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keyFile, err := s.settingService.GetSubKeyFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listen, err := s.settingService.GetSubListen()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
port, err := s.settingService.GetSubPort()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
||||||
|
listener, err := net.Listen("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||||
|
if err != nil {
|
||||||
|
listener.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{cert},
|
||||||
|
}
|
||||||
|
listener = network.NewAutoHttpsListener(listener)
|
||||||
|
listener = tls.NewListener(listener, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if certFile != "" || keyFile != "" {
|
||||||
|
logger.Info("Sub server run https on", listener.Addr())
|
||||||
|
} else {
|
||||||
|
logger.Info("Sub server run http on", listener.Addr())
|
||||||
|
}
|
||||||
|
s.listener = listener
|
||||||
|
|
||||||
|
s.httpServer = &http.Server{
|
||||||
|
Handler: engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s.httpServer.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() error {
|
||||||
|
s.cancel()
|
||||||
|
|
||||||
|
var err1 error
|
||||||
|
var err2 error
|
||||||
|
if s.httpServer != nil {
|
||||||
|
err1 = s.httpServer.Shutdown(s.ctx)
|
||||||
|
}
|
||||||
|
if s.listener != nil {
|
||||||
|
err2 = s.listener.Close()
|
||||||
|
}
|
||||||
|
return common.Combine(err1, err2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) GetCtx() context.Context {
|
||||||
|
return s.ctx
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package controller
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
@@ -9,9 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SUBController struct {
|
type SUBController struct {
|
||||||
BaseController
|
subService SubService
|
||||||
|
settingService service.SettingService
|
||||||
subService service.SubService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||||
@@ -21,15 +20,17 @@ func NewSUBController(g *gin.RouterGroup) *SUBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/sub")
|
g = g.Group("/")
|
||||||
|
|
||||||
g.GET("/:subid", a.subs)
|
g.GET("/:subid", a.subs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
func (a *SUBController) subs(c *gin.Context) {
|
||||||
|
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
||||||
|
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
||||||
subId := c.Param("subid")
|
subId := c.Param("subid")
|
||||||
host := strings.Split(c.Request.Host, ":")[0]
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
subs, header, err := a.subService.GetSubs(subId, host)
|
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
||||||
if err != nil || len(subs) == 0 {
|
if err != nil || len(subs) == 0 {
|
||||||
c.String(400, "Error!")
|
c.String(400, "Error!")
|
||||||
} else {
|
} else {
|
||||||
@@ -38,9 +39,15 @@ func (a *SUBController) subs(c *gin.Context) {
|
|||||||
result += sub + "\n"
|
result += sub + "\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add subscription-userinfo
|
// Add headers
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
c.Writer.Header().Set("Subscription-Userinfo", headers[0])
|
||||||
|
c.Writer.Header().Set("Profile-Update-Interval", headers[1])
|
||||||
|
c.Writer.Header().Set("Profile-Title", headers[2])
|
||||||
|
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
if subEncrypt {
|
||||||
|
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||||
|
} else {
|
||||||
|
c.String(200, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,42 +1,62 @@
|
|||||||
package service
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/service"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
"github.com/goccy/go-json"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SubService struct {
|
type SubService struct {
|
||||||
address string
|
address string
|
||||||
inboundService InboundService
|
showInfo bool
|
||||||
|
inboundService service.InboundService
|
||||||
|
settingServics service.SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||||
s.address = host
|
s.address = host
|
||||||
|
s.showInfo = showInfo
|
||||||
var result []string
|
var result []string
|
||||||
var header string
|
var headers []string
|
||||||
var traffic xray.ClientTraffic
|
var traffic xray.ClientTraffic
|
||||||
var clientTraffics []xray.ClientTraffic
|
var clientTraffics []xray.ClientTraffic
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
inbounds, err := s.getInboundsBySubId(subId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
for _, inbound := range inbounds {
|
for _, inbound := range inbounds {
|
||||||
clients, err := s.inboundService.getClients(inbound)
|
clients, err := s.inboundService.GetClients(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
||||||
}
|
}
|
||||||
if clients == nil {
|
if clients == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||||
|
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
|
||||||
|
if err == nil {
|
||||||
|
inbound.Listen = fallbackMaster.Listen
|
||||||
|
inbound.Port = fallbackMaster.Port
|
||||||
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
var masterStream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(fallbackMaster.StreamSettings), &masterStream)
|
||||||
|
stream["security"] = masterStream["security"]
|
||||||
|
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||||
|
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||||
|
inbound.StreamSettings = string(modifiedStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Enable && client.SubID == subId {
|
if client.Enable && client.SubID == subId {
|
||||||
link := s.getLink(inbound, client.Email)
|
link := s.getLink(inbound, client.Email)
|
||||||
@@ -66,15 +86,18 @@ func (s *SubService) GetSubs(subId string, host string) ([]string, string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||||
return result, header, nil
|
updateInterval, _ := s.settingServics.GetSubUpdates()
|
||||||
|
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||||
|
headers = append(headers, subId)
|
||||||
|
return result, headers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds []*model.Inbound
|
var inbounds []*model.Inbound
|
||||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Preload("ClientStats").Where("settings like ? and enable = ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId), true).Find(&inbounds).Error
|
||||||
if err != nil && err != gorm.ErrRecordNotFound {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
@@ -89,6 +112,19 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
|||||||
return xray.ClientTraffic{}
|
return xray.ClientTraffic{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||||
|
db := database.GetDB()
|
||||||
|
var inbound *model.Inbound
|
||||||
|
err := db.Model(model.Inbound{}).
|
||||||
|
Where("JSON_TYPE(settings, '$.fallbacks') = 'array'").
|
||||||
|
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
||||||
|
Find(&inbound).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||||
switch inbound.Protocol {
|
switch inbound.Protocol {
|
||||||
case "vmess":
|
case "vmess":
|
||||||
@@ -107,10 +143,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if inbound.Protocol != model.VMess {
|
if inbound.Protocol != model.VMess {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
|
||||||
obj := map[string]interface{}{
|
obj := map[string]interface{}{
|
||||||
"v": "2",
|
"v": "2",
|
||||||
"ps": remark,
|
|
||||||
"add": s.address,
|
"add": s.address,
|
||||||
"port": inbound.Port,
|
"port": inbound.Port,
|
||||||
"type": "none",
|
"type": "none",
|
||||||
@@ -162,6 +196,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
obj["tls"] = security
|
obj["tls"] = security
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -184,6 +219,9 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
||||||
obj["allowInsecure"], _ = insecure.(bool)
|
obj["allowInsecure"], _ = insecure.(bool)
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
if serverName != "" {
|
if serverName != "" {
|
||||||
@@ -191,7 +229,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -200,7 +238,23 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj["id"] = clients[clientIndex].ID
|
obj["id"] = clients[clientIndex].ID
|
||||||
obj["aid"] = clients[clientIndex].AlterIds
|
|
||||||
|
if len(domains) > 0 {
|
||||||
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string))
|
||||||
|
obj["add"] = domain["domain"].(string)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
obj["ps"] = s.genRemark(inbound, email, "")
|
||||||
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
||||||
@@ -213,7 +267,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
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)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -269,6 +323,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -293,6 +348,9 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||||
@@ -326,6 +384,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
||||||
|
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
||||||
|
params["spx"] = spx
|
||||||
|
}
|
||||||
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
||||||
address = sname
|
address = sname
|
||||||
@@ -375,6 +438,10 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
|
params["security"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
q := url.Query()
|
q := url.Query()
|
||||||
@@ -386,8 +453,21 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
if len(domains) > 0 {
|
||||||
url.Fragment = remark
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
||||||
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +478,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
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)
|
||||||
clientIndex := -1
|
clientIndex := -1
|
||||||
for i, client := range clients {
|
for i, client := range clients {
|
||||||
if client.Email == email {
|
if client.Email == email {
|
||||||
@@ -454,6 +534,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
security, _ := stream["security"].(string)
|
||||||
|
var domains []interface{}
|
||||||
if security == "tls" {
|
if security == "tls" {
|
||||||
params["security"] = "tls"
|
params["security"] = "tls"
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
tlsSetting, _ := stream["tlsSettings"].(map[string]interface{})
|
||||||
@@ -478,6 +559,9 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["allowInsecure"] = "1"
|
params["allowInsecure"] = "1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if domainSettings, ok := searchKey(tlsSettings, "domains"); ok {
|
||||||
|
domains, _ = domainSettings.([]interface{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverName, _ := tlsSetting["serverName"].(string)
|
serverName, _ := tlsSetting["serverName"].(string)
|
||||||
@@ -498,7 +582,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||||
params["pbk"], _ = pbkValue.(string)
|
params["pbk"], _ = pbkValue.(string)
|
||||||
}
|
}
|
||||||
if sidValue, ok := searchKey(realitySettings, "shortIds"); ok {
|
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||||
shortIds, _ := sidValue.([]interface{})
|
shortIds, _ := sidValue.([]interface{})
|
||||||
params["sid"], _ = shortIds[0].(string)
|
params["sid"], _ = shortIds[0].(string)
|
||||||
}
|
}
|
||||||
@@ -507,6 +591,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
params["fp"] = fp
|
params["fp"] = fp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
||||||
|
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
||||||
|
params["spx"] = spx
|
||||||
|
}
|
||||||
|
}
|
||||||
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
if serverName, ok := searchKey(realitySettings, "serverName"); ok {
|
||||||
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
if sname, ok := serverName.(string); ok && len(sname) > 0 {
|
||||||
address = sname
|
address = sname
|
||||||
@@ -556,6 +645,10 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if security != "tls" && security != "reality" && security != "xtls" {
|
||||||
|
params["security"] = "none"
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
url, _ := url.Parse(link)
|
||||||
@@ -568,8 +661,21 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
|||||||
// Set the new query values on the URL
|
// Set the new query values on the URL
|
||||||
url.RawQuery = q.Encode()
|
url.RawQuery = q.Encode()
|
||||||
|
|
||||||
remark := fmt.Sprintf("%s-%s", inbound.Remark, email)
|
if len(domains) > 0 {
|
||||||
url.Fragment = remark
|
links := ""
|
||||||
|
for index, d := range domains {
|
||||||
|
domain := d.(map[string]interface{})
|
||||||
|
url.Fragment = s.genRemark(inbound, email, domain["remark"].(string))
|
||||||
|
url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port)
|
||||||
|
if index > 0 {
|
||||||
|
links += "\n"
|
||||||
|
}
|
||||||
|
links += url.String()
|
||||||
|
}
|
||||||
|
return links
|
||||||
|
}
|
||||||
|
|
||||||
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
return url.String()
|
return url.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,7 +684,9 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
if inbound.Protocol != model.Shadowsocks {
|
if inbound.Protocol != model.Shadowsocks {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
clients, _ := s.inboundService.getClients(inbound)
|
var stream map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
||||||
|
clients, _ := s.inboundService.GetClients(inbound)
|
||||||
|
|
||||||
var settings map[string]interface{}
|
var settings map[string]interface{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
@@ -591,8 +699,112 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
streamNetwork := stream["network"].(string)
|
||||||
return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, clients[clientIndex].Email)
|
params := make(map[string]string)
|
||||||
|
params["type"] = streamNetwork
|
||||||
|
|
||||||
|
switch streamNetwork {
|
||||||
|
case "tcp":
|
||||||
|
tcp, _ := stream["tcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := tcp["header"].(map[string]interface{})
|
||||||
|
typeStr, _ := header["type"].(string)
|
||||||
|
if typeStr == "http" {
|
||||||
|
request := header["request"].(map[string]interface{})
|
||||||
|
requestPath, _ := request["path"].([]interface{})
|
||||||
|
params["path"] = requestPath[0].(string)
|
||||||
|
headers, _ := request["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
params["headerType"] = "http"
|
||||||
|
}
|
||||||
|
case "kcp":
|
||||||
|
kcp, _ := stream["kcpSettings"].(map[string]interface{})
|
||||||
|
header, _ := kcp["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
params["seed"] = kcp["seed"].(string)
|
||||||
|
case "ws":
|
||||||
|
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||||
|
params["path"] = ws["path"].(string)
|
||||||
|
headers, _ := ws["headers"].(map[string]interface{})
|
||||||
|
params["host"] = searchHost(headers)
|
||||||
|
case "http":
|
||||||
|
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||||
|
params["path"] = http["path"].(string)
|
||||||
|
params["host"] = searchHost(http)
|
||||||
|
case "quic":
|
||||||
|
quic, _ := stream["quicSettings"].(map[string]interface{})
|
||||||
|
params["quicSecurity"] = quic["security"].(string)
|
||||||
|
params["key"] = quic["key"].(string)
|
||||||
|
header := quic["header"].(map[string]interface{})
|
||||||
|
params["headerType"] = header["type"].(string)
|
||||||
|
case "grpc":
|
||||||
|
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||||
|
params["serviceName"] = grpc["serviceName"].(string)
|
||||||
|
if grpc["multiMode"].(bool) {
|
||||||
|
params["mode"] = "multi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
||||||
|
if method[0] == '2' {
|
||||||
|
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
||||||
|
}
|
||||||
|
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
||||||
|
url, _ := url.Parse(link)
|
||||||
|
q := url.Query()
|
||||||
|
|
||||||
|
for k, v := range params {
|
||||||
|
q.Add(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the new query values on the URL
|
||||||
|
url.RawQuery = q.Encode()
|
||||||
|
url.Fragment = s.genRemark(inbound, email, "")
|
||||||
|
return url.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
||||||
|
var remark []string
|
||||||
|
if len(email) > 0 {
|
||||||
|
if len(inbound.Remark) > 0 {
|
||||||
|
remark = append(remark, inbound.Remark)
|
||||||
|
}
|
||||||
|
remark = append(remark, email)
|
||||||
|
if len(extra) > 0 {
|
||||||
|
remark = append(remark, extra)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return inbound.Remark
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.showInfo {
|
||||||
|
statsExist := false
|
||||||
|
var stats xray.ClientTraffic
|
||||||
|
for _, clientStat := range inbound.ClientStats {
|
||||||
|
if clientStat.Email == email {
|
||||||
|
stats = clientStat
|
||||||
|
statsExist = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get remained days
|
||||||
|
if statsExist {
|
||||||
|
if !stats.Enable {
|
||||||
|
return fmt.Sprintf("⛔️N/A-%s", strings.Join(remark, "-"))
|
||||||
|
}
|
||||||
|
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
||||||
|
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
||||||
|
}
|
||||||
|
now := time.Now().Unix()
|
||||||
|
switch exp := stats.ExpiryTime / 1000; {
|
||||||
|
case exp > 0:
|
||||||
|
remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days"))
|
||||||
|
case exp < 0:
|
||||||
|
remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(remark, " : ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchKey(data interface{}, key string) (interface{}, bool) {
|
func searchKey(data interface{}, key string) (interface{}, bool) {
|
||||||
@@ -24,8 +24,8 @@ func getLinesNum(filename string) (int, error) {
|
|||||||
|
|
||||||
var buffPosition int
|
var buffPosition int
|
||||||
for {
|
for {
|
||||||
i := bytes.IndexByte(buf[buffPosition:], '\n')
|
i := bytes.IndexByte(buf[buffPosition:n], '\n')
|
||||||
if i < 0 || n == buffPosition {
|
if i < 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
buffPosition += i + 1
|
buffPosition += i + 1
|
||||||
@@ -33,11 +33,12 @@ func getLinesNum(filename string) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
return sum, nil
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return sum, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return sum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
@@ -45,11 +46,11 @@ func GetTCPCount() (int, error) {
|
|||||||
|
|
||||||
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
|
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tcp4, err
|
return 0, err
|
||||||
}
|
}
|
||||||
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
|
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tcp4 + tcp6, nil
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcp4 + tcp6, nil
|
return tcp4 + tcp6, nil
|
||||||
@@ -60,11 +61,11 @@ func GetUDPCount() (int, error) {
|
|||||||
|
|
||||||
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
|
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return udp4, err
|
return 0, err
|
||||||
}
|
}
|
||||||
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
|
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return udp4 + udp6, nil
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return udp4 + udp6, nil
|
return udp4 + udp6, nil
|
||||||
|
|||||||
@@ -4,21 +4,27 @@
|
|||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v3/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetConnectionCount(proto string) (int, error) {
|
||||||
stats, err := net.Connections("tcp")
|
if proto != "tcp" && proto != "udp" {
|
||||||
|
return 0, errors.New("invalid protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := net.Connections(proto)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(stats), nil
|
return len(stats), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUDPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
stats, err := net.Connections("udp")
|
return GetConnectionCount("tcp")
|
||||||
if err != nil {
|
}
|
||||||
return 0, err
|
|
||||||
}
|
func GetUDPCount() (int, error) {
|
||||||
return len(stats), nil
|
return GetConnectionCount("udp")
|
||||||
}
|
}
|
||||||
|
|||||||
28
v2ui/db.go
@@ -1,28 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var v2db *gorm.DB
|
|
||||||
|
|
||||||
func initDB(dbPath string) error {
|
|
||||||
c := &gorm.Config{
|
|
||||||
Logger: logger.Discard,
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
v2db, err = gorm.Open(sqlite.Open(dbPath), c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getV2Inbounds() ([]*V2Inbound, error) {
|
|
||||||
inbounds := make([]*V2Inbound, 0)
|
|
||||||
err := v2db.Model(V2Inbound{}).Find(&inbounds).Error
|
|
||||||
return inbounds, err
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import "x-ui/database/model"
|
|
||||||
|
|
||||||
type V2Inbound struct {
|
|
||||||
Id int `gorm:"primaryKey;autoIncrement"`
|
|
||||||
Port int `gorm:"unique"`
|
|
||||||
Listen string
|
|
||||||
Protocol string
|
|
||||||
Settings string
|
|
||||||
StreamSettings string
|
|
||||||
Tag string `gorm:"unique"`
|
|
||||||
Sniffing string
|
|
||||||
Remark string
|
|
||||||
Up int64
|
|
||||||
Down int64
|
|
||||||
Enable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *V2Inbound) TableName() string {
|
|
||||||
return "inbound"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *V2Inbound) ToInbound(userId int) *model.Inbound {
|
|
||||||
return &model.Inbound{
|
|
||||||
UserId: userId,
|
|
||||||
Up: i.Up,
|
|
||||||
Down: i.Down,
|
|
||||||
Total: 0,
|
|
||||||
Remark: i.Remark,
|
|
||||||
Enable: i.Enable,
|
|
||||||
ExpiryTime: 0,
|
|
||||||
Listen: i.Listen,
|
|
||||||
Port: i.Port,
|
|
||||||
Protocol: model.Protocol(i.Protocol),
|
|
||||||
Settings: i.Settings,
|
|
||||||
StreamSettings: i.StreamSettings,
|
|
||||||
Tag: i.Tag,
|
|
||||||
Sniffing: i.Sniffing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
v2ui/v2ui.go
@@ -1,51 +0,0 @@
|
|||||||
package v2ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"x-ui/config"
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/util/common"
|
|
||||||
"x-ui/web/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func MigrateFromV2UI(dbPath string) error {
|
|
||||||
err := initDB(dbPath)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("init v2-ui database failed:", err)
|
|
||||||
}
|
|
||||||
err = database.InitDB(config.GetDBPath())
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("init x-ui database failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
v2Inbounds, err := getV2Inbounds()
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("get v2-ui inbounds failed:", err)
|
|
||||||
}
|
|
||||||
if len(v2Inbounds) == 0 {
|
|
||||||
fmt.Println("migrate v2-ui inbounds success: 0")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
userService := service.UserService{}
|
|
||||||
user, err := userService.GetFirstUser()
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("get x-ui user failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
inbounds := make([]*model.Inbound, 0)
|
|
||||||
for _, v2inbound := range v2Inbounds {
|
|
||||||
inbounds = append(inbounds, v2inbound.ToInbound(user.Id))
|
|
||||||
}
|
|
||||||
|
|
||||||
inboundService := service.InboundService{}
|
|
||||||
err = inboundService.AddInbounds(inbounds)
|
|
||||||
if err != nil {
|
|
||||||
return common.NewError("add x-ui inbounds failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("migrate v2-ui inbounds success:", len(inbounds))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
11
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
@@ -529,7 +529,7 @@ to{transform:scaleY(0);transform-origin:0 0;opacity:0}
|
|||||||
.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
.ant-select-selection-selected-value{float:left;max-width:100%;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
||||||
.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}
|
.ant-select-no-arrow .ant-select-selection-selected-value{padding-right:0}
|
||||||
.ant-select-disabled{color:rgba(0,0,0,.25)}
|
.ant-select-disabled{color:rgba(0,0,0,.25)}
|
||||||
.ant-select-disabled .ant-select-selection{background:rgb(0 150 112);cursor:not-allowed}
|
.ant-select-disabled .ant-select-selection{background:rgb(221, 221, 221);cursor:not-allowed}
|
||||||
.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;box-shadow:none}
|
.ant-select-disabled .ant-select-selection:active,.ant-select-disabled .ant-select-selection:focus,.ant-select-disabled .ant-select-selection:hover{border-color:#d9d9d9;box-shadow:none}
|
||||||
.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}
|
.ant-select-disabled .ant-select-selection__clear{display:none;visibility:hidden;pointer-events:none}
|
||||||
.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}
|
.ant-select-disabled .ant-select-selection--multiple .ant-select-selection__choice{padding-right:10px;color:rgba(0,0,0,.33);background:#f5f5f5}
|
||||||
@@ -996,7 +996,8 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}
|
.ant-menu-horizontal .ant-menu-item,.ant-menu-horizontal .ant-menu-submenu{margin-top:-1px}
|
||||||
.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}
|
.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu .ant-menu-submenu-title:hover{background-color:transparent}
|
||||||
.ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#1890ff}
|
.ant-menu-item-selected,.ant-menu-item-selected>a,.ant-menu-item-selected>a:hover{color:#1890ff}
|
||||||
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background: linear-gradient(90deg,#009670 0,#026247 100%);color: #fff;border-radius: 0.5rem}
|
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected{background-color: #0a7557;background-image: linear-gradient( 270deg, rgba(123, 199, 77, 0) 30%, #00ab80, rgba(123, 199, 77, 0) 100% );background-repeat: no-repeat;animation: ma-bg-move linear 6.6s infinite;/*background: linear-gradient(90deg,#009670 0,#026247 100%);*/color: #fff;border-radius: 0.5rem}
|
||||||
|
@-webkit-keyframes ma-bg-move {0% {background-position: -500px 0;}100% {background-position: 1000px 0;}}@keyframes ma-bg-move {0% {background-position: -500px 0;}50% {background-position: 1000px 0;}100% {background-position: 1000px 0;}}
|
||||||
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
.ant-menu-vertical-right{border-left:1px solid #e8e8e8}
|
||||||
.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}
|
.ant-menu-vertical-left.ant-menu-sub,.ant-menu-vertical-right.ant-menu-sub,.ant-menu-vertical.ant-menu-sub{min-width:160px;padding:0;border-right:0;transform-origin:0 0}
|
||||||
.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}
|
.ant-menu-vertical-left.ant-menu-sub .ant-menu-item,.ant-menu-vertical-right.ant-menu-sub .ant-menu-item,.ant-menu-vertical.ant-menu-sub .ant-menu-item{left:0;margin-left:0;border-right:0}
|
||||||
@@ -1081,8 +1082,8 @@ to{transform:scale(0) translate(50%,-50%);opacity:0}
|
|||||||
.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}
|
.ant-menu-dark .ant-menu-item-selected{color:#fff;border-right:0}
|
||||||
.ant-menu-dark .ant-menu-item-selected:after{border-right:0}
|
.ant-menu-dark .ant-menu-item-selected:after{border-right:0}
|
||||||
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#ffffff}
|
.ant-menu-dark .ant-menu-item-selected .anticon,.ant-menu-dark .ant-menu-item-selected .anticon+span,.ant-menu-dark .ant-menu-item-selected>a,.ant-menu-dark .ant-menu-item-selected>a:hover{color:#ffffff}
|
||||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#15223a}
|
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected,.ant-menu.ant-menu-dark .ant-menu-item-selected{background-color:#0a7557}
|
||||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-active,.ant-menu.ant-menu-dark .ant-menu-item-active{background-color:#38383800}
|
/*.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-active,.ant-menu.ant-menu-dark .ant-menu-item-active{background-color:#0a7557}*/
|
||||||
.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}
|
.ant-menu-dark .ant-menu-item-disabled,.ant-menu-dark .ant-menu-item-disabled>a,.ant-menu-dark .ant-menu-submenu-disabled,.ant-menu-dark .ant-menu-submenu-disabled>a{color:hsla(0,0%,100%,.35)!important;opacity:.8}
|
||||||
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:hsla(0,0%,100%,.35)!important}
|
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title{color:hsla(0,0%,100%,.35)!important}
|
||||||
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:hsla(0,0%,100%,.35)!important}
|
.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-item-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:after,.ant-menu-dark .ant-menu-submenu-disabled>.ant-menu-submenu-title>.ant-menu-submenu-arrow:before{background:hsla(0,0%,100%,.35)!important}
|
||||||
@@ -3231,7 +3232,7 @@ textarea.ant-input-number{max-width:100%;height:auto;min-height:32px;line-height
|
|||||||
.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}
|
.ant-input-number-handler-down-inner:before,.ant-input-number-handler-up-inner:before{display:none}
|
||||||
.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}
|
.ant-input-number-handler-down-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-down-inner .ant-input-number-handler-up-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-down-inner-icon,.ant-input-number-handler-up-inner .ant-input-number-handler-up-inner-icon{display:block}
|
||||||
.ant-input-number-focused,.ant-input-number:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important}
|
.ant-input-number-focused,.ant-input-number:hover{border-color:rgb(0, 150, 112) !important;border-right-width:1px!important}
|
||||||
.ant-input-number-focused{outline:0;box-shadow:0 0 0 2px rgba(24,144,255,.2)}
|
.ant-input-number-focused{outline:0;box-shadow:rgba(0, 150, 112, 0.2) 0px 0px 0px 2px}
|
||||||
.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
.ant-input-number-disabled{color:rgba(0,0,0,.25);background-color:#f5f5f5;cursor:not-allowed;opacity:1}
|
||||||
.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
.ant-input-number-disabled:hover{border-color:#d9d9d9;border-right-width:1px!important}
|
||||||
.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}
|
.ant-input-number-disabled .ant-input-number-input{cursor:not-allowed}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ body {
|
|||||||
|
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100vh;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -203,9 +202,25 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark:hover {
|
.ant-card-dark:hover {
|
||||||
border-color: #e8e8e8;
|
/*border-color: #e8e8e8;
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 70%);
|
animation:light-shadow ease-in 3s infinite;*/
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 80%);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
@keyframes light-shadow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
box-shadow: 0 1px 11px 2px rgb(154 175 238 / 70%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 60%);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
.ant-setting-textarea {
|
.ant-setting-textarea {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
@@ -214,6 +229,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark-box-nohover{
|
.ant-card-dark-box-nohover{
|
||||||
|
margin-top: .5rem;
|
||||||
padding: 0 20px 20px !important;
|
padding: 0 20px 20px !important;
|
||||||
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
box-shadow: 0 1px 10px -1px rgb(154 175 238 / 0%) !important;
|
||||||
}
|
}
|
||||||
@@ -246,6 +262,7 @@ body {
|
|||||||
.ant-card-dark .ant-collapse-content,
|
.ant-card-dark .ant-collapse-content,
|
||||||
.ant-card-dark .ant-calendar,
|
.ant-card-dark .ant-calendar,
|
||||||
.ant-card-dark .ant-table-placeholder,
|
.ant-card-dark .ant-table-placeholder,
|
||||||
|
.ant-card-dark .ant-select-selection__choice,
|
||||||
.ant-card-dark .ant-input-group-addon {
|
.ant-card-dark .ant-input-group-addon {
|
||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
background-color: #262f3d;
|
background-color: #262f3d;
|
||||||
@@ -273,12 +290,12 @@ body {
|
|||||||
color: hsla(0,0%,100%,.65);
|
color: hsla(0,0%,100%,.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td,
|
.ant-card-dark .ant-table-tbody>tr:hover:not(.ant-table-expanded-row):not(.ant-table-row-selected)>td {
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
|
||||||
.ant-card-dark .ant-select-dropdown-menu-item-active {
|
|
||||||
background-color: #11314d;
|
background-color: #11314d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled),
|
||||||
|
.ant-card-dark .ant-select-dropdown-menu-item-active,
|
||||||
.ant-card-dark .ant-calendar-date:hover,
|
.ant-card-dark .ant-calendar-date:hover,
|
||||||
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
.ant-card-dark li.ant-calendar-time-picker-select-option-selected {
|
||||||
background-color: rgb(4, 119, 90);
|
background-color: rgb(4, 119, 90);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const supportLangs = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'فارسی',
|
name: 'فارسی',
|
||||||
value: 'fa_IR',
|
value: 'fa-IR',
|
||||||
icon: '🇮🇷',
|
icon: '🇮🇷',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -16,9 +16,14 @@ const supportLangs = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Русский',
|
name: 'Русский',
|
||||||
value: 'ru_RU',
|
value: 'ru-RU',
|
||||||
icon: '🇷🇺',
|
icon: '🇷🇺',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Tiếng Việt',
|
||||||
|
value: 'vi-VN',
|
||||||
|
icon: '🇻🇳',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLang() {
|
function getLang() {
|
||||||
|
|||||||
@@ -153,9 +153,9 @@ class DBInbound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
genLink(clientIndex) {
|
genLink(address=this.address, remark=this.remark, clientIndex=0) {
|
||||||
const inbound = this.toInbound();
|
const inbound = this.toInbound();
|
||||||
return inbound.genLink(this.address, this.remark, clientIndex);
|
return inbound.genLink(address, remark, clientIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
get genInboundLinks() {
|
get genInboundLinks() {
|
||||||
@@ -168,6 +168,7 @@ class AllSetting {
|
|||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.webListen = "";
|
this.webListen = "";
|
||||||
|
this.webDomain = "";
|
||||||
this.webPort = 2053;
|
this.webPort = 2053;
|
||||||
this.webCertFile = "";
|
this.webCertFile = "";
|
||||||
this.webKeyFile = "";
|
this.webKeyFile = "";
|
||||||
@@ -180,9 +181,21 @@ class AllSetting {
|
|||||||
this.tgBotChatId = "";
|
this.tgBotChatId = "";
|
||||||
this.tgRunTime = "@daily";
|
this.tgRunTime = "@daily";
|
||||||
this.tgBotBackup = false;
|
this.tgBotBackup = false;
|
||||||
|
this.tgBotLoginNotify = true;
|
||||||
this.tgCpu = "";
|
this.tgCpu = "";
|
||||||
|
this.tgLang = "en-US";
|
||||||
this.xrayTemplateConfig = "";
|
this.xrayTemplateConfig = "";
|
||||||
this.secretEnable = false;
|
this.secretEnable = false;
|
||||||
|
this.subEnable = false;
|
||||||
|
this.subListen = "";
|
||||||
|
this.subPort = "2096";
|
||||||
|
this.subPath = "/sub/";
|
||||||
|
this.subDomain = "";
|
||||||
|
this.subCertFile = "";
|
||||||
|
this.subKeyFile = "";
|
||||||
|
this.subUpdates = 0;
|
||||||
|
this.subEncrypt = true;
|
||||||
|
this.subShowInfo = true;
|
||||||
|
|
||||||
this.timeLocation = "Asia/Tehran";
|
this.timeLocation = "Asia/Tehran";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ const Protocols = {
|
|||||||
TROJAN: 'trojan',
|
TROJAN: 'trojan',
|
||||||
SHADOWSOCKS: 'shadowsocks',
|
SHADOWSOCKS: 'shadowsocks',
|
||||||
DOKODEMO: 'dokodemo-door',
|
DOKODEMO: 'dokodemo-door',
|
||||||
MTPROTO: 'mtproto',
|
|
||||||
SOCKS: 'socks',
|
SOCKS: 'socks',
|
||||||
HTTP: 'http',
|
HTTP: 'http',
|
||||||
};
|
};
|
||||||
@@ -17,8 +16,15 @@ const VmessMethods = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SSMethods = {
|
const SSMethods = {
|
||||||
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
AES_256_GCM: 'aes-256-gcm',
|
||||||
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
AES_128_GCM: 'aes-128-gcm',
|
||||||
|
CHACHA20_POLY1305: 'chacha20-poly1305',
|
||||||
|
CHACHA20_IETF_POLY1305: 'chacha20-ietf-poly1305',
|
||||||
|
XCHACHA20_POLY1305: 'xchacha20-poly1305',
|
||||||
|
XCHACHA20_IETF_POLY1305: 'xchacha20-ietf-poly1305',
|
||||||
|
BLAKE3_AES_128_GCM: '2022-blake3-aes-128-gcm',
|
||||||
|
BLAKE3_AES_256_GCM: '2022-blake3-aes-256-gcm',
|
||||||
|
BLAKE3_CHACHA20_POLY1305: '2022-blake3-chacha20-poly1305',
|
||||||
};
|
};
|
||||||
|
|
||||||
const XTLS_FLOW_CONTROL = {
|
const XTLS_FLOW_CONTROL = {
|
||||||
@@ -72,15 +78,16 @@ const UTLS_FINGERPRINT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ALPN_OPTION = {
|
const ALPN_OPTION = {
|
||||||
HTTP1: "http/1.1",
|
|
||||||
H2: "h2",
|
|
||||||
H3: "h3",
|
H3: "h3",
|
||||||
|
H2: "h2",
|
||||||
|
HTTP1: "http/1.1",
|
||||||
};
|
};
|
||||||
|
|
||||||
const SNIFFING_OPTION = {
|
const SNIFFING_OPTION = {
|
||||||
HTTP: "http",
|
HTTP: "http",
|
||||||
TLS: "tls",
|
TLS: "tls",
|
||||||
QUIC: "quic",
|
QUIC: "quic",
|
||||||
|
FAKEDNS: "fakedns"
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(Protocols);
|
Object.freeze(Protocols);
|
||||||
@@ -379,7 +386,10 @@ class WsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HttpStreamSettings extends XrayCommonClass {
|
class HttpStreamSettings extends XrayCommonClass {
|
||||||
constructor(path='/', host=['']) {
|
constructor(
|
||||||
|
path='/',
|
||||||
|
host=[''],
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.host = host.length === 0 ? [''] : host;
|
this.host = host.length === 0 ? [''] : host;
|
||||||
@@ -413,7 +423,7 @@ class HttpStreamSettings extends XrayCommonClass {
|
|||||||
|
|
||||||
class QuicStreamSettings extends XrayCommonClass {
|
class QuicStreamSettings extends XrayCommonClass {
|
||||||
constructor(security=VmessMethods.NONE,
|
constructor(security=VmessMethods.NONE,
|
||||||
key='', type='none') {
|
key=RandomUtil.randomSeq(10), type='none') {
|
||||||
super();
|
super();
|
||||||
this.security = security;
|
this.security = security;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
@@ -442,7 +452,7 @@ class QuicStreamSettings extends XrayCommonClass {
|
|||||||
class GrpcStreamSettings extends XrayCommonClass {
|
class GrpcStreamSettings extends XrayCommonClass {
|
||||||
constructor(
|
constructor(
|
||||||
serviceName="",
|
serviceName="",
|
||||||
multiMode=false
|
multiMode=false,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.serviceName = serviceName;
|
this.serviceName = serviceName;
|
||||||
@@ -459,31 +469,34 @@ class GrpcStreamSettings extends XrayCommonClass {
|
|||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
serviceName: this.serviceName,
|
serviceName: this.serviceName,
|
||||||
multiMode: this.multiMode
|
multiMode: this.multiMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TlsStreamSettings extends XrayCommonClass {
|
class TlsStreamSettings extends XrayCommonClass {
|
||||||
constructor(serverName='',
|
constructor(serverName='',
|
||||||
minVersion = TLS_VERSION_OPTION.TLS12,
|
minVersion = TLS_VERSION_OPTION.TLS12,
|
||||||
maxVersion = TLS_VERSION_OPTION.TLS13,
|
maxVersion = TLS_VERSION_OPTION.TLS13,
|
||||||
cipherSuites = '',
|
cipherSuites = '',
|
||||||
|
rejectUnknownSni = false,
|
||||||
certificates=[new TlsStreamSettings.Cert()],
|
certificates=[new TlsStreamSettings.Cert()],
|
||||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
alpn=[ALPN_OPTION.HTTP1,ALPN_OPTION.H2],
|
||||||
settings=new TlsStreamSettings.Settings()) {
|
settings=new TlsStreamSettings.Settings()) {
|
||||||
super();
|
super();
|
||||||
this.server = serverName;
|
this.server = serverName;
|
||||||
this.minVersion = minVersion;
|
this.minVersion = minVersion;
|
||||||
this.maxVersion = maxVersion;
|
this.maxVersion = maxVersion;
|
||||||
this.cipherSuites = cipherSuites;
|
this.cipherSuites = cipherSuites;
|
||||||
|
this.rejectUnknownSni = rejectUnknownSni;
|
||||||
this.certs = certificates;
|
this.certs = certificates;
|
||||||
this.alpn = alpn;
|
this.alpn = alpn;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCert(cert) {
|
addCert() {
|
||||||
this.certs.push(cert);
|
this.certs.push(new TlsStreamSettings.Cert());
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCert(index) {
|
removeCert(index) {
|
||||||
@@ -498,13 +511,14 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName);
|
settings = new TlsStreamSettings.Settings(json.settings.allowInsecure , json.settings.fingerprint, json.settings.serverName, json.settings.domains);
|
||||||
}
|
}
|
||||||
return new TlsStreamSettings(
|
return new TlsStreamSettings(
|
||||||
json.serverName,
|
json.serverName,
|
||||||
json.minVersion,
|
json.minVersion,
|
||||||
json.maxVersion,
|
json.maxVersion,
|
||||||
json.cipherSuites,
|
json.cipherSuites,
|
||||||
|
json.rejectUnknownSni,
|
||||||
certs,
|
certs,
|
||||||
json.alpn,
|
json.alpn,
|
||||||
settings,
|
settings,
|
||||||
@@ -517,6 +531,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
minVersion: this.minVersion,
|
minVersion: this.minVersion,
|
||||||
maxVersion: this.maxVersion,
|
maxVersion: this.maxVersion,
|
||||||
cipherSuites: this.cipherSuites,
|
cipherSuites: this.cipherSuites,
|
||||||
|
rejectUnknownSni: this.rejectUnknownSni,
|
||||||
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
certificates: TlsStreamSettings.toJsonArray(this.certs),
|
||||||
alpn: this.alpn,
|
alpn: this.alpn,
|
||||||
settings: this.settings,
|
settings: this.settings,
|
||||||
@@ -525,13 +540,14 @@ class TlsStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TlsStreamSettings.Cert = class extends XrayCommonClass {
|
TlsStreamSettings.Cert = class extends XrayCommonClass {
|
||||||
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='') {
|
constructor(useFile=true, certificateFile='', keyFile='', certificate='', key='', ocspStapling=3600) {
|
||||||
super();
|
super();
|
||||||
this.useFile = useFile;
|
this.useFile = useFile;
|
||||||
this.certFile = certificateFile;
|
this.certFile = certificateFile;
|
||||||
this.keyFile = keyFile;
|
this.keyFile = keyFile;
|
||||||
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
this.cert = certificate instanceof Array ? certificate.join('\n') : certificate;
|
||||||
this.key = key instanceof Array ? key.join('\n') : key;
|
this.key = key instanceof Array ? key.join('\n') : key;
|
||||||
|
this.ocspStapling = ocspStapling;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
@@ -539,13 +555,15 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
return new TlsStreamSettings.Cert(
|
return new TlsStreamSettings.Cert(
|
||||||
true,
|
true,
|
||||||
json.certificateFile,
|
json.certificateFile,
|
||||||
json.keyFile,
|
json.keyFile, '', '',
|
||||||
|
json.ocspStapling,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new TlsStreamSettings.Cert(
|
return new TlsStreamSettings.Cert(
|
||||||
false, '', '',
|
false, '', '',
|
||||||
json.certificate.join('\n'),
|
json.certificate.join('\n'),
|
||||||
json.key.join('\n'),
|
json.key.join('\n'),
|
||||||
|
json.ocspStapling,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -555,28 +573,32 @@ TlsStreamSettings.Cert = class extends XrayCommonClass {
|
|||||||
return {
|
return {
|
||||||
certificateFile: this.certFile,
|
certificateFile: this.certFile,
|
||||||
keyFile: this.keyFile,
|
keyFile: this.keyFile,
|
||||||
|
ocspStapling: this.ocspStapling,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
certificate: this.cert.split('\n'),
|
certificate: this.cert.split('\n'),
|
||||||
key: this.key.split('\n'),
|
key: this.key.split('\n'),
|
||||||
|
ocspStapling: this.ocspStapling,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
TlsStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(allowInsecure = false, fingerprint = '', serverName = '') {
|
constructor(allowInsecure = false, fingerprint = '', serverName = '', domains = []) {
|
||||||
super();
|
super();
|
||||||
this.allowInsecure = allowInsecure;
|
this.allowInsecure = allowInsecure;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
|
this.domains = domains;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new TlsStreamSettings.Settings(
|
return new TlsStreamSettings.Settings(
|
||||||
json.allowInsecure,
|
json.allowInsecure,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.servername,
|
json.serverName,
|
||||||
|
json.domains,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -584,6 +606,7 @@ TlsStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
allowInsecure: this.allowInsecure,
|
allowInsecure: this.allowInsecure,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
|
domains: this.domains,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -599,8 +622,8 @@ class XtlsStreamSettings extends XrayCommonClass {
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
addCert(cert) {
|
addCert() {
|
||||||
this.certs.push(cert);
|
this.certs.push(new XtlsStreamSettings.Cert());
|
||||||
}
|
}
|
||||||
|
|
||||||
removeCert(index) {
|
removeCert(index) {
|
||||||
@@ -706,7 +729,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
minClient = '',
|
minClient = '',
|
||||||
maxClient = '',
|
maxClient = '',
|
||||||
maxTimediff = 0,
|
maxTimediff = 0,
|
||||||
shortIds = RandomUtil.randowShortId(),
|
shortIds = RandomUtil.randomShortId(),
|
||||||
settings= new RealityStreamSettings.Settings()
|
settings= new RealityStreamSettings.Settings()
|
||||||
){
|
){
|
||||||
super();
|
super();
|
||||||
@@ -725,7 +748,7 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
let settings;
|
let settings;
|
||||||
if (!ObjectUtil.isEmpty(json.settings)) {
|
if (!ObjectUtil.isEmpty(json.settings)) {
|
||||||
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName);
|
settings = new RealityStreamSettings.Settings(json.settings.publicKey , json.settings.fingerprint, json.settings.serverName, json.settings.spiderX);
|
||||||
}
|
}
|
||||||
return new RealityStreamSettings(
|
return new RealityStreamSettings(
|
||||||
json.show,
|
json.show,
|
||||||
@@ -758,17 +781,19 @@ class RealityStreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
RealityStreamSettings.Settings = class extends XrayCommonClass {
|
||||||
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '') {
|
constructor(publicKey = '', fingerprint = UTLS_FINGERPRINT.UTLS_FIREFOX, serverName = '', spiderX= '/') {
|
||||||
super();
|
super();
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
this.serverName = serverName;
|
this.serverName = serverName;
|
||||||
|
this.spiderX = spiderX;
|
||||||
}
|
}
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new RealityStreamSettings.Settings(
|
return new RealityStreamSettings.Settings(
|
||||||
json.publicKey,
|
json.publicKey,
|
||||||
json.fingerprint,
|
json.fingerprint,
|
||||||
json.serverName,
|
json.serverName,
|
||||||
|
json.spiderX,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
toJson() {
|
toJson() {
|
||||||
@@ -776,10 +801,39 @@ RealityStreamSettings.Settings = class extends XrayCommonClass {
|
|||||||
publicKey: this.publicKey,
|
publicKey: this.publicKey,
|
||||||
fingerprint: this.fingerprint,
|
fingerprint: this.fingerprint,
|
||||||
serverName: this.serverName,
|
serverName: this.serverName,
|
||||||
|
spiderX: this.spiderX,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SockoptStreamSettings extends XrayCommonClass {
|
||||||
|
constructor(acceptProxyProtocol = false, tcpFastOpen = false, mark = 0, tproxy="off") {
|
||||||
|
super();
|
||||||
|
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||||
|
this.tcpFastOpen = tcpFastOpen;
|
||||||
|
this.mark = mark;
|
||||||
|
this.tproxy = tproxy;
|
||||||
|
}
|
||||||
|
static fromJson(json = {}) {
|
||||||
|
if (Object.keys(json).length === 0) return undefined;
|
||||||
|
return new SockoptStreamSettings(
|
||||||
|
json.acceptProxyProtocol,
|
||||||
|
json.tcpFastOpen,
|
||||||
|
json.mark,
|
||||||
|
json.tproxy,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson() {
|
||||||
|
return {
|
||||||
|
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||||
|
tcpFastOpen: this.tcpFastOpen,
|
||||||
|
mark: this.mark,
|
||||||
|
tproxy: this.tproxy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StreamSettings extends XrayCommonClass {
|
class StreamSettings extends XrayCommonClass {
|
||||||
constructor(network='tcp',
|
constructor(network='tcp',
|
||||||
security='none',
|
security='none',
|
||||||
@@ -792,6 +846,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings=new HttpStreamSettings(),
|
httpSettings=new HttpStreamSettings(),
|
||||||
quicSettings=new QuicStreamSettings(),
|
quicSettings=new QuicStreamSettings(),
|
||||||
grpcSettings=new GrpcStreamSettings(),
|
grpcSettings=new GrpcStreamSettings(),
|
||||||
|
sockopt = undefined,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.network = network;
|
this.network = network;
|
||||||
@@ -805,6 +860,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
this.http = httpSettings;
|
this.http = httpSettings;
|
||||||
this.quic = quicSettings;
|
this.quic = quicSettings;
|
||||||
this.grpc = grpcSettings;
|
this.grpc = grpcSettings;
|
||||||
|
this.sockopt = sockopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isTls() {
|
get isTls() {
|
||||||
@@ -844,6 +900,14 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get sockoptSwitch() {
|
||||||
|
return this.sockopt != undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set sockoptSwitch(value) {
|
||||||
|
this.sockopt = value ? new SockoptStreamSettings() : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
|
|
||||||
return new StreamSettings(
|
return new StreamSettings(
|
||||||
@@ -858,6 +922,7 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
HttpStreamSettings.fromJson(json.httpSettings),
|
HttpStreamSettings.fromJson(json.httpSettings),
|
||||||
QuicStreamSettings.fromJson(json.quicSettings),
|
QuicStreamSettings.fromJson(json.quicSettings),
|
||||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||||
|
SockoptStreamSettings.fromJson(json.sockopt),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,12 +940,13 @@ class StreamSettings extends XrayCommonClass {
|
|||||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||||
|
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sniffing extends XrayCommonClass {
|
class Sniffing extends XrayCommonClass {
|
||||||
constructor(enabled=true, destOverride=['http', 'tls', 'quic']) {
|
constructor(enabled=true, destOverride=['http', 'tls', 'quic', 'fakedns']) {
|
||||||
super();
|
super();
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
this.destOverride = destOverride;
|
this.destOverride = destOverride;
|
||||||
@@ -890,7 +956,7 @@ class Sniffing extends XrayCommonClass {
|
|||||||
let destOverride = ObjectUtil.clone(json.destOverride);
|
let destOverride = ObjectUtil.clone(json.destOverride);
|
||||||
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
if (!ObjectUtil.isEmpty(destOverride) && !ObjectUtil.isArrEmpty(destOverride)) {
|
||||||
if (ObjectUtil.isEmpty(destOverride[0])) {
|
if (ObjectUtil.isEmpty(destOverride[0])) {
|
||||||
destOverride = ['http', 'tls', 'quic'];
|
destOverride = ['http', 'tls', 'quic', 'fakedns'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Sniffing(
|
return new Sniffing(
|
||||||
@@ -960,7 +1026,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//for Reality
|
|
||||||
get reality() {
|
get reality() {
|
||||||
return this.stream.security === 'reality';
|
return this.stream.security === 'reality';
|
||||||
}
|
}
|
||||||
@@ -1014,6 +1079,12 @@ class Inbound extends XrayCommonClass {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
get isSSMultiUser() {
|
||||||
|
return this.method != SSMethods.BLAKE3_CHACHA20_POLY1305;
|
||||||
|
}
|
||||||
|
get isSS2022(){
|
||||||
|
return this.method.substring(0,4) === "2022";
|
||||||
|
}
|
||||||
|
|
||||||
get serverName() {
|
get serverName() {
|
||||||
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
if (this.stream.isTls || this.stream.isXtls || this.stream.isReality) {
|
||||||
@@ -1039,7 +1110,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
} else if (this.isWs) {
|
} else if (this.isWs) {
|
||||||
return this.stream.ws.path;
|
return this.stream.ws.path;
|
||||||
} else if (this.isH2) {
|
} else if (this.isH2) {
|
||||||
return this.stream.http.path[0];
|
return this.stream.http.path;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1154,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
return this.settings.trojans[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
case Protocols.SHADOWSOCKS:
|
case Protocols.SHADOWSOCKS:
|
||||||
if(this.settings.shadowsockses[index].expiryTime > 0)
|
if(this.settings.shadowsockses.length > 0 && this.settings.shadowsockses[index].expiryTime > 0)
|
||||||
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
return this.settings.shadowsockses[index].expiryTime < new Date().getTime();
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
@@ -1164,6 +1235,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1202,7 +1274,6 @@ class Inbound extends XrayCommonClass {
|
|||||||
add: address,
|
add: address,
|
||||||
port: this.port,
|
port: this.port,
|
||||||
id: this.settings.vmesses[clientIndex].id,
|
id: this.settings.vmesses[clientIndex].id,
|
||||||
aid: this.settings.vmesses[clientIndex].alterId,
|
|
||||||
net: this.stream.network,
|
net: this.stream.network,
|
||||||
type: 'none',
|
type: 'none',
|
||||||
tls: this.stream.security,
|
tls: this.stream.security,
|
||||||
@@ -1339,7 +1410,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
else if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
@@ -1354,7 +1425,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
params.set("flow", this.settings.vlesses[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
else if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
@@ -1370,6 +1441,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
address = this.stream.reality.settings.serverName;
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
params.set("security", "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `vless://${uuid}@${address}:${port}`;
|
const link = `vless://${uuid}@${address}:${port}`;
|
||||||
@@ -1384,8 +1462,68 @@ class Inbound extends XrayCommonClass {
|
|||||||
genSSLink(address='', remark='', clientIndex = 0) {
|
genSSLink(address='', remark='', clientIndex = 0) {
|
||||||
let settings = this.settings;
|
let settings = this.settings;
|
||||||
const port = this.port;
|
const port = this.port;
|
||||||
|
const type = this.stream.network;
|
||||||
|
const params = new Map();
|
||||||
|
params.set("type", this.stream.network);
|
||||||
|
switch (type) {
|
||||||
|
case "tcp":
|
||||||
|
const tcp = this.stream.tcp;
|
||||||
|
if (tcp.type === 'http') {
|
||||||
|
const request = tcp.request;
|
||||||
|
params.set("path", request.path.join(','));
|
||||||
|
const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
const host = request.headers[index].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
|
params.set("headerType", 'http');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "kcp":
|
||||||
|
const kcp = this.stream.kcp;
|
||||||
|
params.set("headerType", kcp.type);
|
||||||
|
params.set("seed", kcp.seed);
|
||||||
|
break;
|
||||||
|
case "ws":
|
||||||
|
const ws = this.stream.ws;
|
||||||
|
params.set("path", ws.path);
|
||||||
|
const index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||||
|
if (index >= 0) {
|
||||||
|
const host = ws.headers[index].value;
|
||||||
|
params.set("host", host);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "http":
|
||||||
|
const http = this.stream.http;
|
||||||
|
params.set("path", http.path);
|
||||||
|
params.set("host", http.host);
|
||||||
|
break;
|
||||||
|
case "quic":
|
||||||
|
const quic = this.stream.quic;
|
||||||
|
params.set("quicSecurity", quic.security);
|
||||||
|
params.set("key", quic.key);
|
||||||
|
params.set("headerType", quic.type);
|
||||||
|
break;
|
||||||
|
case "grpc":
|
||||||
|
const grpc = this.stream.grpc;
|
||||||
|
params.set("serviceName", grpc.serviceName);
|
||||||
|
if(grpc.multiMode){
|
||||||
|
params.set("mode", "multi");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return 'ss://' + safeBase64(settings.method + ':' + settings.password + ':' +settings.shadowsockses[clientIndex].password) + '@' + address + ':' + this.port + '#' + encodeURIComponent(remark);
|
let password = new Array();
|
||||||
|
if (this.isSS2022) password.push(settings.password);
|
||||||
|
if (this.isSSMultiUser) password.push(settings.shadowsockses[clientIndex].password);
|
||||||
|
|
||||||
|
let link = `ss://${safeBase64(settings.method + ':' + password.join(':'))}@${address}:${this.port}`;
|
||||||
|
const url = new URL(link);
|
||||||
|
for (const [key, value] of params) {
|
||||||
|
url.searchParams.set(key, value)
|
||||||
|
}
|
||||||
|
url.hash = encodeURIComponent(remark);
|
||||||
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
genTrojanLink(address = '', remark = '', clientIndex = 0) {
|
||||||
@@ -1457,7 +1595,7 @@ class Inbound extends XrayCommonClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.reality) {
|
else if (this.reality) {
|
||||||
params.set("security", "reality");
|
params.set("security", "reality");
|
||||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||||
@@ -1470,9 +1608,12 @@ class Inbound extends XrayCommonClass {
|
|||||||
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.serverName)) {
|
||||||
address = this.stream.reality.settings.serverName;
|
address = this.stream.reality.settings.serverName;
|
||||||
}
|
}
|
||||||
|
if (!ObjectUtil.isEmpty(this.stream.reality.settings.spiderX)) {
|
||||||
|
params.set("spx", this.stream.reality.settings.spiderX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.xtls) {
|
else if (this.xtls) {
|
||||||
params.set("security", "xtls");
|
params.set("security", "xtls");
|
||||||
params.set("alpn", this.stream.xtls.alpn);
|
params.set("alpn", this.stream.xtls.alpn);
|
||||||
if(this.stream.xtls.settings.allowInsecure){
|
if(this.stream.xtls.settings.allowInsecure){
|
||||||
@@ -1487,6 +1628,10 @@ class Inbound extends XrayCommonClass {
|
|||||||
params.set("flow", this.settings.trojans[clientIndex].flow);
|
params.set("flow", this.settings.trojans[clientIndex].flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
params.set("security", "none");
|
||||||
|
}
|
||||||
|
|
||||||
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
const link = `trojan://${settings.trojans[clientIndex].password}@${address}:${this.port}`;
|
||||||
const url = new URL(link);
|
const url = new URL(link);
|
||||||
for (const [key, value] of params) {
|
for (const [key, value] of params) {
|
||||||
@@ -1498,25 +1643,13 @@ class Inbound extends XrayCommonClass {
|
|||||||
|
|
||||||
genLink(address='', remark='', clientIndex=0) {
|
genLink(address='', remark='', clientIndex=0) {
|
||||||
switch (this.protocol) {
|
switch (this.protocol) {
|
||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
if (this.settings.vmesses[clientIndex].email != ""){
|
|
||||||
remark += '-' + this.settings.vmesses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genVmessLink(address, remark, clientIndex);
|
return this.genVmessLink(address, remark, clientIndex);
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
if (this.settings.vlesses[clientIndex].email != ""){
|
|
||||||
remark += '-' + this.settings.vlesses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genVLESSLink(address, remark, clientIndex);
|
return this.genVLESSLink(address, remark, clientIndex);
|
||||||
case Protocols.SHADOWSOCKS:
|
case Protocols.SHADOWSOCKS:
|
||||||
if (this.settings.shadowsockses[clientIndex].email != ""){
|
|
||||||
remark = this.settings.shadowsockses[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genSSLink(address, remark, clientIndex);
|
return this.genSSLink(address, remark, clientIndex);
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
if (this.settings.trojans[clientIndex].email != ""){
|
|
||||||
remark += '-' + this.settings.trojans[clientIndex].email
|
|
||||||
}
|
|
||||||
return this.genTrojanLink(address, remark, clientIndex);
|
return this.genTrojanLink(address, remark, clientIndex);
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
@@ -1528,12 +1661,17 @@ class Inbound extends XrayCommonClass {
|
|||||||
case Protocols.VMESS:
|
case Protocols.VMESS:
|
||||||
case Protocols.VLESS:
|
case Protocols.VLESS:
|
||||||
case Protocols.TROJAN:
|
case Protocols.TROJAN:
|
||||||
JSON.parse(this.settings).clients.forEach((_,index) => {
|
case Protocols.SHADOWSOCKS:
|
||||||
link += this.genLink(address, remark, index) + '\r\n';
|
JSON.parse(this.settings).clients.forEach((client,index) => {
|
||||||
|
if(this.tls && !ObjectUtil.isArrEmpty(this.stream.tls.settings.domains)){
|
||||||
|
this.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
link += this.genLink(domain.domain, [remark, client.email, domain.remark].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
link += this.genLink(address, [remark, client.email].filter(x => x.length > 0).join('-'), index) + '\r\n';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return link;
|
return link;
|
||||||
case Protocols.SHADOWSOCKS:
|
|
||||||
return (this.genSSLink(address, remark) + '\r\n');
|
|
||||||
default: return '';
|
default: return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1582,7 +1720,6 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
case Protocols.TROJAN: return new Inbound.TrojanSettings(protocol);
|
||||||
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings(protocol);
|
||||||
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
||||||
case Protocols.MTPROTO: return new Inbound.MtprotoSettings(protocol);
|
|
||||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||||
default: return null;
|
default: return null;
|
||||||
@@ -1596,7 +1733,6 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
case Protocols.TROJAN: return Inbound.TrojanSettings.fromJson(json);
|
||||||
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
case Protocols.SHADOWSOCKS: return Inbound.ShadowsocksSettings.fromJson(json);
|
||||||
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
||||||
case Protocols.MTPROTO: return Inbound.MtprotoSettings.fromJson(json);
|
|
||||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||||
default: return null;
|
default: return null;
|
||||||
@@ -1610,11 +1746,9 @@ Inbound.Settings = class extends XrayCommonClass {
|
|||||||
|
|
||||||
Inbound.VmessSettings = class extends Inbound.Settings {
|
Inbound.VmessSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol,
|
constructor(protocol,
|
||||||
vmesses=[new Inbound.VmessSettings.Vmess()],
|
vmesses=[new Inbound.VmessSettings.Vmess()]) {
|
||||||
disableInsecureEncryption=false) {
|
|
||||||
super(protocol);
|
super(protocol);
|
||||||
this.vmesses = vmesses;
|
this.vmesses = vmesses;
|
||||||
this.disableInsecure = disableInsecureEncryption;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
indexOfVmessById(id) {
|
indexOfVmessById(id) {
|
||||||
@@ -1639,22 +1773,19 @@ Inbound.VmessSettings = class extends Inbound.Settings {
|
|||||||
return new Inbound.VmessSettings(
|
return new Inbound.VmessSettings(
|
||||||
Protocols.VMESS,
|
Protocols.VMESS,
|
||||||
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
json.clients.map(client => Inbound.VmessSettings.Vmess.fromJson(client)),
|
||||||
ObjectUtil.isEmpty(json.disableInsecureEncryption) ? false : json.disableInsecureEncryption,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
clients: Inbound.VmessSettings.toJsonArray(this.vmesses),
|
||||||
disableInsecureEncryption: this.disableInsecure,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(id=RandomUtil.randomUUID(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.alterId = alterId;
|
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
this.limitIp = limitIp;
|
||||||
this.totalGB = totalGB;
|
this.totalGB = totalGB;
|
||||||
@@ -1667,7 +1798,6 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
|||||||
static fromJson(json={}) {
|
static fromJson(json={}) {
|
||||||
return new Inbound.VmessSettings.Vmess(
|
return new Inbound.VmessSettings.Vmess(
|
||||||
json.id,
|
json.id,
|
||||||
json.alterId,
|
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
json.limitIp,
|
||||||
json.totalGB,
|
json.totalGB,
|
||||||
@@ -1743,7 +1873,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings {
|
|||||||
|
|
||||||
};
|
};
|
||||||
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||||
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -1866,7 +1996,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.flow = flow;
|
this.flow = flow;
|
||||||
@@ -2008,8 +2138,9 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||||
constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') {
|
constructor(method='', password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomLowerAndNum(8),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId=RandomUtil.randomLowerAndNum(16)) {
|
||||||
super();
|
super();
|
||||||
|
this.method = method;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.limitIp = limitIp;
|
this.limitIp = limitIp;
|
||||||
@@ -2022,6 +2153,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
|
|
||||||
toJson() {
|
toJson() {
|
||||||
return {
|
return {
|
||||||
|
method: this.method,
|
||||||
password: this.password,
|
password: this.password,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
limitIp: this.limitIp,
|
limitIp: this.limitIp,
|
||||||
@@ -2035,6 +2167,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
|||||||
|
|
||||||
static fromJson(json = {}) {
|
static fromJson(json = {}) {
|
||||||
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
return new Inbound.ShadowsocksSettings.Shadowsocks(
|
||||||
|
json.method,
|
||||||
json.password,
|
json.password,
|
||||||
json.email,
|
json.email,
|
||||||
json.limitIp,
|
json.limitIp,
|
||||||
@@ -2102,36 +2235,6 @@ Inbound.DokodemoSettings = class extends Inbound.Settings {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Inbound.MtprotoSettings = class extends Inbound.Settings {
|
|
||||||
constructor(protocol, users=[new Inbound.MtprotoSettings.MtUser()]) {
|
|
||||||
super(protocol);
|
|
||||||
this.users = users;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
return new Inbound.MtprotoSettings(
|
|
||||||
Protocols.MTPROTO,
|
|
||||||
json.users.map(user => Inbound.MtprotoSettings.MtUser.fromJson(user)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
toJson() {
|
|
||||||
return {
|
|
||||||
users: XrayCommonClass.toJsonArray(this.users),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Inbound.MtprotoSettings.MtUser = class extends XrayCommonClass {
|
|
||||||
constructor(secret=RandomUtil.randomMTSecret()) {
|
|
||||||
super();
|
|
||||||
this.secret = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromJson(json={}) {
|
|
||||||
return new Inbound.MtprotoSettings.MtUser(json.secret);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Inbound.SocksSettings = class extends Inbound.Settings {
|
Inbound.SocksSettings = class extends Inbound.Settings {
|
||||||
constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') {
|
constructor(protocol, auth='password', accounts=[new Inbound.SocksSettings.SocksAccount()], udp=false, ip='127.0.0.1') {
|
||||||
super(protocol);
|
super(protocol);
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ const ONE_TB = ONE_GB * 1024;
|
|||||||
const ONE_PB = ONE_TB * 1024;
|
const ONE_PB = ONE_TB * 1024;
|
||||||
|
|
||||||
function sizeFormat(size) {
|
function sizeFormat(size) {
|
||||||
if (size < ONE_KB) {
|
if (size < 0) {
|
||||||
|
return "0 B";
|
||||||
|
} else if (size < ONE_KB) {
|
||||||
return size.toFixed(0) + " B";
|
return size.toFixed(0) + " B";
|
||||||
} else if (size < ONE_MB) {
|
} else if (size < ONE_MB) {
|
||||||
return (size / ONE_KB).toFixed(2) + " KB";
|
return (size / ONE_KB).toFixed(2) + " KB";
|
||||||
@@ -20,6 +22,23 @@ function sizeFormat(size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cpuSpeedFormat(speed) {
|
||||||
|
if (speed > 1000) {
|
||||||
|
const GHz = speed / 1000;
|
||||||
|
return GHz.toFixed(2) + " GHz";
|
||||||
|
} else {
|
||||||
|
return speed.toFixed(2) + " MHz";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cpuCoreFormat(cores) {
|
||||||
|
if (cores === 1) {
|
||||||
|
return "1 Core";
|
||||||
|
} else {
|
||||||
|
return cores + " Cores";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function base64(str) {
|
function base64(str) {
|
||||||
return Base64.encode(str);
|
return Base64.encode(str);
|
||||||
}
|
}
|
||||||
@@ -70,23 +89,67 @@ function debounce(fn, delay) {
|
|||||||
|
|
||||||
function getCookie(cname) {
|
function getCookie(cname) {
|
||||||
let name = cname + '=';
|
let name = cname + '=';
|
||||||
let decodedCookie = decodeURIComponent(document.cookie);
|
let ca = document.cookie.split(';');
|
||||||
let ca = decodedCookie.split(';');
|
|
||||||
for (let i = 0; i < ca.length; i++) {
|
for (let i = 0; i < ca.length; i++) {
|
||||||
let c = ca[i];
|
let c = ca[i];
|
||||||
while (c.charAt(0) == ' ') {
|
while (c.charAt(0) == ' ') {
|
||||||
c = c.substring(1);
|
c = c.substring(1);
|
||||||
}
|
}
|
||||||
if (c.indexOf(name) == 0) {
|
if (c.indexOf(name) == 0) {
|
||||||
return c.substring(name.length, c.length);
|
// decode cookie value only
|
||||||
|
return decodeURIComponent(c.substring(name.length, c.length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setCookie(cname, cvalue, exdays) {
|
function setCookie(cname, cvalue, exdays) {
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||||
let expires = 'expires=' + d.toUTCString();
|
let expires = 'expires=' + d.toUTCString();
|
||||||
document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/';
|
// encode cookie value
|
||||||
|
document.cookie = cname + '=' + encodeURIComponent(cvalue) + ';' + expires + ';path=/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function usageColor(data, threshold, total) {
|
||||||
|
switch (true) {
|
||||||
|
case data === null:
|
||||||
|
return 'blue';
|
||||||
|
case total <= 0:
|
||||||
|
return 'blue';
|
||||||
|
case data < total - threshold:
|
||||||
|
return 'cyan';
|
||||||
|
case data < total:
|
||||||
|
return 'orange';
|
||||||
|
default:
|
||||||
|
return 'red';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doAllItemsExist(array1, array2) {
|
||||||
|
for (let i = 0; i < array1.length; i++) {
|
||||||
|
if (!array2.includes(array1[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildURL({ host, port, isTLS, base, path }) {
|
||||||
|
if (!host || host.length === 0) host = window.location.hostname;
|
||||||
|
if (!port || port.length === 0) port = window.location.port;
|
||||||
|
|
||||||
|
if (isTLS === undefined) isTLS = window.location.protocol === "https:";
|
||||||
|
|
||||||
|
const protocol = isTLS ? "https:" : "http:";
|
||||||
|
|
||||||
|
port = String(port);
|
||||||
|
if (port === "" || (isTLS && port === "443") || (!isTLS && port === "80")) {
|
||||||
|
port = "";
|
||||||
|
} else {
|
||||||
|
port = `:${port}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${protocol}//${host}${port}${base}${path}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,26 +75,11 @@ class PromiseUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const seq = [
|
const seq = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g',
|
|
||||||
'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
|
||||||
'o', 'p', 'q', 'r', 's', 't',
|
|
||||||
'u', 'v', 'w', 'x', 'y', 'z',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
'A', 'B', 'C', 'D', 'E', 'F', 'G',
|
|
||||||
'H', 'I', 'J', 'K', 'L', 'M', 'N',
|
|
||||||
'O', 'P', 'Q', 'R', 'S', 'T',
|
|
||||||
'U', 'V', 'W', 'X', 'Y', 'Z'
|
|
||||||
];
|
|
||||||
|
|
||||||
const shortIdSeq = [
|
|
||||||
'a', 'b', 'c', 'd', 'e', 'f',
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
|
||||||
];
|
|
||||||
|
|
||||||
class RandomUtil {
|
class RandomUtil {
|
||||||
static randomIntRange(min, max) {
|
static randomIntRange(min, max) {
|
||||||
return parseInt(Math.random() * (max - min) + min, 10);
|
return Math.floor(Math.random() * (max - min) + min);
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomInt(n) {
|
static randomInt(n) {
|
||||||
@@ -109,59 +94,33 @@ class RandomUtil {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomShortIdSeq(count) {
|
static randomShortId() {
|
||||||
let str = '';
|
let str = '';
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < 8; ++i) {
|
||||||
str += shortIdSeq[this.randomInt(16)];
|
str += seq[this.randomInt(16)];
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomLowerAndNum(count) {
|
static randomLowerAndNum(len) {
|
||||||
let str = '';
|
let str = '';
|
||||||
for (let i = 0; i < count; ++i) {
|
for (let i = 0; i < len; ++i) {
|
||||||
str += seq[this.randomInt(36)];
|
str += seq[this.randomInt(36)];
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomMTSecret() {
|
|
||||||
let str = '';
|
|
||||||
for (let i = 0; i < 32; ++i) {
|
|
||||||
let index = this.randomInt(16);
|
|
||||||
if (index <= 9) {
|
|
||||||
str += index;
|
|
||||||
} else {
|
|
||||||
str += seq[index - 10];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomUUID() {
|
static randomUUID() {
|
||||||
let d = new Date().getTime();
|
const template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
return template.replace(/[xy]/g, function (c) {
|
||||||
let r = (d + Math.random() * 16) % 16 | 0;
|
const randomValues = new Uint8Array(1);
|
||||||
d = Math.floor(d / 16);
|
crypto.getRandomValues(randomValues);
|
||||||
return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
|
let randomValue = randomValues[0] % 16;
|
||||||
|
let calculatedValue = (c === 'x') ? randomValue : (randomValue & 0x3 | 0x8);
|
||||||
|
return calculatedValue.toString(16);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static randomText() {
|
|
||||||
var chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
|
|
||||||
var string = '';
|
|
||||||
for (var ii = 0; ii < 8; ii++) {
|
|
||||||
string += chars[Math.floor(Math.random() * chars.length)];
|
|
||||||
}
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randowShortId() {
|
|
||||||
let str = '';
|
|
||||||
str += this.randomShortIdSeq(8);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static randomShadowsocksPassword() {
|
static randomShadowsocksPassword() {
|
||||||
let array = new Uint8Array(32);
|
let array = new Uint8Array(32);
|
||||||
window.crypto.getRandomValues(array);
|
window.crypto.getRandomValues(array);
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
|
Tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAPIController(g *gin.RouterGroup) *APIController {
|
func NewAPIController(g *gin.RouterGroup) *APIController {
|
||||||
@@ -32,7 +37,7 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
g.GET("/createbackup", a.createBackup)
|
||||||
a.inboundController = NewInboundController(g)
|
a.inboundController = NewInboundController(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,3 +100,7 @@ func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
func (a *APIController) delDepletedClients(c *gin.Context) {
|
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||||
a.inboundController.delDepletedClients(c)
|
a.inboundController.delDepletedClients(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APIController) createBackup(c *gin.Context) {
|
||||||
|
a.Tgbot.SendBackupToAdmins()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/web/locale"
|
||||||
"x-ui/web/session"
|
"x-ui/web/session"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -13,7 +15,7 @@ type BaseController struct {
|
|||||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||||
if !session.IsLogin(c) {
|
if !session.IsLogin(c) {
|
||||||
if isAjax(c) {
|
if isAjax(c) {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.loginAgain"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||||
}
|
}
|
||||||
@@ -23,11 +25,13 @@ func (a *BaseController) checkLogin(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func I18n(c *gin.Context, name string) string {
|
func I18nWeb(c *gin.Context, name string, params ...string) string {
|
||||||
anyfunc, _ := c.Get("I18n")
|
anyfunc, funcExists := c.Get("I18n")
|
||||||
i18n, _ := anyfunc.(func(key string, params ...string) (string, error))
|
if !funcExists {
|
||||||
|
logger.Warning("I18n function not exists in gin context!")
|
||||||
message, _ := i18n(name)
|
return ""
|
||||||
|
}
|
||||||
return message
|
i18nFunc, _ := anyfunc.(func(i18nType locale.I18nType, key string, keyParams ...string) string)
|
||||||
|
msg := i18nFunc(locale.Web, name, params...)
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ func (a *InboundController) initRouter(g *gin.RouterGroup) {
|
|||||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) startTask() {
|
func (a *InboundController) startTask() {
|
||||||
@@ -60,20 +59,21 @@ func (a *InboundController) getInbounds(c *gin.Context) {
|
|||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
inbounds, err := a.inboundService.GetInbounds(user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbounds, nil)
|
jsonObj(c, inbounds, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) getInbound(c *gin.Context) {
|
func (a *InboundController) getInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "get"), err)
|
jsonMsg(c, I18nWeb(c, "get"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err := a.inboundService.GetInbound(id)
|
inbound, err := a.inboundService.GetInbound(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.toasts.obtain"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.toasts.obtain"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, inbound, nil)
|
jsonObj(c, inbound, nil)
|
||||||
@@ -93,16 +93,17 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.create"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.create"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
inbound.UserId = user.Id
|
inbound.UserId = user.Id
|
||||||
inbound.Enable = true
|
|
||||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||||
inbound, err = a.inboundService.AddInbound(inbound)
|
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.create"), inbound, err)
|
needRestart := false
|
||||||
if err == nil {
|
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||||
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.create"), inbound, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,12 +111,13 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) delInbound(c *gin.Context) {
|
func (a *InboundController) delInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "delete"), err)
|
jsonMsg(c, I18nWeb(c, "delete"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelInbound(id)
|
needRestart := true
|
||||||
jsonMsgObj(c, I18n(c, "delete"), id, err)
|
needRestart, err = a.inboundService.DelInbound(id)
|
||||||
if err == nil {
|
jsonMsgObj(c, I18nWeb(c, "delete"), id, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +125,7 @@ func (a *InboundController) delInbound(c *gin.Context) {
|
|||||||
func (a *InboundController) updateInbound(c *gin.Context) {
|
func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound := &model.Inbound{
|
inbound := &model.Inbound{
|
||||||
@@ -131,12 +133,13 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
err = c.ShouldBind(inbound)
|
err = c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
inbound, err = a.inboundService.UpdateInbound(inbound)
|
needRestart := true
|
||||||
jsonMsgObj(c, I18n(c, "pages.inbounds.update"), inbound, err)
|
inbound, needRestart, err = a.inboundService.UpdateInbound(inbound)
|
||||||
if err == nil {
|
jsonMsgObj(c, I18nWeb(c, "pages.inbounds.update"), inbound, err)
|
||||||
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,12 +148,14 @@ func (a *InboundController) getClientIps(c *gin.Context) {
|
|||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
ips, err := a.inboundService.GetInboundClientIps(email)
|
ips, err := a.inboundService.GetInboundClientIps(email)
|
||||||
if err != nil {
|
if err != nil || ips == "" {
|
||||||
jsonObj(c, "No IP Record", nil)
|
jsonObj(c, "No IP Record", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonObj(c, ips, nil)
|
jsonObj(c, ips, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) clearClientIps(c *gin.Context) {
|
func (a *InboundController) clearClientIps(c *gin.Context) {
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
@@ -161,21 +166,24 @@ func (a *InboundController) clearClientIps(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
jsonMsg(c, "Log Cleared", nil)
|
jsonMsg(c, "Log Cleared", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *InboundController) addInboundClient(c *gin.Context) {
|
func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||||
data := &model.Inbound{}
|
data := &model.Inbound{}
|
||||||
err := c.ShouldBind(data)
|
err := c.ShouldBind(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.AddInboundClient(data)
|
needRestart := true
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client(s) added", nil)
|
jsonMsg(c, "Client(s) added", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,18 +191,20 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
|||||||
func (a *InboundController) delInboundClient(c *gin.Context) {
|
func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clientId := c.Param("clientId")
|
clientId := c.Param("clientId")
|
||||||
|
|
||||||
err = a.inboundService.DelInboundClient(id, clientId)
|
needRestart := true
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client deleted", nil)
|
jsonMsg(c, "Client deleted", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,17 +215,19 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.inboundService.UpdateInboundClient(inbound, clientId)
|
needRestart := true
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
jsonMsg(c, "Client updated", nil)
|
jsonMsg(c, "Client updated", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,18 +235,20 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
|||||||
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
email := c.Param("email")
|
email := c.Param("email")
|
||||||
|
|
||||||
err = a.inboundService.ResetClientTraffic(id, email)
|
needRestart := true
|
||||||
|
|
||||||
|
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 reseted", nil)
|
||||||
if err == nil {
|
if err == nil && needRestart {
|
||||||
a.xrayService.SetToNeedRestart()
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,6 +258,8 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics reseted", nil)
|
jsonMsg(c, "All traffics reseted", nil)
|
||||||
}
|
}
|
||||||
@@ -251,7 +267,7 @@ func (a *InboundController) resetAllTraffics(c *gin.Context) {
|
|||||||
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +275,8 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, "Something went wrong!", err)
|
jsonMsg(c, "Something went wrong!", err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
a.xrayService.SetToNeedRestart()
|
||||||
}
|
}
|
||||||
jsonMsg(c, "All traffics of client reseted", nil)
|
jsonMsg(c, "All traffics of client reseted", nil)
|
||||||
}
|
}
|
||||||
@@ -266,7 +284,7 @@ func (a *InboundController) resetAllClientTraffics(c *gin.Context) {
|
|||||||
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
func (a *InboundController) delDepletedClients(c *gin.Context) {
|
||||||
id, err := strconv.Atoi(c.Param("id"))
|
id, err := strconv.Atoi(c.Param("id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.inbounds.update"), err)
|
jsonMsg(c, I18nWeb(c, "pages.inbounds.update"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.inboundService.DelDepletedClients(id)
|
err = a.inboundService.DelDepletedClients(id)
|
||||||
|
|||||||
@@ -49,44 +49,45 @@ func (a *IndexController) login(c *gin.Context) {
|
|||||||
var form LoginForm
|
var form LoginForm
|
||||||
err := c.ShouldBind(&form)
|
err := c.ShouldBind(&form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.invalidFormData"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Username == "" {
|
if form.Username == "" {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyUsername"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.Password == "" {
|
if form.Password == "" {
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.emptyPassword"))
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
user := a.userService.CheckUser(form.Username, form.Password, form.LoginSecret)
|
||||||
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
timeStr := time.Now().Format("2006-01-02 15:04:05")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
logger.Warningf("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||||
pureJsonMsg(c, false, I18n(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%s login success,Ip Address:%s\n", form.Username, getRemoteIp(c))
|
logger.Infof("%s login success, Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 1)
|
a.tgbot.UserLoginNotify(form.Username, 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.Warningf("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.Warningf("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.Info("user", user.Id, "login success")
|
||||||
jsonMsg(c, I18n(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) {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
@@ -8,6 +11,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9_\-.]+$`)
|
||||||
|
|
||||||
type ServerController struct {
|
type ServerController struct {
|
||||||
BaseController
|
BaseController
|
||||||
|
|
||||||
@@ -76,7 +81,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
|||||||
|
|
||||||
versions, err := a.serverService.GetXrayVersions()
|
versions, err := a.serverService.GetXrayVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "getVersion"), err)
|
jsonMsg(c, I18nWeb(c, "getVersion"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +94,7 @@ func (a *ServerController) getXrayVersion(c *gin.Context) {
|
|||||||
func (a *ServerController) installXray(c *gin.Context) {
|
func (a *ServerController) installXray(c *gin.Context) {
|
||||||
version := c.Param("version")
|
version := c.Param("version")
|
||||||
err := a.serverService.UpdateXray(version)
|
err := a.serverService.UpdateXray(version)
|
||||||
jsonMsg(c, I18n(c, "install")+" xray", err)
|
jsonMsg(c, I18nWeb(c, "install")+" xray", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ServerController) stopXrayService(c *gin.Context) {
|
func (a *ServerController) stopXrayService(c *gin.Context) {
|
||||||
@@ -113,11 +118,9 @@ func (a *ServerController) restartXrayService(c *gin.Context) {
|
|||||||
|
|
||||||
func (a *ServerController) getLogs(c *gin.Context) {
|
func (a *ServerController) getLogs(c *gin.Context) {
|
||||||
count := c.Param("count")
|
count := c.Param("count")
|
||||||
logs, err := a.serverService.GetLogs(count)
|
level := c.PostForm("level")
|
||||||
if err != nil {
|
syslog := c.PostForm("syslog")
|
||||||
jsonMsg(c, "getLogs", err)
|
logs := a.serverService.GetLogs(count, level, syslog)
|
||||||
return
|
|
||||||
}
|
|
||||||
jsonObj(c, logs, nil)
|
jsonObj(c, logs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +139,27 @@ func (a *ServerController) getDb(c *gin.Context) {
|
|||||||
jsonMsg(c, "get Database", err)
|
jsonMsg(c, "get Database", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filename := "x-ui.db"
|
||||||
|
|
||||||
|
if !isValidFilename(filename) {
|
||||||
|
c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid filename"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Set the headers for the response
|
// Set the headers for the response
|
||||||
c.Header("Content-Type", "application/octet-stream")
|
c.Header("Content-Type", "application/octet-stream")
|
||||||
c.Header("Content-Disposition", "attachment; filename=x-ui.db")
|
c.Header("Content-Disposition", "attachment; filename="+filename)
|
||||||
|
|
||||||
// Write the file contents to the response
|
// Write the file contents to the response
|
||||||
c.Writer.Write(db)
|
c.Writer.Write(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidFilename(filename string) bool {
|
||||||
|
// Validate that the filename only contains allowed characters
|
||||||
|
return filenameRegex.MatchString(filename)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ServerController) importDB(c *gin.Context) {
|
func (a *ServerController) importDB(c *gin.Context) {
|
||||||
// Get the file from the request body
|
// Get the file from the request body
|
||||||
file, _, err := c.Request.FormFile("db")
|
file, _, err := c.Request.FormFile("db")
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (a *SettingController) initRouter(g *gin.RouterGroup) {
|
|||||||
func (a *SettingController) getAllSetting(c *gin.Context) {
|
func (a *SettingController) getAllSetting(c *gin.Context) {
|
||||||
allSetting, err := a.settingService.GetAllSetting()
|
allSetting, err := a.settingService.GetAllSetting()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, allSetting, nil)
|
jsonObj(c, allSetting, nil)
|
||||||
@@ -58,39 +58,51 @@ func (a *SettingController) getAllSetting(c *gin.Context) {
|
|||||||
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
func (a *SettingController) getDefaultJsonConfig(c *gin.Context) {
|
||||||
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
defaultJsonConfig, err := a.settingService.GetDefaultJsonConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonObj(c, defaultJsonConfig, nil)
|
jsonObj(c, defaultJsonConfig, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
func (a *SettingController) getDefaultSettings(c *gin.Context) {
|
||||||
expireDiff, err := a.settingService.GetExpireDiff()
|
type settingFunc func() (interface{}, error)
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
settings := map[string]settingFunc{
|
||||||
return
|
"expireDiff": func() (interface{}, error) { return a.settingService.GetExpireDiff() },
|
||||||
|
"trafficDiff": func() (interface{}, error) { return a.settingService.GetTrafficDiff() },
|
||||||
|
"defaultCert": func() (interface{}, error) { return a.settingService.GetCertFile() },
|
||||||
|
"defaultKey": func() (interface{}, error) { return a.settingService.GetKeyFile() },
|
||||||
|
"tgBotEnable": func() (interface{}, error) { return a.settingService.GetTgbotenabled() },
|
||||||
|
"subEnable": func() (interface{}, error) { return a.settingService.GetSubEnable() },
|
||||||
|
"subPort": func() (interface{}, error) { return a.settingService.GetSubPort() },
|
||||||
|
"subPath": func() (interface{}, error) { return a.settingService.GetSubPath() },
|
||||||
|
"subDomain": func() (interface{}, error) { return a.settingService.GetSubDomain() },
|
||||||
|
"subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() },
|
||||||
|
"subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() },
|
||||||
|
"subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() },
|
||||||
|
"subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() },
|
||||||
}
|
}
|
||||||
trafficDiff, err := a.settingService.GetTrafficDiff()
|
|
||||||
if err != nil {
|
result := make(map[string]interface{})
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
for key, fn := range settings {
|
||||||
|
value, err := fn()
|
||||||
|
if err != nil {
|
||||||
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.getSettings"), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
}
|
}
|
||||||
defaultCert, err := a.settingService.GetCertFile()
|
|
||||||
if err != nil {
|
subTLS := false
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
if result["subKeyFile"] != "" || result["subCertFile"] != "" {
|
||||||
return
|
subTLS = true
|
||||||
}
|
|
||||||
defaultKey, err := a.settingService.GetKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.getSettings"), err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result := map[string]interface{}{
|
|
||||||
"expireDiff": expireDiff,
|
|
||||||
"trafficDiff": trafficDiff,
|
|
||||||
"defaultCert": defaultCert,
|
|
||||||
"defaultKey": defaultKey,
|
|
||||||
}
|
}
|
||||||
|
result["subTLS"] = subTLS
|
||||||
|
|
||||||
|
delete(result, "subKeyFile")
|
||||||
|
delete(result, "subCertFile")
|
||||||
|
|
||||||
jsonObj(c, result, nil)
|
jsonObj(c, result, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,27 +110,27 @@ func (a *SettingController) updateSetting(c *gin.Context) {
|
|||||||
allSetting := &entity.AllSetting{}
|
allSetting := &entity.AllSetting{}
|
||||||
err := c.ShouldBind(allSetting)
|
err := c.ShouldBind(allSetting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.settingService.UpdateAllSetting(allSetting)
|
err = a.settingService.UpdateAllSetting(allSetting)
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) updateUser(c *gin.Context) {
|
func (a *SettingController) updateUser(c *gin.Context) {
|
||||||
form := &updateUserForm{}
|
form := &updateUserForm{}
|
||||||
err := c.ShouldBind(form)
|
err := c.ShouldBind(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
if user.Username != form.OldUsername || user.Password != form.OldPassword {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), errors.New(I18n(c, "pages.settings.toasts.originalUserPassIncorrect")))
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.originalUserPassIncorrect")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if form.NewUsername == "" || form.NewPassword == "" {
|
if form.NewUsername == "" || form.NewPassword == "" {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), errors.New(I18n(c, "pages.settings.toasts.userPassMustBeNotEmpty")))
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), errors.New(I18nWeb(c, "pages.settings.toasts.userPassMustBeNotEmpty")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
err = a.userService.UpdateUser(user.Id, form.NewUsername, form.NewPassword)
|
||||||
@@ -127,19 +139,19 @@ func (a *SettingController) updateUser(c *gin.Context) {
|
|||||||
user.Password = form.NewPassword
|
user.Password = form.NewPassword
|
||||||
session.SetLoginUser(c, user)
|
session.SetLoginUser(c, user)
|
||||||
}
|
}
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) restartPanel(c *gin.Context) {
|
func (a *SettingController) restartPanel(c *gin.Context) {
|
||||||
err := a.panelService.RestartPanel(time.Second * 3)
|
err := a.panelService.RestartPanel(time.Second * 3)
|
||||||
jsonMsg(c, I18n(c, "pages.settings.restartPanel"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.restartPanel"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) updateSecret(c *gin.Context) {
|
func (a *SettingController) updateSecret(c *gin.Context) {
|
||||||
form := &updateSecretForm{}
|
form := &updateSecretForm{}
|
||||||
err := c.ShouldBind(form)
|
err := c.ShouldBind(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifySettings"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifySettings"), err)
|
||||||
}
|
}
|
||||||
user := session.GetLoginUser(c)
|
user := session.GetLoginUser(c)
|
||||||
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
err = a.userService.UpdateUserSecret(user.Id, form.LoginSecret)
|
||||||
@@ -147,7 +159,7 @@ func (a *SettingController) updateSecret(c *gin.Context) {
|
|||||||
user.LoginSecret = form.LoginSecret
|
user.LoginSecret = form.LoginSecret
|
||||||
session.SetLoginUser(c, user)
|
session.SetLoginUser(c, user)
|
||||||
}
|
}
|
||||||
jsonMsg(c, I18n(c, "pages.settings.toasts.modifyUser"), err)
|
jsonMsg(c, I18nWeb(c, "pages.settings.toasts.modifyUser"), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SettingController) getUserSecret(c *gin.Context) {
|
func (a *SettingController) getUserSecret(c *gin.Context) {
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
m.Success = true
|
m.Success = true
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
m.Msg = msg + I18n(c, "success")
|
m.Msg = msg + I18nWeb(c, "success")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.Success = false
|
m.Success = false
|
||||||
m.Msg = msg + I18n(c, "fail") + ": " + err.Error()
|
m.Msg = msg + I18nWeb(c, "fail") + ": " + err.Error()
|
||||||
logger.Warning(msg+I18n(c, "fail")+": ", err)
|
logger.Warning(msg+I18nWeb(c, "fail")+": ", err)
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, m)
|
c.JSON(http.StatusOK, m)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type Pager struct {
|
|||||||
|
|
||||||
type AllSetting struct {
|
type AllSetting struct {
|
||||||
WebListen string `json:"webListen" form:"webListen"`
|
WebListen string `json:"webListen" form:"webListen"`
|
||||||
|
WebDomain string `json:"webDomain" form:"webDomain"`
|
||||||
WebPort int `json:"webPort" form:"webPort"`
|
WebPort int `json:"webPort" form:"webPort"`
|
||||||
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
WebCertFile string `json:"webCertFile" form:"webCertFile"`
|
||||||
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
WebKeyFile string `json:"webKeyFile" form:"webKeyFile"`
|
||||||
@@ -40,10 +41,22 @@ type AllSetting struct {
|
|||||||
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
TgBotChatId string `json:"tgBotChatId" form:"tgBotChatId"`
|
||||||
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
TgRunTime string `json:"tgRunTime" form:"tgRunTime"`
|
||||||
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
TgBotBackup bool `json:"tgBotBackup" form:"tgBotBackup"`
|
||||||
|
TgBotLoginNotify bool `json:"tgBotLoginNotify" form:"tgBotLoginNotify"`
|
||||||
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
TgCpu int `json:"tgCpu" form:"tgCpu"`
|
||||||
|
TgLang string `json:"tgLang" form:"tgLang"`
|
||||||
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
XrayTemplateConfig string `json:"xrayTemplateConfig" form:"xrayTemplateConfig"`
|
||||||
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
TimeLocation string `json:"timeLocation" form:"timeLocation"`
|
||||||
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
SecretEnable bool `json:"secretEnable" form:"secretEnable"`
|
||||||
|
SubEnable bool `json:"subEnable" form:"subEnable"`
|
||||||
|
SubListen string `json:"subListen" form:"subListen"`
|
||||||
|
SubPort int `json:"subPort" form:"subPort"`
|
||||||
|
SubPath string `json:"subPath" form:"subPath"`
|
||||||
|
SubDomain string `json:"subDomain" form:"subDomain"`
|
||||||
|
SubCertFile string `json:"subCertFile" form:"subCertFile"`
|
||||||
|
SubKeyFile string `json:"subKeyFile" form:"subKeyFile"`
|
||||||
|
SubUpdates int `json:"subUpdates" form:"subUpdates"`
|
||||||
|
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||||
|
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AllSetting) CheckValid() error {
|
func (s *AllSetting) CheckValid() error {
|
||||||
@@ -54,10 +67,25 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubListen != "" {
|
||||||
|
ip := net.ParseIP(s.SubListen)
|
||||||
|
if ip == nil {
|
||||||
|
return common.NewError("Sub listen is not valid ip:", s.SubListen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if s.WebPort <= 0 || s.WebPort > 65535 {
|
if s.WebPort <= 0 || s.WebPort > 65535 {
|
||||||
return common.NewError("web port is not a valid port:", s.WebPort)
|
return common.NewError("web port is not a valid port:", s.WebPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubPort <= 0 || s.SubPort > 65535 {
|
||||||
|
return common.NewError("Sub port is not a valid port:", s.SubPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.SubPort == s.WebPort {
|
||||||
|
return common.NewError("Sub and Web could not use same port:", s.SubPort)
|
||||||
|
}
|
||||||
|
|
||||||
if s.WebCertFile != "" || s.WebKeyFile != "" {
|
if s.WebCertFile != "" || s.WebKeyFile != "" {
|
||||||
_, err := tls.LoadX509KeyPair(s.WebCertFile, s.WebKeyFile)
|
_, err := tls.LoadX509KeyPair(s.WebCertFile, s.WebKeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -65,6 +93,13 @@ func (s *AllSetting) CheckValid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.SubCertFile != "" || s.SubKeyFile != "" {
|
||||||
|
_, err := tls.LoadX509KeyPair(s.SubCertFile, s.SubKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewErrorf("cert file <%v> or key file <%v> invalid: %v", s.SubCertFile, s.SubKeyFile, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(s.WebBasePath, "/") {
|
if !strings.HasPrefix(s.WebBasePath, "/") {
|
||||||
s.WebBasePath = "/" + s.WebBasePath
|
s.WebBasePath = "/" + s.WebBasePath
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var webServer WebServer
|
var webServer WebServer
|
||||||
|
var subServer SubServer
|
||||||
|
|
||||||
type WebServer interface {
|
type WebServer interface {
|
||||||
GetCron() *cron.Cron
|
GetCron() *cron.Cron
|
||||||
GetCtx() context.Context
|
GetCtx() context.Context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SubServer interface {
|
||||||
|
GetCtx() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
func SetWebServer(s WebServer) {
|
func SetWebServer(s WebServer) {
|
||||||
webServer = s
|
webServer = s
|
||||||
}
|
}
|
||||||
@@ -21,3 +26,11 @@ func SetWebServer(s WebServer) {
|
|||||||
func GetWebServer() WebServer {
|
func GetWebServer() WebServer {
|
||||||
return webServer
|
return webServer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSubServer(s SubServer) {
|
||||||
|
subServer = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSubServer() SubServer {
|
||||||
|
return subServer
|
||||||
|
}
|
||||||
|
|||||||
80
web/global/hashStorage.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HashEntry struct {
|
||||||
|
Hash string
|
||||||
|
Value string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type HashStorage struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Data map[string]HashEntry
|
||||||
|
Expiration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHashStorage(expiration time.Duration) *HashStorage {
|
||||||
|
return &HashStorage{
|
||||||
|
Data: make(map[string]HashEntry),
|
||||||
|
Expiration: expiration,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) SaveHash(query string) string {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
md5Hash := md5.Sum([]byte(query))
|
||||||
|
md5HashString := hex.EncodeToString(md5Hash[:])
|
||||||
|
|
||||||
|
entry := HashEntry{
|
||||||
|
Hash: md5HashString,
|
||||||
|
Value: query,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Data[md5HashString] = entry
|
||||||
|
|
||||||
|
return md5HashString
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) GetValue(hash string) (string, bool) {
|
||||||
|
h.RLock()
|
||||||
|
defer h.RUnlock()
|
||||||
|
|
||||||
|
entry, exists := h.Data[hash]
|
||||||
|
|
||||||
|
return entry.Value, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) IsMD5(hash string) bool {
|
||||||
|
match, _ := regexp.MatchString("^[a-f0-9]{32}$", hash)
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) RemoveExpiredHashes() {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for hash, entry := range h.Data {
|
||||||
|
if now.Sub(entry.Timestamp) > h.Expiration {
|
||||||
|
delete(h.Data, hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HashStorage) Reset() {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
h.Data = make(map[string]HashEntry)
|
||||||
|
}
|
||||||
@@ -7,47 +7,55 @@
|
|||||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;">
|
||||||
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
{{ i18n "pages.inbounds.clickOnQRcode" }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
<a-tag v-if="qrModal.clientName" color="orange" style="margin-bottom: 10px;display: block;text-align: center;">
|
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||||
{{ i18n "pages.inbounds.email" }}: "[[ qrModal.clientName ]]"
|
<a-divider>Subscription</a-divider>
|
||||||
</a-tag>
|
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
|
||||||
<canvas @click="copyToClipboard()" id="qrCode" style="width: 100%; height: 100%; margin-top: 10px;"></canvas>
|
</template>
|
||||||
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
|
<template v-for="(row, index) in qrModal.qrcodes">
|
||||||
|
<a-tag color="orange" style="margin-top: 10px;display: block;text-align: center;">[[ row.remark ]]</a-tag>
|
||||||
|
<canvas @click="copyToClipboard('qrCode-'+index, row.link)" :id="'qrCode-'+index" style="width: 100%; height: 100%;"></canvas>
|
||||||
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const qrModal = {
|
const qrModal = {
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
clientIndex: 0,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
dbInbound: new DBInbound(),
|
dbInbound: new DBInbound(),
|
||||||
copyText: '',
|
client: null,
|
||||||
clientName: null,
|
qrcodes: [],
|
||||||
qrcode: null,
|
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
visible: false,
|
visible: false,
|
||||||
show: function (title = '', content = '', dbInbound = new DBInbound(), copyText = '', clientName = null) {
|
subId: '',
|
||||||
|
show: function (title = '', dbInbound = new DBInbound(), clientIndex = 0) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.content = content;
|
this.clientIndex = clientIndex;
|
||||||
this.dbInbound = dbInbound;
|
this.dbInbound = dbInbound;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clientName = clientName;
|
settings = JSON.parse(this.inbound.settings);
|
||||||
if (ObjectUtil.isEmpty(copyText)) {
|
this.client = settings.clients[clientIndex];
|
||||||
this.copyText = content;
|
remark = [this.dbInbound.remark, ( this.client ? this.client.email : '')].filter(Boolean).join('-');
|
||||||
|
address = this.dbInbound.address;
|
||||||
|
this.subId = '';
|
||||||
|
this.qrcodes = [];
|
||||||
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||||
|
this.qrcodes.push({
|
||||||
|
remark: remarkText,
|
||||||
|
link: this.inbound.genLink(domain.domain, remarkText, clientIndex)
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.copyText = copyText;
|
this.qrcodes.push({
|
||||||
|
remark: remark,
|
||||||
|
link: this.inbound.genLink(address, remark, clientIndex)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
qrModalApp.$nextTick(() => {
|
|
||||||
if (this.qrcode === null) {
|
|
||||||
this.qrcode = new QRious({
|
|
||||||
element: document.querySelector('#qrCode'),
|
|
||||||
size: 260,
|
|
||||||
value: content,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.qrcode.value = content;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
close: function () {
|
close: function () {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
@@ -61,16 +69,36 @@
|
|||||||
qrModal: qrModal,
|
qrModal: qrModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard() {
|
copyToClipboard(elmentId, content) {
|
||||||
this.qrModal.clipboard = new ClipboardJS('#qrCode', {
|
this.qrModal.clipboard = new ClipboardJS('#' + elmentId, {
|
||||||
text: () => this.qrModal.copyText,
|
text: () => content,
|
||||||
});
|
});
|
||||||
this.qrModal.clipboard.on('success', () => {
|
this.qrModal.clipboard.on('success', () => {
|
||||||
app.$message.success('{{ i18n "copied" }}')
|
app.$message.success('{{ i18n "copied" }}')
|
||||||
this.qrModal.clipboard.destroy();
|
this.qrModal.clipboard.destroy();
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
setQrCode(elmentId, content) {
|
||||||
|
new QRious({
|
||||||
|
element: document.querySelector('#' + elmentId),
|
||||||
|
size: 260,
|
||||||
|
value: content,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
genSubLink(subID) {
|
||||||
|
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||||
|
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updated() {
|
||||||
|
if (qrModal.client && qrModal.client.subId) {
|
||||||
|
qrModal.subId = qrModal.client.subId;
|
||||||
|
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
||||||
|
}
|
||||||
|
qrModal.qrcodes.forEach((element, index) => {
|
||||||
|
this.setQrCode("qrCode-" + index, element.link);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -86,8 +86,8 @@
|
|||||||
<a-row justify="center" class="centered">
|
<a-row justify="center" class="centered">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select ref="selectLang" v-model="lang" @change="setLang(lang)" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option :value="l.value" label="English" v-for="l in supportLangs">
|
<a-select-option :value="l.value" :label="l.value" v-for="l in supportLangs">
|
||||||
<span role="img" aria-label="l.name" v-text="l.icon"></span>
|
<span role="img" :aria-label="l.name" v-text="l.icon"></span>
|
||||||
<span v-text="l.name"></span>
|
<span v-text="l.name"></span>
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
<span slot="label">{{ i18n "pages.client.clientCount" }}</span>
|
||||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item v-if="app.subSettings.enable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Subscription
|
Subscription
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item v-if="app.tgBotEnable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Telegram ID
|
Telegram ID
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimit" }}</span>
|
||||||
@@ -158,8 +159,8 @@
|
|||||||
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
newClient = clientsBulkModal.newClient(clientsBulkModal.dbInbound.protocol);
|
||||||
if (method == 4) newClient.email = "";
|
if (method == 4) newClient.email = "";
|
||||||
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
newClient.email += useNum ? prefix + i.toString() + postfix : prefix + postfix;
|
||||||
newClient.subId = clientsBulkModal.subId;
|
if (clientsBulkModal.subId.length > 0) newClient.subId = clientsBulkModal.subId;
|
||||||
newClient.tgId = clientsBulkModal.tgId;
|
if (clientsBulkModal.tgId.length > 0) newClient.tgId = clientsBulkModal.tgId;
|
||||||
newClient.limitIp = clientsBulkModal.limitIp;
|
newClient.limitIp = clientsBulkModal.limitIp;
|
||||||
newClient._totalGB = clientsBulkModal.totalGB;
|
newClient._totalGB = clientsBulkModal.totalGB;
|
||||||
newClient._expiryTime = clientsBulkModal.expiryTime;
|
newClient._expiryTime = clientsBulkModal.expiryTime;
|
||||||
@@ -199,19 +200,12 @@
|
|||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
},
|
},
|
||||||
getClients(protocol, clientSettings) {
|
|
||||||
switch (protocol) {
|
|
||||||
case Protocols.VMESS: return clientSettings.vmesses;
|
|
||||||
case Protocols.VLESS: return clientSettings.vlesses;
|
|
||||||
case Protocols.TROJAN: return clientSettings.trojans;
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
newClient(protocol) {
|
newClient(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
case Protocols.VMESS: return new Inbound.VmessSettings.Vmess();
|
||||||
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
case Protocols.VLESS: return new Inbound.VLESSSettings.VLESS();
|
||||||
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
case Protocols.TROJAN: return new Inbound.TrojanSettings.Trojan();
|
||||||
|
case Protocols.SHADOWSOCKS: return new Inbound.ShadowsocksSettings.Shadowsocks(clientsBulkModal.inbound.settings.shadowsockses[0].method);
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
oldClientId: "",
|
oldClientId: "",
|
||||||
index: null,
|
index: null,
|
||||||
clientIps: null,
|
clientIps: null,
|
||||||
isExpired: false,
|
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
ok() {
|
ok() {
|
||||||
if (clientModal.isEdit) {
|
if (clientModal.isEdit) {
|
||||||
@@ -38,7 +37,6 @@
|
|||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
this.clients = this.getClients(this.inbound.protocol, this.inbound.settings);
|
||||||
this.index = index === null ? this.clients.length : index;
|
this.index = index === null ? this.clients.length : index;
|
||||||
this.isExpired = isEdit ? this.inbound.isExpiry(this.index) : false;
|
|
||||||
this.delayedStart = false;
|
this.delayedStart = false;
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
if (this.clients[index].expiryTime < 0) {
|
if (this.clients[index].expiryTime < 0) {
|
||||||
@@ -72,7 +70,7 @@
|
|||||||
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess());
|
||||||
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS());
|
||||||
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan());
|
||||||
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks());
|
case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks(clients[0].method));
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -109,13 +107,10 @@
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
get isExpiry() {
|
get isExpiry() {
|
||||||
return this.clientModal.isExpired
|
return this.clientModal.isEdit && this.client.expiryTime >0 ? (this.client.expiryTime < new Date().getTime()) : false;
|
||||||
},
|
},
|
||||||
get statsColor() {
|
get statsColor() {
|
||||||
if (!clientStats) return 'blue'
|
return usageColor(clientStats.up + clientStats.down, app.trafficDiff, this.client.totalGB);
|
||||||
if (clientStats.total <= 0) return 'blue'
|
|
||||||
else if (clientStats.total > 0 && (clientStats.down + clientStats.up) < clientStats.total) return 'cyan'
|
|
||||||
else return 'red'
|
|
||||||
},
|
},
|
||||||
get delayedExpireDays() {
|
get delayedExpireDays() {
|
||||||
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
return this.client && this.client.expiryTime < 0 ? this.client.expiryTime / -86400000 : 0;
|
||||||
@@ -125,26 +120,32 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getDBClientIps(email, event) {
|
async getDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post('/panel/inbound/clientIps/' + email);
|
const msg = await HttpUtil.post(`/panel/inbound/clientIps/${email}`);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
document.getElementById("clientIPs").value = msg.obj;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
let ips = msg.obj;
|
||||||
ips = JSON.parse(msg.obj)
|
if (typeof ips === 'string' && ips.startsWith('[') && ips.endsWith(']')) {
|
||||||
ips = ips.join(",")
|
try {
|
||||||
event.target.value = ips
|
ips = JSON.parse(ips);
|
||||||
} catch (error) {
|
ips = Array.isArray(ips) ? ips.join("\n") : ips;
|
||||||
// text
|
} catch (e) {
|
||||||
event.target.value = msg.obj
|
console.error('Error parsing JSON:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
document.getElementById("clientIPs").value = ips;
|
||||||
},
|
},
|
||||||
async clearDBClientIps(email) {
|
async clearDBClientIps(email) {
|
||||||
const msg = await HttpUtil.post('/panel/inbound/clearClientIps/' + email);
|
try {
|
||||||
if (!msg.success) {
|
const msg = await HttpUtil.post(`/panel/inbound/clearClientIps/${email}`);
|
||||||
return;
|
if (!msg.success) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById("clientIPs").value = "";
|
||||||
|
} catch (error) {
|
||||||
}
|
}
|
||||||
document.getElementById("clientIPs").value = ""
|
|
||||||
},
|
},
|
||||||
resetClientTraffic(email, dbInboundId, iconElement) {
|
resetClientTraffic(email, dbInboundId, iconElement) {
|
||||||
this.$confirm({
|
this.$confirm({
|
||||||
|
|||||||
@@ -18,23 +18,20 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
<a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||||
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS"
|
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
@click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
<a-icon v-if="inbound.protocol === Protocols.TROJAN" @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.password" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.password" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS">
|
|
||||||
<a-input-number v-model="client.alterId"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
<a-form-item label="ID" v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Subscription
|
Subscription
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -44,10 +41,10 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.tgBotEnable" >
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Telegram ID
|
Telegram ID
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -72,31 +69,32 @@
|
|||||||
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
<a-input-number v-model="client.limitIp" min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
<a-form-item v-if="client.email && client.limitIp > 0 && isEdit">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlog" }}</span>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
<span>{{ i18n "pages.inbounds.IPLimitlogclear" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<span style="color: #FF4D4F">
|
<span style="color: #FF4D4F">
|
||||||
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
<a-icon type="delete" @click="clearDBClientIps(client.email)"></a-icon>
|
||||||
</span>
|
</span>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-form layout="block">
|
<a-form layout="block">
|
||||||
<a-textarea id="clientIPs" readonly
|
<a-textarea id="clientIPs" readonly
|
||||||
@click="getDBClientIps(client.email,$event)"
|
@click="getDBClientIps(client.email)"
|
||||||
placeholder="Click To Get IPs"
|
placeholder="Click To Get IPs"
|
||||||
:auto-size="{ minRows: 2, maxRows: 10 }">
|
:auto-size="{ minRows: 5, maxRows: 10 }"
|
||||||
</a-textarea>
|
>
|
||||||
</a-form>
|
</a-textarea>
|
||||||
</a-form-item>
|
</a-form>
|
||||||
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item v-if="inbound.xtls" label="Flow">
|
<a-form-item v-if="inbound.xtls" label="Flow">
|
||||||
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="client.flow" style="width: 200px" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{{define "form/inbound"}}
|
{{define "form/inbound"}}
|
||||||
<!-- base -->
|
<!-- base -->
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "remark" }}'>
|
|
||||||
<a-input v-model.trim="dbInbound.remark"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "enable" }}'>
|
<a-form-item label='{{ i18n "enable" }}'>
|
||||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label='{{ i18n "remark" }}'>
|
||||||
|
<a-input v-model.trim="dbInbound.remark"></a-input>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "protocol" }}'>
|
<a-form-item label='{{ i18n "protocol" }}'>
|
||||||
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.protocol" style="width: 160px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass"
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
v-model="dbInbound._expiryTime" style="width: 300px;"></a-date-picker>
|
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
{{define "form/http"}}
|
{{define "form/http"}}
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "username"}}'>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
<a-row>
|
||||||
</a-form-item>
|
<a-button type="primary" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
</a-row>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
||||||
|
<a-input style="width: 45%" v-model.trim="account.user"
|
||||||
|
addon-before='{{ i18n "username" }}'></a-input>
|
||||||
|
<a-input style="width: 55%" v-model.trim="account.pass"
|
||||||
|
addon-before='{{ i18n "password" }}'>
|
||||||
|
<template slot="addonAfter">
|
||||||
|
<a-button type="primary" size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
{{define "form/shadowsocks"}}
|
{{define "form/shadowsocks"}}
|
||||||
<a-form layout="inline" style="padding: 10px 0px;">
|
<a-form layout="inline" style="padding: 10px 0px;">
|
||||||
|
<template v-if="inbound.isSSMultiUser">
|
||||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit">
|
||||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -11,14 +12,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Password">
|
||||||
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
<a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.password" style="width: 250px;"></a-input>
|
<a-input v-model.trim="client.password" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Subscription
|
Subscription
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -28,10 +29,10 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Telegram ID
|
Telegram ID
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -96,22 +97,26 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Password</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.password ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form layout="inline">
|
<a-form layout="inline">
|
||||||
<a-form-item label='{{ i18n "encryption" }}'>
|
<a-form-item label='{{ i18n "encryption" }}'>
|
||||||
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.darkCardClass" @change="SSMethodChange">
|
||||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item v-if="inbound.isSS2022" label='{{ i18n "password" }}'>
|
||||||
|
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
<a-input v-model.trim="inbound.settings.password" style="width: 250px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||||
|
|||||||
@@ -6,11 +6,20 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<template v-if="inbound.settings.auth === 'password'">
|
<template v-if="inbound.settings.auth === 'password'">
|
||||||
<a-form-item label='{{ i18n "username" }}'>
|
<a-form-item>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].user"></a-input>
|
<a-row>
|
||||||
</a-form-item>
|
<a-button type="primary" size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
</a-row>
|
||||||
<a-input v-model.trim="inbound.settings.accounts[0].pass"></a-input>
|
<a-input-group v-for="(account, index) in inbound.settings.accounts">
|
||||||
|
<a-input style="width: 45%" v-model.trim="account.user"
|
||||||
|
addon-before='{{ i18n "username" }}'></a-input>
|
||||||
|
<a-input style="width: 55%" v-model.trim="account.pass"
|
||||||
|
addon-before='{{ i18n "password" }}'>
|
||||||
|
<template slot="addonAfter">
|
||||||
|
<a-button type="primary" size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@@ -11,13 +11,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Password">
|
<a-form-item label="Password">
|
||||||
|
<a-icon @click="client.password = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.password" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Subscription
|
Subscription
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -27,10 +28,10 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Telegram ID
|
Telegram ID
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -101,10 +102,12 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.trojans.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.trojans[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Password</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.trojans" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.password ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -11,14 +11,14 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Subscription
|
Subscription
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -28,10 +28,10 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Telegram ID
|
Telegram ID
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -108,10 +108,14 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.vlesses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vlesses[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>Flow</th>
|
||||||
|
<th>ID</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.vlesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.flow ]]</td>
|
||||||
|
<td>[[ client.id ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|||||||
@@ -11,19 +11,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.email = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.email = RandomUtil.randomLowerAndNum(8)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
<a-input v-model.trim="client.email" style="width: 200px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "additional" }} ID'>
|
|
||||||
<a-input-number v-model="client.alterId"></a-input-number>
|
|
||||||
</a-form-item>
|
|
||||||
<br>
|
|
||||||
<a-form-item label="ID">
|
<a-form-item label="ID">
|
||||||
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
<a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
<a-input v-model.trim="client.id" style="width: 300px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Subscription
|
Subscription
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -33,10 +29,10 @@
|
|||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
<a-icon @click="client.subId = RandomUtil.randomText()" type="sync"> </a-icon>
|
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"> </a-icon>
|
||||||
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
<a-input v-model.trim="client.subId" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item v-if="client.email">
|
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||||
<span slot="label">
|
<span slot="label">
|
||||||
Telegram ID
|
Telegram ID
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
@@ -101,18 +97,15 @@
|
|||||||
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
<a-collapse-panel :header="'{{ i18n "pages.client.clientCount" }}: ' + inbound.settings.vmesses.length">
|
||||||
<table width="100%">
|
<table width="100%">
|
||||||
<tr class="client-table-header">
|
<tr class="client-table-header">
|
||||||
<th v-for="col in Object.keys(inbound.settings.vmesses[0]).slice(0, 3)">[[ col ]]</th>
|
<th>{{ i18n "pages.inbounds.email" }}</th>
|
||||||
|
<th>ID</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
<tr v-for="(client, index) in inbound.settings.vmesses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''">
|
||||||
<td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td>
|
<td>[[ client.email ]]</td>
|
||||||
|
<td>[[ client.id ]]</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
</a-collapse>
|
</a-collapse>
|
||||||
</a-form>
|
</a-form>
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.disableInsecureEncryption" }}'>
|
|
||||||
<a-switch v-model.number="inbound.settings.disableInsecure"></a-switch>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
Sniffing
|
Sniffing
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span >{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model="inbound.stream.kcp.seed"></a-input>
|
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
|
<a-input v-model="inbound.stream.kcp.seed" style="width: 150px;" ></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<br>
|
<br>
|
||||||
<a-form-item label="MTU">
|
<a-form-item label="MTU">
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "password" }}'>
|
<a-form-item label='{{ i18n "password" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="inbound.stream.quic.key" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||||
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
<a-select v-model="inbound.stream.quic.type" style="width: 280px;" :dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
|||||||
@@ -42,4 +42,9 @@
|
|||||||
<template v-if="inbound.stream.network === 'grpc'">
|
<template v-if="inbound.stream.network === 'grpc'">
|
||||||
{{template "form/streamGRPC"}}
|
{{template "form/streamGRPC"}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- sockopt -->
|
||||||
|
<template>
|
||||||
|
{{template "form/streamSockopt"}}
|
||||||
|
</template>
|
||||||
{{end}}
|
{{end}}
|
||||||
46
web/html/xui/form/stream/stream_sockopt.html
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{{define "form/streamSockopt"}}
|
||||||
|
<a-form layout="inline">
|
||||||
|
<a-form-item label="Transparent Proxy">
|
||||||
|
<a-switch v-model="inbound.stream.sockoptSwitch"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<table width="100%" class="ant-table-tbody" v-if="inbound.stream.sockoptSwitch">
|
||||||
|
<tr>
|
||||||
|
<td>Accept Proxy Protocol</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>TCP FastOpen</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Route Mark</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>T-Proxy</td>
|
||||||
|
<td>
|
||||||
|
<a-form-item>
|
||||||
|
<a-select v-model="inbound.stream.sockopt.tproxy" style="width: 250px;"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
<a-select-option value="off">OFF</a-select-option>
|
||||||
|
<a-select-option value="redirect">Redirect</a-select-option>
|
||||||
|
<a-select-option value="tproxy">T-Proxy</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</a-form>
|
||||||
|
{{end}}
|
||||||
@@ -25,11 +25,11 @@
|
|||||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.request.path[index]"></a-input>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<br>
|
||||||
|
<a-form-item>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
+
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('Host', 'xxx.com')">+</a-button>
|
||||||
</a-button>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
<a-input-group v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
@@ -37,13 +37,12 @@
|
|||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small" @click="inbound.stream.tcp.request.removeHeader(index)">
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- tcp response -->
|
<!-- tcp response -->
|
||||||
@@ -57,11 +56,10 @@
|
|||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
<a-form-item>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button size="small" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">
|
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
|
||||||
+
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||||
</a-button>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
<a-input-group v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
@@ -69,9 +67,7 @@
|
|||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -3,16 +3,15 @@
|
|||||||
<a-form-item label="AcceptProxyProtocol">
|
<a-form-item label="AcceptProxyProtocol">
|
||||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
<br>
|
||||||
<a-form layout="inline">
|
|
||||||
<a-form-item label='{{ i18n "path" }}'>
|
<a-form-item label='{{ i18n "path" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.stream.general.requestHeader" }}'>
|
<br>
|
||||||
|
<a-form-item>
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-button size="small" @click="inbound.stream.ws.addHeader('Host', '')">
|
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||||
+
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader('Host', '')">+</a-button>
|
||||||
</a-button>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
<a-input-group v-for="(header, index) in inbound.stream.ws.headers">
|
||||||
<a-input style="width: 50%" v-model.trim="header.name"
|
<a-input style="width: 50%" v-model.trim="header.name"
|
||||||
@@ -20,9 +19,7 @@
|
|||||||
<a-input style="width: 50%" v-model.trim="header.value"
|
<a-input style="width: 50%" v-model.trim="header.value"
|
||||||
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
addon-before='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||||
<template slot="addonAfter">
|
<template slot="addonAfter">
|
||||||
<a-button size="small" @click="inbound.stream.ws.removeHeader(index)">
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
||||||
-
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
Reality
|
Reality
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.realityDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
XTLS
|
XTLS
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
<span>{{ i18n "pages.inbounds.xtlsDesc" }}</span>
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
@@ -33,7 +33,24 @@
|
|||||||
|
|
||||||
<!-- tls settings -->
|
<!-- tls settings -->
|
||||||
<a-form v-if="inbound.tls" layout="inline">
|
<a-form v-if="inbound.tls" layout="inline">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='Multi Domain'>
|
||||||
|
<a-switch v-model="multiDomain"></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-if="multiDomain">
|
||||||
|
<a-row>
|
||||||
|
<span>Domains:</span>
|
||||||
|
<a-button v-if="multiDomain" type="primary" size="small" @click="inbound.stream.tls.settings.domains.push({remark: '', domain: ''})" style="margin-left: 10px">+</a-button>
|
||||||
|
</a-row>
|
||||||
|
<a-input-group v-for="(row, index) in inbound.stream.tls.settings.domains">
|
||||||
|
<a-input style="width: 40%" v-model.trim="row.remark" addon-before='{{ i18n "remark" }}'></a-input>
|
||||||
|
<a-input style="width: 60%" v-model.trim="row.domain" addon-before='{{ i18n "host" }}'>
|
||||||
|
<template slot="addonAfter">
|
||||||
|
<a-button type="primary" size="small" style="margin-left: 10px" @click="inbound.stream.tls.settings.domains.splice(index, 1)">-</a-button>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-input-group>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item v-else label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
<a-input v-model.trim="inbound.stream.tls.server" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="CipherSuites">
|
<a-form-item label="CipherSuites">
|
||||||
@@ -63,40 +80,57 @@
|
|||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Alpn">
|
<a-form-item label="Alpn">
|
||||||
<a-checkbox-group v-model="inbound.stream.tls.alpn" style="width:200px">
|
<a-select
|
||||||
<a-checkbox v-for="key,value in ALPN_OPTION" :value="key">[[ value ]]</a-checkbox>
|
mode="multiple"
|
||||||
</a-checkbox-group>
|
style="width: 250px"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass"
|
||||||
|
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>
|
||||||
|
<br>
|
||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<br>
|
||||||
<a-radio-group v-model="inbound.stream.tls.certs[0].useFile" button-style="solid">
|
<a-form-item label="Reject Unknown SNI">
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
|
||||||
</a-radio-group>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="inbound.stream.tls.certs[0].useFile">
|
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].certFile" style="width:300px;"></a-input>
|
<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>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
<template v-if="cert.useFile">
|
||||||
<a-input v-model.trim="inbound.stream.tls.certs[0].keyFile" style="width:300px;"></a-input>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||||
</a-form-item>
|
<a-input v-model.trim="cert.certFile" style="width:300px;"></a-input>
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertData">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
</a-form-item>
|
||||||
</template>
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
<template v-else>
|
<a-input v-model.trim="cert.keyFile" style="width:300px;"></a-input>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
</a-form-item>
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].cert"></a-input>
|
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
</a-form-item>
|
</template>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
<template v-else>
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.tls.certs[0].key"></a-input>
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" 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:300px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
<br>
|
||||||
|
<a-form-item label="ocspStapling">
|
||||||
|
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
<!-- xtls settings -->
|
<!-- xtls settings -->
|
||||||
<a-form v-if="inbound.xtls" layout="inline">
|
<a-form v-else-if="inbound.xtls" layout="inline">
|
||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
<a-input v-model.trim="inbound.stream.xtls.server"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@@ -111,28 +145,32 @@
|
|||||||
<a-form-item label="Allow insecure">
|
<a-form-item label="Allow insecure">
|
||||||
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
<a-switch v-model="inbound.stream.xtls.settings.allowInsecure"></a-switch>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label='{{ i18n "certificate" }}'>
|
<template v-for="cert,index in inbound.stream.xtls.certs">
|
||||||
<a-radio-group v-model="inbound.stream.xtls.certs[0].useFile" button-style="solid">
|
<a-form-item label='{{ i18n "certificate" }}'>
|
||||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||||
</a-radio-group>
|
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||||
</a-form-item>
|
</a-radio-group>
|
||||||
<template v-if="inbound.stream.xtls.certs[0].useFile">
|
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.xtls.addCert()" style="margin-left: 10px">+</a-button>
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
<a-button v-if="inbound.stream.xtls.certs.length>1" type="primary" size="small" @click="inbound.stream.xtls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||||
<a-input v-model.trim="inbound.stream.xtls.certs[0].certFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
|
||||||
<a-input v-model.trim="inbound.stream.xtls.certs[0].keyFile" style="width:300px;"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-button type="primary" icon="import" @click="setDefaultCertXtls">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].cert"></a-input>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
|
||||||
<a-input type="textarea" :rows="3" style="width:300px;" v-model="inbound.stream.xtls.certs[0].key"></a-input>
|
|
||||||
</a-form-item>
|
</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:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||||
|
<a-input v-model.trim="cert.keyFile" style="width:300px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button type="primary" icon="import" @click="setDefaultCertXtls(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||||
|
<a-input type="textarea" :rows="3" style="width:300px;" 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:300px;" v-model="cert.key"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
||||||
@@ -154,14 +192,19 @@
|
|||||||
<a-form-item label='{{ i18n "domainName" }}'>
|
<a-form-item label='{{ i18n "domainName" }}'>
|
||||||
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.settings.serverName" style="width: 250px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="dest">
|
<a-form-item label="Dest">
|
||||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Server Names">
|
<a-form-item label="Server Names">
|
||||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 300px"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="ShortIds">
|
<a-form-item label="ShortIds">
|
||||||
<a-input v-model.trim="inbound.stream.reality.shortIds"></a-input>
|
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId()" type="sync"> </a-icon>
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width: 150px;"></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
<br>
|
||||||
|
<a-form-item label="SpiderX">
|
||||||
|
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width: 150px;"></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="Private Key">
|
<a-form-item label="Private Key">
|
||||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 300px"></a-input>
|
||||||
|
|||||||
@@ -29,18 +29,31 @@
|
|||||||
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
<a-tag v-if="!isClientEnabled(record, client.email)" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, client">
|
<template slot="traffic" slot-scope="text, client">
|
||||||
<a-tag color="blue">
|
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
||||||
[[ sizeFormat(getUpStats(record, client.email)) ]] / [[ sizeFormat(getDownStats(record, client.email)) ]]
|
<template slot="content" v-if="client.email">
|
||||||
</a-tag>
|
<table cellpadding="2" width="100%">
|
||||||
<template v-if="client._totalGB > 0">
|
<tr>
|
||||||
<a-tag v-if="isTrafficExhausted(record, client.email)" color="red">[[client._totalGB]] GB</a-tag>
|
<td>↑[[ sizeFormat(getUpStats(record, client.email)) ]]</td>
|
||||||
<a-tag v-else color="cyan">[[client._totalGB]] GB</a-tag>
|
<td>↓[[ sizeFormat(getDownStats(record, client.email)) ]]</td>
|
||||||
</template>
|
</tr>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<tr v-if="client.totalGB > 0">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(client.totalGB - getUpStats(record, client.email) - getDownStats(record, client.email)) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="statsColor(record, client.email)">
|
||||||
|
[[ sizeFormat(getUpStats(record, client.email) + getDownStats(record, client.email)) ]] /
|
||||||
|
<template v-if="client.totalGB > 0">[[client._totalGB]]GB</template>
|
||||||
|
<template v-else>
|
||||||
|
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
|
||||||
|
</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="expiryTime" slot-scope="text, client, index">
|
<template slot="expiryTime" slot-scope="text, client, index">
|
||||||
<template v-if="client.expiryTime > 0">
|
<template v-if="client.expiryTime > 0">
|
||||||
<a-tag :color="isExpiry(record, index)? 'red' : 'blue'">
|
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, client.expiryTime)">
|
||||||
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
[[ DateUtil.formatMillis(client._expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,8 +8,7 @@
|
|||||||
width="600px"
|
width="600px"
|
||||||
>
|
>
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr><td>
|
||||||
<td>
|
|
||||||
<table>
|
<table>
|
||||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="green">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag color="blue">[[ dbInbound.address ]]</a-tag></td></tr>
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
<td>{{ i18n "transmission" }}</td><td><a-tag color="green">[[ inbound.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||||
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
<tr v-if="inbound.host"><td>{{ i18n "host" }}</td><td><a-tag color="green">[[ inbound.host ]]</a-tag></td></tr>
|
||||||
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
<tr v-else><td>{{ i18n "host" }}</td><td><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||||
@@ -46,8 +44,7 @@
|
|||||||
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
<tr><td>grpc multiMode</td><td><a-tag color="green">[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||||
</template>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td></tr>
|
||||||
</tr>
|
|
||||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||||
<td v-if="inbound.tls">
|
<td v-if="inbound.tls">
|
||||||
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
tls: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
@@ -59,49 +56,79 @@
|
|||||||
</td>
|
</td>
|
||||||
<td v-else-if="inbound.reality">
|
<td v-else-if="inbound.reality">
|
||||||
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
reality: <a-tag color="green">{{ i18n "enabled" }}</a-tag><br />
|
||||||
reality {{ i18n "domainName" }}: <a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
reality Destination: <a-tag :color="inbound.stream.reality.dest ? 'green' : 'orange'">[[ inbound.stream.reality.dest ]]</a-tag>
|
||||||
</td>
|
|
||||||
<td v-else>
|
|
||||||
tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag>
|
|
||||||
</td>
|
</td>
|
||||||
|
<td v-else>tls: <a-tag color="red">{{ i18n "disabled" }}</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "encryption" }}</td>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
||||||
|
</tr><tr v-if="inbound.isSS2022">
|
||||||
|
<td>{{ i18n "password" }}</td>
|
||||||
|
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
||||||
|
</tr><tr>
|
||||||
|
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||||
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<template v-if="infoModal.clientSettings">
|
<template v-if="infoModal.clientSettings">
|
||||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||||
<table style="margin-bottom: 10px;">
|
<table style="margin-bottom: 10px;">
|
||||||
<tr v-for="col,index in Object.keys(infoModal.clientSettings).slice(0, 3)">
|
|
||||||
<td>[[ col ]]</td>
|
|
||||||
<td><a-tag color="green">[[ infoModal.clientSettings[col] ]]</a-tag></td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>{{ i18n "status" }}</td>
|
|
||||||
<td>
|
|
||||||
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
|
||||||
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
|
||||||
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<table style="margin-bottom: 10px; width: 100%;">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "usage" }}</th>
|
<td>{{ i18n "pages.inbounds.email" }}</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.email ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.id">
|
||||||
|
<td>ID</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.id ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.inbound.canEnableTlsFlow()">
|
||||||
|
<td>Flow</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientSettings.password">
|
||||||
|
<td>Password</td>
|
||||||
|
<td><a-tag color="green">[[ infoModal.clientSettings.password ]]</a-tag></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{ i18n "status" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag v-if="isEnable" color="blue">{{ i18n "enabled" }}</a-tag>
|
||||||
|
<a-tag v-else color="red">{{ i18n "disabled" }}</a-tag>
|
||||||
|
<a-tag v-if="!isActive" color="red">{{ i18n "depleted" }}</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="infoModal.clientStats">
|
||||||
|
<td>{{ i18n "usage" }}</td>
|
||||||
|
<td>
|
||||||
|
<a-tag color="green">[[ sizeFormat(infoModal.clientStats.up + infoModal.clientStats.down) ]]</a-tag>
|
||||||
|
<a-tag color="blue">↑ [[ sizeFormat(infoModal.clientStats.up) ]] / [[ sizeFormat(infoModal.clientStats.down) ]] ↓</a-tag>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table style="margin-bottom: 10px; width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n "remained" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
<th>{{ i18n "pages.inbounds.totalFlow" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
<th>{{ i18n "pages.inbounds.expireDate" }}</th>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientStats" :color="statsColor(infoModal.clientStats)">
|
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
[[ sizeFormat(infoModal.clientStats['up']) ]] /
|
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
||||||
[[ sizeFormat(infoModal.clientStats['down']) ]]
|
|
||||||
([[ sizeFormat(infoModal.clientStats['up'] + infoModal.clientStats['down']) ]])
|
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">[[ sizeFormat(infoModal.clientSettings.totalGB) ]]</a-tag>
|
<a-tag v-if="infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||||
|
[[ sizeFormat(infoModal.clientSettings.totalGB) ]]
|
||||||
|
</a-tag>
|
||||||
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
<a-tag v-else color="green">{{ i18n "indefinite" }}</a-tag>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
<template v-if="infoModal.clientSettings.expiryTime > 0">
|
||||||
<a-tag :color="infoModal.isExpired ? 'red' : 'blue'">
|
<a-tag :color="usageColor(new Date().getTime(), app.expireDiff, infoModal.clientSettings.expiryTime)">
|
||||||
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
[[ DateUtil.formatMillis(infoModal.clientSettings.expiryTime) ]]
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
@@ -110,48 +137,74 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="infoModal.clientSettings.subId + infoModal.clientSettings.tgId" style="margin-bottom: 10px;">
|
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||||
<tr v-if="infoModal.clientSettings.subId">
|
<a-divider>Subscription link</a-divider>
|
||||||
<td>Subscription link</td>
|
<a-row>
|
||||||
<td><a :href="[[ subBase + infoModal.clientSettings.subId ]]" target="_blank">[[ subBase + infoModal.clientSettings.subId ]]</a></td>
|
<a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||||
<td><a-icon id="copy-sub-link" type="snippets" @click="copyToClipboard('copy-sub-link', subBase + infoModal.clientSettings.subId)"></a-icon></td>
|
<a-col :span="2">
|
||||||
</tr>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
<tr v-if="infoModal.clientSettings.tgId">
|
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||||
<td>Telegram ID</td>
|
<a-icon type="snippets"></a-icon>
|
||||||
<td><a :href="[[ tgBase + infoModal.clientSettings.tgId ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></td>
|
</button>
|
||||||
</tr>
|
</a-tooltip>
|
||||||
</table>
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||||
|
<a-divider>Telegram Username</a-divider>
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||||
|
<a-col :span="2">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" id="copy-tg-link" @click="copyToClipboard('copy-tg-link', '@' + infoModal.clientSettings.tgId)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
<template v-if="dbInbound.hasLink()">
|
||||||
|
<a-divider>URL</a-divider>
|
||||||
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
|
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
|
<a-col :span="2" style="text-align: right;">
|
||||||
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
|
<a-icon type="snippets"></a-icon>
|
||||||
|
</button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<a-divider></a-divider>
|
<template v-if="dbInbound.isSS && !inbound.isSSMultiUser">
|
||||||
<table v-if="inbound.protocol == Protocols.SHADOWSOCKS" style="margin-bottom: 10px; width: 100%;">
|
<a-divider>URL</a-divider>
|
||||||
<tr>
|
<a-row v-for="(link,index) in infoModal.links">
|
||||||
<th>{{ i18n "encryption" }}</th>
|
<a-col :span="22"><a-tag color="cyan">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||||
<th>{{ i18n "password" }}</th>
|
<a-col :span="2" style="text-align: right;">
|
||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<a-tooltip title='{{ i18n "copy" }}'>
|
||||||
</tr>
|
<button class="ant-btn ant-btn-primary" :id="'copy-url-link-'+index" @click="copyToClipboard('copy-url-link-'+index, link.link)">
|
||||||
<tr>
|
<a-icon type="snippets"></a-icon>
|
||||||
<td><a-tag color="green">[[ inbound.settings.method ]]</a-tag></td>
|
</button>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.password ]]</a-tag></td>
|
</a-tooltip>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
</a-col>
|
||||||
</tr>
|
</a-row>
|
||||||
</table>
|
</template>
|
||||||
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="inbound.protocol == Protocols.DOKODEMO" style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
<th>{{ i18n "pages.inbounds.targetAddress" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
<th>{{ i18n "pages.inbounds.destinationPort" }}</th>
|
||||||
<th>{{ i18n "pages.inbounds.network" }}</th>
|
<th>{{ i18n "pages.inbounds.network" }}</th>
|
||||||
<th>FollowRedirect</th>
|
<th>FollowRedirect</th>
|
||||||
</tr>
|
</tr><tr>
|
||||||
<tr>
|
|
||||||
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.address ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.port ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.network ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.followRedirect ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</table>
|
<table v-if="dbInbound.isSocks" style="margin-bottom: 10px; width: 100%;">
|
||||||
<table v-if="inbound.protocol == Protocols.SOCKS" style="margin-bottom: 10px; width: 100%;">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ i18n "password" }} Auth</th>
|
<th>{{ i18n "password" }} Auth</th>
|
||||||
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
<th>{{ i18n "pages.inbounds.enable" }} udp</th>
|
||||||
@@ -162,40 +215,32 @@
|
|||||||
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
<td><a-tag color="blue">[[ inbound.settings.udp]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
<td><a-tag color="green">[[ inbound.settings.ip ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-if="inbound.settings.auth == 'password'">
|
<template v-if="inbound.settings.auth == 'password'">
|
||||||
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td>{{ i18n "username" }}</td>
|
<td>{{ i18n "username" }}</td>
|
||||||
<td>{{ i18n "password" }}</td>
|
<td>{{ i18n "password" }}</td>
|
||||||
</tr>
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<tr v-for="account,index in inbound.settings.accounts">
|
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</template>
|
||||||
</table>
|
</table>
|
||||||
<table v-if="inbound.protocol == Protocols.HTTP" style="margin-bottom: 10px; width: 100%;">
|
<table v-if="dbInbound.isHTTP" style="margin-bottom: 10px; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th>{{ i18n "username" }}</th>
|
<th>{{ i18n "username" }}</th>
|
||||||
<th>{{ i18n "password" }}</th>
|
<th>{{ i18n "password" }}</th>
|
||||||
</tr>
|
</tr><tr v-for="account,index in inbound.settings.accounts">
|
||||||
<tr v-for="account,index in inbound.settings.accounts">
|
|
||||||
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
<td><a-tag color="green">[[ index ]]</a-tag></td>
|
||||||
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
<td><a-tag color="blue">[[ account.user ]]</a-tag></td>
|
||||||
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
<td><a-tag color="green">[[ account.pass ]]</a-tag></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</table>
|
|
||||||
</template>
|
</template>
|
||||||
<div v-if="dbInbound.hasLink()">
|
|
||||||
<a-divider>URL</a-divider>
|
|
||||||
<p>[[ infoModal.link ]]</p>
|
|
||||||
<button class="ant-btn ant-btn-primary" id="copy-url-link" @click="copyToClipboard('copy-url-link', infoModal.link)"><a-icon type="snippets"></a-icon>{{ i18n "copy" }}</button>
|
|
||||||
</div>
|
|
||||||
</a-modal>
|
</a-modal>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const infoModal = {
|
const infoModal = {
|
||||||
visible: false,
|
visible: false,
|
||||||
inbound: new Inbound(),
|
inbound: new Inbound(),
|
||||||
@@ -206,23 +251,53 @@
|
|||||||
upStats: 0,
|
upStats: 0,
|
||||||
downStats: 0,
|
downStats: 0,
|
||||||
clipboard: null,
|
clipboard: null,
|
||||||
link: null,
|
links: [],
|
||||||
index: null,
|
index: null,
|
||||||
isExpired: false,
|
isExpired: false,
|
||||||
|
subLink: '',
|
||||||
|
tgLink: '',
|
||||||
show(dbInbound, index) {
|
show(dbInbound, index) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.inbound = dbInbound.toInbound();
|
this.inbound = dbInbound.toInbound();
|
||||||
this.dbInbound = new DBInbound(dbInbound);
|
this.dbInbound = new DBInbound(dbInbound);
|
||||||
this.link = dbInbound.genLink(index);
|
|
||||||
this.settings = JSON.parse(this.inbound.settings);
|
this.settings = JSON.parse(this.inbound.settings);
|
||||||
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
this.clientSettings = this.settings.clients ? Object.values(this.settings.clients)[index] : null;
|
||||||
this.isExpired = this.inbound.isExpiry(index);
|
this.isExpired = this.inbound.isExpiry(index);
|
||||||
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
this.clientStats = this.settings.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||||
|
remark = [this.dbInbound.remark, ( this.clientSettings ? this.clientSettings.email : '')].filter(Boolean).join('-');
|
||||||
|
address = this.dbInbound.address;
|
||||||
|
this.links = [];
|
||||||
|
if (this.inbound.tls && !ObjectUtil.isArrEmpty(this.inbound.stream.tls.settings.domains)) {
|
||||||
|
this.inbound.stream.tls.settings.domains.forEach((domain) => {
|
||||||
|
remarkText = [remark, domain.remark].filter(Boolean).join('-');
|
||||||
|
this.links.push({
|
||||||
|
remark: remarkText,
|
||||||
|
link: this.inbound.genLink(domain.domain, remarkText, index)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.links.push({
|
||||||
|
remark: remark,
|
||||||
|
link: this.inbound.genLink(address, remark, index)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.clientSettings) {
|
||||||
|
if (this.clientSettings.subId) {
|
||||||
|
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||||
|
}
|
||||||
|
if (this.clientSettings.tgId) {
|
||||||
|
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
infoModal.visible = false;
|
infoModal.visible = false;
|
||||||
},
|
},
|
||||||
|
genSubLink(subID) {
|
||||||
|
const { domain: host, port, tls: isTLS, path: base } = app.subSettings;
|
||||||
|
return buildURL({ host, port, isTLS, base, path: subID+'?name='+remark });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const infoModalApp = new Vue({
|
const infoModalApp = new Vue({
|
||||||
@@ -248,12 +323,6 @@
|
|||||||
}
|
}
|
||||||
return infoModal.dbInbound.isEnable;
|
return infoModal.dbInbound.isEnable;
|
||||||
},
|
},
|
||||||
get subBase() {
|
|
||||||
return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "") + basePath + "sub/";
|
|
||||||
},
|
|
||||||
get tgBase() {
|
|
||||||
return "https://t.me/"
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
copyToClipboard(elmentId, content) {
|
copyToClipboard(elmentId, content) {
|
||||||
@@ -266,10 +335,7 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
statsColor(stats) {
|
statsColor(stats) {
|
||||||
if (!stats) return 'blue'
|
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||||
if (stats['total'] === 0) return 'blue'
|
|
||||||
else if (stats['total'] > 0 && (stats['down'] + stats['up']) < stats['total']) return 'cyan'
|
|
||||||
else return 'red'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,23 +54,11 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const protocols = {
|
|
||||||
VMESS: Protocols.VMESS,
|
|
||||||
VLESS: Protocols.VLESS,
|
|
||||||
TROJAN: Protocols.TROJAN,
|
|
||||||
SHADOWSOCKS: Protocols.SHADOWSOCKS,
|
|
||||||
DOKODEMO: Protocols.DOKODEMO,
|
|
||||||
SOCKS: Protocols.SOCKS,
|
|
||||||
HTTP: Protocols.HTTP,
|
|
||||||
};
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
delimiters: ['[[', ']]'],
|
delimiters: ['[[', ']]'],
|
||||||
el: '#inbound-modal',
|
el: '#inbound-modal',
|
||||||
data: {
|
data: {
|
||||||
inModal: inModal,
|
inModal: inModal,
|
||||||
Protocols: protocols,
|
|
||||||
SSMethods: SSMethods,
|
|
||||||
delayedStart: false,
|
delayedStart: false,
|
||||||
get inbound() {
|
get inbound() {
|
||||||
return inModal.inbound;
|
return inModal.inbound;
|
||||||
@@ -90,6 +78,18 @@
|
|||||||
set delayedExpireDays(days) {
|
set delayedExpireDays(days) {
|
||||||
this.client.expiryTime = -86400000 * days;
|
this.client.expiryTime = -86400000 * days;
|
||||||
},
|
},
|
||||||
|
get multiDomain() {
|
||||||
|
return this.inbound.stream.tls.settings.domains.length > 0;
|
||||||
|
},
|
||||||
|
set multiDomain(value) {
|
||||||
|
if (value) {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [{ remark: "", domain: window.location.hostname }];
|
||||||
|
} else {
|
||||||
|
inModal.inbound.stream.tls.server = "";
|
||||||
|
inModal.inbound.stream.tls.settings.domains = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
streamNetworkChange() {
|
streamNetworkChange() {
|
||||||
@@ -99,14 +99,39 @@
|
|||||||
if (!inModal.inbound.canEnableReality()) {
|
if (!inModal.inbound.canEnableReality()) {
|
||||||
this.inModal.inbound.reality = false;
|
this.inModal.inbound.reality = false;
|
||||||
}
|
}
|
||||||
|
if (this.inModal.inbound.protocol == Protocols.VLESS && !inModal.inbound.canEnableTlsFlow()) {
|
||||||
|
this.inModal.inbound.settings.vlesses.forEach(client => {
|
||||||
|
client.flow = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setDefaultCertData() {
|
SSMethodChange() {
|
||||||
inModal.inbound.stream.tls.certs[0].certFile = app.defaultCert;
|
if (this.inModal.inbound.isSSMultiUser) {
|
||||||
inModal.inbound.stream.tls.certs[0].keyFile = app.defaultKey;
|
if (this.inModal.inbound.settings.shadowsockses.length ==0){
|
||||||
|
this.inModal.inbound.settings.shadowsockses = [new Inbound.ShadowsocksSettings.Shadowsocks()];
|
||||||
|
}
|
||||||
|
if (!this.inModal.inbound.isSS2022) {
|
||||||
|
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||||
|
client.method = this.inModal.inbound.settings.method;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.inModal.inbound.settings.shadowsockses.forEach(client => {
|
||||||
|
client.method = "";
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.inModal.inbound.settings.shadowsockses.length > 0){
|
||||||
|
this.inModal.inbound.settings.shadowsockses = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setDefaultCertXtls() {
|
setDefaultCertData(index) {
|
||||||
inModal.inbound.stream.xtls.certs[0].certFile = app.defaultCert;
|
inModal.inbound.stream.tls.certs[index].certFile = app.defaultCert;
|
||||||
inModal.inbound.stream.xtls.certs[0].keyFile = app.defaultKey;
|
inModal.inbound.stream.tls.certs[index].keyFile = app.defaultKey;
|
||||||
|
},
|
||||||
|
setDefaultCertXtls(index) {
|
||||||
|
inModal.inbound.stream.xtls.certs[index].certFile = app.defaultCert;
|
||||||
|
inModal.inbound.stream.xtls.certs[index].keyFile = app.defaultKey;
|
||||||
},
|
},
|
||||||
async getNewX25519Cert() {
|
async getNewX25519Cert() {
|
||||||
inModal.loading(true);
|
inModal.loading(true);
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
.ant-col-sm-24 {
|
.ant-col-sm-24 {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
tr.hideExpandIcon .ant-table-row-expand-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -105,11 +108,25 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</div>
|
</div>
|
||||||
<a-input v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
<a-switch v-model="enableFilter"
|
||||||
|
checked-children='{{ i18n "search" }}' un-checked-children='{{ i18n "filter" }}'
|
||||||
|
@change="toggleFilter" style="margin-right: 10px;">
|
||||||
|
</a-switch>
|
||||||
|
<a-input v-if="!enableFilter" v-model.lazy="searchKey" placeholder='{{ i18n "search" }}' autofocus style="max-width: 300px"></a-input>
|
||||||
|
<a-radio-group v-if="enableFilter" v-model="filterBy" @change="filterInbounds" button-style="solid">
|
||||||
|
<a-radio-button value="">{{ i18n "none" }}</a-radio-button>
|
||||||
|
<a-radio-button value="deactive">{{ i18n "disabled" }}</a-radio-button>
|
||||||
|
<a-radio-button value="depleted">{{ i18n "depleted" }}</a-radio-button>
|
||||||
|
<a-radio-button value="expiring">{{ i18n "depletingSoon" }}</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
<a-table :columns="columns" :row-key="dbInbound => dbInbound.id"
|
||||||
:data-source="searchedInbounds"
|
:data-source="searchedInbounds"
|
||||||
:loading="spinning" :scroll="{ x: 1300 }"
|
:loading="spinning" :scroll="{ x: 1200 }"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
:expand-icon-as-cell="false"
|
||||||
|
:expand-row-by-click="false"
|
||||||
|
:expand-icon-column-index="0"
|
||||||
|
:row-class-name="dbInbound => (dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser) ? '' : 'hideExpandIcon')"
|
||||||
style="margin-top: 20px"
|
style="margin-top: 20px"
|
||||||
@change="() => getDBInbounds()">
|
@change="() => getDBInbounds()">
|
||||||
<template slot="action" slot-scope="text, dbInbound">
|
<template slot="action" slot-scope="text, dbInbound">
|
||||||
@@ -121,7 +138,11 @@
|
|||||||
<a-icon type="edit"></a-icon>
|
<a-icon type="edit"></a-icon>
|
||||||
{{ i18n "edit" }}
|
{{ i18n "edit" }}
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.isSS">
|
<a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser">
|
||||||
|
<a-icon type="qrcode"></a-icon>
|
||||||
|
{{ i18n "qrCode" }}
|
||||||
|
</a-menu-item>
|
||||||
|
<template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || (dbInbound.isSS && dbInbound.toInbound().isSSMultiUser)">
|
||||||
<a-menu-item key="addClient">
|
<a-menu-item key="addClient">
|
||||||
<a-icon type="user-add"></a-icon>
|
<a-icon type="user-add"></a-icon>
|
||||||
{{ i18n "pages.client.add"}}
|
{{ i18n "pages.client.add"}}
|
||||||
@@ -165,7 +186,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template slot="protocol" slot-scope="text, dbInbound">
|
<template slot="protocol" slot-scope="text, dbInbound">
|
||||||
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
<a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag>
|
||||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan">
|
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||||
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
<a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag>
|
||||||
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
<a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag>
|
||||||
@@ -196,12 +217,29 @@
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template slot="traffic" slot-scope="text, dbInbound">
|
<template slot="traffic" slot-scope="text, dbInbound">
|
||||||
<a-tag color="blue">[[ sizeFormat(dbInbound.up) ]] / [[ sizeFormat(dbInbound.down) ]]</a-tag>
|
<a-popover :overlay-class-name="themeSwitcher.darkClass">
|
||||||
<template v-if="dbInbound.total > 0">
|
<template slot="content">
|
||||||
<a-tag v-if="dbInbound.up + dbInbound.down < dbInbound.total" color="cyan">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<table cellpadding="2" width="100%">
|
||||||
<a-tag v-else color="red">[[ sizeFormat(dbInbound.total) ]]</a-tag>
|
<tr>
|
||||||
</template>
|
<td>↑[[ sizeFormat(dbInbound.up) ]]</td>
|
||||||
<a-tag v-else color="green">{{ i18n "unlimited" }}</a-tag>
|
<td>↓[[ sizeFormat(dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="dbInbound.total > 0 && dbInbound.up + dbInbound.down < dbInbound.total">
|
||||||
|
<td>{{ i18n "remained" }}</td>
|
||||||
|
<td>[[ sizeFormat(dbInbound.total - dbInbound.up - dbInbound.down) ]]</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
<a-tag :color="dbInbound.total == 0 ? 'green' : dbInbound.up + dbInbound.down < dbInbound.total ? 'cyan' : 'red'">
|
||||||
|
[[ sizeFormat(dbInbound.up + dbInbound.down) ]] /
|
||||||
|
<template v-if="dbInbound.total > 0">
|
||||||
|
[[ sizeFormat(dbInbound.total) ]]
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<svg style="fill: currentColor; height: 10px;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M484.4 96C407 96 349.2 164.1 320 208.5C290.8 164.1 233 96 155.6 96C69.75 96 0 167.8 0 256s69.75 160 155.6 160C233.1 416 290.8 347.9 320 303.5C349.2 347.9 407 416 484.4 416C570.3 416 640 344.2 640 256S570.3 96 484.4 96zM155.6 368C96.25 368 48 317.8 48 256s48.25-112 107.6-112c67.75 0 120.5 82.25 137.1 112C276 285.8 223.4 368 155.6 368zM484.4 368c-67.75 0-120.5-82.25-137.1-112C364 226.2 416.6 144 484.4 144C543.8 144 592 194.2 592 256S543.8 368 484.4 368z"/></svg>
|
||||||
|
</template>
|
||||||
|
</a-tag>
|
||||||
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
<template slot="enable" slot-scope="text, dbInbound">
|
<template slot="enable" slot-scope="text, dbInbound">
|
||||||
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
<a-switch v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||||
@@ -224,15 +262,17 @@
|
|||||||
:columns="innerColumns"
|
:columns="innerColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
style="margin-left: 20px;"
|
||||||
>
|
>
|
||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
<a-table
|
<a-table
|
||||||
v-else-if="record.protocol === Protocols.TROJAN || record.protocol === Protocols.SHADOWSOCKS"
|
v-else-if="record.protocol === Protocols.TROJAN || record.toInbound().isSSMultiUser"
|
||||||
:row-key="client => client.id"
|
:row-key="client => client.id"
|
||||||
:columns="innerTrojanColumns"
|
:columns="innerTrojanColumns"
|
||||||
:data-source="getInboundClients(record)"
|
:data-source="getInboundClients(record)"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
|
style="margin-left: 20px;"
|
||||||
>
|
>
|
||||||
{{template "client_table"}}
|
{{template "client_table"}}
|
||||||
</a-table>
|
</a-table>
|
||||||
@@ -247,22 +287,21 @@
|
|||||||
{{template "js" .}}
|
{{template "js" .}}
|
||||||
{{template "component/themeSwitcher" .}}
|
{{template "component/themeSwitcher" .}}
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
const columns = [{
|
const columns = [{
|
||||||
|
title: "ID",
|
||||||
|
align: 'right',
|
||||||
|
dataIndex: "id",
|
||||||
|
width: 30,
|
||||||
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 60,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'action' },
|
scopedSlots: { customRender: 'action' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.enable" }}',
|
title: '{{ i18n "pages.inbounds.enable" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 40,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'enable' },
|
scopedSlots: { customRender: 'enable' },
|
||||||
}, {
|
|
||||||
title: "ID",
|
|
||||||
align: 'center',
|
|
||||||
dataIndex: "id",
|
|
||||||
width: 40,
|
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.remark" }}',
|
title: '{{ i18n "pages.inbounds.remark" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
@@ -281,12 +320,12 @@
|
|||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "clients" }}',
|
title: '{{ i18n "clients" }}',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 50,
|
width: 40,
|
||||||
scopedSlots: { customRender: 'clients' },
|
scopedSlots: { customRender: 'clients' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.traffic" }}↑|↓',
|
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 120,
|
width: 60,
|
||||||
scopedSlots: { customRender: 'traffic' },
|
scopedSlots: { customRender: 'traffic' },
|
||||||
}, {
|
}, {
|
||||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||||
@@ -299,18 +338,18 @@
|
|||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'UID', width: 120, dataIndex: "id" },
|
{ title: 'UUID', width: 120, dataIndex: "id" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const innerTrojanColumns = [
|
const innerTrojanColumns = [
|
||||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 70, scopedSlots: { customRender: 'actions' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 40, scopedSlots: { customRender: 'enable' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.traffic" }}↑|↓', width: 120, scopedSlots: { customRender: 'traffic' } },
|
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 50, scopedSlots: { customRender: 'traffic' } },
|
||||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 70, scopedSlots: { customRender: 'expiryTime' } },
|
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 50, scopedSlots: { customRender: 'expiryTime' } },
|
||||||
{ title: 'Password', width: 170, dataIndex: "password" },
|
{ title: '{{ i18n "password" }}', width: 170, dataIndex: "password" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
@@ -323,15 +362,25 @@
|
|||||||
inbounds: [],
|
inbounds: [],
|
||||||
dbInbounds: [],
|
dbInbounds: [],
|
||||||
searchKey: '',
|
searchKey: '',
|
||||||
|
enableFilter: false,
|
||||||
|
filterBy: '',
|
||||||
searchedInbounds: [],
|
searchedInbounds: [],
|
||||||
expireDiff: 0,
|
expireDiff: 0,
|
||||||
trafficDiff: 0,
|
trafficDiff: 0,
|
||||||
defaultCert: '',
|
defaultCert: '',
|
||||||
defaultKey: '',
|
defaultKey: '',
|
||||||
clientCount: {},
|
clientCount: [],
|
||||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
|
subSettings: {
|
||||||
|
enable : false,
|
||||||
|
port: 0,
|
||||||
|
path: '',
|
||||||
|
domain: '',
|
||||||
|
tls: false
|
||||||
|
},
|
||||||
|
tgBotEnable: false
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loading(spinning = true) {
|
loading(spinning = true) {
|
||||||
@@ -341,6 +390,7 @@
|
|||||||
this.refreshing = true;
|
this.refreshing = true;
|
||||||
const msg = await HttpUtil.post('/panel/inbound/list');
|
const msg = await HttpUtil.post('/panel/inbound/list');
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
|
this.refreshing = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.setInbounds(msg.obj);
|
this.setInbounds(msg.obj);
|
||||||
@@ -353,24 +403,42 @@
|
|||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.expireDiff = msg.obj.expireDiff * 86400000;
|
with(msg.obj){
|
||||||
this.trafficDiff = msg.obj.trafficDiff * 1073741824;
|
this.expireDiff = expireDiff * 86400000;
|
||||||
this.defaultCert = msg.obj.defaultCert;
|
this.trafficDiff = trafficDiff * 1073741824;
|
||||||
this.defaultKey = msg.obj.defaultKey;
|
this.defaultCert = defaultCert;
|
||||||
|
this.defaultKey = defaultKey;
|
||||||
|
this.tgBotEnable = tgBotEnable;
|
||||||
|
this.subSettings = {
|
||||||
|
enable : subEnable,
|
||||||
|
port: subPort,
|
||||||
|
path: subPath,
|
||||||
|
domain: subDomain,
|
||||||
|
tls: subTLS
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setInbounds(dbInbounds) {
|
setInbounds(dbInbounds) {
|
||||||
this.inbounds.splice(0);
|
this.inbounds.splice(0);
|
||||||
this.dbInbounds.splice(0);
|
this.dbInbounds.splice(0);
|
||||||
|
this.clientCount.splice(0);
|
||||||
for (const inbound of dbInbounds) {
|
for (const inbound of dbInbounds) {
|
||||||
const dbInbound = new DBInbound(inbound);
|
const dbInbound = new DBInbound(inbound);
|
||||||
to_inbound = dbInbound.toInbound()
|
to_inbound = dbInbound.toInbound()
|
||||||
this.inbounds.push(to_inbound);
|
this.inbounds.push(to_inbound);
|
||||||
this.dbInbounds.push(dbInbound);
|
this.dbInbounds.push(dbInbound);
|
||||||
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN].includes(inbound.protocol)) {
|
if ([Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(inbound.protocol)) {
|
||||||
|
if (inbound.protocol === Protocols.SHADOWSOCKS && (!to_inbound.isSSMultiUser)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
this.clientCount[inbound.id] = this.getClientCounts(inbound, to_inbound);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.searchInbounds(this.searchKey);
|
if(this.enableFilter){
|
||||||
|
this.filterInbounds();
|
||||||
|
} else {
|
||||||
|
this.searchInbounds(this.searchKey);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getClientCounts(dbInbound, inbound) {
|
getClientCounts(dbInbound, inbound) {
|
||||||
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
let clientCount = 0, active = [], deactive = [], depleted = [], expiring = [];
|
||||||
@@ -428,6 +496,38 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
filterInbounds() {
|
||||||
|
if (ObjectUtil.isEmpty(this.filterBy)) {
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
} else {
|
||||||
|
this.searchedInbounds.splice(0, this.searchedInbounds.length);
|
||||||
|
this.dbInbounds.forEach(inbound => {
|
||||||
|
const newInbound = new DBInbound(inbound);
|
||||||
|
const inboundSettings = JSON.parse(inbound.settings);
|
||||||
|
if (this.clientCount[inbound.id] && this.clientCount[inbound.id].hasOwnProperty(this.filterBy)){
|
||||||
|
const list = this.clientCount[inbound.id][this.filterBy];
|
||||||
|
if (list.length > 0) {
|
||||||
|
const filteredSettings = { "clients": [] };
|
||||||
|
inboundSettings.clients.forEach(client => {
|
||||||
|
if (list.includes(client.email)) {
|
||||||
|
filteredSettings.clients.push(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newInbound.settings = Inbound.Settings.fromJson(inbound.protocol, filteredSettings);
|
||||||
|
this.searchedInbounds.push(newInbound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleFilter(){
|
||||||
|
if(this.enableFilter) {
|
||||||
|
this.searchKey = '';
|
||||||
|
} else {
|
||||||
|
this.filterBy = '';
|
||||||
|
this.searchedInbounds = this.dbInbounds.slice();
|
||||||
|
}
|
||||||
|
},
|
||||||
generalActions(action) {
|
generalActions(action) {
|
||||||
switch (action.key) {
|
switch (action.key) {
|
||||||
case "export":
|
case "export":
|
||||||
@@ -611,7 +711,7 @@
|
|||||||
openEditClient(dbInboundId, client) {
|
openEditClient(dbInboundId, client) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
clients = this.getInboundClients(dbInbound);
|
clients = this.getInboundClients(dbInbound);
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
clientModal.show({
|
clientModal.show({
|
||||||
title: '{{ i18n "pages.client.edit"}}',
|
title: '{{ i18n "pages.client.edit"}}',
|
||||||
okText: '{{ i18n "pages.client.submitEdit"}}',
|
okText: '{{ i18n "pages.client.submitEdit"}}',
|
||||||
@@ -625,9 +725,13 @@
|
|||||||
isEdit: true
|
isEdit: true
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
findIndexOfClient(clients, client) {
|
findIndexOfClient(protocol, clients, client) {
|
||||||
firstKey = Object.keys(client)[0];
|
switch (protocol) {
|
||||||
return clients.findIndex(c => c[firstKey] === client[firstKey]);
|
case Protocols.TROJAN:
|
||||||
|
case Protocols.SHADOWSOCKS:
|
||||||
|
return clients.findIndex(item => item.password === client.password && item.email === client.email);
|
||||||
|
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async addClient(clients, dbInboundId) {
|
async addClient(clients, dbInboundId) {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -697,13 +801,32 @@
|
|||||||
default: return client.id;
|
default: return client.id;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
checkFallback(dbInbound) {
|
||||||
|
newDbInbound = new DBInbound(dbInbound);
|
||||||
|
if (dbInbound.listen.startsWith("@")){
|
||||||
|
rootInbound = this.inbounds.find((i) =>
|
||||||
|
i.tls &&
|
||||||
|
['trojan','vless'].includes(i.protocol) &&
|
||||||
|
i.settings.fallbacks.find(f => f.dest === dbInbound.listen)
|
||||||
|
);
|
||||||
|
if (rootInbound) {
|
||||||
|
newDbInbound.listen = rootInbound.listen;
|
||||||
|
newDbInbound.port = rootInbound.port;
|
||||||
|
newInbound = newDbInbound.toInbound();
|
||||||
|
newInbound.stream.security = 'tls';
|
||||||
|
newInbound.stream.tls = rootInbound.stream.tls;
|
||||||
|
newDbInbound.streamSettings = newInbound.stream.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newDbInbound;
|
||||||
|
},
|
||||||
showQrcode(dbInbound, clientIndex) {
|
showQrcode(dbInbound, clientIndex) {
|
||||||
const clientName = JSON.parse(dbInbound.settings).clients[clientIndex].email;
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
const link = dbInbound.genLink(clientIndex);
|
qrModal.show('{{ i18n "qrCode"}}', newDbInbound, clientIndex);
|
||||||
qrModal.show('{{ i18n "qrCode"}}', link, dbInbound, '', clientName);
|
|
||||||
},
|
},
|
||||||
showInfo(dbInbound, index) {
|
showInfo(dbInbound, index) {
|
||||||
infoModal.show(dbInbound, index);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
|
infoModal.show(newDbInbound, index);
|
||||||
},
|
},
|
||||||
switchEnable(dbInboundId) {
|
switchEnable(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
@@ -714,7 +837,7 @@
|
|||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
inbound = dbInbound.toInbound();
|
inbound = dbInbound.toInbound();
|
||||||
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
clients = this.getClients(dbInbound.protocol, inbound.settings);
|
||||||
index = this.findIndexOfClient(clients, client);
|
index = this.findIndexOfClient(dbInbound.protocol, clients, client);
|
||||||
clients[index].enable = !clients[index].enable;
|
clients[index].enable = !clients[index].enable;
|
||||||
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
clientId = this.getClientId(dbInbound.protocol, clients[index]);
|
||||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||||
@@ -790,10 +913,10 @@
|
|||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
||||||
return clientStats ? clientStats.down : 0
|
return clientStats ? clientStats.down : 0
|
||||||
},
|
},
|
||||||
isTrafficExhausted(dbInbound, email) {
|
statsColor(dbInbound, email) {
|
||||||
if (email.length == 0) return false
|
if(email.length == 0) return 'blue';
|
||||||
clientStats = dbInbound.clientStats.find(stats => stats.email === email)
|
clientStats = dbInbound.clientStats.find(stats => stats.email === email);
|
||||||
return clientStats ? clientStats.down + clientStats.up > clientStats.total : false
|
return usageColor(clientStats.down + clientStats.up, this.trafficDiff, clientStats.total);
|
||||||
},
|
},
|
||||||
isClientEnabled(dbInbound, email) {
|
isClientEnabled(dbInbound, email) {
|
||||||
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
clientStats = dbInbound.clientStats ? dbInbound.clientStats.find(stats => stats.email === email) : null
|
||||||
@@ -804,7 +927,8 @@
|
|||||||
},
|
},
|
||||||
inboundLinks(dbInboundId) {
|
inboundLinks(dbInboundId) {
|
||||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', dbInbound.genInboundLinks, dbInbound.remark);
|
newDbInbound = this.checkFallback(dbInbound);
|
||||||
|
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks, newDbInbound.remark);
|
||||||
},
|
},
|
||||||
exportAllLinks() {
|
exportAllLinks() {
|
||||||
let copyText = '';
|
let copyText = '';
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
:stroke-color="status.cpu.color"
|
:stroke-color="status.cpu.color"
|
||||||
:class="themeSwitcher.darkCardClass"
|
:class="themeSwitcher.darkCardClass"
|
||||||
:percent="status.cpu.percent"></a-progress>
|
:percent="status.cpu.percent"></a-progress>
|
||||||
<div>CPU</div>
|
<div>CPU: [[ cpuCoreFormat(status.cpuCores) ]]</div>
|
||||||
|
<div>Speed: [[ cpuSpeedFormat(status.cpuSpeedMhz) ]]</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12" style="text-align: center">
|
<a-col :span="12" style="text-align: center">
|
||||||
<a-progress type="dashboard" status="normal"
|
<a-progress type="dashboard" status="normal"
|
||||||
@@ -77,21 +78,17 @@
|
|||||||
<a-row>
|
<a-row>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
3x-ui: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
3X: <a href="https://github.com/MHSanaei/3x-ui/releases" target="_blank"><a-tag color="green">v{{ .cur_ver }}</a-tag></a>
|
||||||
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
Xray: <a-tag color="green" style="cursor: pointer;" @click="openSelectV2rayVersion">v[[ status.xray.version ]]</a-tag>
|
||||||
Telegram: <a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
<a href="https://t.me/panel3xui" target="_blank"><a-tag color="green">@panel3xui</a-tag></a>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "pages.index.operationHours" }}:
|
{{ i18n "menu.link" }}:
|
||||||
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
<a-tag color="blue" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||||
<a-tooltip>
|
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||||
<template slot="title">
|
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||||
{{ i18n "pages.index.operationHoursDesc" }}
|
|
||||||
</template>
|
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
|
||||||
</a-tooltip>
|
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
@@ -111,34 +108,86 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "menu.link" }}:
|
{{ i18n "pages.index.operationHours" }}:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag>
|
Xray:
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
<a-tag color="green">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||||
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
OS:
|
||||||
|
<a-tag color="green">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
{{ i18n "pages.index.systemLoad" }}: [[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]
|
||||||
</a-card>
|
|
||||||
</a-col>
|
|
||||||
<a-col :sm="24" :md="12">
|
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
|
||||||
TCP / UDP {{ i18n "pages.index.connectionCount" }}: [[ status.tcpCount ]] / [[ status.udpCount ]]
|
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.connectionCountDesc" }}
|
{{ i18n "pages.index.systemLoadDesc" }}
|
||||||
</template>
|
</template>
|
||||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</a-card>
|
</a-card>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
|
{{ i18n "usage"}}:
|
||||||
|
Memory [[ sizeFormat(status.appStats.mem) ]] -
|
||||||
|
Threads [[ status.appStats.threads ]]
|
||||||
|
</a-tooltip>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
IPv4:
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
[[ status.publicIP.ipv4 ]]
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
IPv6:
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
[[ status.publicIP.ipv6 ]]
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
<a-col :sm="24" :md="12">
|
||||||
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
|
<a-row>
|
||||||
|
<a-col :span="12">
|
||||||
|
TCP: [[ status.tcpCount ]]
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
UDP: [[ status.udpCount ]]
|
||||||
|
<a-tooltip>
|
||||||
|
<template slot="title">
|
||||||
|
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||||
|
</template>
|
||||||
|
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
<a-col :sm="24" :md="12">
|
<a-col :sm="24" :md="12">
|
||||||
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
<a-card hoverable :class="themeSwitcher.darkCardClass">
|
||||||
<a-row>
|
<a-row>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-up"></a-icon>
|
<a-icon type="arrow-up"></a-icon>
|
||||||
[[ sizeFormat(status.netIO.up) ]] / S
|
[[ sizeFormat(status.netIO.up) ]]/S
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.upSpeed" }}
|
{{ i18n "pages.index.upSpeed" }}
|
||||||
@@ -148,7 +197,7 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-icon type="arrow-down"></a-icon>
|
<a-icon type="arrow-down"></a-icon>
|
||||||
[[ sizeFormat(status.netIO.down) ]] / S
|
[[ sizeFormat(status.netIO.down) ]]/S
|
||||||
<a-tooltip>
|
<a-tooltip>
|
||||||
<template slot="title">
|
<template slot="title">
|
||||||
{{ i18n "pages.index.downSpeed" }}
|
{{ i18n "pages.index.downSpeed" }}
|
||||||
@@ -213,7 +262,7 @@
|
|||||||
<a-form-item label="Count">
|
<a-form-item label="Count">
|
||||||
<a-select v-model="logModal.rows"
|
<a-select v-model="logModal.rows"
|
||||||
style="width: 80px"
|
style="width: 80px"
|
||||||
@change="openLogs(logModal.rows)"
|
@change="openLogs()"
|
||||||
:dropdown-class-name="themeSwitcher.darkCardClass">
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
<a-select-option value="10">10</a-select-option>
|
<a-select-option value="10">10</a-select-option>
|
||||||
<a-select-option value="20">20</a-select-option>
|
<a-select-option value="20">20</a-select-option>
|
||||||
@@ -221,8 +270,23 @@
|
|||||||
<a-select-option value="100">100</a-select-option>
|
<a-select-option value="100">100</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
<a-form-item label="Log Level">
|
||||||
|
<a-select v-model="logModal.level"
|
||||||
|
style="width: 120px"
|
||||||
|
@change="openLogs()"
|
||||||
|
:dropdown-class-name="themeSwitcher.darkCardClass">
|
||||||
|
<a-select-option value="debug">Debug</a-select-option>
|
||||||
|
<a-select-option value="info">Info</a-select-option>
|
||||||
|
<a-select-option value="notice">Notice</a-select-option>
|
||||||
|
<a-select-option value="warning">Warning</a-select-option>
|
||||||
|
<a-select-option value="err">Error</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="SysLog">
|
||||||
|
<a-checkbox v-model="logModal.syslog" @change="openLogs()"></a-checkbox>
|
||||||
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<button class="ant-btn ant-btn-primary" @click="openLogs(logModal.rows)"><a-icon type="sync"></a-icon> Reload</button>
|
<button class="ant-btn ant-btn-primary" @click="openLogs()"><a-icon type="sync"></a-icon> Reload</button>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-button type="primary" style="margin-bottom: 10px;"
|
<a-button type="primary" style="margin-bottom: 10px;"
|
||||||
@@ -294,30 +358,40 @@
|
|||||||
class Status {
|
class Status {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.cpu = new CurTotal(0, 0);
|
this.cpu = new CurTotal(0, 0);
|
||||||
|
this.cpuCores = 0;
|
||||||
|
this.cpuSpeedMhz = 0;
|
||||||
this.disk = new CurTotal(0, 0);
|
this.disk = new CurTotal(0, 0);
|
||||||
this.loads = [0, 0, 0];
|
this.loads = [0, 0, 0];
|
||||||
this.mem = new CurTotal(0, 0);
|
this.mem = new CurTotal(0, 0);
|
||||||
this.netIO = { up: 0, down: 0 };
|
this.netIO = { up: 0, down: 0 };
|
||||||
this.netTraffic = { sent: 0, recv: 0 };
|
this.netTraffic = { sent: 0, recv: 0 };
|
||||||
|
this.publicIP = { ipv4: 0, ipv6: 0 };
|
||||||
this.swap = new CurTotal(0, 0);
|
this.swap = new CurTotal(0, 0);
|
||||||
this.tcpCount = 0;
|
this.tcpCount = 0;
|
||||||
this.udpCount = 0;
|
this.udpCount = 0;
|
||||||
this.uptime = 0;
|
this.uptime = 0;
|
||||||
|
this.appUptime = 0;
|
||||||
|
this.appStats = {threads: 0, mem: 0, uptime: 0};
|
||||||
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
this.xray = { state: State.Stop, errorMsg: "", version: "", color: "" };
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cpu = new CurTotal(data.cpu, 100);
|
this.cpu = new CurTotal(data.cpu, 100);
|
||||||
|
this.cpuCores = data.cpuCores;
|
||||||
|
this.cpuSpeedMhz = data.cpuSpeedMhz;
|
||||||
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
this.disk = new CurTotal(data.disk.current, data.disk.total);
|
||||||
this.loads = data.loads.map(load => toFixed(load, 2));
|
this.loads = data.loads.map(load => toFixed(load, 2));
|
||||||
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
this.mem = new CurTotal(data.mem.current, data.mem.total);
|
||||||
this.netIO = data.netIO;
|
this.netIO = data.netIO;
|
||||||
this.netTraffic = data.netTraffic;
|
this.netTraffic = data.netTraffic;
|
||||||
|
this.publicIP = data.publicIP;
|
||||||
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
this.swap = new CurTotal(data.swap.current, data.swap.total);
|
||||||
this.tcpCount = data.tcpCount;
|
this.tcpCount = data.tcpCount;
|
||||||
this.udpCount = data.udpCount;
|
this.udpCount = data.udpCount;
|
||||||
this.uptime = data.uptime;
|
this.uptime = data.uptime;
|
||||||
|
this.appUptime = data.appUptime;
|
||||||
|
this.appStats = data.appStats;
|
||||||
this.xray = data.xray;
|
this.xray = data.xray;
|
||||||
switch (this.xray.state) {
|
switch (this.xray.state) {
|
||||||
case State.Running:
|
case State.Running:
|
||||||
@@ -351,10 +425,11 @@
|
|||||||
visible: false,
|
visible: false,
|
||||||
logs: '',
|
logs: '',
|
||||||
rows: 20,
|
rows: 20,
|
||||||
show(logs, rows) {
|
level: 'info',
|
||||||
|
syslog: false,
|
||||||
|
show(logs) {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
this.rows = rows;
|
this.logs = logs? logs.join("\n"): "No Record...";
|
||||||
this.logs = logs.join("\n");
|
|
||||||
},
|
},
|
||||||
hide() {
|
hide() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
@@ -403,9 +478,13 @@
|
|||||||
this.loadingTip = tip;
|
this.loadingTip = tip;
|
||||||
},
|
},
|
||||||
async getStatus() {
|
async getStatus() {
|
||||||
const msg = await HttpUtil.post('/server/status');
|
try {
|
||||||
if (msg.success) {
|
const msg = await HttpUtil.post('/server/status');
|
||||||
this.setStatus(msg.obj);
|
if (msg.success) {
|
||||||
|
this.setStatus(msg.obj);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to get status:", e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setStatus(data) {
|
setStatus(data) {
|
||||||
@@ -451,14 +530,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async openLogs(rows) {
|
async openLogs(){
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
const msg = await HttpUtil.post('server/logs/' + rows);
|
const msg = await HttpUtil.post('server/logs/'+logModal.rows,{level: logModal.level, syslog: logModal.syslog});
|
||||||
this.loading(false);
|
this.loading(false);
|
||||||
if (!msg.success) {
|
if (!msg.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logModal.show(msg.obj, rows);
|
logModal.show(msg.obj);
|
||||||
},
|
},
|
||||||
async openConfig() {
|
async openConfig() {
|
||||||
this.loading(true);
|
this.loading(true);
|
||||||
@@ -514,11 +593,14 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
while (true) {
|
let retries = 0;
|
||||||
|
while (retries < 5) {
|
||||||
try {
|
try {
|
||||||
await this.getStatus();
|
await this.getStatus();
|
||||||
|
retries = 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error("Error occurred while fetching status:", e);
|
||||||
|
retries++;
|
||||||
}
|
}
|
||||||
await PromiseUtil.sleep(2000);
|
await PromiseUtil.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,28 @@ package job
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/web/service"
|
|
||||||
"x-ui/xray"
|
|
||||||
|
|
||||||
"net"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-cmd/cmd"
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckClientIpJob struct {
|
type CheckClientIpJob struct{}
|
||||||
xrayService service.XrayService
|
|
||||||
}
|
|
||||||
|
|
||||||
var job *CheckClientIpJob
|
var job *CheckClientIpJob
|
||||||
var disAllowedIps []string
|
var disAllowedIps []string
|
||||||
|
var ipFiles = []string{
|
||||||
|
xray.GetIPLimitLogPath(),
|
||||||
|
xray.GetIPLimitBannedLogPath(),
|
||||||
|
xray.GetAccessPersistentLogPath(),
|
||||||
|
}
|
||||||
|
|
||||||
func NewCheckClientIpJob() *CheckClientIpJob {
|
func NewCheckClientIpJob() *CheckClientIpJob {
|
||||||
job = new(CheckClientIpJob)
|
job = new(CheckClientIpJob)
|
||||||
@@ -31,31 +31,59 @@ func NewCheckClientIpJob() *CheckClientIpJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (j *CheckClientIpJob) Run() {
|
func (j *CheckClientIpJob) Run() {
|
||||||
logger.Debug("Check Client IP Job...")
|
|
||||||
processLogFile()
|
|
||||||
|
|
||||||
// disAllowedIps = []string{"192.168.1.183","192.168.1.197"}
|
// create files required for iplimit if not exists
|
||||||
blockedIps := []byte(strings.Join(disAllowedIps, ","))
|
for i := 0; i < len(ipFiles); i++ {
|
||||||
err := os.WriteFile(xray.GetBlockedIPsPath(), blockedIps, 0755)
|
file, err := os.OpenFile(ipFiles[i], os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
defer file.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for limit ip
|
||||||
|
if j.hasLimitIp() {
|
||||||
|
j.processLogFile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func processLogFile() {
|
func (j *CheckClientIpJob) hasLimitIp() bool {
|
||||||
accessLogPath := GetAccessLogPath()
|
db := database.GetDB()
|
||||||
|
var inbounds []*model.Inbound
|
||||||
|
|
||||||
|
err := db.Model(model.Inbound{}).Find(&inbounds).Error
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
if inbound.Settings == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
settings := map[string][]model.Client{}
|
||||||
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
|
clients := settings["clients"]
|
||||||
|
|
||||||
|
for _, client := range clients {
|
||||||
|
limitIp := client.LimitIP
|
||||||
|
if limitIp > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) processLogFile() {
|
||||||
|
accessLogPath := xray.GetAccessLogPath()
|
||||||
if accessLogPath == "" {
|
if accessLogPath == "" {
|
||||||
logger.Warning("xray log not init in config.json")
|
logger.Warning("access.log doesn't exist in your config.json")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := os.ReadFile(accessLogPath)
|
data, err := os.ReadFile(accessLogPath)
|
||||||
InboundClientIps := make(map[string][]string)
|
InboundClientIps := make(map[string][]string)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
// clean log
|
|
||||||
if err := os.Truncate(GetAccessLogPath(), 0); err != nil {
|
|
||||||
checkError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(string(data), "\n")
|
lines := strings.Split(string(data), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
@@ -73,10 +101,10 @@ func processLogFile() {
|
|||||||
if matchesEmail == "" {
|
if matchesEmail == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
matchesEmail = strings.Split(matchesEmail, "email: ")[1]
|
matchesEmail = strings.TrimSpace(strings.Split(matchesEmail, "email: ")[1])
|
||||||
|
|
||||||
if InboundClientIps[matchesEmail] != nil {
|
if InboundClientIps[matchesEmail] != nil {
|
||||||
if contains(InboundClientIps[matchesEmail], ip) {
|
if j.contains(InboundClientIps[matchesEmail], ip) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
@@ -85,54 +113,50 @@ func processLogFile() {
|
|||||||
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
InboundClientIps[matchesEmail] = append(InboundClientIps[matchesEmail], ip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disAllowedIps = []string{}
|
disAllowedIps = []string{}
|
||||||
|
shouldCleanLog := false
|
||||||
|
|
||||||
for clientEmail, ips := range InboundClientIps {
|
for clientEmail, ips := range InboundClientIps {
|
||||||
inboundClientIps, err := GetInboundClientIps(clientEmail)
|
inboundClientIps, err := j.getInboundClientIps(clientEmail)
|
||||||
sort.Strings(ips)
|
sort.Strings(ips)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
addInboundClientIps(clientEmail, ips)
|
j.addInboundClientIps(clientEmail, ips)
|
||||||
} else {
|
} else {
|
||||||
updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
shouldCleanLog = j.updateInboundClientIps(inboundClientIps, clientEmail, ips)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if inbound connection is more than limited ip and drop connection
|
// added delay before cleaning logs to reduce chance of logging IP that already has been banned
|
||||||
LimitDevice := func() { LimitDevice() }
|
time.Sleep(time.Second * 2)
|
||||||
|
|
||||||
stop := schedule(LimitDevice, 1000*time.Millisecond)
|
if shouldCleanLog {
|
||||||
time.Sleep(10 * time.Second)
|
// copy access log to persistent file
|
||||||
stop <- true
|
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
j.checkError(err)
|
||||||
|
input, err := os.ReadFile(accessLogPath)
|
||||||
|
j.checkError(err)
|
||||||
|
if _, err := logAccessP.Write(input); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
|
}
|
||||||
|
defer logAccessP.Close()
|
||||||
|
|
||||||
}
|
// clean access log
|
||||||
func GetAccessLogPath() string {
|
if err := os.Truncate(xray.GetAccessLogPath(), 0); err != nil {
|
||||||
|
j.checkError(err)
|
||||||
config, err := os.ReadFile(xray.GetConfigPath())
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
jsonConfig := map[string]interface{}{}
|
|
||||||
err = json.Unmarshal([]byte(config), &jsonConfig)
|
|
||||||
checkError(err)
|
|
||||||
if jsonConfig["log"] != nil {
|
|
||||||
jsonLog := jsonConfig["log"].(map[string]interface{})
|
|
||||||
if jsonLog["access"] != nil {
|
|
||||||
|
|
||||||
accessLogPath := jsonLog["access"].(string)
|
|
||||||
|
|
||||||
return accessLogPath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
|
||||||
}
|
}
|
||||||
func checkError(e error) {
|
|
||||||
|
func (j *CheckClientIpJob) checkError(e error) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
logger.Warning("client ip job err:", e)
|
logger.Warning("client ip job err:", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func contains(s []string, str string) bool {
|
|
||||||
|
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
||||||
for _, v := range s {
|
for _, v := range s {
|
||||||
if v == str {
|
if v == str {
|
||||||
return true
|
return true
|
||||||
@@ -141,7 +165,8 @@ func contains(s []string, str string) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|
||||||
|
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
InboundClientIps := &model.InboundClientIps{}
|
InboundClientIps := &model.InboundClientIps{}
|
||||||
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
err := db.Model(model.InboundClientIps{}).Where("client_email = ?", clientEmail).First(InboundClientIps).Error
|
||||||
@@ -150,13 +175,11 @@ func GetInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
|||||||
}
|
}
|
||||||
return InboundClientIps, nil
|
return InboundClientIps, nil
|
||||||
}
|
}
|
||||||
func addInboundClientIps(clientEmail string, ips []string) error {
|
|
||||||
|
func (j *CheckClientIpJob) addInboundClientIps(clientEmail string, ips []string) error {
|
||||||
inboundClientIps := &model.InboundClientIps{}
|
inboundClientIps := &model.InboundClientIps{}
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
// Trim any leading/trailing whitespace from clientEmail
|
|
||||||
clientEmail = strings.TrimSpace(clientEmail)
|
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
inboundClientIps.Ips = string(jsonIps)
|
||||||
@@ -178,37 +201,50 @@ func addInboundClientIps(clientEmail string, ips []string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) error {
|
|
||||||
|
|
||||||
|
func (j *CheckClientIpJob) updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmail string, ips []string) bool {
|
||||||
jsonIps, err := json.Marshal(ips)
|
jsonIps, err := json.Marshal(ips)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
inboundClientIps.ClientEmail = clientEmail
|
inboundClientIps.ClientEmail = clientEmail
|
||||||
inboundClientIps.Ips = string(jsonIps)
|
inboundClientIps.Ips = string(jsonIps)
|
||||||
|
|
||||||
// check inbound limitation
|
// check inbound limitation
|
||||||
inbound, err := GetInboundByEmail(clientEmail)
|
inbound, err := j.getInboundByEmail(clientEmail)
|
||||||
checkError(err)
|
j.checkError(err)
|
||||||
|
|
||||||
if inbound.Settings == "" {
|
if inbound.Settings == "" {
|
||||||
logger.Debug("wrong data ", inbound)
|
logger.Debug("wrong data ", inbound)
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
settings := map[string][]model.Client{}
|
settings := map[string][]model.Client{}
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
json.Unmarshal([]byte(inbound.Settings), &settings)
|
||||||
clients := settings["clients"]
|
clients := settings["clients"]
|
||||||
|
shouldCleanLog := false
|
||||||
|
|
||||||
var disAllowedIps []string // initialize the slice
|
// create iplimit log file channel
|
||||||
|
logIpFile, err := os.OpenFile(xray.GetIPLimitLogPath(), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to create or open ip limit log file: %s", err)
|
||||||
|
}
|
||||||
|
defer logIpFile.Close()
|
||||||
|
log.SetOutput(logIpFile)
|
||||||
|
log.SetFlags(log.LstdFlags)
|
||||||
|
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client.Email == clientEmail {
|
if client.Email == clientEmail {
|
||||||
|
|
||||||
limitIp := client.LimitIP
|
limitIp := client.LimitIP
|
||||||
|
|
||||||
if limitIp < len(ips) && limitIp != 0 && inbound.Enable {
|
if limitIp != 0 {
|
||||||
|
shouldCleanLog = true
|
||||||
|
|
||||||
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
if limitIp < len(ips) && inbound.Enable {
|
||||||
|
disAllowedIps = append(disAllowedIps, ips[limitIp:]...)
|
||||||
|
for i := limitIp; i < len(ips); i++ {
|
||||||
|
log.Printf("[LIMIT_IP] Email = %s || SRC = %s", clientEmail, ips[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,132 +254,19 @@ func updateInboundClientIps(inboundClientIps *model.InboundClientIps, clientEmai
|
|||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
err = db.Save(inboundClientIps).Error
|
err = db.Save(inboundClientIps).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
return nil
|
return shouldCleanLog
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableInbound(id int) error {
|
func (j *CheckClientIpJob) getInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
||||||
db := database.GetDB()
|
|
||||||
result := db.Model(model.Inbound{}).
|
|
||||||
Where("id = ? and enable = ?", id, true).
|
|
||||||
Update("enable", false)
|
|
||||||
err := result.Error
|
|
||||||
logger.Warning("disable inbound with id:", id)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
job.xrayService.SetToNeedRestart()
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInboundByEmail(clientEmail string) (*model.Inbound, error) {
|
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
var inbounds *model.Inbound
|
var inbounds *model.Inbound
|
||||||
|
|
||||||
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
err := db.Model(model.Inbound{}).Where("settings LIKE ?", "%"+clientEmail+"%").Find(&inbounds).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return inbounds, nil
|
return inbounds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LimitDevice() {
|
|
||||||
var destIp, destPort, srcIp, srcPort string
|
|
||||||
|
|
||||||
localIp, err := LocalIP()
|
|
||||||
checkError(err)
|
|
||||||
|
|
||||||
c := cmd.NewCmd("bash", "-c", "ss --tcp | grep -E '"+IPsToRegex(localIp)+"'| awk '{if($1==\"ESTAB\") print $4,$5;}'", "| sort | uniq -c | sort -nr | head")
|
|
||||||
|
|
||||||
<-c.Start()
|
|
||||||
if len(c.Status().Stdout) > 0 {
|
|
||||||
ipRegx, _ := regexp.Compile(`[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+`)
|
|
||||||
portRegx, _ := regexp.Compile(`(?:(:))([0-9]..[^.][0-9]+)`)
|
|
||||||
|
|
||||||
for _, row := range c.Status().Stdout {
|
|
||||||
|
|
||||||
data := strings.Split(row, " ")
|
|
||||||
|
|
||||||
if len(data) < 2 {
|
|
||||||
continue // Skip this row if it doesn't have at least two elements
|
|
||||||
}
|
|
||||||
|
|
||||||
destIp = string(ipRegx.FindString(data[0]))
|
|
||||||
destPort = portRegx.FindString(data[0])
|
|
||||||
destPort = strings.Replace(destPort, ":", "", -1)
|
|
||||||
|
|
||||||
srcIp = string(ipRegx.FindString(data[1]))
|
|
||||||
srcPort = portRegx.FindString(data[1])
|
|
||||||
srcPort = strings.Replace(srcPort, ":", "", -1)
|
|
||||||
|
|
||||||
if contains(disAllowedIps, srcIp) {
|
|
||||||
dropCmd := cmd.NewCmd("bash", "-c", "ss -K dport = "+srcPort)
|
|
||||||
dropCmd.Start()
|
|
||||||
|
|
||||||
logger.Debug("request droped : ", srcIp, srcPort, "to", destIp, destPort)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func LocalIP() ([]string, error) {
|
|
||||||
// get machine ips
|
|
||||||
|
|
||||||
ifaces, err := net.Interfaces()
|
|
||||||
ips := []string{}
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
for _, i := range ifaces {
|
|
||||||
addrs, err := i.Addrs()
|
|
||||||
if err != nil {
|
|
||||||
return ips, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, addr := range addrs {
|
|
||||||
var ip net.IP
|
|
||||||
switch v := addr.(type) {
|
|
||||||
case *net.IPNet:
|
|
||||||
ip = v.IP
|
|
||||||
case *net.IPAddr:
|
|
||||||
ip = v.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
ips = append(ips, ip.String())
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.Debug("System IPs : ", ips)
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IPsToRegex(ips []string) string {
|
|
||||||
|
|
||||||
regx := ""
|
|
||||||
for _, ip := range ips {
|
|
||||||
regx += "(" + strings.Replace(ip, ".", "\\.", -1) + ")"
|
|
||||||
|
|
||||||
}
|
|
||||||
regx = "(" + strings.Replace(regx, ")(", ")|(.", -1) + ")"
|
|
||||||
|
|
||||||
return regx
|
|
||||||
}
|
|
||||||
|
|
||||||
func schedule(LimitDevice func(), delay time.Duration) chan bool {
|
|
||||||
stop := make(chan bool)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
LimitDevice()
|
|
||||||
select {
|
|
||||||
case <-time.After(delay):
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return stop
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package job
|
package job
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
@@ -24,7 +24,10 @@ func (j *CheckCpuJob) Run() {
|
|||||||
// 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)
|
||||||
if err == nil && percent[0] > float64(threshold) {
|
if err == nil && percent[0] > float64(threshold) {
|
||||||
msg := fmt.Sprintf("🔴 CPU usage %.2f%% is more than threshold %d%%", percent[0], threshold)
|
msg := j.tgbotService.I18nBot("tgbot.messages.cpuThreshold",
|
||||||
|
"Percent=="+strconv.FormatFloat(percent[0], 'f', 2, 64),
|
||||||
|
"Threshold=="+strconv.Itoa(threshold))
|
||||||
|
|
||||||
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
j.tgbotService.SendMsgToTgbotAdmins(msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
web/job/check_hash_storage.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"x-ui/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckHashStorageJob struct {
|
||||||
|
tgbotService service.Tgbot
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCheckHashStorageJob() *CheckHashStorageJob {
|
||||||
|
return new(CheckHashStorageJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here Run is an interface method of the Job interface
|
||||||
|
func (j *CheckHashStorageJob) Run() {
|
||||||
|
// Remove expired hashes from storage
|
||||||
|
j.tgbotService.GetHashStorage().RemoveExpiredHashes()
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package job
|
|
||||||
|
|
||||||
import (
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/web/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CheckInboundJob struct {
|
|
||||||
xrayService service.XrayService
|
|
||||||
inboundService service.InboundService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCheckInboundJob() *CheckInboundJob {
|
|
||||||
return new(CheckInboundJob)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (j *CheckInboundJob) Run() {
|
|
||||||
count, err := j.inboundService.DisableInvalidClients()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("disable invalid Client err:", err)
|
|
||||||
} else if count > 0 {
|
|
||||||
logger.Debugf("disabled %v Client", count)
|
|
||||||
j.xrayService.SetToNeedRestart()
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err = j.inboundService.DisableInvalidInbounds()
|
|
||||||
if err != nil {
|
|
||||||
logger.Warning("disable invalid inbounds err:", err)
|
|
||||||
} else if count > 0 {
|
|
||||||
logger.Debugf("disabled %v inbounds", count)
|
|
||||||
j.xrayService.SetToNeedRestart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
web/job/clear_logs_job.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package job
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"x-ui/logger"
|
||||||
|
"x-ui/xray"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClearLogsJob struct{}
|
||||||
|
|
||||||
|
func NewClearLogsJob() *ClearLogsJob {
|
||||||
|
return new(ClearLogsJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here Run is an interface method of the Job interface
|
||||||
|
func (j *ClearLogsJob) Run() {
|
||||||
|
logFiles := []string{xray.GetIPLimitLogPath(), xray.GetIPLimitBannedLogPath(), xray.GetAccessPersistentLogPath()}
|
||||||
|
|
||||||
|
// clear log files
|
||||||
|
for i := 0; i < len(logFiles); i++ {
|
||||||
|
if err := os.Truncate(logFiles[i], 0); err != nil {
|
||||||
|
logger.Warning("clear logs job err:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,14 +24,12 @@ func (j *XrayTrafficJob) Run() {
|
|||||||
logger.Warning("get xray traffic failed:", err)
|
logger.Warning("get xray traffic failed:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = j.inboundService.AddTraffic(traffics)
|
err, needRestart := j.inboundService.AddTraffic(traffics, clientTraffics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("add traffic failed:", err)
|
logger.Warning("add traffic failed:", err)
|
||||||
}
|
}
|
||||||
|
if needRestart {
|
||||||
err = j.inboundService.AddClientTraffic(clientTraffics)
|
j.xrayService.SetToNeedRestart()
|
||||||
if err != nil {
|
|
||||||
logger.Warning("add client traffic failed:", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
144
web/locale/locale.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package locale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"strings"
|
||||||
|
"x-ui/logger"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
)
|
||||||
|
|
||||||
|
var i18nBundle *i18n.Bundle
|
||||||
|
var LocalizerWeb *i18n.Localizer
|
||||||
|
var LocalizerBot *i18n.Localizer
|
||||||
|
|
||||||
|
type I18nType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Bot I18nType = "bot"
|
||||||
|
Web I18nType = "web"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingService interface {
|
||||||
|
GetTgLang() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
|
||||||
|
// set default bundle to english
|
||||||
|
i18nBundle = i18n.NewBundle(language.MustParse("en-US"))
|
||||||
|
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||||
|
|
||||||
|
// parse files
|
||||||
|
if err := parseTranslationFiles(i18nFS, i18nBundle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup bot locale
|
||||||
|
if err := initTGBotLocalizer(settingService); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTemplateData(params []string, seperator ...string) map[string]interface{} {
|
||||||
|
var sep string = "=="
|
||||||
|
if len(seperator) > 0 {
|
||||||
|
sep = seperator[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData := make(map[string]interface{})
|
||||||
|
for _, param := range params {
|
||||||
|
parts := strings.SplitN(param, sep, 2)
|
||||||
|
templateData[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return templateData
|
||||||
|
}
|
||||||
|
|
||||||
|
func I18n(i18nType I18nType, key string, params ...string) string {
|
||||||
|
var localizer *i18n.Localizer
|
||||||
|
|
||||||
|
switch i18nType {
|
||||||
|
case "bot":
|
||||||
|
localizer = LocalizerBot
|
||||||
|
case "web":
|
||||||
|
localizer = LocalizerWeb
|
||||||
|
default:
|
||||||
|
logger.Errorf("Invalid type for I18n: %s", i18nType)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
templateData := createTemplateData(params)
|
||||||
|
|
||||||
|
msg, err := localizer.Localize(&i18n.LocalizeConfig{
|
||||||
|
MessageID: key,
|
||||||
|
TemplateData: templateData,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to localize message: %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func initTGBotLocalizer(settingService SettingService) error {
|
||||||
|
botLang, err := settingService.GetTgLang()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalizerBot = i18n.NewLocalizer(i18nBundle, botLang)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LocalizerMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var lang string
|
||||||
|
|
||||||
|
if cookie, err := c.Request.Cookie("lang"); err == nil {
|
||||||
|
lang = cookie.Value
|
||||||
|
} else {
|
||||||
|
lang = c.GetHeader("Accept-Language")
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalizerWeb = i18n.NewLocalizer(i18nBundle, lang)
|
||||||
|
|
||||||
|
c.Set("localizer", LocalizerWeb)
|
||||||
|
c.Set("I18n", I18n)
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
||||||
|
err := fs.WalkDir(i18nFS, "translation",
|
||||||
|
func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := i18nFS.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
21
web/middleware/domainValidator.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DomainValidatorMiddleware(domain string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
host := strings.Split(c.Request.Host, ":")[0]
|
||||||
|
|
||||||
|
if host != domain {
|
||||||
|
c.AbortWithStatus(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
34
web/middleware/redirect.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RedirectMiddleware(basePath string) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Redirect from old '/xui' path to '/panel'
|
||||||
|
redirects := map[string]string{
|
||||||
|
"panel/API": "panel/api",
|
||||||
|
"xui/API": "panel/api",
|
||||||
|
"xui": "panel",
|
||||||
|
}
|
||||||
|
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
for from, to := range redirects {
|
||||||
|
from, to = basePath+from, basePath+to
|
||||||
|
|
||||||
|
if strings.HasPrefix(path, from) {
|
||||||
|
newPath := to + path[len(from):]
|
||||||
|
|
||||||
|
c.Redirect(http.StatusMovedPermanently, newPath)
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,11 @@
|
|||||||
},
|
},
|
||||||
"api": {
|
"api": {
|
||||||
"tag": "api",
|
"tag": "api",
|
||||||
"services": ["HandlerService", "LoggerService", "StatsService"]
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
{
|
{
|
||||||
@@ -47,20 +51,26 @@
|
|||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"inboundTag": ["api"],
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
"outboundTag": "api"
|
"outboundTag": "api"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"ip": ["geoip:private"]
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "field",
|
"type": "field",
|
||||||
"outboundTag": "blocked",
|
"outboundTag": "blocked",
|
||||||
"protocol": ["bittorrent"]
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"stats": {}
|
"stats": {}
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
@@ -38,9 +40,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
T time.Time `json:"-"`
|
T time.Time `json:"-"`
|
||||||
Cpu float64 `json:"cpu"`
|
Cpu float64 `json:"cpu"`
|
||||||
Mem struct {
|
CpuCores int `json:"cpuCores"`
|
||||||
|
CpuSpeedMhz float64 `json:"cpuSpeedMhz"`
|
||||||
|
Mem struct {
|
||||||
Current uint64 `json:"current"`
|
Current uint64 `json:"current"`
|
||||||
Total uint64 `json:"total"`
|
Total uint64 `json:"total"`
|
||||||
} `json:"mem"`
|
} `json:"mem"`
|
||||||
@@ -69,6 +73,15 @@ type Status struct {
|
|||||||
Sent uint64 `json:"sent"`
|
Sent uint64 `json:"sent"`
|
||||||
Recv uint64 `json:"recv"`
|
Recv uint64 `json:"recv"`
|
||||||
} `json:"netTraffic"`
|
} `json:"netTraffic"`
|
||||||
|
PublicIP struct {
|
||||||
|
IPv4 string `json:"ipv4"`
|
||||||
|
IPv6 string `json:"ipv6"`
|
||||||
|
} `json:"publicIP"`
|
||||||
|
AppStats struct {
|
||||||
|
Threads uint32 `json:"threads"`
|
||||||
|
Mem uint64 `json:"mem"`
|
||||||
|
Uptime uint64 `json:"uptime"`
|
||||||
|
} `json:"appStats"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Release struct {
|
type Release struct {
|
||||||
@@ -80,6 +93,26 @@ type ServerService struct {
|
|||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPublicIP(url string) string {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
ip, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
ipString := string(ip)
|
||||||
|
if ipString == "" {
|
||||||
|
return "N/A"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ipString
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
status := &Status{
|
status := &Status{
|
||||||
@@ -93,6 +126,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.Cpu = percents[0]
|
status.Cpu = percents[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status.CpuCores, err = cpu.Counts(false)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("get cpu cores count failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cpuInfos, err := cpu.Info()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("get cpu info failed:", err)
|
||||||
|
} else if len(cpuInfos) > 0 {
|
||||||
|
cpuInfo := cpuInfos[0]
|
||||||
|
status.CpuSpeedMhz = cpuInfo.Mhz // setting CPU speed in MHz
|
||||||
|
} else {
|
||||||
|
logger.Warning("could not find cpu info")
|
||||||
|
}
|
||||||
|
|
||||||
upTime, err := host.Uptime()
|
upTime, err := host.Uptime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warning("get uptime failed:", err)
|
logger.Warning("get uptime failed:", err)
|
||||||
@@ -161,6 +209,9 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
logger.Warning("get udp connections failed:", err)
|
logger.Warning("get udp connections failed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status.PublicIP.IPv4 = getPublicIP("https://api.ipify.org")
|
||||||
|
status.PublicIP.IPv6 = getPublicIP("https://api6.ipify.org")
|
||||||
|
|
||||||
if s.xrayService.IsXrayRunning() {
|
if s.xrayService.IsXrayRunning() {
|
||||||
status.Xray.State = Running
|
status.Xray.State = Running
|
||||||
status.Xray.ErrorMsg = ""
|
status.Xray.ErrorMsg = ""
|
||||||
@@ -174,12 +225,22 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||||||
status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
|
status.Xray.ErrorMsg = s.xrayService.GetXrayResult()
|
||||||
}
|
}
|
||||||
status.Xray.Version = s.xrayService.GetXrayVersion()
|
status.Xray.Version = s.xrayService.GetXrayVersion()
|
||||||
|
var rtm runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&rtm)
|
||||||
|
|
||||||
|
status.AppStats.Mem = rtm.Sys
|
||||||
|
status.AppStats.Threads = uint32(runtime.NumGoroutine())
|
||||||
|
if p.IsRunning() {
|
||||||
|
status.AppStats.Uptime = p.GetUptime()
|
||||||
|
} else {
|
||||||
|
status.AppStats.Uptime = 0
|
||||||
|
}
|
||||||
|
|
||||||
return status
|
return status
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||||
url := "https://api.github.com/repos/MHSanaei/Xray-core/releases"
|
url := "https://api.github.com/repos/XTLS/Xray-core/releases"
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -198,15 +259,16 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
versions := make([]string, 0, len(releases))
|
var versions []string
|
||||||
for _, release := range releases {
|
for _, release := range releases {
|
||||||
versions = append(versions, release.TagName)
|
if release.TagName >= "v1.7.5" {
|
||||||
|
versions = append(versions, release.TagName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return versions, nil
|
return versions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) StopXrayService() (string error) {
|
func (s *ServerService) StopXrayService() (string error) {
|
||||||
|
|
||||||
err := s.xrayService.StopXray()
|
err := s.xrayService.StopXray()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("stop xray failed:", err)
|
logger.Error("stop xray failed:", err)
|
||||||
@@ -217,7 +279,6 @@ func (s *ServerService) StopXrayService() (string error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) RestartXrayService() (string error) {
|
func (s *ServerService) RestartXrayService() (string error) {
|
||||||
|
|
||||||
s.xrayService.StopXray()
|
s.xrayService.StopXray()
|
||||||
defer func() {
|
defer func() {
|
||||||
err := s.xrayService.RestartXray(true)
|
err := s.xrayService.RestartXray(true)
|
||||||
@@ -246,7 +307,7 @@ func (s *ServerService) downloadXRay(version string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
fileName := fmt.Sprintf("Xray-%s-%s.zip", osName, arch)
|
||||||
url := fmt.Sprintf("https://github.com/MHSanaei/Xray-core/releases/download/%s/%s", version, fileName)
|
url := fmt.Sprintf("https://github.com/XTLS/Xray-core/releases/download/%s/%s", version, fileName)
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -315,66 +376,86 @@ func (s *ServerService) UpdateXray(version string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = copyZipFile("xray", xray.GetBinaryPath())
|
downloadFile := func(fileName string, url string) error {
|
||||||
if err != nil {
|
os.Remove(fileName)
|
||||||
|
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, fs.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("download file failed: %s", resp.Status)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = copyZipFile("geosite.dat", xray.GetGeositePath())
|
|
||||||
if err != nil {
|
copyFiles := map[string]string{
|
||||||
return err
|
"xray": xray.GetBinaryPath(),
|
||||||
|
"geosite.dat": xray.GetGeositePath(),
|
||||||
|
"geoip.dat": xray.GetGeoipPath(),
|
||||||
}
|
}
|
||||||
err = copyZipFile("geoip.dat", xray.GetGeoipPath())
|
|
||||||
if err != nil {
|
downloadFiles := map[string]string{
|
||||||
return err
|
xray.GetIranPath(): "https://github.com/MasterKia/iran-hosted-domains/releases/latest/download/iran.dat",
|
||||||
}
|
}
|
||||||
err = copyZipFile("iran.dat", xray.GetIranPath())
|
|
||||||
if err != nil {
|
for fileName, filePath := range copyFiles {
|
||||||
return err
|
err := copyZipFile(fileName, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for fileName, filePath := range downloadFiles {
|
||||||
|
err := downloadFile(fileName, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetLogs(count string) ([]string, error) {
|
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
||||||
// Define the journalctl command and its arguments
|
c, _ := strconv.Atoi(count)
|
||||||
var cmdArgs []string
|
var lines []string
|
||||||
if runtime.GOOS == "linux" {
|
|
||||||
cmdArgs = []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count}
|
if syslog == "true" {
|
||||||
|
cmdArgs := []string{"journalctl", "-u", "x-ui", "--no-pager", "-n", count, "-p", level}
|
||||||
|
// Run the command
|
||||||
|
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return []string{"Failed to run journalctl command!"}
|
||||||
|
}
|
||||||
|
lines = strings.Split(out.String(), "\n")
|
||||||
} else {
|
} else {
|
||||||
return []string{"Unsupported operating system"}, nil
|
lines = logger.GetLogs(c, level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the command
|
return lines
|
||||||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
|
|
||||||
var out bytes.Buffer
|
|
||||||
cmd.Stdout = &out
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(out.String(), "\n")
|
|
||||||
|
|
||||||
return lines, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
func (s *ServerService) GetConfigJson() (interface{}, error) {
|
||||||
// Open the file for reading
|
config, err := s.xrayService.GetXrayConfig()
|
||||||
file, err := os.Open(xray.GetConfigPath())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
contents, err := json.MarshalIndent(config, "", " ")
|
||||||
|
|
||||||
// Read the file contents
|
|
||||||
fileContents, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsonData interface{}
|
var jsonData interface{}
|
||||||
err = json.Unmarshal(fileContents, &jsonData)
|
err = json.Unmarshal(contents, &jsonData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var xrayTemplateConfig string
|
|||||||
var defaultValueMap = map[string]string{
|
var defaultValueMap = map[string]string{
|
||||||
"xrayTemplateConfig": xrayTemplateConfig,
|
"xrayTemplateConfig": xrayTemplateConfig,
|
||||||
"webListen": "",
|
"webListen": "",
|
||||||
|
"webDomain": "",
|
||||||
"webPort": "2053",
|
"webPort": "2053",
|
||||||
"webCertFile": "",
|
"webCertFile": "",
|
||||||
"webKeyFile": "",
|
"webKeyFile": "",
|
||||||
@@ -38,8 +39,20 @@ var defaultValueMap = map[string]string{
|
|||||||
"tgBotChatId": "",
|
"tgBotChatId": "",
|
||||||
"tgRunTime": "@daily",
|
"tgRunTime": "@daily",
|
||||||
"tgBotBackup": "false",
|
"tgBotBackup": "false",
|
||||||
|
"tgBotLoginNotify": "true",
|
||||||
"tgCpu": "0",
|
"tgCpu": "0",
|
||||||
|
"tgLang": "en-US",
|
||||||
"secretEnable": "false",
|
"secretEnable": "false",
|
||||||
|
"subEnable": "false",
|
||||||
|
"subListen": "",
|
||||||
|
"subPort": "2096",
|
||||||
|
"subPath": "/sub/",
|
||||||
|
"subDomain": "",
|
||||||
|
"subCertFile": "",
|
||||||
|
"subKeyFile": "",
|
||||||
|
"subUpdates": "12",
|
||||||
|
"subEncrypt": "true",
|
||||||
|
"subShowInfo": "true",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct {
|
type SettingService struct {
|
||||||
@@ -216,6 +229,10 @@ func (s *SettingService) GetListen() (string, error) {
|
|||||||
return s.getString("webListen")
|
return s.getString("webListen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetWebDomain() (string, error) {
|
||||||
|
return s.getString("webDomain")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgBotToken() (string, error) {
|
func (s *SettingService) GetTgBotToken() (string, error) {
|
||||||
return s.getString("tgBotToken")
|
return s.getString("tgBotToken")
|
||||||
}
|
}
|
||||||
@@ -252,10 +269,18 @@ func (s *SettingService) GetTgBotBackup() (bool, error) {
|
|||||||
return s.getBool("tgBotBackup")
|
return s.getBool("tgBotBackup")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgBotLoginNotify() (bool, error) {
|
||||||
|
return s.getBool("tgBotLoginNotify")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetTgCpu() (int, error) {
|
func (s *SettingService) GetTgCpu() (int, error) {
|
||||||
return s.getInt("tgCpu")
|
return s.getInt("tgCpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetTgLang() (string, error) {
|
||||||
|
return s.getString("tgLang")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetPort() (int, error) {
|
func (s *SettingService) GetPort() (int, error) {
|
||||||
return s.getInt("webPort")
|
return s.getInt("webPort")
|
||||||
}
|
}
|
||||||
@@ -331,6 +356,56 @@ func (s *SettingService) GetTimeLocation() (*time.Location, error) {
|
|||||||
return location, nil
|
return location, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubEnable() (bool, error) {
|
||||||
|
return s.getBool("subEnable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubListen() (string, error) {
|
||||||
|
return s.getString("subListen")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubPort() (int, error) {
|
||||||
|
return s.getInt("subPort")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubPath() (string, error) {
|
||||||
|
subPath, err := s.getString("subPath")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(subPath, "/") {
|
||||||
|
subPath = "/" + subPath
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(subPath, "/") {
|
||||||
|
subPath += "/"
|
||||||
|
}
|
||||||
|
return subPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubDomain() (string, error) {
|
||||||
|
return s.getString("subDomain")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubCertFile() (string, error) {
|
||||||
|
return s.getString("subCertFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||||
|
return s.getString("subKeyFile")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubUpdates() (int, error) {
|
||||||
|
return s.getInt("subUpdates")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||||
|
return s.getBool("subEncrypt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSubShowInfo() (bool, error) {
|
||||||
|
return s.getBool("subShowInfo")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||||
if err := allSetting.CheckValid(); err != nil {
|
if err := allSetting.CheckValid(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
1026
web/service/tgbot.go
@@ -18,6 +18,7 @@ var result string
|
|||||||
type XrayService struct {
|
type XrayService struct {
|
||||||
inboundService InboundService
|
inboundService InboundService
|
||||||
settingService SettingService
|
settingService SettingService
|
||||||
|
xrayAPI xray.XrayAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) IsXrayRunning() bool {
|
func (s *XrayService) IsXrayRunning() bool {
|
||||||
@@ -68,7 +69,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s.inboundService.DisableInvalidClients()
|
s.inboundService.AddTraffic(nil, nil)
|
||||||
|
|
||||||
inbounds, err := s.inboundService.GetAllInbounds()
|
inbounds, err := s.inboundService.GetAllInbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -115,7 +116,7 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key := range c {
|
for key := range c {
|
||||||
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "alterId" {
|
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" {
|
||||||
delete(c, key)
|
delete(c, key)
|
||||||
}
|
}
|
||||||
if c["flow"] == "xtls-rprx-vision-udp443" {
|
if c["flow"] == "xtls-rprx-vision-udp443" {
|
||||||
@@ -143,7 +144,9 @@ func (s *XrayService) GetXrayTraffic() ([]*xray.Traffic, []*xray.ClientTraffic,
|
|||||||
if !s.IsXrayRunning() {
|
if !s.IsXrayRunning() {
|
||||||
return nil, nil, errors.New("xray is not running")
|
return nil, nil, errors.New("xray is not running")
|
||||||
}
|
}
|
||||||
return p.GetTraffic(true)
|
s.xrayAPI.Init(p.GetAPIPort())
|
||||||
|
defer s.xrayAPI.Close()
|
||||||
|
return s.xrayAPI.GetTraffic(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) RestartXray(isForce bool) error {
|
func (s *XrayService) RestartXray(isForce bool) error {
|
||||||
@@ -158,7 +161,7 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
|||||||
|
|
||||||
if p != nil && p.IsRunning() {
|
if p != nil && p.IsRunning() {
|
||||||
if !isForce && p.GetConfig().Equals(xrayConfig) {
|
if !isForce && p.GetConfig().Equals(xrayConfig) {
|
||||||
logger.Debug("not need to restart xray")
|
logger.Debug("It does not need to restart xray")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
p.Stop()
|
p.Stop()
|
||||||
@@ -166,7 +169,11 @@ func (s *XrayService) RestartXray(isForce bool) error {
|
|||||||
|
|
||||||
p = xray.NewProcess(xrayConfig)
|
p = xray.NewProcess(xrayConfig)
|
||||||
result = ""
|
result = ""
|
||||||
return p.Start()
|
err = p.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *XrayService) StopXray() error {
|
func (s *XrayService) StopXray() error {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
sessions "github.com/Calidity/gin-sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"enable" = "Enable"
|
"enable" = "Enable"
|
||||||
"protocol" = "Protocol"
|
"protocol" = "Protocol"
|
||||||
"search" = "Search"
|
"search" = "Search"
|
||||||
|
"filter" = "Filter"
|
||||||
"loading" = "Loading"
|
"loading" = "Loading"
|
||||||
"second" = "Second"
|
"second" = "Second"
|
||||||
"minute" = "Minute"
|
"minute" = "Minute"
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
"depleted" = "Depleted"
|
"depleted" = "Depleted"
|
||||||
"depletingSoon" = "Depleting soon"
|
"depletingSoon" = "Depleting soon"
|
||||||
"domainName" = "Domain name"
|
"domainName" = "Domain name"
|
||||||
"additional" = "Alter"
|
|
||||||
"monitor" = "Listening IP"
|
"monitor" = "Listening IP"
|
||||||
"certificate" = "Certificate"
|
"certificate" = "Certificate"
|
||||||
"fail" = "Fail"
|
"fail" = "Fail"
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"clients" = "Clients"
|
"clients" = "Clients"
|
||||||
"usage" = "Usage"
|
"usage" = "Usage"
|
||||||
"secretToken" = "Secret Token"
|
"secretToken" = "Secret Token"
|
||||||
|
"remained" = "Remained"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "System Status"
|
"dashboard" = "System Status"
|
||||||
@@ -72,17 +72,18 @@
|
|||||||
"title" = "System Status"
|
"title" = "System Status"
|
||||||
"memory" = "Memory"
|
"memory" = "Memory"
|
||||||
"hard" = "Hard Disk"
|
"hard" = "Hard Disk"
|
||||||
"xrayStatus" = "Xray Status"
|
"xrayStatus" = "Status"
|
||||||
"stopXray" = "Stop"
|
"stopXray" = "Stop"
|
||||||
"restartXray" = "Restart"
|
"restartXray" = "Restart"
|
||||||
"xraySwitch" = "Switch Version"
|
"xraySwitch" = "SwitchV"
|
||||||
"xraySwitchClick" = "Choose the version you want to switch to."
|
"xraySwitchClick" = "Choose the version you want to switch to."
|
||||||
"xraySwitchClickDesk" = "Choose wisely, as older versions may not be compatible with current configurations."
|
"xraySwitchClickDesk" = "Choose wisely, as older versions may not be compatible with current configurations."
|
||||||
"operationHours" = "Operation Hours"
|
"operationHours" = "Uptime"
|
||||||
"operationHoursDesc" = "System uptime: time since startup."
|
|
||||||
"systemLoad" = "System Load"
|
"systemLoad" = "System Load"
|
||||||
|
"systemLoadDesc" = "system load average for the past 1, 5, and 15 minutes"
|
||||||
|
"connectionTcpCountDesc" = "Total TCP connections across all network cards."
|
||||||
|
"connectionUdpCountDesc" = "Total UDP connections across all network cards."
|
||||||
"connectionCount" = "Number of Connections"
|
"connectionCount" = "Number of Connections"
|
||||||
"connectionCountDesc" = "Total connections across all network cards."
|
|
||||||
"upSpeed" = "Total upload speed for all network cards."
|
"upSpeed" = "Total upload speed for all network cards."
|
||||||
"downSpeed" = "Total download speed for all network cards."
|
"downSpeed" = "Total download speed for all network cards."
|
||||||
"totalSent" = "Total upload traffic of all network cards since system startup."
|
"totalSent" = "Total upload traffic of all network cards since system startup."
|
||||||
@@ -126,7 +127,6 @@
|
|||||||
"network" = "Network"
|
"network" = "Network"
|
||||||
"destinationPort" = "Destination Port"
|
"destinationPort" = "Destination Port"
|
||||||
"targetAddress" = "Target Address"
|
"targetAddress" = "Target Address"
|
||||||
"disableInsecureEncryption" = "Disable Insecure Encryption"
|
|
||||||
"monitorDesc" = "Leave blank by default"
|
"monitorDesc" = "Leave blank by default"
|
||||||
"meansNoLimit" = "Means No Limit"
|
"meansNoLimit" = "Means No Limit"
|
||||||
"totalFlow" = "Total Flow"
|
"totalFlow" = "Total Flow"
|
||||||
@@ -148,8 +148,6 @@
|
|||||||
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
"resetAllTraffic" = "Reset All Inbounds Traffic"
|
||||||
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
"resetAllTrafficTitle" = "Reset all inbounds traffic"
|
||||||
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
"resetAllTrafficContent" = "Are you sure you want to reset all inbounds traffic?"
|
||||||
"resetAllTrafficOkText" = "Confirm"
|
|
||||||
"resetAllTrafficCancelText" = "Cancel"
|
|
||||||
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
"resetInboundClientTraffics" = "Reset Clients Traffic"
|
||||||
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
"resetInboundClientTrafficTitle" = "Reset all client traffic"
|
||||||
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
"resetInboundClientTrafficContent" = "Are you sure you want to reset all traffic for this inbound's clients?"
|
||||||
@@ -169,7 +167,7 @@
|
|||||||
"setDefaultCert" = "Set cert from panel"
|
"setDefaultCert" = "Set cert from panel"
|
||||||
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
"xtlsDesc" = "Xray core needs to be 1.7.5"
|
||||||
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
"realityDesc" = "Xray core needs to be 1.8.0 or higher."
|
||||||
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot )"
|
"telegramDesc" = "use Telegram ID without @ or chat IDs ( you can get it here @userinfobot or use '/id' command in bot )"
|
||||||
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
"subscriptionDesc" = "you can find your sub link on Details, also you can use the same name for several configurations"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -211,6 +209,7 @@
|
|||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "Settings"
|
"title" = "Settings"
|
||||||
"save" = "Save"
|
"save" = "Save"
|
||||||
|
"infoDesc" = "Every change made here needs to be saved. Please restart the panel to apply changes."
|
||||||
"restartPanel" = "Restart Panel "
|
"restartPanel" = "Restart Panel "
|
||||||
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
|
"restartPanelDesc" = "Are you sure you want to restart the panel? Click OK to restart after 3 seconds. If you cannot access the panel after restarting, please view the panel log information on the server."
|
||||||
"actions" = "Actions"
|
"actions" = "Actions"
|
||||||
@@ -220,29 +219,33 @@
|
|||||||
"xrayConfiguration" = "Xray Configuration"
|
"xrayConfiguration" = "Xray Configuration"
|
||||||
"TGBotSettings" = "Telegram Bot Settings"
|
"TGBotSettings" = "Telegram Bot Settings"
|
||||||
"panelListeningIP" = "Panel Listening IP"
|
"panelListeningIP" = "Panel Listening IP"
|
||||||
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs. Restart the panel to apply changes."
|
"panelListeningIPDesc" = "Leave blank by default to monitor all IPs."
|
||||||
|
"panelListeningDomain" = "Panel Listening Domain"
|
||||||
|
"panelListeningDomainDesc" = "Leave blank by default to monitor all domains and IPs"
|
||||||
"panelPort" = "Panel Port"
|
"panelPort" = "Panel Port"
|
||||||
"panelPortDesc" = "Restart the panel to apply changes."
|
"panelPortDesc" = "The port used to display this panel"
|
||||||
"publicKeyPath" = "Panel Certificate Public Key File Path"
|
"publicKeyPath" = "Panel Certificate Public Key File Path"
|
||||||
"publicKeyPathDesc" = "Fill in an absolute path starting with '/'. Restart the panel to apply changes."
|
"publicKeyPathDesc" = "Fill in an absolute path starting with."
|
||||||
"privateKeyPath" = "Panel Certificate Private Key File Path"
|
"privateKeyPath" = "Panel Certificate Private Key File Path"
|
||||||
"privateKeyPathDesc" = "Fill in an absolute path starting with '/'. Restart the panel to apply changes."
|
"privateKeyPathDesc" = "Fill in an absolute path starting with."
|
||||||
"panelUrlPath" = "Panel URL Root Path"
|
"panelUrlPath" = "Panel URL Root Path"
|
||||||
"panelUrlPathDesc" = "Must start with '/' and end with '/'. Restart the panel to apply changes."
|
"panelUrlPathDesc" = "Must start with '/' and end with."
|
||||||
"oldUsername" = "Current Username"
|
"oldUsername" = "Current Username"
|
||||||
"currentPassword" = "Current Password"
|
"currentPassword" = "Current Password"
|
||||||
"newUsername" = "New Username"
|
"newUsername" = "New Username"
|
||||||
"newPassword" = "New Password"
|
"newPassword" = "New Password"
|
||||||
"telegramBotEnable" = "Enable Telegram bot"
|
"telegramBotEnable" = "Enable Telegram bot"
|
||||||
"telegramBotEnableDesc" = "Restart the panel to take effect."
|
"telegramBotEnableDesc" = "Connect to the features of this panel through the Telegram bot"
|
||||||
"telegramToken" = "Telegram Token"
|
"telegramToken" = "Telegram Token"
|
||||||
"telegramTokenDesc" = "Restart the panel to take effect."
|
"telegramTokenDesc" = "You must get the token from the manager of Telegram bots @botfather"
|
||||||
"telegramChatId" = "Telegram Admin Chat IDs"
|
"telegramChatId" = "Telegram Admin Chat IDs"
|
||||||
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot to get your Chat IDs. Restart the panel to apply changes."
|
"telegramChatIdDesc" = "Multiple Chat IDs separated by comma. use @userinfobot or use '/id' command in bot to get your Chat IDs."
|
||||||
"telegramNotifyTime" = "Telegram bot notification time"
|
"telegramNotifyTime" = "Telegram bot notification time"
|
||||||
"telegramNotifyTimeDesc" = "Use Crontab timing format. Restart the panel to apply changes."
|
"telegramNotifyTimeDesc" = "Use Crontab timing format."
|
||||||
"tgNotifyBackup" = "Database Backup"
|
"tgNotifyBackup" = "Database Backup"
|
||||||
"tgNotifyBackupDesc" = "Include database backup file with report notification. Restart the panel to apply changes."
|
"tgNotifyBackupDesc" = "Include database backup file with report notification."
|
||||||
|
"tgNotifyLogin" = "Login Notification"
|
||||||
|
"tgNotifyLoginDesc" = "Displays the username, IP address, and time when someone tries to log into your panel."
|
||||||
"sessionMaxAge" = "Session maximum age"
|
"sessionMaxAge" = "Session maximum age"
|
||||||
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
|
"sessionMaxAgeDesc" = "The duration of a login session (unit: minute)"
|
||||||
"expireTimeDiff" = "Expiration threshold for notification"
|
"expireTimeDiff" = "Expiration threshold for notification"
|
||||||
@@ -252,7 +255,28 @@
|
|||||||
"tgNotifyCpu" = "CPU percentage alert threshold"
|
"tgNotifyCpu" = "CPU percentage alert threshold"
|
||||||
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
|
"tgNotifyCpuDesc" = "Receive notification if CPU usage exceeds this threshold (unit: %)"
|
||||||
"timeZone" = "Time zone"
|
"timeZone" = "Time zone"
|
||||||
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone. Restart the panel to apply changes."
|
"timeZoneDesc" = "Scheduled tasks run according to the time in this time zone."
|
||||||
|
"subSettings" = "Subscription"
|
||||||
|
"subEnable" = "Enable service"
|
||||||
|
"subEnableDesc" = "Subscription feature with separate configuration"
|
||||||
|
"subListen" = "Listening IP"
|
||||||
|
"subListenDesc" = "Leave blank by default to monitor all IPs"
|
||||||
|
"subPort" = "Subscription Port"
|
||||||
|
"subPortDesc" = "Port number for serving the subscription service must be unused in server"
|
||||||
|
"subCertPath" = "Subscription Certificate Public Key File Path"
|
||||||
|
"subCertPathDesc" = "Fill in an absolute path starting with '/'"
|
||||||
|
"subKeyPath" = "Subscription Certificate Private Key File Path"
|
||||||
|
"subKeyPathDesc" = "Fill in an absolute path starting with '/'"
|
||||||
|
"subPath" = "Subscription URL Root Path"
|
||||||
|
"subPathDesc" = "Must start with '/' and end with '/'"
|
||||||
|
"subDomain" = "Listening Domain"
|
||||||
|
"subDomainDesc" = "Leave blank by default to monitor all domains and IPs"
|
||||||
|
"subUpdates" = "Subscription update intervals"
|
||||||
|
"subUpdatesDesc" = "Interval hours between updates in client application"
|
||||||
|
"subEncrypt" = "Encrypt configs"
|
||||||
|
"subEncryptDesc" = "Encrypt the returned configs in subscription"
|
||||||
|
"subShowInfo" = "Show usage info"
|
||||||
|
"subShowInfoDesc" = "Show remianed traffic and date after config name"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "Templates"
|
"title" = "Templates"
|
||||||
@@ -260,57 +284,85 @@
|
|||||||
"advancedTemplate" = "Advanced Template"
|
"advancedTemplate" = "Advanced Template"
|
||||||
"completeTemplate" = "Complete Template"
|
"completeTemplate" = "Complete Template"
|
||||||
"generalConfigs" = "General Configs"
|
"generalConfigs" = "General Configs"
|
||||||
"generalConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites."
|
"generalConfigsDesc" = "These options will provide general adjustments."
|
||||||
"countryConfigs" = "Country Configs"
|
"blockConfigs" = "Blocking Configs"
|
||||||
"countryConfigsDesc" = "These options will prevent users from connecting to specific country domains."
|
"blockConfigsDesc" = "These options will prevent users from connecting to specific protocols and websites."
|
||||||
|
"blockCountryConfigs" = "Block Country Configs"
|
||||||
|
"blockCountryConfigsDesc" = "These options will prevent users from connecting to specific country domains."
|
||||||
|
"directCountryConfigs" = "Direct Country Configs"
|
||||||
|
"directCountryConfigsDesc" = "These options will connect users directly to specific country domains."
|
||||||
"ipv4Configs" = "IPv4 Configs"
|
"ipv4Configs" = "IPv4 Configs"
|
||||||
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
|
"ipv4ConfigsDesc" = "These options will route to target domains only via IPv4."
|
||||||
"warpConfigs" = "WARP Configs"
|
"warpConfigs" = "WARP Configs"
|
||||||
"warpConfigsDesc" = "Caution: Before using these options, install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
"warpConfigsDesc" = "Caution: Before using these options, install WARP in socks5 proxy mode on your server by following the steps on the panel's GitHub. WARP will route traffic to websites through Cloudflare servers."
|
||||||
"xrayConfigTemplate" = "Xray Configuration Template"
|
"xrayConfigTemplate" = "Xray Configuration Template"
|
||||||
"xrayConfigTemplateDesc" = "Generate the final Xray configuration file based on this template. Restart the panel to apply changes."
|
"xrayConfigTemplateDesc" = "Generate the final Xray configuration file based on this template."
|
||||||
|
"xrayConfigFreedomStrategy" = "Configure Strategy for Freedom Protocol"
|
||||||
|
"xrayConfigFreedomStrategyDesc" = "Set the output strategy of the network in the Freedom Protocol."
|
||||||
|
"xrayConfigRoutingStrategy" = "Configure Domains Routing Strategy"
|
||||||
|
"xrayConfigRoutingStrategyDesc" = "Set the overall routing strategy for DNS resolving."
|
||||||
"xrayConfigTorrent" = "Ban BitTorrent Usage"
|
"xrayConfigTorrent" = "Ban BitTorrent Usage"
|
||||||
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using BitTorrent by users. Restart the panel to apply changes."
|
"xrayConfigTorrentDesc" = "Change the configuration template to avoid using BitTorrent by users."
|
||||||
"xrayConfigPrivateIp" = "Ban Private IP Ranges to Connect"
|
"xrayConfigPrivateIp" = "Ban Private IP Ranges to Connect"
|
||||||
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting with private IP ranges. Restart the panel to apply changes."
|
"xrayConfigPrivateIpDesc" = "Change the configuration template to avoid connecting to private IP ranges."
|
||||||
"xrayConfigAds" = "Block Ads"
|
"xrayConfigAds" = "Block Ads"
|
||||||
"xrayConfigAdsDesc" = "Change the configuration template to block ads. Restart the panel to apply changes."
|
"xrayConfigAdsDesc" = "Change the configuration template to block ads."
|
||||||
"xrayConfigPorn" = "Block Porn Websites"
|
"xrayConfigFamily" = "Block Malware and Adult Content"
|
||||||
"xrayConfigPornDesc" = "Change the configuration template to avoid connecting to porn websites. Restart the panel to apply changes."
|
"xrayConfigFamilyDesc" = "DNS resolvers to block malware and adult content for family protection."
|
||||||
"xrayConfigSpeedtest" = "Block Speedtest Websites"
|
"xrayConfigSpeedtest" = "Block Speedtest Websites"
|
||||||
"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites. Restart the panel to apply changes."
|
"xrayConfigSpeedtestDesc" = "Change the configuration template to avoid connecting to speedtest websites."
|
||||||
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
"xrayConfigIRIp" = "Disable connection to Iran IP ranges"
|
||||||
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting with Iran IP ranges. Restart the panel to apply changes."
|
"xrayConfigIRIpDesc" = "Change the configuration template to avoid connecting to Iran IP ranges."
|
||||||
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
"xrayConfigIRDomain" = "Disable connection to Iran domains"
|
||||||
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting with Iran domains. Restart the panel to apply changes."
|
"xrayConfigIRDomainDesc" = "Change the configuration template to avoid connecting to Iran domains."
|
||||||
"xrayConfigChinaIp" = "Disable connection to China IP ranges"
|
"xrayConfigChinaIp" = "Disable connection to China IP ranges"
|
||||||
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting with China IP ranges. Restart the panel to apply changes."
|
"xrayConfigChinaIpDesc" = "Change the configuration template to avoid connecting to China IP ranges."
|
||||||
"xrayConfigChinaDomain" = "Disable connection to China domains"
|
"xrayConfigChinaDomain" = "Disable connection to China domains"
|
||||||
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting with China domains. Restart the panel to apply changes."
|
"xrayConfigChinaDomainDesc" = "Change the configuration template to avoid connecting to China domains."
|
||||||
"xrayConfigRussiaIp" = "Disable connection to Russia IP ranges"
|
"xrayConfigRussiaIp" = "Disable connection to Russia IP ranges"
|
||||||
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting with Russia IP ranges. Restart the panel to apply changes."
|
"xrayConfigRussiaIpDesc" = "Change the configuration template to avoid connecting to Russia IP ranges."
|
||||||
"xrayConfigRussiaDomain" = "Disable connection to Russia domains"
|
"xrayConfigRussiaDomain" = "Disable connection to Russia domains"
|
||||||
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting with Russia domains. Restart the panel to apply changes."
|
"xrayConfigRussiaDomainDesc" = "Change the configuration template to avoid connecting to Russia domains."
|
||||||
|
"xrayConfigDirectIRIp" = "Direct connection to Iran IP ranges"
|
||||||
|
"xrayConfigDirectIRIpDesc" = "Change the configuration template for direct connecting to Iran IP ranges."
|
||||||
|
"xrayConfigDirectIRDomain" = "Direct connection to Iran domains"
|
||||||
|
"xrayConfigDirectIRDomainDesc" = "Change the configuration template for direct connecting to Iran domains."
|
||||||
|
"xrayConfigDirectChinaIp" = "Direct connection to China IP ranges"
|
||||||
|
"xrayConfigDirectChinaIpDesc" = "Change the configuration template for direct connecting to China IP ranges."
|
||||||
|
"xrayConfigDirectChinaDomain" = "Direct connection to China domains"
|
||||||
|
"xrayConfigDirectChinaDomainDesc" = "Change the configuration template for direct connecting to China domains."
|
||||||
|
"xrayConfigDirectRussiaIp" = "Direct connection to Russia IP ranges"
|
||||||
|
"xrayConfigDirectRussiaIpDesc" = "Change the configuration template for direct connecting to Russia IP ranges."
|
||||||
|
"xrayConfigDirectRussiaDomain" = "Direct connection to Russia domains"
|
||||||
|
"xrayConfigDirectRussiaDomainDesc" = "Change the configuration template for direct connecting to Russia domains."
|
||||||
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
"xrayConfigGoogleIPv4" = "Use IPv4 for Google"
|
||||||
"xrayConfigGoogleIPv4Desc" = "Add routing for Google to connect with IPv4. Restart the panel to apply changes."
|
"xrayConfigGoogleIPv4Desc" = "Add routing for Google to connect with IPv4."
|
||||||
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
"xrayConfigNetflixIPv4" = "Use IPv4 for Netflix"
|
||||||
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4. Restart the panel to apply changes."
|
"xrayConfigNetflixIPv4Desc" = "Add routing for Netflix to connect with IPv4."
|
||||||
"xrayConfigGoogleWARP" = "Route Google through WARP."
|
"xrayConfigGoogleWARP" = "Route Google through WARP."
|
||||||
"xrayConfigGoogleWARPDesc" = "Add routing for Google via WARP. Restart the panel to apply changes."
|
"xrayConfigGoogleWARPDesc" = "Add routing for Google via WARP."
|
||||||
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) through WARP."
|
"xrayConfigOpenAIWARP" = "Route OpenAI (ChatGPT) through WARP."
|
||||||
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) via WARP. Restart the panel to apply changes."
|
"xrayConfigOpenAIWARPDesc" = "Add routing for OpenAI (ChatGPT) via WARP."
|
||||||
"xrayConfigNetflixWARP" = "Route Netflix through WARP."
|
"xrayConfigNetflixWARP" = "Route Netflix through WARP."
|
||||||
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix via WARP. Restart the panel to apply changes."
|
"xrayConfigNetflixWARPDesc" = "Add routing for Netflix via WARP."
|
||||||
"xrayConfigSpotifyWARP" = "Route Spotify through WARP."
|
"xrayConfigSpotifyWARP" = "Route Spotify through WARP."
|
||||||
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify via WARP. Restart the panel to apply changes."
|
"xrayConfigSpotifyWARPDesc" = "Add routing for Spotify via WARP."
|
||||||
"xrayConfigIRWARP" = "Route Iran domains through WARP."
|
"xrayConfigIRWARP" = "Route Iran domains through WARP."
|
||||||
"xrayConfigIRWARPDesc" = "Add routing for Iran domains via WARP. Restart the panel to apply changes."
|
"xrayConfigIRWARPDesc" = "Add routing for Iran domains via WARP."
|
||||||
"xrayConfigInbounds" = "Configuration of Inbounds"
|
"xrayConfigInbounds" = "Configuration of Inbounds"
|
||||||
"xrayConfigInboundsDesc" = "Change the configuration template to accept specific clients. Restart the panel to apply changes."
|
"xrayConfigInboundsDesc" = "Change the configuration template to accept specific clients."
|
||||||
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
"xrayConfigOutbounds" = "Configuration of Outbounds"
|
||||||
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server. Restart the panel to apply changes."
|
"xrayConfigOutboundsDesc" = "Change the configuration template to define outgoing ways for this server."
|
||||||
"xrayConfigRoutings" = "Configuration of routing rules."
|
"xrayConfigRoutings" = "Configuration of routing rules."
|
||||||
"xrayConfigRoutingsDesc" = "Change the configuration template to define routing rules for this server. Restart the panel to apply changes."
|
"xrayConfigRoutingsDesc" = "Change the configuration template to define routing rules for this server."
|
||||||
|
"manualLists" = "Manual Lists"
|
||||||
|
"manualListsDesc" = "Please use the JSON array format."
|
||||||
|
"manualBlockedIPs" = "List of Blocked IPs"
|
||||||
|
"manualBlockedDomains" = "List of Blocked Domains"
|
||||||
|
"manualDirectIPs" = "List of Direct IPs"
|
||||||
|
"manualDirectDomains" = "List of Direct Domains"
|
||||||
|
"manualIPv4Domains" = "List of IPv4 Domains"
|
||||||
|
"manualWARPDomains" = "List of WARP Domains"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "Admin"
|
"admin" = "Admin"
|
||||||
@@ -326,3 +378,118 @@
|
|||||||
"modifyUser" = "Modify User "
|
"modifyUser" = "Modify User "
|
||||||
"originalUserPassIncorrect" = "Incorrect original username or password"
|
"originalUserPassIncorrect" = "Incorrect original username or password"
|
||||||
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
"userPassMustBeNotEmpty" = "New username and new password cannot be empty"
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ Custom keyboard closed!"
|
||||||
|
"noResult" = "❗ No result!"
|
||||||
|
"noQuery" = "❌ Query not found! Please use the command again!"
|
||||||
|
"wentWrong" = "❌ Something went wrong!"
|
||||||
|
"noIpRecord" = "❗ No IP Record!"
|
||||||
|
"noInbounds" = "❗ No inbound found!"
|
||||||
|
"unlimited" = "♾ Unlimited"
|
||||||
|
"month" = "Month"
|
||||||
|
"months" = "Months"
|
||||||
|
"day" = "Day"
|
||||||
|
"days" = "Days"
|
||||||
|
"hours" = "Hours"
|
||||||
|
"unknown" = "Unknown"
|
||||||
|
"inbounds" = "Inbounds"
|
||||||
|
"clients" = "Clients"
|
||||||
|
|
||||||
|
[tgbot.commands]
|
||||||
|
"unknown" = "❗ Unknown command"
|
||||||
|
"pleaseChoose" = "👇 Please choose:\r\n"
|
||||||
|
"help" = "🤖 Welcome to this bot! It's designed to offer you specific data from the server, and it allows you to make modifications as needed.\r\n\r\n"
|
||||||
|
"start" = "👋 Hello <i>{{ .Firstname }}</i>.\r\n"
|
||||||
|
"welcome" = "🤖 Welcome to <b>{{ .Hostname }}</b> management bot.\r\n"
|
||||||
|
"status" = "✅ Bot is ok!"
|
||||||
|
"usage" = "❗ Please provide a text to search!"
|
||||||
|
"getID" = "🆔 Your ID: <code>{{ .ID }}</code>"
|
||||||
|
"helpAdminCommands" = "Search for a client email:\r\n<code>/usage [Email]</code>\r\n \r\nSearch for inbounds (with client stats):\r\n<code>/inbound [Remark]</code>"
|
||||||
|
"helpClientCommands" = "To search for statistics, just use folowing command:\r\n \r\n<code>/usage [UUID|Password]</code>\r\n \r\nUse UUID for vmess/vless and Password for Trojan."
|
||||||
|
|
||||||
|
[tgbot.messages]
|
||||||
|
"cpuThreshold" = "🔴 The CPU usage {{ .Percent }}% is more than threshold {{ .Threshold }}%"
|
||||||
|
"selectUserFailed" = "❌ Error in user selection!"
|
||||||
|
"userSaved" = "✅ Telegram User saved."
|
||||||
|
"loginSuccess" = "✅ Successfully logged-in to the panel.\r\n"
|
||||||
|
"loginFailed" = "❗️ Login to the panel failed.\r\n"
|
||||||
|
"report" = "🕰 Scheduled Reports: {{ .RunTime }}\r\n"
|
||||||
|
"datetime" = "⏰ Date-Time: {{ .DateTime }}\r\n"
|
||||||
|
"hostname" = "💻 Hostname: {{ .Hostname }}\r\n"
|
||||||
|
"version" = "🚀 X-UI Version: {{ .Version }}\r\n"
|
||||||
|
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||||
|
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||||
|
"ip" = "🌐 IP: {{ .IP }}\r\n"
|
||||||
|
"ips" = "🔢 IPs: \r\n{{ .IPs }}\r\n"
|
||||||
|
"serverUpTime" = "⏳ Server Uptime: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||||
|
"serverLoad" = "📈 Server Load: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||||
|
"serverMemory" = "📋 Server Memory: {{ .Current }}/{{ .Total }}\r\n"
|
||||||
|
"tcpCount" = "🔹 TcpCount: {{ .Count }}\r\n"
|
||||||
|
"udpCount" = "🔸 UdpCount: {{ .Count }}\r\n"
|
||||||
|
"traffic" = "🚦 Traffic: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||||
|
"xrayStatus" = "ℹ️ Xray Status: {{ .State }}\r\n"
|
||||||
|
"username" = "👤 Username: {{ .Username }}\r\n"
|
||||||
|
"time" = "⏰ Time: {{ .Time }}\r\n"
|
||||||
|
"inbound" = "📍 Inbound: {{ .Remark }}\r\n"
|
||||||
|
"port" = "🔌 Port: {{ .Port }}\r\n"
|
||||||
|
"expire" = "📅 Expire Date: {{ .Time }}\r\n"
|
||||||
|
"expireIn" = "📅 Expire In: {{ .Time }}\r\n"
|
||||||
|
"active" = "💡 Active: ✅ Yes\r\n"
|
||||||
|
"inactive" = "💡 Active: ❌ No\r\n"
|
||||||
|
"email" = "📧 Email: {{ .Email }}\r\n"
|
||||||
|
"upload" = "🔼 Upload: ↑{{ .Upload }}\r\n"
|
||||||
|
"download" = "🔽 Download: ↓{{ .Download }}\r\n"
|
||||||
|
"total" = "📊 Total: ↑↓{{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
|
"TGUser" = "👤 Telegram User: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 Exhausted {{ .Type }}:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 Exhausted {{ .Type }} count:\r\n"
|
||||||
|
"disabled" = "🛑 Disabled: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 Deplete soon: {{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 Backup Time: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "\r\n📋🔄 Refreshed On: {{ .Time }}\r\n \r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ Close Keyboard"
|
||||||
|
"cancel" = "❌ Cancel"
|
||||||
|
"cancelReset" = "❌ Cancel Reset"
|
||||||
|
"cancelIpLimit" = "❌ Cancel IP Limit"
|
||||||
|
"confirmResetTraffic" = "✅ Confirm Reset Traffic?"
|
||||||
|
"confirmClearIps" = "✅ Confirm Clear IPs?"
|
||||||
|
"confirmRemoveTGUser" = "✅ Confirm Remove Telegram User?"
|
||||||
|
"dbBackup" = "Get DB Backup"
|
||||||
|
"serverUsage" = "Server Usage"
|
||||||
|
"getInbounds" = "Get Inbounds"
|
||||||
|
"depleteSoon" = "Deplete soon"
|
||||||
|
"clientUsage" = "Get Usage"
|
||||||
|
"commands" = "Commands"
|
||||||
|
"refresh" = "🔄 Refresh"
|
||||||
|
"clearIPs" = "❌ Clear IPs"
|
||||||
|
"removeTGUser" = "❌ Remove Telegram User"
|
||||||
|
"selectTGUser" = "👤 Select Telegram User"
|
||||||
|
"selectOneTGUser" = "👤 Select a telegram user:"
|
||||||
|
"resetTraffic" = "📈 Reset Traffic"
|
||||||
|
"resetExpire" = "📅 Reset Expire Days"
|
||||||
|
"ipLog" = "🔢 IP Log"
|
||||||
|
"ipLimit" = "🔢 IP Limit"
|
||||||
|
"setTGUser" = "👤 Set Telegram User"
|
||||||
|
"toggle" = "🔘 Enable / Disable"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ Error in Operation."
|
||||||
|
"getInboundsFailed" = "❌ Failed to get inbounds"
|
||||||
|
"canceled" = "❌ {{ .Email }} : Operation canceled."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }} : Client refreshed successfully."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }} : IPs refreshed successfully."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }} : Client's Telegram User refreshed successfully."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }} : Traffic reset successfully."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }} : Expire days reset successfully."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }} : IP limit {{ .Count }} saved successfully."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }} : IPs cleared successfully."
|
||||||
|
"getIpLog" = "✅ {{ .Email }} : Get IP Log."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }} : Get Telegram User Info."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }} : Telegram User removed successfully."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }} : Enabled successfully."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }} : Disabled successfully."
|
||||||
|
"askToAddUserId" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram user id in your configuration(s).\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "Your configuration is not found!\r\nPlease ask your Admin to use your telegram username or user id in your configuration(s).\r\n\r\nYour username: <b>@{{ .TgUserName }}</b>\r\n\r\nYour user id: <b>{{ .TgUserID }}</b>"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"enable" = "فعال"
|
"enable" = "فعال"
|
||||||
"protocol" = "پروتکل"
|
"protocol" = "پروتکل"
|
||||||
"search" = "جستجو"
|
"search" = "جستجو"
|
||||||
|
"filter" = "فیلتر"
|
||||||
"loading" = "در حال بروزرسانی..."
|
"loading" = "در حال بروزرسانی..."
|
||||||
"second" = "ثانیه"
|
"second" = "ثانیه"
|
||||||
"minute" = "دقیقه"
|
"minute" = "دقیقه"
|
||||||
@@ -39,7 +39,6 @@
|
|||||||
"depleted" = "منقضی"
|
"depleted" = "منقضی"
|
||||||
"depletingSoon" = "در حال انقضا"
|
"depletingSoon" = "در حال انقضا"
|
||||||
"domainName" = "آدرس دامنه"
|
"domainName" = "آدرس دامنه"
|
||||||
"additional" = "آی دی جایگزین"
|
|
||||||
"monitor" = "آی پی اتصال"
|
"monitor" = "آی پی اتصال"
|
||||||
"certificate" = "گواهی دیجیتال"
|
"certificate" = "گواهی دیجیتال"
|
||||||
"fail" = "خطا"
|
"fail" = "خطا"
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"clients" = "کاربران"
|
"clients" = "کاربران"
|
||||||
"usage" = "استفاده"
|
"usage" = "استفاده"
|
||||||
"secretToken" = "توکن امنیتی"
|
"secretToken" = "توکن امنیتی"
|
||||||
|
"remained" = "باقیمانده"
|
||||||
|
|
||||||
[menu]
|
[menu]
|
||||||
"dashboard" = "وضعیت سیستم"
|
"dashboard" = "وضعیت سیستم"
|
||||||
@@ -72,17 +72,18 @@
|
|||||||
"title" = "وضعیت سیستم"
|
"title" = "وضعیت سیستم"
|
||||||
"memory" = "حافظه رم"
|
"memory" = "حافظه رم"
|
||||||
"hard" = "حافظه دیسک"
|
"hard" = "حافظه دیسک"
|
||||||
"xrayStatus" = "وضعیت Xray"
|
"xrayStatus" = "وضعیت"
|
||||||
"stopXray" = "توقف"
|
"stopXray" = "توقف"
|
||||||
"restartXray" = "شروع مجدد"
|
"restartXray" = "شروع مجدد"
|
||||||
"xraySwitch" = "تغییر ورژن"
|
"xraySwitch" = "تغییر ورژن"
|
||||||
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
"xraySwitchClick" = "ورژن مورد نظر را انتخاب کنید"
|
||||||
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد ."
|
"xraySwitchClickDesk" = "لطفا با دقت انتخاب کنید ، در صورت انتخاب اشتباه امکان قطعی سیستم وجود دارد "
|
||||||
"operationHours" = "مدت فعالیت"
|
"operationHours" = "آپ تایم سیستم"
|
||||||
"operationHoursDesc" = "مدت فعالیت سیستم بعد از روشن شدن"
|
"systemLoad" = "بار سیستم"
|
||||||
"systemLoad" = "بار روی سیستم"
|
"systemLoadDesc" = "میانگین بار سیستم برای 1، 5 و 15 دقیقه گذشته"
|
||||||
|
"connectionTcpCountDesc" = "مجموع اتصالات TCP در تمام کارت های شبکه"
|
||||||
|
"connectionUdpCountDesc" = "مجموع اتصالات UDP در تمام کارت های شبکه"
|
||||||
"connectionCount" = "تعداد کانکشن ها"
|
"connectionCount" = "تعداد کانکشن ها"
|
||||||
"connectionCountDesc" = "تعداد کانکشن ها برای کل شبکه"
|
|
||||||
"upSpeed" = "سرعت آپلود در حال حاضر سیستم"
|
"upSpeed" = "سرعت آپلود در حال حاضر سیستم"
|
||||||
"downSpeed" = "سرعت دانلود در حال حاضر سیستم"
|
"downSpeed" = "سرعت دانلود در حال حاضر سیستم"
|
||||||
"totalSent" = "جمع کل ترافیک آپلود مصرفی"
|
"totalSent" = "جمع کل ترافیک آپلود مصرفی"
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
"config" = "تنظیمات"
|
"config" = "تنظیمات"
|
||||||
"backup" = "پشتیبان گیری و بازیابی"
|
"backup" = "پشتیبان گیری و بازیابی"
|
||||||
"backupTitle" = "پشتیبان گیری و بازیابی دیتابیس"
|
"backupTitle" = "پشتیبان گیری و بازیابی دیتابیس"
|
||||||
"backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید."
|
"backupDescription" = "به یاد داشته باشید که قبل از وارد کردن یک دیتابیس جدید، نسخه پشتیبان تهیه کنید"
|
||||||
"exportDatabase" = "دانلود دیتابیس"
|
"exportDatabase" = "دانلود دیتابیس"
|
||||||
"importDatabase" = "آپلود دیتابیس"
|
"importDatabase" = "آپلود دیتابیس"
|
||||||
|
|
||||||
@@ -126,7 +127,6 @@
|
|||||||
"network" = "شبکه"
|
"network" = "شبکه"
|
||||||
"destinationPort" = "پورت مقصد"
|
"destinationPort" = "پورت مقصد"
|
||||||
"targetAddress" = "آدرس مقصد"
|
"targetAddress" = "آدرس مقصد"
|
||||||
"disableInsecureEncryption" = "رمزگذاری ناامن را غیرفعال کنید"
|
|
||||||
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
"monitorDesc" = "به طور پیش فرض خالی بگذارید"
|
||||||
"meansNoLimit" = "یعنی بدون محدودیت"
|
"meansNoLimit" = "یعنی بدون محدودیت"
|
||||||
"totalFlow" = "کل ترافیک"
|
"totalFlow" = "کل ترافیک"
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
"setDefaultCert" = "استفاده از گواهی پنل"
|
"setDefaultCert" = "استفاده از گواهی پنل"
|
||||||
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد"
|
"xtlsDesc" = "هسته Xray باید 1.7.5 باشد"
|
||||||
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
"realityDesc" = "هسته Xray باید 1.8.0 و بالاتر باشد"
|
||||||
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot)"
|
"telegramDesc" = "از آیدی تلگرام بدون @ یا آیدی چت استفاده کنید (می توانید آن را از اینجا دریافت کنید @userinfobot یا در ربات دستور '/id' را وارد کنید)"
|
||||||
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
"subscriptionDesc" = "می توانید ساب لینک خود را در جزئیات پیدا کنید، همچنین می توانید از همین نام برای چندین کانفیگ استفاده کنید"
|
||||||
|
|
||||||
[pages.client]
|
[pages.client]
|
||||||
@@ -209,6 +209,7 @@
|
|||||||
[pages.settings]
|
[pages.settings]
|
||||||
"title" = "تنظیمات"
|
"title" = "تنظیمات"
|
||||||
"save" = "ذخیره"
|
"save" = "ذخیره"
|
||||||
|
"infoDesc" = "برای اعمال تغییرات در این بخش باید پس از ذخیره کردن، پنل را ریستارت کنید"
|
||||||
"restartPanel" = "ریستارت پنل"
|
"restartPanel" = "ریستارت پنل"
|
||||||
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
"restartPanelDesc" = "آیا مطمئن هستید که می خواهید پنل را دوباره راه اندازی کنید؟ برای راه اندازی مجدد روی OK کلیک کنید. اگر بعد از 3 ثانیه نمی توانید به پنل دسترسی پیدا کنید، لطفاً برای مشاهده اطلاعات گزارش پانل به سرور برگردید"
|
||||||
"actions" = "عملیات ها"
|
"actions" = "عملیات ها"
|
||||||
@@ -218,29 +219,33 @@
|
|||||||
"xrayConfiguration" = "تنظیمات Xray"
|
"xrayConfiguration" = "تنظیمات Xray"
|
||||||
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
"TGBotSettings" = "تنظیمات ربات تلگرام"
|
||||||
"panelListeningIP" = "محدودیت آی پی پنل"
|
"panelListeningIP" = "محدودیت آی پی پنل"
|
||||||
"panelListeningIPDesc" = "برای استفاده از تمام IP ها به طور پیش فرض خالی بگذارید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelListeningIPDesc" = "برای استفاده از تمام آیپیها به طور پیش فرض خالی بگذارید"
|
||||||
|
"panelListeningDomain" = "محدودیت دامین پنل"
|
||||||
|
"panelListeningDomainDesc" = "برای استفاده از تمام دامنهها و آیپیها به طور پیش فرض خالی بگذارید"
|
||||||
"panelPort" = "پورت پنل"
|
"panelPort" = "پورت پنل"
|
||||||
"panelPortDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelPortDesc" = "پورت مورد استفاده برای نمایش این پنل"
|
||||||
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
"publicKeyPath" = "مسیر فایل گواهی کلید عمومی پنل"
|
||||||
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"publicKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
|
||||||
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
|
"privateKeyPath" = "مسیر فایل گواهی کلید خصوصی پنل"
|
||||||
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"privateKeyPathDesc" = "باید یک مسیر مطلق باشد که با / شروع می شود "
|
||||||
"panelUrlPath" = "آدرس روت پنل"
|
"panelUrlPath" = "آدرس روت پنل"
|
||||||
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"panelUrlPathDesc" = "باید با '/' شروع شود و با '/' تمام شود"
|
||||||
"oldUsername" = "نام کاربری فعلی"
|
"oldUsername" = "نام کاربری فعلی"
|
||||||
"currentPassword" = "رمز عبور فعلی"
|
"currentPassword" = "رمز عبور فعلی"
|
||||||
"newUsername" = "نام کاربری جدید"
|
"newUsername" = "نام کاربری جدید"
|
||||||
"newPassword" = "رمز عبور جدید"
|
"newPassword" = "رمز عبور جدید"
|
||||||
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
"telegramBotEnable" = "فعالسازی ربات تلگرام"
|
||||||
"telegramBotEnableDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramBotEnableDesc" = "از طریق بات تلگرام به امکانات این پنل متصل شوید"
|
||||||
"telegramToken" = "توکن تلگرام"
|
"telegramToken" = "توکن تلگرام"
|
||||||
"telegramTokenDesc" = "پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramTokenDesc" = "توکن را باید از مدیر بات های تلگرام دریافت کنید @botfather"
|
||||||
"telegramChatId" = "آی دی تلگرام مدیریت"
|
"telegramChatId" = "آی دی تلگرام مدیریت"
|
||||||
"telegramChatIdDesc" = "از @userinfobot برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramChatIdDesc" = "از @userinfobot یا دستور '/id' در ربات برای دریافت شناسه های چت خود استفاده کنید. با استفاده از کاما میتونید چند آی دی را از هم جدا کنید. "
|
||||||
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
"telegramNotifyTime" = "مدت زمان نوتیفیکیشن ربات تلگرام"
|
||||||
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید . پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"telegramNotifyTimeDesc" = "از فرمت زمان بندی لینوکس استفاده کنید "
|
||||||
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
"tgNotifyBackup" = "پشتیبان گیری از پایگاه داده"
|
||||||
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
"tgNotifyBackupDesc" = "ارسال کپی فایل پایگاه داده به همراه گزارش دوره ای"
|
||||||
|
"tgNotifyLogin" = "اعلان ورود"
|
||||||
|
"tgNotifyLoginDesc" = "نام کاربری، آدرس ای پی، و زمان وقتی که فردی سعی میکند به پنل شما وارد شود نمایش میدهد"
|
||||||
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
"sessionMaxAge" = "بیشینه زمان جلسه وب"
|
||||||
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
"sessionMaxAgeDesc" = "بیشینه زمانی که میتوانید لاگین بمانید (واحد: دقیقه)"
|
||||||
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
"expireTimeDiff" = "آستانه زمان باقی مانده"
|
||||||
@@ -251,6 +256,27 @@
|
|||||||
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
"tgNotifyCpuDesc" = "این ربات تلگرام در صورت استفاده پردازنده بیشتر از این درصد برای شما پیام ارسال می کند.(واحد: درصد)"
|
||||||
"timeZone" = "منظقه زمانی"
|
"timeZone" = "منظقه زمانی"
|
||||||
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
"timeZoneDesc" = "وظایف برنامه ریزی شده بر اساس این منطقه زمانی اجرا می شوند. پنل را مجدداً راه اندازی می کند تا اعمال شود"
|
||||||
|
"subSettings" = "سابسکریپشن"
|
||||||
|
"subEnable" = "فعال کردن سرویس"
|
||||||
|
"subEnableDesc" = "ویژگی سابسکریپشن با پیکربندی جداگانه"
|
||||||
|
"subListen" = "محدودیت آیپی"
|
||||||
|
"subListenDesc" = "برای استفاده از همه آیپی ها به طور پیش فرض خالی بگذارید"
|
||||||
|
"subPort" = "پورت سرویس"
|
||||||
|
"subPortDesc" = "شماره پورت برای ارائه خدمات سابسکریپشن باید خالی باشد"
|
||||||
|
"subCertPath" = "مسیر فایل کلید عمومی گواهی سابسکریپشن"
|
||||||
|
"subCertPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
|
||||||
|
"subKeyPath" = "مسیر فایل کلید خصوصی گواهی سابسکریپشن"
|
||||||
|
"subKeyPathDesc" = "یک مسیر مطلق که با '/' شروع می شود را پر کنید."
|
||||||
|
"subPath" = "مسیر ریشه سابسکریپشن"
|
||||||
|
"subPathDesc" = "باید با '/' شروع شود و با '/' ختم شود."
|
||||||
|
"subDomain" = "دامنه مخصوص سابسکریپشن"
|
||||||
|
"subDomainDesc" = "برای نظارت بر همه دامنه ها و آیپی ها به طور پیش فرض خالی بگذارید"
|
||||||
|
"subUpdates" = "فاصله به روز رسانی های سابسکریپشن"
|
||||||
|
"subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر"
|
||||||
|
"subEncrypt" = "رمزگذاری کانفیگ ها"
|
||||||
|
"subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن"
|
||||||
|
"subShowInfo" = "نمایش اطلاعات مصرف"
|
||||||
|
"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد"
|
||||||
|
|
||||||
[pages.settings.templates]
|
[pages.settings.templates]
|
||||||
"title" = "الگوها"
|
"title" = "الگوها"
|
||||||
@@ -258,57 +284,85 @@
|
|||||||
"advancedTemplate" = "بخش الگو پیشرفته"
|
"advancedTemplate" = "بخش الگو پیشرفته"
|
||||||
"completeTemplate" = "بخش الگو کامل"
|
"completeTemplate" = "بخش الگو کامل"
|
||||||
"generalConfigs" = "تنظیمات عمومی"
|
"generalConfigs" = "تنظیمات عمومی"
|
||||||
"generalConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند."
|
"generalConfigsDesc" = "این تنظیمات میتواند ترافیک کلی سرویس را متاثر کند"
|
||||||
"countryConfigs" = "تنظیمات برای کشورها"
|
"blockConfigs" = "مسدود سازی"
|
||||||
"countryConfigsDesc" = "این گزینه از اتصال کاربران به دامنه های کشوری خاص جلوگیری می کند."
|
"blockConfigsDesc" = "این گزینه ها از اتصال کاربران به پروتکل ها و وب سایت های خاص جلوگیری می کند"
|
||||||
|
"blockCountryConfigs" = "تنظیمات برای مسدودسازی کشورها"
|
||||||
|
"blockCountryConfigsDesc" = "این گزینه اتصال کاربران به دامنه های کشوری خاص را مسدود می کند"
|
||||||
|
"directCountryConfigs" = "تنظیمات برای اتصال مستقیم کشورها"
|
||||||
|
"directCountryConfigsDesc" = "این گزینه کاربران را به دامنه های کشوری خاص را به طور مستقیم، متصل می کند"
|
||||||
"ipv4Configs" = "تنظیمات برای IPv4"
|
"ipv4Configs" = "تنظیمات برای IPv4"
|
||||||
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن 4 به دامنه های هدف هدایت می شود."
|
"ipv4ConfigsDesc" = "این گزینه فقط از طریق آیپی ورژن ۴ به دامنه های هدف هدایت می شود"
|
||||||
"warpConfigs" = "تنظیمات برای WARP"
|
"warpConfigs" = "تنظیمات برای WARP"
|
||||||
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند."
|
"warpConfigsDesc" = "هشدار: قبل از استفاده از این گزینه، WARP را در حالت پراکسی socks5 با دنبال کردن مراحل در GitHub پنل روی سرور خود نصب کنید. WARP ترافیک را از طریق سرورهای Cloudflare به وب سایت ها هدایت می کند"
|
||||||
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
"xrayConfigTemplate" = "تنظیمات الگو ایکس ری"
|
||||||
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید! پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigTemplateDesc" = "فایل پیکربندی ایکس ری نهایی بر اساس این الگو ایجاد میشود. لطفاً این را تغییر ندهید مگر اینکه دقیقاً بدانید که چه کاری انجام می دهید!"
|
||||||
|
"xrayConfigFreedomStrategy" = "روش استفاده از شبکه خروجی مستقیم"
|
||||||
|
"xrayConfigFreedomStrategyDesc" = "تعیین روش استفاده از خروجی برای پرتکل مستقیم"
|
||||||
|
"xrayConfigRoutingStrategy" = "پیکربندی استراتژی حل دامنه در مسیریابی"
|
||||||
|
"xrayConfigRoutingStrategyDesc" = "تعیین استراتژی مسیریابی کلی برای پیدا کردن دامنه"
|
||||||
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
"xrayConfigTorrent" = "فیلتر کردن بیت تورنت"
|
||||||
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigTorrentDesc" = "الگوی تنظیمات را برای فیلتر کردن پروتکل بیت تورنت برای کاربران تغییر میدهد"
|
||||||
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
"xrayConfigPrivateIp" = "جلوگیری از اتصال آیپی های خصوصی یا محلی"
|
||||||
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigPrivateIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های خصوصی یا محلی و بسته های سرگردان تغییر میدهد"
|
||||||
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
"xrayConfigAds" = "مسدود کردن تبلیغات"
|
||||||
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigAdsDesc" = "الگوی تنظیمات را برای مسدود کردن تبلیغات تغییر میدهد"
|
||||||
"xrayConfigPorn" = "جلوگیری از اتصال به سایت های پورن"
|
"xrayConfigFamily" = "فعال کردن حالت خانواده"
|
||||||
"xrayConfigPornDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های پورن تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigFamilyDesc" = "برای جلوگیری از ارتباط با وبسایت های ناامن"
|
||||||
"xrayConfigSpeedtest" = "جلوگیری از اتصال به سایت های تست سرعت"
|
"xrayConfigSpeedtest" = "جلوگیری از اتصال به سایت های تست سرعت"
|
||||||
"xrayConfigSpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigSpeedtestDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال به سایت های تست سرعت تغییر میدهد"
|
||||||
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
"xrayConfigIRIp" = "جلوگیری از اتصال آیپی های ایران"
|
||||||
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigIRIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های ایران تغییر میدهد"
|
||||||
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
"xrayConfigIRDomain" = "جلوگیری از اتصال دامنه های ایران"
|
||||||
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigIRDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های ایران تغییر میدهد"
|
||||||
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
"xrayConfigChinaIp" = "جلوگیری از اتصال آیپی های چین"
|
||||||
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigChinaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های چین تغییر میدهد"
|
||||||
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
"xrayConfigChinaDomain" = "جلوگیری از اتصال دامنه های چین"
|
||||||
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigChinaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های چین تغییر میدهد"
|
||||||
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
"xrayConfigRussiaIp" = "جلوگیری از اتصال آیپی های روسیه"
|
||||||
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigRussiaIpDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال آیپی های روسیه تغییر میدهد"
|
||||||
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
"xrayConfigRussiaDomain" = "جلوگیری از اتصال دامنه های روسیه"
|
||||||
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigRussiaDomainDesc" = "الگوی تنظیمات را برای فیلتر کردن اتصال دامنه های روسیه تغییر میدهد"
|
||||||
|
"xrayConfigDirectIRIp" = "ارتباط مستقیم به آیپی های ایران"
|
||||||
|
"xrayConfigDirectIRIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های ایران تغییر میدهد"
|
||||||
|
"xrayConfigDirectIRDomain" = "ارتباط مستقیم به دامنه های ایران"
|
||||||
|
"xrayConfigDirectIRDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های ایران تغییر میدهد"
|
||||||
|
"xrayConfigDirectChinaIp" = "ارتباط مستقیم به آیپی های چین"
|
||||||
|
"xrayConfigDirectChinaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های چین تغییر میدهد"
|
||||||
|
"xrayConfigDirectChinaDomain" = "ارتباط مستقیم به دامنه های چین"
|
||||||
|
"xrayConfigDirectChinaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های چین تغییر میدهد"
|
||||||
|
"xrayConfigDirectRussiaIp" = "ارتباط مستقیم به آیپی های روسیه"
|
||||||
|
"xrayConfigDirectRussiaIpDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به آیپی های روسیه تغییر میدهد"
|
||||||
|
"xrayConfigDirectRussiaDomain" = "ارتباط مستقیم به دامنه های روسیه"
|
||||||
|
"xrayConfigDirectRussiaDomainDesc" = "الگوی تنظیمات را برای ارتباط مستقیم به دامنه های روسیه تغییر میدهد"
|
||||||
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
"xrayConfigGoogleIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به گوگل"
|
||||||
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigGoogleIPv4Desc" = "مسیردهی جدید برای اتصال به گوگل با آیپی ورژن 4 اضافه میکند"
|
||||||
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
"xrayConfigNetflixIPv4" = "استفاده از آیپی ورژن 4 برای اتصال به نتفلیکس"
|
||||||
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigNetflixIPv4Desc" = "مسیردهی جدید برای اتصال به نتفلیکس با آیپی ورژن 4 اضافه میکند"
|
||||||
"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
|
"xrayConfigGoogleWARP" = "مسیردهی گوگل به WARP"
|
||||||
"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigGoogleWARPDesc" = "مسیردهی جدید برای اتصال به گوگل به WARP اضافه میکند"
|
||||||
"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
|
"xrayConfigOpenAIWARP" = "مسیردهی OpenAI (ChatGPT) به WARP"
|
||||||
"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigOpenAIWARPDesc" = "مسیردهی جدید برای اتصال به OpenAI (ChatGPT) به WARP اضافه میکند"
|
||||||
"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
|
"xrayConfigNetflixWARP" = "مسیردهی نتفلیکس به WARP"
|
||||||
"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigNetflixWARPDesc" = "مسیردهی جدید برای اتصال به نتفلیکس به WARP اضافه میکند"
|
||||||
"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
|
"xrayConfigSpotifyWARP" = "مسیردهی اسپاتیفای به WARP"
|
||||||
"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigSpotifyWARPDesc" = "مسیردهی جدید برای اتصال به اسپاتیفای به WARP اضافه میکند"
|
||||||
"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
|
"xrayConfigIRWARP" = "مسیردهی دامنه های ایران به WARP"
|
||||||
"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigIRWARPDesc" = "مسیردهی جدید برای اتصال به دامنه های ایران به WARP اضافه میکند"
|
||||||
"xrayConfigInbounds" = "تنظیمات ورودی"
|
"xrayConfigInbounds" = "تنظیمات ورودی"
|
||||||
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigInboundsDesc" = "میتوانید الگوی تنظیمات را برای ورودی های خاص تنظیم نمایید"
|
||||||
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
"xrayConfigOutbounds" = "تنظیمات خروجی"
|
||||||
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigOutboundsDesc" = "میتوانید الگوی تنظیمات را برای خروجی اینترنت تنظیم نمایید"
|
||||||
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
"xrayConfigRoutings" = "تنظیمات قوانین مسیریابی"
|
||||||
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید. پنل را مجدداً راه اندازی کنید تا اعمال شود"
|
"xrayConfigRoutingsDesc" = "میتوانید الگوی تنظیمات را برای مسیریابی تنظیم نمایید"
|
||||||
|
"manualLists" = "لیست های دستی"
|
||||||
|
"manualListsDesc" = "فرمت: JSON Array"
|
||||||
|
"manualBlockedIPs" = "لیست آیپی های مسدود شده"
|
||||||
|
"manualBlockedDomains" = "لیست دامنه های مسدود شده"
|
||||||
|
"manualDirectIPs" = "لیست آیپی های مستقیم"
|
||||||
|
"manualDirectDomains" = "لیست دامنه های مستقیم"
|
||||||
|
"manualIPv4Domains" = "لیست دامنههای IPv4"
|
||||||
|
"manualWARPDomains" = "لیست دامنه های WARP"
|
||||||
|
|
||||||
[pages.settings.security]
|
[pages.settings.security]
|
||||||
"admin" = "مدیر"
|
"admin" = "مدیر"
|
||||||
@@ -322,5 +376,120 @@
|
|||||||
"modifySettings" = "ویرایش تنظیمات"
|
"modifySettings" = "ویرایش تنظیمات"
|
||||||
"getSettings" = "دریافت تنظیمات"
|
"getSettings" = "دریافت تنظیمات"
|
||||||
"modifyUser" = "ویرایش کاربر"
|
"modifyUser" = "ویرایش کاربر"
|
||||||
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد ."
|
"originalUserPassIncorrect" = "نام کاربری و رمز عبور فعلی اشتباه می باشد "
|
||||||
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد ."
|
"userPassMustBeNotEmpty" = "نام کاربری و رمز عبور جدید نمیتواند خالی باشد "
|
||||||
|
|
||||||
|
[tgbot]
|
||||||
|
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
|
||||||
|
"noResult" = "❗ نتیجهای یافت نشد!"
|
||||||
|
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
|
||||||
|
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||||
|
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||||
|
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||||
|
"unlimited" = "♾ نامحدود"
|
||||||
|
"month" = "ماه"
|
||||||
|
"months" = "ماهها"
|
||||||
|
"day" = "روز"
|
||||||
|
"days" = "روزها"
|
||||||
|
"hours" = "ساعت ها"
|
||||||
|
"unknown" = "نامشخص"
|
||||||
|
"inbounds" = "ورودیها"
|
||||||
|
"clients" = "کلاینتها"
|
||||||
|
|
||||||
|
[tgbot.commands]
|
||||||
|
"unknown" = "❗ دستور ناشناخته"
|
||||||
|
"pleaseChoose" = "👇 لطفاً انتخاب کنید:\r\n"
|
||||||
|
"help" = "🤖 به این ربات خوش آمدید! این ربات برای ارائه دادههای خاص از سرور طراحی شده است و به شما امکان تغییرات لازم را میدهد.\r\n\r\n"
|
||||||
|
"start" = "👋 سلام <i>{{ .Firstname }}</i>.\r\n"
|
||||||
|
"welcome" = "🤖 به ربات مدیریت <b>{{ .Hostname }}</b> خوش آمدید.\r\n"
|
||||||
|
"status" = "✅ ربات در حالت عادی است!"
|
||||||
|
"usage" = "❗ لطفاً یک متن برای جستجو وارد کنید!"
|
||||||
|
"getID" = "🆔 شناسه شما: <code>{{ .ID }}</code>"
|
||||||
|
"helpAdminCommands" = "برای جستجوی ایمیل مشتری:\r\n<code>/usage [ایمیل]</code>\r\n \r\nبرای جستجوی ورودیها (با آمار مشتری):\r\n<code>/inbound [توضیح]</code>"
|
||||||
|
"helpClientCommands" = "برای جستجوی آمار، فقط از دستور زیر استفاده کنید:\r\n \r\n<code>/usage [UUID|رمز عبور]</code>\r\n \r\nاز UUID برای vmess/vless و از رمز عبور برای Trojan استفاده کنید."
|
||||||
|
|
||||||
|
[tgbot.messages]
|
||||||
|
"cpuThreshold" = "🔴 میزان استفاده از CPU {{ .Percent }}% بیشتر از آستانه {{ .Threshold }}% است."
|
||||||
|
"selectUserFailed" = "❌ خطا در انتخاب کاربر!"
|
||||||
|
"userSaved" = "✅ کاربر تلگرام ذخیره شد."
|
||||||
|
"loginSuccess" = "✅ با موفقیت به پنل وارد شدید.\r\n"
|
||||||
|
"loginFailed" = "❗️ ورود به پنل ناموفق بود.\r\n"
|
||||||
|
"report" = "🕰 گزارشات زمانبندی شده: {{ .RunTime }}\r\n"
|
||||||
|
"datetime" = "⏰ تاریخ-زمان: {{ .DateTime }}\r\n"
|
||||||
|
"hostname" = "💻 نام میزبان: {{ .Hostname }}\r\n"
|
||||||
|
"version" = "🚀 نسخه X-UI: {{ .Version }}\r\n"
|
||||||
|
"ipv6" = "🌐 IPv6: {{ .IPv6 }}\r\n"
|
||||||
|
"ipv4" = "🌐 IPv4: {{ .IPv4 }}\r\n"
|
||||||
|
"ip" = "🌐 آدرس IP: {{ .IP }}\r\n"
|
||||||
|
"ips" = "🔢 آدرسهای IP: \r\n{{ .IPs }}\r\n"
|
||||||
|
"serverUpTime" = "⏳ زمان کارکرد سرور: {{ .UpTime }} {{ .Unit }}\r\n"
|
||||||
|
"serverLoad" = "📈 بار سرور: {{ .Load1 }}, {{ .Load2 }}, {{ .Load3 }}\r\n"
|
||||||
|
"serverMemory" = "📋 حافظه سرور: {{ .Current }}/{{ .Total }}\r\n"
|
||||||
|
"tcpCount" = "🔹 تعداد ترافیک TCP: {{ .Count }}\r\n"
|
||||||
|
"udpCount" = "🔸 تعداد ترافیک UDP: {{ .Count }}\r\n"
|
||||||
|
"traffic" = "🚦 ترافیک: {{ .Total }} (↑{{ .Upload }},↓{{ .Download }})\r\n"
|
||||||
|
"xrayStatus" = "ℹ️ وضعیت Xray: {{ .State }}\r\n"
|
||||||
|
"username" = "👤 نام کاربری: {{ .Username }}\r\n"
|
||||||
|
"time" = "⏰ زمان: {{ .Time }}\r\n"
|
||||||
|
"inbound" = "📍 ورودی: {{ .Remark }}\r\n"
|
||||||
|
"port" = "🔌 پورت: {{ .Port }}\r\n"
|
||||||
|
"expire" = "📅 تاریخ انقضا: {{ .Time }}\r\n"
|
||||||
|
"expireIn" = "📅 باقیمانده از انقضا: {{ .Time }}\r\n"
|
||||||
|
"active" = "💡 فعال: ✅\r\n"
|
||||||
|
"inactive" = "💡 فعال: ❌\r\n"
|
||||||
|
"email" = "📧 ایمیل: {{ .Email }}\r\n"
|
||||||
|
"upload" = "🔼 آپلود↑: {{ .Upload }}\r\n"
|
||||||
|
"download" = "🔽 دانلود↓: {{ .Download }}\r\n"
|
||||||
|
"total" = "📊 کل: {{ .UpDown }} / {{ .Total }}\r\n"
|
||||||
|
"TGUser" = "👤 کاربر تلگرام: {{ .TelegramID }}\r\n"
|
||||||
|
"exhaustedMsg" = "🚨 {{ .Type }} به اتمام رسیده است:\r\n"
|
||||||
|
"exhaustedCount" = "🚨 تعداد {{ .Type }} به اتمام رسیده:\r\n"
|
||||||
|
"disabled" = "🛑 غیرفعال: {{ .Disabled }}\r\n"
|
||||||
|
"depleteSoon" = "🔜 به زودی به پایان خواهد رسید: {{ .Deplete }}\r\n \r\n"
|
||||||
|
"backupTime" = "🗄 زمان پشتیبانگیری: {{ .Time }}\r\n"
|
||||||
|
"refreshedOn" = "\r\n📋🔄 تازهسازی شده در: {{ .Time }}\r\n \r\n"
|
||||||
|
|
||||||
|
[tgbot.buttons]
|
||||||
|
"closeKeyboard" = "❌ بستن کیبورد"
|
||||||
|
"cancel" = "❌ لغو"
|
||||||
|
"cancelReset" = "❌ لغو تنظیم مجدد"
|
||||||
|
"cancelIpLimit" = "❌ لغو محدودیت IP"
|
||||||
|
"confirmResetTraffic" = "✅ تأیید تنظیم مجدد ترافیک؟"
|
||||||
|
"confirmClearIps" = "✅ تأیید پاکسازی آدرسهای IP؟"
|
||||||
|
"confirmRemoveTGUser" = "✅ تأیید حذف کاربر تلگرام؟"
|
||||||
|
"dbBackup" = "دریافت پشتیبان پایگاه داده"
|
||||||
|
"serverUsage" = "استفاده از سرور"
|
||||||
|
"getInbounds" = "دریافت ورودیها"
|
||||||
|
"depleteSoon" = "به زودی به پایان خواهد رسید"
|
||||||
|
"clientUsage" = "دریافت آمار کاربر"
|
||||||
|
"commands" = "دستورات"
|
||||||
|
"refresh" = "🔄 تازهسازی"
|
||||||
|
"clearIPs" = "❌ پاکسازی آدرسها"
|
||||||
|
"removeTGUser" = "❌ حذف کاربر تلگرام"
|
||||||
|
"selectTGUser" = "👤 انتخاب کاربر تلگرام"
|
||||||
|
"selectOneTGUser" = "👤 یک کاربر تلگرام را انتخاب کنید:"
|
||||||
|
"resetTraffic" = "📈 تنظیم مجدد ترافیک"
|
||||||
|
"resetExpire" = "📅 تنظیم مجدد تاریخ انقضا"
|
||||||
|
"ipLog" = "🔢 لاگ آدرسهای IP"
|
||||||
|
"ipLimit" = "🔢 محدودیت IP"
|
||||||
|
"setTGUser" = "👤 تنظیم کاربر تلگرام"
|
||||||
|
"toggle" = "🔘 فعال / غیرفعال"
|
||||||
|
|
||||||
|
[tgbot.answers]
|
||||||
|
"errorOperation" = "❗ خطا در عملیات."
|
||||||
|
"getInboundsFailed" = "❌ دریافت ورودیها با خطا مواجه شد."
|
||||||
|
"canceled" = "❌ {{ .Email }} : عملیات لغو شد."
|
||||||
|
"clientRefreshSuccess" = "✅ {{ .Email }} : کلاینت با موفقیت تازهسازی شد."
|
||||||
|
"IpRefreshSuccess" = "✅ {{ .Email }} : آدرسها با موفقیت تازهسازی شدند."
|
||||||
|
"TGIdRefreshSuccess" = "✅ {{ .Email }} : کاربر تلگرام کلاینت با موفقیت تازهسازی شد."
|
||||||
|
"resetTrafficSuccess" = "✅ {{ .Email }} : ترافیک با موفقیت تنظیم مجدد شد."
|
||||||
|
"expireResetSuccess" = "✅ {{ .Email }} : تاریخ انقضا با موفقیت تنظیم مجدد شد."
|
||||||
|
"resetIpSuccess" = "✅ {{ .Email }} : محدودیت آدرس IP {{ .Count }} با موفقیت ذخیره شد."
|
||||||
|
"clearIpSuccess" = "✅ {{ .Email }} : آدرسها با موفقیت پاکسازی شدند."
|
||||||
|
"getIpLog" = "✅ {{ .Email }} : دریافت لاگ آدرسهای IP."
|
||||||
|
"getUserInfo" = "✅ {{ .Email }} : دریافت اطلاعات کاربر تلگرام."
|
||||||
|
"removedTGUserSuccess" = "✅ {{ .Email }} : کاربر تلگرام با موفقیت حذف شد."
|
||||||
|
"enableSuccess" = "✅ {{ .Email }} : با موفقیت فعال شد."
|
||||||
|
"disableSuccess" = "✅ {{ .Email }} : با موفقیت غیرفعال شد."
|
||||||
|
"askToAddUserId" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"
|
||||||
|
"askToAddUserName" = "پیکربندی شما یافت نشد!\r\nلطفاً از مدیر خود بخواهید که نام کاربری یا شناسه کاربر تلگرام خود را در پیکربندی (های) خود استفاده کند.\r\n\r\nنام کاربری شما: <b>@{{ .TgUserName }}</b>\r\n\r\nشناسه کاربری شما: <b>{{ .TgUserID }}</b>"
|
||||||
|
|||||||