Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5477c9bb7 | ||
|
|
9fbe7ba17e | ||
|
|
d57e031424 | ||
|
|
67344e505c | ||
|
|
9b9ecac059 | ||
|
|
b5ad973108 | ||
|
|
ce7e5d07c7 | ||
|
|
da757b7bcf | ||
|
|
229a451faa | ||
|
|
5701d53a24 | ||
|
|
649c96ef16 | ||
|
|
e1696c9fd3 | ||
|
|
117c4f6fd5 | ||
|
|
261630401b | ||
|
|
eccf512540 | ||
|
|
9cd80943ae | ||
|
|
90055cf97d | ||
|
|
1ce6b22443 | ||
|
|
733afb43af | ||
|
|
dfc9b45e92 | ||
|
|
76f255f140 | ||
|
|
a090c6d6e7 | ||
|
|
de6c768149 | ||
|
|
38e2bc3618 | ||
|
|
03dd392a39 | ||
|
|
a338ab8808 | ||
|
|
115635660a | ||
|
|
040edf5c01 | ||
|
|
56928a22c7 | ||
|
|
e6ce891883 | ||
|
|
3b7738a6c9 | ||
|
|
7e4f6e5131 | ||
|
|
c6ce96a63e | ||
|
|
fa541b4719 | ||
|
|
b6fe0c9342 | ||
|
|
00e3db0caa | ||
|
|
ed3a6c36e6 | ||
|
|
ada0287dc9 | ||
|
|
4f9ec33393 | ||
|
|
fd98ff6fad | ||
|
|
ecf89e430d | ||
|
|
af881a8fe4 | ||
|
|
490ed0ab96 | ||
|
|
29d348bd67 | ||
|
|
258101efae | ||
|
|
cec900b596 | ||
|
|
463a120d58 | ||
|
|
cf521367f7 | ||
|
|
2012dd0c0a | ||
|
|
b22d1e082a | ||
|
|
dc332b09fa | ||
|
|
a30043d8ae | ||
|
|
482e04e614 | ||
|
|
5ae613c188 | ||
|
|
1cc72ae6af | ||
|
|
b6ccb2e076 | ||
|
|
e3c2b42048 | ||
|
|
029df32b71 | ||
|
|
629d0da33c | ||
|
|
80c3f84eac | ||
|
|
2a6ed18163 | ||
|
|
271a85e9b5 | ||
|
|
bbb0663198 | ||
|
|
f5c7b03e80 | ||
|
|
e4b9b70c26 | ||
|
|
53fcf4da15 | ||
|
|
bb8f3dab36 | ||
|
|
c834e4cc35 | ||
|
|
225b3fc3a5 | ||
|
|
6b174762e2 | ||
|
|
1c660bbea1 | ||
|
|
50d4e67e59 | ||
|
|
1025eeebf9 | ||
|
|
fe00a61963 | ||
|
|
64e2e62292 | ||
|
|
0f6c8f1ec5 | ||
|
|
5bc6f7518c | ||
|
|
79f2d34541 | ||
|
|
0aec9b6cb7 | ||
|
|
eb36faced9 | ||
|
|
99e9c27d2f | ||
|
|
b3bb31c98c | ||
|
|
09407710a7 | ||
|
|
7a38a2474e | ||
|
|
afd072a430 | ||
|
|
3bbf83924c | ||
|
|
1329b2bb61 | ||
|
|
d80b26f241 | ||
|
|
47efac270d | ||
|
|
368eb89cd9 | ||
|
|
83e360ea1b | ||
|
|
6cdeb7ebeb | ||
|
|
035a1a7b5e | ||
|
|
857f0cb64c | ||
|
|
849a1249ac | ||
|
|
d608961af8 | ||
|
|
343e7a9f15 | ||
|
|
6ad558bd36 | ||
|
|
d6bf64f760 | ||
|
|
f8d20c8303 | ||
|
|
08403bc8f9 | ||
|
|
cdb90da138 | ||
|
|
c3d498f9ee | ||
|
|
0621eb0670 | ||
|
|
577c534e4c | ||
|
|
17dae4a563 | ||
|
|
9f1f841666 | ||
|
|
6b66c250b5 | ||
|
|
0829116fc1 | ||
|
|
aae0011a4f | ||
|
|
f4c565b208 | ||
|
|
2520994b13 | ||
|
|
d3fd56fc97 | ||
|
|
d4df614a9f | ||
|
|
640eb538a4 | ||
|
|
08153d45d1 | ||
|
|
843bb5f3ce | ||
|
|
7995310aa7 | ||
|
|
c277fd29fd | ||
|
|
6249528a58 | ||
|
|
83c26ad81d | ||
|
|
9b5379c0e7 | ||
|
|
8041950c29 | ||
|
|
541170c3c2 | ||
|
|
47ec105d1f | ||
|
|
525327c4ac | ||
|
|
0267a1b32a | ||
|
|
83df4ae7cf | ||
|
|
24465aeb43 | ||
|
|
ee2bbffc8f | ||
|
|
294a3f46a0 | ||
|
|
44a1104bff | ||
|
|
fbc4ba1ba0 | ||
|
|
b829ebca2b | ||
|
|
288c0b982a | ||
|
|
8919099594 | ||
|
|
3ee7761ab0 | ||
|
|
346a82b203 | ||
|
|
45e2d66f4b | ||
|
|
36ce3e0130 | ||
|
|
a04f2306bc | ||
|
|
fdc3b15efe | ||
|
|
2fb5168918 | ||
|
|
1fcf3d68f1 | ||
|
|
de654fdbc4 | ||
|
|
e32c723d68 | ||
|
|
41089df567 | ||
|
|
a703f70302 | ||
|
|
3c922d8673 | ||
|
|
78c3912002 | ||
|
|
0b8f5f7f67 | ||
|
|
ca3d45ddb4 | ||
|
|
fff74d7ea7 | ||
|
|
b3393e402b | ||
|
|
30d7376463 | ||
|
|
07f507dc1f | ||
|
|
49aafa5657 | ||
|
|
a078335d4c | ||
|
|
025545f0a4 | ||
|
|
85c2d2ecd7 | ||
|
|
2026bd7546 | ||
|
|
f286080edd | ||
|
|
d36118fd95 | ||
|
|
1ee6e39bc3 | ||
|
|
d8e4f85e03 | ||
|
|
dd2ce332a6 | ||
|
|
41b0fe4af4 | ||
|
|
c841c1f148 | ||
|
|
3b3d70aeaa | ||
|
|
2a311287bf | ||
|
|
dbb17f5829 | ||
|
|
93412f8a21 | ||
|
|
b63f305737 | ||
|
|
2cd6305fe8 | ||
|
|
37b7c89d5e | ||
|
|
df84b2fa46 | ||
|
|
d0a99a469b | ||
|
|
47792cdbd3 | ||
|
|
4df056f6a2 | ||
|
|
94ac29ba7f | ||
|
|
3a174144c4 | ||
|
|
8b64976eef | ||
|
|
56336cbcef | ||
|
|
43bc53a10e | ||
|
|
babe3e8788 | ||
|
|
c247cd60b8 | ||
|
|
06507f0e1f | ||
|
|
6a39ee037d | ||
|
|
96f9c73f5f | ||
|
|
15f84de363 | ||
|
|
2d07b64839 | ||
|
|
1ef299951e | ||
|
|
83fed835e1 | ||
|
|
6e29022998 | ||
|
|
5e77dcb198 | ||
|
|
2d7607165b | ||
|
|
c9e4918494 | ||
|
|
63b166ce40 | ||
|
|
2ab1726131 | ||
|
|
7dd5449cf4 | ||
|
|
ded780b11a | ||
|
|
2e44501573 | ||
|
|
3e7770ad97 | ||
|
|
0c380aebdd | ||
|
|
93f6ba897c | ||
|
|
b93adb7435 | ||
|
|
538fdb9860 | ||
|
|
c4ed595a1c | ||
|
|
5acfdf584e | ||
|
|
62ceaeba8f | ||
|
|
3c688d85e7 | ||
|
|
99a4cf4179 | ||
|
|
7aa242b64e | ||
|
|
cbb3c009cc | ||
|
|
d8e8195271 | ||
|
|
e31ec2cade | ||
|
|
af42723213 | ||
|
|
94117f94e8 |
2
.github/workflows/docker.yml
vendored
@@ -50,6 +50,6 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
platforms: linux/amd64,linux/arm64/v8, linux/arm/v7, linux/386
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
188
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Release X-ui
|
||||
name: Release X-UI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -6,127 +7,96 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
linuxamd64build:
|
||||
name: build x-ui amd64 version
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- amd64
|
||||
- arm64
|
||||
- armv7
|
||||
- 386
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
- name: build linux amd64 version
|
||||
run: |
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
||||
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/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
rm -f Xray-linux-64.zip geoip.dat geosite.dat LICENSE README.md
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
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.7.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
|
||||
linuxarm64build:
|
||||
name: build x-ui arm64 version
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
- name: build linux arm64 version
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
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
|
||||
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/xtls/xray-core/releases/download/v1.8.6/Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
rm -f Xray-linux-arm64-v8a.zip geoip.dat geosite.dat LICENSE README.md
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
mv xray xray-linux-arm64
|
||||
cd ..
|
||||
cd ..
|
||||
- name: package
|
||||
run: tar -zcvf x-ui-linux-arm64.tar.gz x-ui
|
||||
- name: upload
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
file: x-ui-linux-arm64.tar.gz
|
||||
asset_name: x-ui-linux-arm64.tar.gz
|
||||
prerelease: true
|
||||
linuxs390xbuild:
|
||||
name: build x-ui s390x version
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.21'
|
||||
- name: build linux s390x version
|
||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
sudo apt install gcc-aarch64-linux-gnu
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
sudo apt install gcc-arm-linux-gnueabihf
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
sudo apt install gcc-i686-linux-gnu
|
||||
fi
|
||||
|
||||
- name: Build x-ui
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt install gcc-s390x-linux-gnu -y
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=s390x CC=s390x-linux-gnu-gcc go build -o xui-release -v main.go
|
||||
export CGO_ENABLED=1
|
||||
export GOOS=linux
|
||||
export GOARCH=${{ matrix.platform }}
|
||||
if [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
export GOARCH=arm64
|
||||
export CC=aarch64-linux-gnu-gcc
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
export GOARCH=arm
|
||||
export GOARM=7
|
||||
export CC=arm-linux-gnueabihf-gcc
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
export GOARCH=386
|
||||
export CC=i686-linux-gnu-gcc
|
||||
fi
|
||||
go build -o xui-release -v main.go
|
||||
|
||||
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/xtls/xray-core/releases/download/v1.8.6/Xray-linux-s390x.zip
|
||||
unzip Xray-linux-s390x.zip
|
||||
rm -f Xray-linux-s390x.zip geoip.dat geosite.dat LICENSE README.md
|
||||
cp xui-release x-ui/
|
||||
cp x-ui.service x-ui/
|
||||
cp x-ui.sh x-ui/
|
||||
mv x-ui/xui-release x-ui/x-ui
|
||||
mkdir x-ui/bin
|
||||
cd x-ui/bin
|
||||
|
||||
# Download dependencies
|
||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v1.8.10/"
|
||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-64.zip
|
||||
unzip Xray-linux-64.zip
|
||||
rm -f Xray-linux-64.zip
|
||||
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm64-v8a.zip
|
||||
unzip Xray-linux-arm64-v8a.zip
|
||||
rm -f Xray-linux-arm64-v8a.zip
|
||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
||||
wget ${Xray_URL}Xray-linux-arm32-v7a.zip
|
||||
unzip Xray-linux-arm32-v7a.zip
|
||||
rm -f Xray-linux-arm32-v7a.zip
|
||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
||||
wget ${Xray_URL}Xray-linux-32.zip
|
||||
unzip Xray-linux-32.zip
|
||||
rm -f Xray-linux-32.zip
|
||||
fi
|
||||
rm -f geoip.dat geosite.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||
wget -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
||||
wget -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
||||
mv xray xray-linux-s390x
|
||||
cd ..
|
||||
cd ..
|
||||
- name: package
|
||||
run: tar -zcvf x-ui-linux-s390x.tar.gz x-ui
|
||||
- name: upload
|
||||
uses: svenstaro/upload-release-action@2.7.0
|
||||
mv xray xray-linux-${{ matrix.platform }}
|
||||
cd ../..
|
||||
|
||||
- name: Package
|
||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
||||
|
||||
- name: Upload files to GH release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
file: x-ui-linux-s390x.tar.gz
|
||||
asset_name: x-ui-linux-s390x.tar.gz
|
||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
||||
prerelease: true
|
||||
|
||||
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
.cache
|
||||
.sync*
|
||||
*.tar.gz
|
||||
*.log
|
||||
access.log
|
||||
error.log
|
||||
tmp
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
#!/bin/sh
|
||||
if [ $1 == "amd64" ]; then
|
||||
ARCH="64";
|
||||
FNAME="amd64";
|
||||
elif [ $1 == "arm64" ]; then
|
||||
ARCH="arm64-v8a"
|
||||
FNAME="arm64";
|
||||
else
|
||||
ARCH="64";
|
||||
FNAME="amd64";
|
||||
fi
|
||||
case $1 in
|
||||
amd64)
|
||||
ARCH="64"
|
||||
FNAME="amd64"
|
||||
;;
|
||||
i386)
|
||||
ARCH="32"
|
||||
FNAME="i386"
|
||||
;;
|
||||
armv8 | arm64 | aarch64)
|
||||
ARCH="arm64-v8a"
|
||||
FNAME="arm64"
|
||||
;;
|
||||
armv7 | arm | arm32)
|
||||
ARCH="arm32-v7a"
|
||||
FNAME="arm32"
|
||||
;;
|
||||
*)
|
||||
ARCH="64"
|
||||
FNAME="amd64"
|
||||
;;
|
||||
esac
|
||||
mkdir -p build/bin
|
||||
cd build/bin
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.6/Xray-linux-${ARCH}.zip"
|
||||
wget "https://github.com/XTLS/Xray-core/releases/download/v1.8.10/Xray-linux-${ARCH}.zip"
|
||||
unzip "Xray-linux-${ARCH}.zip"
|
||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat LICENSE README.md
|
||||
mv xray "xray-linux-${FNAME}"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.21-alpine AS builder
|
||||
FROM golang:1.22-alpine AS builder
|
||||
WORKDIR /app
|
||||
ARG TARGETARCH
|
||||
RUN apk --no-cache --update add build-base gcc wget unzip
|
||||
|
||||
279
README.md
@@ -1,5 +1,5 @@
|
||||
# X-UI
|
||||
**Advanced GUI panel based on Xray Core supports multiple protocols and languages**
|
||||
**An Advanced Web Panel • Built on Xray Core**
|
||||
|
||||

|
||||

|
||||
@@ -7,32 +7,29 @@
|
||||
[](https://img.shields.io/github/downloads/alireza0/x-ui/total.svg)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
||||
> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment
|
||||
|
||||
**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:
|
||||
[](https://www.buymeacoffee.com/alireza7)
|
||||
|
||||
**Buy Me a Coffee :**
|
||||
|
||||
- USDT Tron (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
||||
- USDT (TRC20): `TYTq73Gj6dJ67qe58JVPD9zpjW2cc9XgVz`
|
||||
- Tezos (XTZ):
|
||||
`tz2Wnh2SsY1eezXrcLChu6idWpgdHzUFQcts`
|
||||
|
||||
|
||||
## Quick Look
|
||||
## Quick Overview
|
||||
| Features | Enable? |
|
||||
| -------------------------------------- | :----------------: |
|
||||
| Multi-Protocol | :heavy_check_mark: |
|
||||
| Multi-Language | :heavy_check_mark: |
|
||||
| Multi-User Inbounds | :heavy_check_mark: |
|
||||
| Advanced Traffic Routing | :heavy_check_mark: |
|
||||
| Multi-Client/Inbound | :heavy_check_mark: |
|
||||
| Advanced Traffic Routing Interface | :heavy_check_mark: |
|
||||
| Client & Traffic & System Status | :heavy_check_mark: |
|
||||
| Date & Traffic Cap Based on First Use | :heavy_check_mark: |
|
||||
| REST API | :heavy_check_mark: |
|
||||
|Show Online Users | :heavy_check_mark: |
|
||||
| Manage Users Traffic Data & Expiry Date| :heavy_check_mark: |
|
||||
| Apply Expiry Date based on First Usage | :heavy_check_mark: |
|
||||
| Telegram Bot (admin + clients) | :heavy_check_mark: |
|
||||
| Database Backup using Telegram Bot | :heavy_check_mark: |
|
||||
| Subscription Link + UserInfo | :heavy_check_mark: |
|
||||
| TG Bot (DB backup + admin + client) | :heavy_check_mark: |
|
||||
| Subscription Service (link + info) | :heavy_check_mark: |
|
||||
| Search in Deep | :heavy_check_mark: |
|
||||
| Dark/Light Theme | :heavy_check_mark: |
|
||||
|
||||
@@ -45,22 +42,45 @@ bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.s
|
||||
|
||||
## Install Custom Version
|
||||
|
||||
To install your desired version you can add the version to the end of install command. Example for ver `0.5.2`:
|
||||
**Step 1:** To install your desired version, add the version to the end of the installation command. e.g., ver `1.8.0`:
|
||||
|
||||
```sh
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 0.5.2
|
||||
bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh) 1.8.0
|
||||
```
|
||||
|
||||
## Manual Install & Upgrade
|
||||
|
||||
1. First download the latest compressed package from https://github.com/alireza0/x-ui/releases, generally choose Architecture `amd64`
|
||||
2. Then upload the compressed package to the server's `/root/` directory and login to the server with user `root`
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
|
||||
### Usage
|
||||
|
||||
> If your server cpu architecture is not `amd64` replace another architecture
|
||||
1. To download the latest version of the compressed package directly to your server, run the following command:
|
||||
|
||||
```sh
|
||||
ARCH=$(uname -m)
|
||||
[[ "${ARCH}" == "s390x" ]] && XUI_ARCH="s390x" || [[ "${ARCH}" == "aarch64" || "${ARCH}" == "arm64" ]] && XUI_ARCH="arm64" || XUI_ARCH="amd64"
|
||||
case "${ARCH}" in
|
||||
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||
i*86 | x86) XUI_ARCH="386" ;;
|
||||
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||
*) XUI_ARCH="amd64" ;;
|
||||
esac
|
||||
|
||||
wget https://github.com/alireza0/x-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)
|
||||
case "${ARCH}" in
|
||||
x86_64 | x64 | amd64) XUI_ARCH="amd64" ;;
|
||||
i*86 | x86) XUI_ARCH="386" ;;
|
||||
armv8* | armv8 | arm64 | aarch64) XUI_ARCH="arm64" ;;
|
||||
armv7* | armv7) XUI_ARCH="armv7" ;;
|
||||
*) XUI_ARCH="amd64" ;;
|
||||
esac
|
||||
cd /root/
|
||||
rm x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -rf
|
||||
tar zxvf x-ui-linux-${XUI_ARCH}.tar.gz
|
||||
@@ -73,15 +93,35 @@ systemctl enable x-ui
|
||||
systemctl restart x-ui
|
||||
```
|
||||
|
||||
## Install Using Docker
|
||||
</details>
|
||||
|
||||
1. Install Docker
|
||||
## Install using Docker
|
||||
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
|
||||
### Usage
|
||||
|
||||
**Step 1:** Install Docker
|
||||
|
||||
```shell
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
```
|
||||
|
||||
2. Install X-UI
|
||||
**Step 2:** Clone the Project Repository:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/alireza0/x-ui.git
|
||||
cd x-ui
|
||||
```
|
||||
|
||||
**Step 3:** Start the Service
|
||||
|
||||
```sh
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
```shell
|
||||
mkdir x-ui && cd x-ui
|
||||
@@ -94,12 +134,32 @@ docker run -itd \
|
||||
alireza7/x-ui:latest
|
||||
```
|
||||
|
||||
update to latest version
|
||||
|
||||
```sh
|
||||
cd x-ui
|
||||
docker compose down
|
||||
docker compose pull x-ui
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
remove x-ui from docker
|
||||
|
||||
```sh
|
||||
docker stop x-ui
|
||||
docker rm x-ui
|
||||
cd --
|
||||
rm -r x-ui
|
||||
```
|
||||
|
||||
> Build your own image
|
||||
|
||||
```shell
|
||||
docker build -t x-ui .
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Languages
|
||||
|
||||
- English
|
||||
@@ -110,22 +170,21 @@ docker build -t x-ui .
|
||||
|
||||
## Features
|
||||
|
||||
- Supported protocols: VMess, VLESS, Trojan, Shadowsocks, Dokodemo-door, SOCKS, HTTP
|
||||
- Support XTLS native encryptions (Vision, REALITY)
|
||||
- Support advanced JSON editor GUI for Xray-Core configuration
|
||||
- Support advanced GUI for routing traffic (Reverse and Transparent proxy, Multi-Domain, Multi-Certificate, Multi-Port per inbound)
|
||||
- Support Multi-User per inbound
|
||||
- Support applying traffic data limits and expiry dates per user/inbound
|
||||
- Support system status monitoring
|
||||
- Support deep database search
|
||||
- Show traffic statistics
|
||||
- Show online users
|
||||
- Show users with expired date or exceeded traffic limits
|
||||
- Support subscription (multi) link
|
||||
- Support import/export database
|
||||
- Support One-Click SSL certificate application and automatic renewal
|
||||
- Support HTTPS for panel (self-provided domain name + SSL certificate)
|
||||
- Support Dark/Light theme UI
|
||||
- Supports protocols including VLESS, VMess, Trojan, Shadowsocks, Dokodemo-door, SOCKS, HTTP, Wireguard
|
||||
- Supports XTLS protocols, including Vision and REALITY
|
||||
- An advanced interface for routing traffic, incorporating PROXY Protocol, Reverse, External, and Transparent Proxy, along with Multi-Domain, SSL Certificate, and Port
|
||||
- Support auto generate Cloudflare WARP using Wireguard outbound
|
||||
- An interactive JSON interface for Xray template configuration
|
||||
- An advanced interface for inbound and outbound configuration
|
||||
- Clients’ traffic cap and expiration date based on first use
|
||||
- Displays online clients, traffic statistics, and system status monitoring
|
||||
- Deep database search
|
||||
- Displays depleted clients with expired dates or exceeded traffic cap
|
||||
- Subscription service with (multi)link
|
||||
- Importing and exporting databases
|
||||
- One-Click SSL certificate application and automatic renewal
|
||||
- HTTPS for secure access to the web panel and subscription service (self-provided domain + SSL certificate)
|
||||
- Dark/Light theme
|
||||
|
||||
## Recommended OS
|
||||
|
||||
@@ -134,12 +193,13 @@ docker build -t x-ui .
|
||||
- Debian 10+
|
||||
- Fedora 36+
|
||||
|
||||
## Screenshots
|
||||
## Preview
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## API Routes
|
||||
@@ -199,6 +259,17 @@ docker build -t x-ui .
|
||||
<details>
|
||||
<summary>Click for details</summary>
|
||||
|
||||
### Cloudflare
|
||||
|
||||
The admin management script has a built-in SSL certificate application for Cloudflare. To use this script to apply for a certificate, you need the following:
|
||||
|
||||
- Cloudflare registered email
|
||||
- Cloudflare Global API Key
|
||||
- The domain name has been resolved to the current server through cloudflare
|
||||
|
||||
**Step 1:** Run the`x-ui`command on the server's terminal and then choose `17`. Then enter the information as requested.
|
||||
|
||||
|
||||
### Certbot
|
||||
|
||||
```bash
|
||||
@@ -218,40 +289,42 @@ certbot certonly --standalone --register-unsafely-without-email --non-interactiv
|
||||
|
||||
### Usage
|
||||
|
||||
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:
|
||||
The web panel supports daily traffic, panel login, database backup, system status, client info, and other notification and functions through the Telegram Bot. To use the bot, you need to set the bot-related parameters in the panel, including:
|
||||
|
||||
- Tg robot Token
|
||||
- Tg robot ChatId
|
||||
- Tg robot cycle runtime, in crontab syntax
|
||||
- Tg robot Expiration threshold
|
||||
- Tg robot Traffic threshold
|
||||
- Tg robot Enable send backup in cycle runtime
|
||||
- Tg robot Enable CPU usage alarm threshold
|
||||
- Telegram Token
|
||||
- Admin Chat ID(s)
|
||||
- Notification Time (in cron syntax)
|
||||
- Database Backup
|
||||
- CPU Load Threshold Notification
|
||||
|
||||
**Crontab Time Format**
|
||||
|
||||
Reference syntax:
|
||||
|
||||
- 30 \* \* \* \* \* //Notify at the 30s of each point
|
||||
- 0 \*/10 \* \* \* \* //Notify at the first second of each 10 minutes
|
||||
- @hourly // hourly notification
|
||||
- @daily // Daily notification (00:00 in the morning)
|
||||
- @every 8h // notify every 8 hours
|
||||
- `*/30 * * * *` - Notify every 30 minutes, every hour
|
||||
- `30 * * * * *` - Notify at the 30th second of each minute
|
||||
- `0 */10 * * * *` - Notify at the start of every 10 minutes
|
||||
- `@hourly` - Hourly notification
|
||||
- `@daily` - Daily notification (00:00 AM)
|
||||
- `@every 8h` - Notify every 8 hours
|
||||
|
||||
For more info about [Crontab](https://acquia.my.site.com/s/article/360004224494-Cron-time-string-format)
|
||||
|
||||
### Features
|
||||
|
||||
- Report periodic
|
||||
- Login notification
|
||||
- CPU threshold notification
|
||||
- Threshold for Expiration time and Traffic to report in advance
|
||||
- Support client report menu if client's telegram ID or telegram UserName added to the user's configurations
|
||||
- Support telegram traffic report searched with UUID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||
- Menu based bot
|
||||
- Search client by email ( only admin )
|
||||
- Check all inbounds
|
||||
- Check server status
|
||||
- Check depleted users
|
||||
- Receive backup by request and in periodic reports
|
||||
- Multi language bot
|
||||
- Periodic reporting
|
||||
- Login notifications
|
||||
- CPU load threshold notifications
|
||||
- Advance notifications for expiration time and traffic
|
||||
- Client reporting menu with Telegram ID or username in configurations
|
||||
- Anonymous traffic reports, search by UUID (VLESS/VMess) or Password (Trojan/Shadowsocks)
|
||||
- Menu-based bot
|
||||
- Client search by email (admin only)
|
||||
- Inbound checks
|
||||
- System status check
|
||||
- Depleted client checks
|
||||
- Backup on request and in periodic reports
|
||||
- Multilingual support
|
||||
</details>
|
||||
|
||||
## Troubleshoots
|
||||
@@ -261,50 +334,58 @@ Reference syntax:
|
||||
|
||||
### Enable Traffic Usage
|
||||
|
||||
Please be aware if you upgrade from an old X-UI version or other forks, by default data traffic usage for users may not work! it's recommended to follow below steps for enabeling:
|
||||
If you are upgrading from an older version or other forks and find that data traffic usage for clients may not work by default, follow the steps below to enable it:
|
||||
|
||||
1. Find this section in config file
|
||||
**Step 1: Locate the Configuration Section**
|
||||
|
||||
```json
|
||||
"policy": {
|
||||
"system": {
|
||||
```
|
||||
|
||||
2. Add below section just after ` "policy": {` :
|
||||
|
||||
```json
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
- The final output is like:
|
||||
Find the following section in the config file:
|
||||
|
||||
```json
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
}
|
||||
},
|
||||
|
||||
"system": {
|
||||
"statsInboundDownlink": true,
|
||||
"statsInboundUplink": true
|
||||
// Other policy configurations
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
```
|
||||
**Step 2: Add the Required Configuration**
|
||||
|
||||
3. Save and restart panel
|
||||
Add the following section just after `"policy": {`:
|
||||
|
||||
```json
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
}
|
||||
},
|
||||
```
|
||||
**Step 3: Final Configuration**
|
||||
|
||||
Your final config should look like this:
|
||||
|
||||
```json
|
||||
"policy": {
|
||||
"levels": {
|
||||
"0": {
|
||||
"statsUserUplink": true,
|
||||
"statsUserDownlink": true
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"statsInboundDownlink": true,
|
||||
"statsInboundUplink": true
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
// Other routing configurations
|
||||
},
|
||||
```
|
||||
**Step 4: Save and Restart**
|
||||
|
||||
Save your changes and restart the Xray Service
|
||||
</details>
|
||||
|
||||
## a Special Thanks to
|
||||
## A Special Thanks to
|
||||
|
||||
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||
- [MHSanaei](https://github.com/MHSanaei)
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.6.2
|
||||
1.8.2
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database/model"
|
||||
"x-ui/xray"
|
||||
|
||||
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"x-ui/util/json_util"
|
||||
"x-ui/xray"
|
||||
)
|
||||
@@ -36,7 +37,7 @@ type Inbound struct {
|
||||
|
||||
// config part
|
||||
Listen string `json:"listen" form:"listen"`
|
||||
Port int `json:"port" form:"port" gorm:"unique"`
|
||||
Port int `json:"port" form:"port"`
|
||||
Protocol Protocol `json:"protocol" form:"protocol"`
|
||||
Settings string `json:"settings" form:"settings"`
|
||||
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
||||
|
||||
106
go.mod
@@ -1,94 +1,92 @@
|
||||
module x-ui
|
||||
|
||||
go 1.21.4
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/Workiva/go-datastructures v1.1.1
|
||||
github.com/gin-contrib/sessions v0.0.4
|
||||
github.com/Calidity/gin-sessions v1.3.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/nicksnyder/go-i18n/v2 v2.3.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/pelletier/go-toml/v2 v2.2.1
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.11
|
||||
github.com/xtls/xray-core v1.8.6
|
||||
github.com/shirou/gopsutil/v3 v3.24.3
|
||||
github.com/xtls/xray-core v1.8.10
|
||||
go.uber.org/atomic v1.11.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/grpc v1.59.0
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
google.golang.org/grpc v1.63.2
|
||||
gorm.io/driver/sqlite v1.5.5
|
||||
gorm.io/gorm v1.25.9
|
||||
)
|
||||
|
||||
require github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cloudflare/circl v1.3.6 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/bytedance/sonic v1.11.3 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gaukas/godicttls v0.0.4 // indirect
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/gzip v1.0.0
|
||||
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/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/gorilla/sessions v1.2.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||
github.com/klauspost/compress v1.17.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/quic-go/quic-go v0.40.0 // indirect
|
||||
github.com/refraction-networking/utls v1.5.4 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a // indirect
|
||||
github.com/quic-go/quic-go v0.42.0 // indirect
|
||||
github.com/refraction-networking/utls v1.6.3 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/sagernet/sing v0.2.17 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.5 // indirect
|
||||
github.com/sagernet/sing v0.3.8 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 // 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/tklauser/go-sysconf v0.3.13 // indirect
|
||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.15.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/time v0.4.0 // indirect
|
||||
golang.org/x/tools v0.15.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/net v0.22.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b // indirect
|
||||
lukechampine.com/blake3 v1.2.1 // indirect
|
||||
|
||||
326
go.sum
@@ -10,29 +10,29 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Workiva/go-datastructures v1.1.1 h1:9G5u1UqKt6ABseAffHGNfbNQd7omRlWE5QaxNruzhE0=
|
||||
github.com/Workiva/go-datastructures v1.1.1/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/Calidity/gin-sessions v1.3.1 h1:nF3dCBWa7TZ4j26iYLwGRmzZy9YODhWoOS3fmi+snyE=
|
||||
github.com/Calidity/gin-sessions v1.3.1/go.mod h1:I0+QE6qkO50TeN/n6If6novvxHk4Isvr23U8EdvPdns=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
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.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
|
||||
github.com/bytedance/sonic v1.11.3/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-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-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/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
||||
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -44,50 +44,36 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||
github.com/gin-contrib/gzip v1.0.0 h1:UKN586Po/92IDX6ie5CWLgMI81obiIp5nSP85T3wlTk=
|
||||
github.com/gin-contrib/gzip v1.0.0/go.mod h1:CtG7tQrPB3vIBo6Gat9FVUsis+1emjvQqd66ME5TdnE=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
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-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
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/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
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/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
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 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
@@ -95,20 +81,16 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
||||
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/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.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.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -116,21 +98,21 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
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.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/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/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
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/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/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
@@ -141,109 +123,88 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
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.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
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/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lufia/plan9stats v0.0.0-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/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed h1:036IscGBfJsFIgJQzlui7nK1Ncm0tp2ktmPj8xO4N/0=
|
||||
github.com/lufia/plan9stats v0.0.0-20231016141302-07b5767bb0ed/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.3.0 h1:2NPsCsNFCVd7i+Su0xYsBrIhS3bE2XMv5gNTft2O+PQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.3.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU=
|
||||
github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
|
||||
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
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/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.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/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-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-20240219145905-2259734c190a h1:XCUtNgBnZfUBhdfCX2QK+fslr9vevSsUg3W3peZwlak=
|
||||
github.com/power-devops/perfstat v0.0.0-20240219145905-2259734c190a/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
||||
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o=
|
||||
github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw=
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
|
||||
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
|
||||
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/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.2.17 h1:vMPKb3MV0Aa5ws4dCJkRI8XEjrsUcDn810czd0FwmzI=
|
||||
github.com/sagernet/sing v0.2.17/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.5 h1:qxIttos4xu6ii7MTVJYA8EFQR7Q3KG6xMqmLJIFtBaY=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.5/go.mod h1:MGWGkcU2xW2G2mfArT9/QqpVLOGU+dBaahZCtPHdt7A=
|
||||
github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk=
|
||||
github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
|
||||
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
|
||||
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@@ -275,32 +236,28 @@ github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||
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.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
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/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
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.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
@@ -312,40 +269,35 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19 h1:capMfFYRgH9BCLd6A3Er/cH3A9Nz3CU2KwxwOQZIePI=
|
||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19/go.mod h1:dm4y/1QwzjGaK17ofi0Vs6NpKAHegZky8qk6J2JJZAE=
|
||||
github.com/xtls/xray-core v1.8.6 h1:tr3nk/fZnFfCsmgZv7B3RC72N5qUC88oMGVLlybDey8=
|
||||
github.com/xtls/xray-core v1.8.6/go.mod h1:hj2EB8rtcLdlTC8zxiWm5xL+C0k2Aie9Pk0mXtDEP6U=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/xtls/xray-core v1.8.10 h1:qxae6gSteonpPI7EZyOyqw5HmRVRzmU07qs0l1GNqz4=
|
||||
github.com/xtls/xray-core v1.8.10/go.mod h1:Mc1t+kLBPE5a1EpsUNKjMLviGz3Y0XywxeEraJZAMlI=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ=
|
||||
go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.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-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
|
||||
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
|
||||
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -354,12 +306,8 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
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=
|
||||
@@ -369,61 +317,43 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/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-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
|
||||
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-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-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
|
||||
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
|
||||
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/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
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.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb h1:c5tyN8sSp8jSDxdCCDXVOpJwYXXhmTkNMt+g0zTSOic=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231022001213-2e0774f246fb/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
@@ -436,39 +366,32 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA
|
||||
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-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7 h1:em/y72n4XlYRtayY/cVj6pnVzHa//BDA1BdoO+z9mdE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240308144416-29370a3891b7/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
||||
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.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
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.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E=
|
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE=
|
||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b h1:yqkg3pTifuKukuWanp8spDsL4irJkHF5WI0J47hU87o=
|
||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
@@ -477,6 +400,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||
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=
|
||||
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=
|
||||
|
||||
128
install.sh
@@ -23,25 +23,16 @@ else
|
||||
fi
|
||||
echo "The OS release is: $release"
|
||||
|
||||
arch=$(arch)
|
||||
|
||||
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
|
||||
arch="amd64"
|
||||
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
|
||||
arch="arm64"
|
||||
elif [[ $arch == "s390x" ]]; then
|
||||
arch="s390x"
|
||||
else
|
||||
arch="amd64"
|
||||
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
|
||||
fi
|
||||
|
||||
echo "arch: ${arch}"
|
||||
|
||||
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
|
||||
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
|
||||
exit -1
|
||||
fi
|
||||
arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64 | x64 | amd64) echo 'amd64' ;;
|
||||
i*86 | x86) echo '386' ;;
|
||||
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
||||
armv7* | armv7 | arm) echo 'armv7' ;;
|
||||
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
||||
esac
|
||||
}
|
||||
echo "arch: $(arch)"
|
||||
|
||||
os_version=""
|
||||
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||
@@ -50,7 +41,7 @@ if [[ "${release}" == "centos" ]]; then
|
||||
if [[ ${os_version} -lt 8 ]]; then
|
||||
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||
fi
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
elif [[ "${release}" == "ubuntu" ]]; then
|
||||
if [[ ${os_version} -lt 20 ]]; then
|
||||
echo -e "${red}please use Ubuntu 20 or higher version! ${plain}\n" && exit 1
|
||||
fi
|
||||
@@ -68,13 +59,18 @@ else
|
||||
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||
fi
|
||||
|
||||
|
||||
install_base() {
|
||||
if [[ "${release}" == "centos" ]] || [[ "${release}" == "fedora" ]] ; then
|
||||
yum install wget curl tar -y
|
||||
else
|
||||
apt install wget curl tar -y
|
||||
fi
|
||||
install_dependencies() {
|
||||
case "${release}" in
|
||||
centos)
|
||||
yum -y update && yum install -y -q wget curl tar tzdata
|
||||
;;
|
||||
fedora)
|
||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
||||
;;
|
||||
*)
|
||||
apt-get update && apt install -y -q wget curl tar tzdata
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#This function will be called when user installed x-ui out of sercurity
|
||||
@@ -113,6 +109,21 @@ config_after_install() {
|
||||
}
|
||||
|
||||
install_x-ui() {
|
||||
# checks if the installation backup dir exist. if existed then ask user if they want to restore it else continue installation.
|
||||
if [[ -e /usr/local/x-ui-backup/ ]]; then
|
||||
read -p "Failed installation detected. Do you want to restore previously installed version? [y/n]? ": restore_confirm
|
||||
if [[ "${restore_confirm}" == "y" || "${restore_confirm}" == "Y" ]]; then
|
||||
systemctl stop x-ui
|
||||
mv /usr/local/x-ui-backup/x-ui.db /etc/x-ui/ -f
|
||||
mv /usr/local/x-ui-backup/ /usr/local/x-ui/ -f
|
||||
systemctl start x-ui
|
||||
echo -e "${green}previous installed x-ui restored successfully${plain}, it is up and running now..."
|
||||
exit 0
|
||||
else
|
||||
echo -e "Continuing installing x-ui ..."
|
||||
fi
|
||||
fi
|
||||
|
||||
cd /usr/local/
|
||||
|
||||
if [ $# == 0 ]; then
|
||||
@@ -122,36 +133,45 @@ install_x-ui() {
|
||||
exit 1
|
||||
fi
|
||||
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "${red}Dowanloading x-ui failed, please be sure that your server can access Github ${plain}"
|
||||
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
last_version=$1
|
||||
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
||||
echo -e "Begining to install x-ui v$1"
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
||||
url="https://github.com/alireza0/x-ui/releases/download/${last_version}/x-ui-linux-$(arch).tar.gz"
|
||||
echo -e "Beginning to install x-ui v$1"
|
||||
wget -N --no-check-certificate -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "${red}dowanload x-ui v$1 failed,please check the verison exists${plain}"
|
||||
echo -e "${red}download x-ui v$1 failed,please check the version exists${plain}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -e /usr/local/x-ui/ ]]; then
|
||||
systemctl stop x-ui
|
||||
rm /usr/local/x-ui/ -rf
|
||||
mv /usr/local/x-ui/ /usr/local/x-ui-backup/ -f
|
||||
cp /etc/x-ui/x-ui.db /usr/local/x-ui-backup/ -f
|
||||
fi
|
||||
|
||||
tar zxvf x-ui-linux-${arch}.tar.gz
|
||||
rm x-ui-linux-${arch}.tar.gz -f
|
||||
tar zxvf x-ui-linux-$(arch).tar.gz
|
||||
rm x-ui-linux-$(arch).tar.gz -f
|
||||
cd x-ui
|
||||
chmod +x x-ui bin/xray-linux-${arch}
|
||||
chmod +x x-ui
|
||||
|
||||
# Check the system's architecture and rename the file accordingly
|
||||
if [[ $(arch) == "armv7" ]]; then
|
||||
mv bin/xray-linux-$(arch) bin/xray-linux-arm
|
||||
chmod +x bin/xray-linux-arm
|
||||
fi
|
||||
chmod +x x-ui bin/xray-linux-$(arch)
|
||||
cp -f x-ui.service /etc/systemd/system/
|
||||
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/alireza0/x-ui/main/x-ui.sh
|
||||
chmod +x /usr/local/x-ui/x-ui.sh
|
||||
chmod +x /usr/bin/x-ui
|
||||
config_after_install
|
||||
rm /usr/local/x-ui-backup/ -rf
|
||||
#echo -e "If it is a new installation, the default web port is ${green}54321${plain}, The username and password are ${green}admin${plain} by default"
|
||||
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 54321 has been released${plain}"
|
||||
# echo -e "If you want to modify the 54321 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
|
||||
@@ -163,22 +183,24 @@ install_x-ui() {
|
||||
systemctl start x-ui
|
||||
echo -e "${green}x-ui v${last_version}${plain} installation finished, it is up and running now..."
|
||||
echo -e ""
|
||||
echo -e "x-ui control menu usages: "
|
||||
echo -e "----------------------------------------------"
|
||||
echo -e "x-ui - Enter Admin menu"
|
||||
echo -e "x-ui start - Start x-ui"
|
||||
echo -e "x-ui stop - Stop x-ui"
|
||||
echo -e "x-ui restart - Restart x-ui"
|
||||
echo -e "x-ui status - Show x-ui status"
|
||||
echo -e "x-ui enable - Enable x-ui on system startup"
|
||||
echo -e "x-ui disable - Disable x-ui on system startup"
|
||||
echo -e "x-ui log - Check x-ui logs"
|
||||
echo -e "x-ui update - Update x-ui"
|
||||
echo -e "x-ui install - Install x-ui"
|
||||
echo -e "x-ui uninstall - Uninstall x-ui"
|
||||
echo -e "----------------------------------------------"
|
||||
echo "X-UI Control Menu Usage"
|
||||
echo "------------------------------------------"
|
||||
echo "SUBCOMMANDS:"
|
||||
echo "x-ui - Admin Management Script"
|
||||
echo "x-ui start - Start"
|
||||
echo "x-ui stop - Stop"
|
||||
echo "x-ui restart - Restart"
|
||||
echo "x-ui status - Current Status"
|
||||
echo "x-ui enable - Enable Autostart on OS Startup"
|
||||
echo "x-ui disable - Disable Autostart on OS Startup"
|
||||
echo "x-ui log - Check Logs"
|
||||
echo "x-ui update - Update"
|
||||
echo "x-ui install - Install"
|
||||
echo "x-ui uninstall - Uninstall"
|
||||
echo "x-ui help - Control Menu Usage"
|
||||
echo "------------------------------------------"
|
||||
}
|
||||
|
||||
echo -e "${green}Excuting...${plain}"
|
||||
install_base
|
||||
echo -e "${green}Running...${plain}"
|
||||
install_dependencies
|
||||
install_x-ui $1
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
var logger *logging.Logger
|
||||
var logBuffer []struct {
|
||||
time string
|
||||
level logging.Level
|
||||
log string
|
||||
}
|
||||
var (
|
||||
logger *logging.Logger
|
||||
logBuffer []struct {
|
||||
time string
|
||||
level logging.Level
|
||||
log string
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
InitLogger(logging.INFO)
|
||||
|
||||
3
main.go
@@ -8,6 +8,7 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
_ "unsafe"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/logger"
|
||||
@@ -318,7 +319,7 @@ func main() {
|
||||
updateTgbotEnableSts(enabletgbot)
|
||||
}
|
||||
default:
|
||||
fmt.Println("except 'run' or 'setting' subcommands")
|
||||
fmt.Println("Invalid subcommands")
|
||||
fmt.Println()
|
||||
runCmd.Usage()
|
||||
fmt.Println()
|
||||
|
||||
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 96 KiB |
BIN
media/warp.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
87
sub/default.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"remarks": "",
|
||||
"dns": {
|
||||
"tag": "dns_out",
|
||||
"queryStrategy": "UseIP",
|
||||
"servers": [
|
||||
{
|
||||
"address": "8.8.8.8",
|
||||
"skipFallback": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"port": 10808,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
"auth": "noauth",
|
||||
"udp": true,
|
||||
"userLevel": 8
|
||||
},
|
||||
"sniffing": {
|
||||
"destOverride": [
|
||||
"http",
|
||||
"tls",
|
||||
"fakedns"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
"tag": "socks"
|
||||
},
|
||||
{
|
||||
"port": 10809,
|
||||
"protocol": "http",
|
||||
"settings": {
|
||||
"userLevel": 8
|
||||
},
|
||||
"tag": "http"
|
||||
}
|
||||
],
|
||||
"log": {
|
||||
"loglevel": "warning"
|
||||
},
|
||||
"outbounds": [
|
||||
{
|
||||
"tag": "direct",
|
||||
"protocol": "freedom",
|
||||
"settings": {
|
||||
"domainStrategy": "UseIP"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tag": "block",
|
||||
"protocol": "blackhole",
|
||||
"settings": {
|
||||
"response": {
|
||||
"type": "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"levels": {
|
||||
"8": {
|
||||
"connIdle": 300,
|
||||
"downlinkOnly": 1,
|
||||
"handshake": 4,
|
||||
"uplinkOnly": 1
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"statsOutboundUplink": true,
|
||||
"statsOutboundDownlink": true
|
||||
}
|
||||
},
|
||||
"routing": {
|
||||
"domainStrategy": "IPIfNonMatch",
|
||||
"rules": [
|
||||
{
|
||||
"type": "field",
|
||||
"network": "tcp,udp",
|
||||
"outboundTag": "proxy"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {}
|
||||
}
|
||||
83
sub/sub.go
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
@@ -47,11 +48,6 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
|
||||
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
|
||||
@@ -61,15 +57,62 @@ func (s *Server) initRouter() (*gin.Engine, error) {
|
||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
||||
}
|
||||
|
||||
g := engine.Group(subPath)
|
||||
LinksPath, err := s.settingService.GetSubPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.sub = NewSUBController(g)
|
||||
JsonPath, err := s.settingService.GetSubJsonPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Encrypt, err := s.settingService.GetSubEncrypt()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ShowInfo, err := s.settingService.GetSubShowInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
RemarkModel, err := s.settingService.GetRemarkModel()
|
||||
if err != nil {
|
||||
RemarkModel = "-ieo"
|
||||
}
|
||||
|
||||
SubUpdates, err := s.settingService.GetSubUpdates()
|
||||
if err != nil {
|
||||
SubUpdates = "10"
|
||||
}
|
||||
|
||||
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
|
||||
if err != nil {
|
||||
SubJsonFragment = ""
|
||||
}
|
||||
|
||||
SubJsonMux, err := s.settingService.GetSubJsonMux()
|
||||
if err != nil {
|
||||
SubJsonMux = ""
|
||||
}
|
||||
|
||||
SubJsonRules, err := s.settingService.GetSubJsonRules()
|
||||
if err != nil {
|
||||
SubJsonRules = ""
|
||||
}
|
||||
|
||||
g := engine.Group("/")
|
||||
|
||||
s.sub = NewSUBController(
|
||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
||||
SubJsonFragment, SubJsonMux, SubJsonRules)
|
||||
|
||||
return engine, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() (err error) {
|
||||
//This is an anonymous function, no function name
|
||||
// This is an anonymous function, no function name
|
||||
defer func() {
|
||||
if err != nil {
|
||||
s.Stop()
|
||||
@@ -114,21 +157,19 @@ func (s *Server) Start() (err error) {
|
||||
|
||||
if certFile != "" || keyFile != "" {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
listener.Close()
|
||||
return err
|
||||
if err == nil {
|
||||
c := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
listener = network.NewAutoHttpsListener(listener)
|
||||
listener = tls.NewListener(listener, c)
|
||||
logger.Info("sub server run https on", listener.Addr())
|
||||
} else {
|
||||
logger.Error("error in loading certificates: ", err)
|
||||
logger.Info("sub server run http on", listener.Addr())
|
||||
}
|
||||
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())
|
||||
logger.Info("sub server run http on", listener.Addr())
|
||||
}
|
||||
s.listener = listener
|
||||
|
||||
|
||||
@@ -3,34 +3,59 @@ package sub
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"x-ui/web/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SUBController struct {
|
||||
subService SubService
|
||||
settingService service.SettingService
|
||||
subPath string
|
||||
subJsonPath string
|
||||
subEncrypt bool
|
||||
updateInterval string
|
||||
|
||||
subService *SubService
|
||||
subJsonService *SubJsonService
|
||||
}
|
||||
|
||||
func NewSUBController(g *gin.RouterGroup) *SUBController {
|
||||
a := &SUBController{}
|
||||
func NewSUBController(
|
||||
g *gin.RouterGroup,
|
||||
subPath string,
|
||||
jsonPath string,
|
||||
encrypt bool,
|
||||
showInfo bool,
|
||||
rModel string,
|
||||
update string,
|
||||
jsonFragment string,
|
||||
jsonMux string,
|
||||
jsonRules string,
|
||||
) *SUBController {
|
||||
sub := NewSubService(showInfo, rModel)
|
||||
a := &SUBController{
|
||||
subPath: subPath,
|
||||
subJsonPath: jsonPath,
|
||||
subEncrypt: encrypt,
|
||||
updateInterval: update,
|
||||
|
||||
subService: sub,
|
||||
subJsonService: NewSubJsonService(jsonFragment, jsonMux, jsonRules, sub),
|
||||
}
|
||||
a.initRouter(g)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/")
|
||||
gLink := g.Group(a.subPath)
|
||||
gJson := g.Group(a.subJsonPath)
|
||||
|
||||
g.GET("/:subid", a.subs)
|
||||
gLink.GET(":subid", a.subs)
|
||||
|
||||
gJson.GET(":subid", a.subJsons)
|
||||
}
|
||||
|
||||
func (a *SUBController) subs(c *gin.Context) {
|
||||
subEncrypt, _ := a.settingService.GetSubEncrypt()
|
||||
subShowInfo, _ := a.settingService.GetSubShowInfo()
|
||||
subId := c.Param("subid")
|
||||
host := strings.Split(c.Request.Host, ":")[0]
|
||||
subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo)
|
||||
subs, header, err := a.subService.GetSubs(subId, host)
|
||||
if err != nil || len(subs) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
@@ -40,14 +65,31 @@ func (a *SUBController) subs(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Add headers
|
||||
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.Writer.Header().Set("Subscription-Userinfo", header)
|
||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||
c.Writer.Header().Set("Profile-Title", subId)
|
||||
|
||||
if subEncrypt {
|
||||
if a.subEncrypt {
|
||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
||||
} else {
|
||||
c.String(200, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *SUBController) subJsons(c *gin.Context) {
|
||||
subId := c.Param("subid")
|
||||
host := strings.Split(c.Request.Host, ":")[0]
|
||||
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
||||
if err != nil || len(jsonSub) == 0 {
|
||||
c.String(400, "Error!")
|
||||
} else {
|
||||
|
||||
// Add headers
|
||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
||||
c.Writer.Header().Set("Profile-Title", subId)
|
||||
|
||||
c.String(200, jsonSub)
|
||||
}
|
||||
}
|
||||
|
||||
385
sub/subJsonService.go
Normal file
@@ -0,0 +1,385 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/json_util"
|
||||
"x-ui/util/random"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
)
|
||||
|
||||
//go:embed default.json
|
||||
var defaultJson string
|
||||
|
||||
type SubJsonService struct {
|
||||
configJson map[string]interface{}
|
||||
defaultOutbounds []json_util.RawMessage
|
||||
fragment string
|
||||
mux string
|
||||
|
||||
inboundService service.InboundService
|
||||
SubService *SubService
|
||||
}
|
||||
|
||||
func NewSubJsonService(fragment string, mux string, rules string, subService *SubService) *SubJsonService {
|
||||
var configJson map[string]interface{}
|
||||
var defaultOutbounds []json_util.RawMessage
|
||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
||||
if outboundSlices, ok := configJson["outbounds"].([]interface{}); ok {
|
||||
for _, defaultOutbound := range outboundSlices {
|
||||
jsonBytes, _ := json.Marshal(defaultOutbound)
|
||||
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
||||
}
|
||||
}
|
||||
|
||||
if rules != "" {
|
||||
var newRules []interface{}
|
||||
routing, _ := configJson["routing"].(map[string]interface{})
|
||||
defaultRules, _ := routing["rules"].([]interface{})
|
||||
json.Unmarshal([]byte(rules), &newRules)
|
||||
defaultRules = append(newRules, defaultRules...)
|
||||
routing["rules"] = defaultRules
|
||||
configJson["routing"] = routing
|
||||
}
|
||||
|
||||
if fragment != "" {
|
||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
||||
}
|
||||
|
||||
return &SubJsonService{
|
||||
configJson: configJson,
|
||||
defaultOutbounds: defaultOutbounds,
|
||||
fragment: fragment,
|
||||
mux: mux,
|
||||
SubService: subService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
|
||||
inbounds, err := s.SubService.getInboundsBySubId(subId)
|
||||
if err != nil || len(inbounds) == 0 {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
var header string
|
||||
var traffic xray.ClientTraffic
|
||||
var clientTraffics []xray.ClientTraffic
|
||||
var configArray []json_util.RawMessage
|
||||
|
||||
// Prepare Inbounds
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.GetClients(inbound)
|
||||
if err != nil {
|
||||
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
|
||||
}
|
||||
if clients == nil {
|
||||
continue
|
||||
}
|
||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||
listen, port, streamSettings, err := s.SubService.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
||||
if err == nil {
|
||||
inbound.Listen = listen
|
||||
inbound.Port = port
|
||||
inbound.StreamSettings = streamSettings
|
||||
}
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
if client.Enable && client.SubID == subId {
|
||||
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
||||
newConfigs := s.getConfig(inbound, client, host)
|
||||
configArray = append(configArray, newConfigs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(configArray) == 0 {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// Prepare statistics
|
||||
for index, clientTraffic := range clientTraffics {
|
||||
if index == 0 {
|
||||
traffic.Up = clientTraffic.Up
|
||||
traffic.Down = clientTraffic.Down
|
||||
traffic.Total = clientTraffic.Total
|
||||
if clientTraffic.ExpiryTime > 0 {
|
||||
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
||||
}
|
||||
} else {
|
||||
traffic.Up += clientTraffic.Up
|
||||
traffic.Down += clientTraffic.Down
|
||||
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
||||
traffic.Total = 0
|
||||
} else {
|
||||
traffic.Total += clientTraffic.Total
|
||||
}
|
||||
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
||||
traffic.ExpiryTime = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combile outbounds
|
||||
var finalJson []byte
|
||||
if len(configArray) == 1 {
|
||||
finalJson, _ = json.MarshalIndent(configArray[0], "", " ")
|
||||
} else {
|
||||
finalJson, _ = json.MarshalIndent(configArray, "", " ")
|
||||
}
|
||||
|
||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||
return string(finalJson), header, nil
|
||||
}
|
||||
|
||||
func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, host string) []json_util.RawMessage {
|
||||
var newJsonArray []json_util.RawMessage
|
||||
stream := s.streamData(inbound.StreamSettings)
|
||||
|
||||
externalProxies, ok := stream["externalProxy"].([]interface{})
|
||||
if !ok || len(externalProxies) == 0 {
|
||||
externalProxies = []interface{}{
|
||||
map[string]interface{}{
|
||||
"forceTls": "same",
|
||||
"dest": host,
|
||||
"port": float64(inbound.Port),
|
||||
"remark": "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
delete(stream, "externalProxy")
|
||||
|
||||
for _, ep := range externalProxies {
|
||||
extPrxy := ep.(map[string]interface{})
|
||||
inbound.Listen = extPrxy["dest"].(string)
|
||||
inbound.Port = int(extPrxy["port"].(float64))
|
||||
newStream := stream
|
||||
switch extPrxy["forceTls"].(string) {
|
||||
case "tls":
|
||||
if newStream["security"] != "tls" {
|
||||
newStream["security"] = "tls"
|
||||
newStream["tslSettings"] = map[string]interface{}{}
|
||||
}
|
||||
case "none":
|
||||
if newStream["security"] != "none" {
|
||||
newStream["security"] = "none"
|
||||
delete(newStream, "tslSettings")
|
||||
}
|
||||
}
|
||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
||||
|
||||
var newOutbounds []json_util.RawMessage
|
||||
|
||||
switch inbound.Protocol {
|
||||
case "vmess", "vless":
|
||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
||||
case "trojan", "shadowsocks":
|
||||
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
||||
}
|
||||
|
||||
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
|
||||
newConfigJson := make(map[string]interface{})
|
||||
for key, value := range s.configJson {
|
||||
newConfigJson[key] = value
|
||||
}
|
||||
newConfigJson["outbounds"] = newOutbounds
|
||||
newConfigJson["remarks"] = s.SubService.genRemark(inbound, client.Email, extPrxy["remark"].(string))
|
||||
|
||||
newConfig, _ := json.MarshalIndent(newConfigJson, "", " ")
|
||||
newJsonArray = append(newJsonArray, newConfig)
|
||||
}
|
||||
|
||||
return newJsonArray
|
||||
}
|
||||
|
||||
func (s *SubJsonService) streamData(stream string) map[string]interface{} {
|
||||
var streamSettings map[string]interface{}
|
||||
json.Unmarshal([]byte(stream), &streamSettings)
|
||||
security, _ := streamSettings["security"].(string)
|
||||
if security == "tls" {
|
||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]interface{}))
|
||||
} else if security == "reality" {
|
||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]interface{}))
|
||||
}
|
||||
delete(streamSettings, "sockopt")
|
||||
|
||||
if s.fragment != "" {
|
||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpNoDelay": true}`)
|
||||
}
|
||||
|
||||
// remove proxy protocol
|
||||
network, _ := streamSettings["network"].(string)
|
||||
switch network {
|
||||
case "tcp":
|
||||
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
|
||||
case "ws":
|
||||
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
|
||||
case "httpupgrade":
|
||||
streamSettings["httpupgradeSettings"] = s.removeAcceptProxy(streamSettings["httpupgradeSettings"])
|
||||
}
|
||||
|
||||
return streamSettings
|
||||
}
|
||||
|
||||
func (s *SubJsonService) removeAcceptProxy(setting interface{}) map[string]interface{} {
|
||||
netSettings, ok := setting.(map[string]interface{})
|
||||
if ok {
|
||||
delete(netSettings, "acceptProxyProtocol")
|
||||
}
|
||||
return netSettings
|
||||
}
|
||||
|
||||
func (s *SubJsonService) tlsData(tData map[string]interface{}) map[string]interface{} {
|
||||
tlsData := make(map[string]interface{}, 1)
|
||||
tlsClientSettings, _ := tData["settings"].(map[string]interface{})
|
||||
|
||||
tlsData["serverName"] = tData["serverName"]
|
||||
tlsData["alpn"] = tData["alpn"]
|
||||
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(bool); ok {
|
||||
tlsData["allowInsecure"] = allowInsecure
|
||||
}
|
||||
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
||||
tlsData["fingerprint"] = fingerprint
|
||||
}
|
||||
return tlsData
|
||||
}
|
||||
|
||||
func (s *SubJsonService) realityData(rData map[string]interface{}) map[string]interface{} {
|
||||
rltyData := make(map[string]interface{}, 1)
|
||||
rltyClientSettings, _ := rData["settings"].(map[string]interface{})
|
||||
|
||||
rltyData["show"] = false
|
||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
||||
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
||||
|
||||
// Set random data
|
||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
||||
shortIds, ok := rData["shortIds"].([]interface{})
|
||||
if ok && len(shortIds) > 0 {
|
||||
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
} else {
|
||||
rltyData["shortId"] = ""
|
||||
}
|
||||
serverNames, ok := rData["serverNames"].([]interface{})
|
||||
if ok && len(serverNames) > 0 {
|
||||
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
||||
} else {
|
||||
rltyData["serverName"] = ""
|
||||
}
|
||||
|
||||
return rltyData
|
||||
}
|
||||
|
||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
||||
outbound := Outbound{}
|
||||
usersData := make([]UserVnext, 1)
|
||||
|
||||
usersData[0].ID = client.ID
|
||||
usersData[0].Level = 8
|
||||
if inbound.Protocol == model.VLESS {
|
||||
usersData[0].Flow = client.Flow
|
||||
usersData[0].Encryption = "none"
|
||||
}
|
||||
|
||||
vnextData := make([]VnextSetting, 1)
|
||||
vnextData[0] = VnextSetting{
|
||||
Address: inbound.Listen,
|
||||
Port: inbound.Port,
|
||||
Users: usersData,
|
||||
}
|
||||
|
||||
outbound.Protocol = string(inbound.Protocol)
|
||||
outbound.Tag = "proxy"
|
||||
if s.mux != "" {
|
||||
outbound.Mux = json_util.RawMessage(s.mux)
|
||||
}
|
||||
outbound.StreamSettings = streamSettings
|
||||
outbound.Settings = OutboundSettings{
|
||||
Vnext: vnextData,
|
||||
}
|
||||
|
||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
||||
outbound := Outbound{}
|
||||
|
||||
serverData := make([]ServerSetting, 1)
|
||||
serverData[0] = ServerSetting{
|
||||
Address: inbound.Listen,
|
||||
Port: inbound.Port,
|
||||
Level: 8,
|
||||
Password: client.Password,
|
||||
}
|
||||
|
||||
if inbound.Protocol == model.Shadowsocks {
|
||||
var inboundSettings map[string]interface{}
|
||||
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
||||
method, _ := inboundSettings["method"].(string)
|
||||
serverData[0].Method = method
|
||||
|
||||
// server password in multi-user 2022 protocols
|
||||
if strings.HasPrefix(method, "2022") {
|
||||
if serverPassword, ok := inboundSettings["password"].(string); ok {
|
||||
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outbound.Protocol = string(inbound.Protocol)
|
||||
outbound.Tag = "proxy"
|
||||
if s.mux != "" {
|
||||
outbound.Mux = json_util.RawMessage(s.mux)
|
||||
}
|
||||
outbound.StreamSettings = streamSettings
|
||||
outbound.Settings = OutboundSettings{
|
||||
Servers: serverData,
|
||||
}
|
||||
|
||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||
return result
|
||||
}
|
||||
|
||||
type Outbound struct {
|
||||
Protocol string `json:"protocol"`
|
||||
Tag string `json:"tag"`
|
||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
||||
ProxySettings map[string]interface{} `json:"proxySettings,omitempty"`
|
||||
Settings OutboundSettings `json:"settings,omitempty"`
|
||||
}
|
||||
|
||||
type OutboundSettings struct {
|
||||
Vnext []VnextSetting `json:"vnext,omitempty"`
|
||||
Servers []ServerSetting `json:"servers,omitempty"`
|
||||
}
|
||||
|
||||
type VnextSetting struct {
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Users []UserVnext `json:"users"`
|
||||
}
|
||||
|
||||
type UserVnext struct {
|
||||
Encryption string `json:"encryption,omitempty"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Level int `json:"level"`
|
||||
}
|
||||
|
||||
type ServerSetting struct {
|
||||
Password string `json:"password"`
|
||||
Level int `json:"level"`
|
||||
Address string `json:"address"`
|
||||
Port int `json:"port"`
|
||||
Flow string `json:"flow,omitempty"`
|
||||
Method string `json:"method,omitempty"`
|
||||
}
|
||||
@@ -6,10 +6,12 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
"x-ui/util/common"
|
||||
"x-ui/util/random"
|
||||
"x-ui/web/service"
|
||||
"x-ui/xray"
|
||||
|
||||
@@ -17,50 +19,46 @@ import (
|
||||
)
|
||||
|
||||
type SubService struct {
|
||||
address string
|
||||
showInfo bool
|
||||
remarkModel string
|
||||
address string
|
||||
showInfo bool
|
||||
remarkModel string
|
||||
|
||||
inboundService service.InboundService
|
||||
settingService service.SettingService
|
||||
}
|
||||
|
||||
func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) {
|
||||
func NewSubService(showInfo bool, remarkModel string) *SubService {
|
||||
return &SubService{
|
||||
showInfo: showInfo,
|
||||
remarkModel: remarkModel,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
||||
s.address = host
|
||||
s.showInfo = showInfo
|
||||
var result []string
|
||||
var headers []string
|
||||
var header string
|
||||
var traffic xray.ClientTraffic
|
||||
var clientTraffics []xray.ClientTraffic
|
||||
inbounds, err := s.getInboundsBySubId(subId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
s.remarkModel, err = s.settingService.GetRemarkModel()
|
||||
if err != nil {
|
||||
s.remarkModel = "-ieo"
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Prepare Inbounds
|
||||
for _, inbound := range inbounds {
|
||||
clients, err := s.inboundService.GetClients(inbound)
|
||||
if err != nil {
|
||||
logger.Error("SubService - GetSub: Unable to get clients from inbound")
|
||||
logger.Error("SubService - GetClients: Unable to get clients from inbound")
|
||||
}
|
||||
if clients == nil {
|
||||
continue
|
||||
}
|
||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
||||
fallbackMaster, err := s.getFallbackMaster(inbound.Listen)
|
||||
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
||||
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"]
|
||||
stream["externalProxy"] = masterStream["externalProxy"]
|
||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||
inbound.StreamSettings = string(modifiedStream)
|
||||
inbound.Listen = listen
|
||||
inbound.Port = port
|
||||
inbound.StreamSettings = streamSettings
|
||||
}
|
||||
}
|
||||
for _, client := range clients {
|
||||
@@ -71,6 +69,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare statistics
|
||||
for index, clientTraffic := range clientTraffics {
|
||||
if index == 0 {
|
||||
traffic.Up = clientTraffic.Up
|
||||
@@ -92,11 +92,8 @@ func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string
|
||||
}
|
||||
}
|
||||
}
|
||||
headers = append(headers, fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000))
|
||||
updateInterval, _ := s.settingService.GetSubUpdates()
|
||||
headers = append(headers, fmt.Sprintf("%d", updateInterval))
|
||||
headers = append(headers, subId)
|
||||
return result, headers, nil
|
||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
||||
return result, header, nil
|
||||
}
|
||||
|
||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
||||
@@ -125,7 +122,7 @@ func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email stri
|
||||
return xray.ClientTraffic{}
|
||||
}
|
||||
|
||||
func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
|
||||
db := database.GetDB()
|
||||
var inbound *model.Inbound
|
||||
err := db.Model(model.Inbound{}).
|
||||
@@ -133,9 +130,19 @@ func (s *SubService) getFallbackMaster(dest string) (*model.Inbound, error) {
|
||||
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
||||
Find(&inbound).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", 0, "", err
|
||||
}
|
||||
return inbound, nil
|
||||
|
||||
var stream map[string]interface{}
|
||||
json.Unmarshal([]byte(streamSettings), &stream)
|
||||
var masterStream map[string]interface{}
|
||||
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
||||
stream["security"] = masterStream["security"]
|
||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
||||
stream["externalProxy"] = masterStream["externalProxy"]
|
||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
||||
|
||||
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
||||
}
|
||||
|
||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
||||
@@ -187,8 +194,12 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
obj["path"] = ws["path"].(string)
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
obj["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
obj["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
obj["net"] = "h2"
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
@@ -202,10 +213,15 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
||||
obj["path"], _ = quic["key"].(string)
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
obj["path"] = grpc["serviceName"].(string)
|
||||
obj["path"], _ = grpc["serviceName"].(string)
|
||||
obj["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
obj["type"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
obj["path"] = httpupgrade["path"].(string)
|
||||
obj["host"] = httpupgrade["host"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -322,8 +338,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
params["path"] = ws["path"].(string)
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
@@ -337,9 +357,14 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
params["path"] = httpupgrade["path"].(string)
|
||||
params["host"] = httpupgrade["host"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -382,25 +407,21 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]interface{})
|
||||
params["sni"], _ = sNames[0].(string)
|
||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]interface{})
|
||||
params["sid"], _ = shortIds[0].(string)
|
||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||
params["fp"] = fp
|
||||
}
|
||||
}
|
||||
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
||||
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
||||
params["spx"] = spx
|
||||
}
|
||||
}
|
||||
params["spx"] = "/" + random.Seq(15)
|
||||
}
|
||||
|
||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
||||
@@ -408,6 +429,10 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
||||
}
|
||||
}
|
||||
|
||||
if security != "tls" && security != "reality" {
|
||||
params["security"] = "none"
|
||||
}
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
@@ -503,8 +528,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
params["path"] = ws["path"].(string)
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
@@ -518,9 +547,14 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
params["path"] = httpupgrade["path"].(string)
|
||||
params["host"] = httpupgrade["host"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
@@ -559,28 +593,28 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
|
||||
if realitySetting != nil {
|
||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
||||
sNames, _ := sniValue.([]interface{})
|
||||
params["sni"], _ = sNames[0].(string)
|
||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
||||
}
|
||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
||||
params["pbk"], _ = pbkValue.(string)
|
||||
}
|
||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
||||
shortIds, _ := sidValue.([]interface{})
|
||||
params["sid"], _ = shortIds[0].(string)
|
||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
||||
}
|
||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
||||
params["fp"] = fp
|
||||
}
|
||||
}
|
||||
if spxValue, ok := searchKey(realitySettings, "spiderX"); ok {
|
||||
if spx, ok := spxValue.(string); ok && len(spx) > 0 {
|
||||
params["spx"] = spx
|
||||
}
|
||||
}
|
||||
params["spx"] = "/" + random.Seq(15)
|
||||
}
|
||||
}
|
||||
|
||||
if security != "tls" && security != "reality" {
|
||||
params["security"] = "none"
|
||||
}
|
||||
|
||||
externalProxies, _ := stream["externalProxy"].([]interface{})
|
||||
|
||||
if len(externalProxies) > 0 {
|
||||
@@ -680,8 +714,12 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
case "ws":
|
||||
ws, _ := stream["wsSettings"].(map[string]interface{})
|
||||
params["path"] = ws["path"].(string)
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
||||
params["host"] = host
|
||||
} else {
|
||||
headers, _ := ws["headers"].(map[string]interface{})
|
||||
params["host"] = searchHost(headers)
|
||||
}
|
||||
case "http":
|
||||
http, _ := stream["httpSettings"].(map[string]interface{})
|
||||
params["path"] = http["path"].(string)
|
||||
@@ -695,9 +733,14 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
|
||||
case "grpc":
|
||||
grpc, _ := stream["grpcSettings"].(map[string]interface{})
|
||||
params["serviceName"] = grpc["serviceName"].(string)
|
||||
params["authority"], _ = grpc["authority"].(string)
|
||||
if grpc["multiMode"].(bool) {
|
||||
params["mode"] = "multi"
|
||||
}
|
||||
case "httpupgrade":
|
||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
|
||||
params["path"] = httpupgrade["path"].(string)
|
||||
params["host"] = httpupgrade["host"].(string)
|
||||
}
|
||||
|
||||
security, _ := stream["security"].(string)
|
||||
|
||||
@@ -3,6 +3,7 @@ package common
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"x-ui/logger"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var numSeq [10]rune
|
||||
var lowerSeq [26]rune
|
||||
var upperSeq [26]rune
|
||||
var numLowerSeq [36]rune
|
||||
var numUpperSeq [36]rune
|
||||
var allSeq [62]rune
|
||||
var (
|
||||
numSeq [10]rune
|
||||
lowerSeq [26]rune
|
||||
upperSeq [26]rune
|
||||
numLowerSeq [36]rune
|
||||
numUpperSeq [36]rune
|
||||
allSeq [62]rune
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
@@ -41,3 +43,7 @@ func Seq(n int) string {
|
||||
}
|
||||
return string(runes)
|
||||
}
|
||||
|
||||
func Num(n int) int {
|
||||
return rand.Intn(n)
|
||||
}
|
||||
|
||||
@@ -538,7 +538,7 @@
|
||||
|
||||
var on = function(emitter, type, f) {
|
||||
if (emitter.addEventListener) {
|
||||
emitter.addEventListener(type, f, false);
|
||||
emitter.addEventListener(type, f, { passive: false });
|
||||
} else if (emitter.attachEvent) {
|
||||
emitter.attachEvent("on" + type, f);
|
||||
} else {
|
||||
|
||||
@@ -55,7 +55,7 @@ style attribute {
|
||||
}
|
||||
.ant-table-tbody > tr > td,
|
||||
.ant-table-thead > tr > th {
|
||||
padding: 12px 16px;
|
||||
padding: 12px 8px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.ant-table-thead > tr > th {
|
||||
@@ -132,6 +132,13 @@ style attribute {
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.ant-modal-body {
|
||||
padding: 10px;
|
||||
}
|
||||
.ant-form-item-label {
|
||||
line-height: 1.5;
|
||||
padding: 8px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout-content {
|
||||
@@ -409,6 +416,10 @@ style attribute {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-setting-textarea {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
@@ -672,7 +683,8 @@ style attribute {
|
||||
border-color: #9ea2a8;
|
||||
}
|
||||
|
||||
.dark .ant-table-row-expand-icon:hover {
|
||||
.dark .ant-table-row-expand-icon:hover,
|
||||
.dark .ant-table-thead>tr>th.ant-table-column-has-actions.ant-table-column-has-sorters:hover {
|
||||
color: #0e49b5;
|
||||
background-color: #fff0;
|
||||
border-color: #0e49b5;
|
||||
@@ -1027,6 +1039,12 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.dark .ant-message-notice-content {
|
||||
background-color: #222d42;
|
||||
border: 1px solid #2c3950;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.ant-input-number-handler-wrap {
|
||||
border-radius: 0;
|
||||
}
|
||||
@@ -1038,3 +1056,29 @@ li.ant-select-dropdown-menu-item:empty:after {
|
||||
.ant-input-number {
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
.ant-modal-body,
|
||||
.ant-collapse-content>.ant-collapse-content-box {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.dark .ant-dropdown-menu-item:hover,
|
||||
.dark .ant-dropdown-menu-submenu-title:hover,
|
||||
.dark .ant-select-dropdown-menu-item-active:not(.ant-select-dropdown-menu-item-disabled),
|
||||
.dark .ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) {
|
||||
background-color: #313f5a;
|
||||
}
|
||||
|
||||
.ant-select-dropdown,
|
||||
.ant-popover-inner {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.ant-popover-inner-content {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ant-input-group-addon:not(:first-child):not(:last-child), .ant-input-group-wrap:not(:first-child):not(:last-child), .ant-input-group>.ant-input:not(:first-child):not(:last-child) {
|
||||
border-radius: 0rem 1rem 1rem 0rem;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -14,3 +14,17 @@ axios.interceptors.request.use(
|
||||
},
|
||||
(error) => Promise.reject(error),
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response) {
|
||||
const statusCode = error.response.status;
|
||||
// Check the status code
|
||||
if (statusCode === 401) { // Unauthorized
|
||||
return window.location.reload();
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -56,6 +56,10 @@ class DBInbound {
|
||||
return this.protocol === Protocols.HTTP;
|
||||
}
|
||||
|
||||
get isWireguard() {
|
||||
return this.protocol === Protocols.WIREGUARD;
|
||||
}
|
||||
|
||||
get address() {
|
||||
let address = location.hostname;
|
||||
if (!ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0") {
|
||||
|
||||
@@ -8,6 +8,7 @@ const Protocols = {
|
||||
Shadowsocks: "shadowsocks",
|
||||
Socks: "socks",
|
||||
HTTP: "http",
|
||||
Wireguard: "wireguard"
|
||||
};
|
||||
|
||||
const SSMethods = {
|
||||
@@ -46,18 +47,34 @@ const ALPN_OPTION = {
|
||||
HTTP1: "http/1.1",
|
||||
};
|
||||
|
||||
const outboundDomainStrategies = [
|
||||
const OutboundDomainStrategies = [
|
||||
"AsIs",
|
||||
"UseIP",
|
||||
"UseIPv4",
|
||||
"UseIPv6"
|
||||
]
|
||||
"UseIPv6",
|
||||
"UseIPv6v4",
|
||||
"UseIPv4v6",
|
||||
"ForceIP",
|
||||
"ForceIPv6v4",
|
||||
"ForceIPv6",
|
||||
"ForceIPv4v6",
|
||||
"ForceIPv4"
|
||||
];
|
||||
|
||||
const WireguardDomainStrategy = [
|
||||
"ForceIP",
|
||||
"ForceIPv4",
|
||||
"ForceIPv4v6",
|
||||
"ForceIPv6",
|
||||
"ForceIPv6v4"
|
||||
];
|
||||
|
||||
Object.freeze(Protocols);
|
||||
Object.freeze(SSMethods);
|
||||
Object.freeze(TLS_FLOW_CONTROL);
|
||||
Object.freeze(ALPN_OPTION);
|
||||
Object.freeze(outboundDomainStrategies);
|
||||
Object.freeze(OutboundDomainStrategies);
|
||||
Object.freeze(WireguardDomainStrategy);
|
||||
|
||||
class CommonClass {
|
||||
|
||||
@@ -177,14 +194,14 @@ class WsStreamSettings extends CommonClass {
|
||||
static fromJson(json={}) {
|
||||
return new WsStreamSettings(
|
||||
json.path,
|
||||
json.headers && !ObjectUtil.isEmpty(json.headers.Host) ? json.headers.Host : '',
|
||||
json.host,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
path: this.path,
|
||||
headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
|
||||
host: this.host,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -240,24 +257,48 @@ class QuicStreamSettings extends CommonClass {
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends CommonClass {
|
||||
constructor(serviceName="", multiMode=false) {
|
||||
constructor(serviceName="", authority="", multiMode=false) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.authority = authority;
|
||||
this.multiMode = multiMode;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
||||
return new GrpcStreamSettings(json.serviceName, json.authority, json.multiMode);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
serviceName: this.serviceName,
|
||||
authority: this.authority,
|
||||
multiMode: this.multiMode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HttpUpgradeStreamSettings extends CommonClass {
|
||||
constructor(path='/', host='') {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new HttpUpgradeStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TlsStreamSettings extends CommonClass {
|
||||
constructor(serverName='',
|
||||
alpn=[],
|
||||
@@ -316,7 +357,36 @@ class RealityStreamSettings extends CommonClass {
|
||||
spiderX: this.spiderX,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class SockoptStreamSettings extends CommonClass {
|
||||
constructor(dialerProxy = "", tcpFastOpen = false, tcpKeepAliveInterval = 0, tcpNoDelay = false) {
|
||||
super();
|
||||
this.dialerProxy = dialerProxy;
|
||||
this.tcpFastOpen = tcpFastOpen;
|
||||
this.tcpKeepAliveInterval = tcpKeepAliveInterval;
|
||||
this.tcpNoDelay = tcpNoDelay;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
if (Object.keys(json).length === 0) return undefined;
|
||||
return new SockoptStreamSettings(
|
||||
json.dialerProxy,
|
||||
json.tcpFastOpen,
|
||||
json.tcpKeepAliveInterval,
|
||||
json.tcpNoDelay,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
dialerProxy: this.dialerProxy,
|
||||
tcpFastOpen: this.tcpFastOpen,
|
||||
tcpKeepAliveInterval: this.tcpKeepAliveInterval,
|
||||
tcpNoDelay: this.tcpNoDelay,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class StreamSettings extends CommonClass {
|
||||
constructor(network='tcp',
|
||||
@@ -329,6 +399,8 @@ class StreamSettings extends CommonClass {
|
||||
httpSettings=new HttpStreamSettings(),
|
||||
quicSettings=new QuicStreamSettings(),
|
||||
grpcSettings=new GrpcStreamSettings(),
|
||||
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
this.network = network;
|
||||
@@ -341,6 +413,8 @@ class StreamSettings extends CommonClass {
|
||||
this.http = httpSettings;
|
||||
this.quic = quicSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.httpupgrade = httpupgradeSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
get isTls() {
|
||||
@@ -351,6 +425,14 @@ class StreamSettings extends CommonClass {
|
||||
return this.security === "reality";
|
||||
}
|
||||
|
||||
get sockoptSwitch() {
|
||||
return this.sockopt != undefined;
|
||||
}
|
||||
|
||||
set sockoptSwitch(value) {
|
||||
this.sockopt = value ? new SockoptStreamSettings() : undefined;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new StreamSettings(
|
||||
json.network,
|
||||
@@ -363,6 +445,8 @@ class StreamSettings extends CommonClass {
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
QuicStreamSettings.fromJson(json.quicSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -379,6 +463,37 @@ class StreamSettings extends CommonClass {
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Mux extends CommonClass {
|
||||
constructor(enabled = false, concurrency = 8, xudpConcurrency = 16, xudpProxyUDP443 = "reject") {
|
||||
super();
|
||||
this.enabled = enabled;
|
||||
this.concurrency = concurrency;
|
||||
this.xudpConcurrency = xudpConcurrency;
|
||||
this.xudpProxyUDP443 = xudpProxyUDP443;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
if (Object.keys(json).length === 0) return undefined;
|
||||
return new Mux(
|
||||
json.enabled,
|
||||
json.concurrency,
|
||||
json.xudpConcurrency,
|
||||
json.xudpProxyUDP443,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
enabled: this.enabled,
|
||||
concurrency: this.concurrency,
|
||||
xudpConcurrency: this.xudpConcurrency,
|
||||
xudpProxyUDP443: this.xudpProxyUDP443,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -389,12 +504,16 @@ class Outbound extends CommonClass {
|
||||
protocol=Protocols.VMess,
|
||||
settings=null,
|
||||
streamSettings = new StreamSettings(),
|
||||
sendThrough,
|
||||
mux = new Mux(),
|
||||
) {
|
||||
super();
|
||||
this.tag = tag;
|
||||
this._protocol = protocol;
|
||||
this.settings = settings == null ? Outbound.Settings.getSettings(protocol) : settings;
|
||||
this.stream = streamSettings;
|
||||
this.sendThrough = sendThrough;
|
||||
this.mux = mux;
|
||||
}
|
||||
|
||||
get protocol() {
|
||||
@@ -408,8 +527,8 @@ class Outbound extends CommonClass {
|
||||
}
|
||||
|
||||
canEnableTls() {
|
||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan].includes(this.protocol)) return false;
|
||||
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.stream.network);
|
||||
if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
|
||||
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.stream.network);
|
||||
}
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
@@ -429,6 +548,10 @@ class Outbound extends CommonClass {
|
||||
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol);
|
||||
}
|
||||
|
||||
canEnableMux() {
|
||||
return [Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks, Protocols.HTTP, Protocols.Socks].includes(this.protocol);
|
||||
}
|
||||
|
||||
hasVnext() {
|
||||
return [Protocols.VMess, Protocols.VLESS].includes(this.protocol);
|
||||
}
|
||||
@@ -459,15 +582,26 @@ class Outbound extends CommonClass {
|
||||
json.protocol,
|
||||
Outbound.Settings.fromJson(json.protocol, json.settings),
|
||||
StreamSettings.fromJson(json.streamSettings),
|
||||
json.sendThrough,
|
||||
Mux.fromJson(json.mux),
|
||||
)
|
||||
}
|
||||
|
||||
toJson() {
|
||||
var stream;
|
||||
if (this.canEnableStream()) {
|
||||
stream = this.stream.toJson();
|
||||
} else {
|
||||
if (this.stream?.sockopt)
|
||||
stream = { sockopt: this.stream.sockopt.toJson() };
|
||||
}
|
||||
return {
|
||||
tag: this.tag == '' ? undefined : this.tag,
|
||||
protocol: this.protocol,
|
||||
settings: this.settings instanceof CommonClass ? this.settings.toJson() : this.settings,
|
||||
streamSettings: this.canEnableStream() ? this.stream.toJson() : undefined,
|
||||
streamSettings: stream,
|
||||
sendThrough: this.sendThrough != "" ? this.sendThrough : undefined,
|
||||
mux: this.mux?.enabled ? this.mux : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -476,7 +610,7 @@ class Outbound extends CommonClass {
|
||||
if(data.length !=2) return null;
|
||||
switch(data[0].toLowerCase()){
|
||||
case Protocols.VMess:
|
||||
return this.fromVmessLink(JSON.parse(atob(data[1])));
|
||||
return this.fromVmessLink(JSON.parse(Base64.decode(data[1])));
|
||||
case Protocols.VLESS:
|
||||
case Protocols.Trojan:
|
||||
case 'ss':
|
||||
@@ -493,8 +627,8 @@ class Outbound extends CommonClass {
|
||||
if (network === 'tcp') {
|
||||
stream.tcp = new TcpStreamSettings(
|
||||
json.type,
|
||||
json.host ? json.host.split(','): [],
|
||||
json.path ? json.path.split(','): []);
|
||||
json.host ?? '',
|
||||
json.path ?? '');
|
||||
} else if (network === 'kcp') {
|
||||
stream.kcp = new KcpStreamSettings();
|
||||
stream.type = json.type;
|
||||
@@ -505,14 +639,16 @@ class Outbound extends CommonClass {
|
||||
stream.network = 'http'
|
||||
stream.http = new HttpStreamSettings(
|
||||
json.path,
|
||||
json.host ? json.host.split(',') : []);
|
||||
json.host);
|
||||
} else if (network === 'quic') {
|
||||
stream.quic = new QuicStreamSettings(
|
||||
json.host ? json.host : 'none',
|
||||
json.path,
|
||||
json.type ? json.type : 'none');
|
||||
} else if (network === 'grpc') {
|
||||
stream.grpc = new GrpcStreamSettings(json.path, json.type == 'multi');
|
||||
stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi');
|
||||
} else if (network === 'httpupgrade') {
|
||||
stream.httpupgrade = new HttpUpgradeStreamSettings(json.path,json.host);
|
||||
}
|
||||
|
||||
if(json.tls && json.tls == 'tls'){
|
||||
@@ -523,21 +659,22 @@ class Outbound extends CommonClass {
|
||||
json.allowInsecure);
|
||||
}
|
||||
|
||||
const port = json.port * 1;
|
||||
|
||||
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, json.port, json.id), stream);
|
||||
return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, port, json.id), stream);
|
||||
}
|
||||
|
||||
static fromParamLink(link){
|
||||
const url = new URL(link);
|
||||
let type = url.searchParams.get('type');
|
||||
let type = url.searchParams.get('type') ?? 'tcp';
|
||||
let security = url.searchParams.get('security') ?? 'none';
|
||||
let stream = new StreamSettings(type, security);
|
||||
|
||||
let headerType = url.searchParams.get('headerType');
|
||||
let host = url.searchParams.get('host');
|
||||
let path = url.searchParams.get('path');
|
||||
let headerType = url.searchParams.get('headerType') ?? undefined;
|
||||
let host = url.searchParams.get('host') ?? undefined;
|
||||
let path = url.searchParams.get('path') ?? undefined;
|
||||
|
||||
if (type === 'tcp') {
|
||||
if (type === 'tcp' || type === 'none') {
|
||||
stream.tcp = new TcpStreamSettings(headerType ?? 'none', host, path);
|
||||
} else if (type === 'kcp') {
|
||||
stream.kcp = new KcpStreamSettings();
|
||||
@@ -553,7 +690,12 @@ class Outbound extends CommonClass {
|
||||
url.searchParams.get('key') ?? '',
|
||||
headerType ?? 'none');
|
||||
} else if (type === 'grpc') {
|
||||
stream.grpc = new GrpcStreamSettings(url.searchParams.get('serviceName') ?? '', url.searchParams.get('mode') == 'multi');
|
||||
stream.grpc = new GrpcStreamSettings(
|
||||
url.searchParams.get('serviceName') ?? '',
|
||||
url.searchParams.get('authority') ?? '',
|
||||
url.searchParams.get('mode') == 'multi');
|
||||
} else if (type === 'httpupgrade') {
|
||||
stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
|
||||
}
|
||||
|
||||
if(security == 'tls'){
|
||||
@@ -570,13 +712,10 @@ class Outbound extends CommonClass {
|
||||
let sni=url.searchParams.get('sni') ?? '';
|
||||
let sid=url.searchParams.get('sid') ?? '';
|
||||
let spx=url.searchParams.get('spx') ?? '';
|
||||
stream.tls = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
||||
stream.reality = new RealityStreamSettings(pbk, fp, sni, sid, spx);
|
||||
}
|
||||
|
||||
let data = link.split('?');
|
||||
if(data.length != 2) return null;
|
||||
|
||||
const regex = /([^@]+):\/\/([^@]+)@([^:]+):(\d+)\?(.*)$/;
|
||||
const regex = /([^@]+):\/\/([^@]+)@(.+):(\d+)(.*)$/;
|
||||
const match = link.match(regex);
|
||||
|
||||
if (!match) return null;
|
||||
@@ -625,6 +764,7 @@ Outbound.Settings = class extends CommonClass {
|
||||
case Protocols.Shadowsocks: return new Outbound.ShadowsocksSettings();
|
||||
case Protocols.Socks: return new Outbound.SocksSettings();
|
||||
case Protocols.HTTP: return new Outbound.HttpSettings();
|
||||
case Protocols.Wireguard: return new Outbound.WireguardSettings();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -640,6 +780,7 @@ Outbound.Settings = class extends CommonClass {
|
||||
case Protocols.Shadowsocks: return Outbound.ShadowsocksSettings.fromJson(json);
|
||||
case Protocols.Socks: return Outbound.SocksSettings.fromJson(json);
|
||||
case Protocols.HTTP: return Outbound.HttpSettings.fromJson(json);
|
||||
case Protocols.Wireguard: return Outbound.WireguardSettings.fromJson(json);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -688,7 +829,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
||||
Outbound.BlackholeSettings = class extends CommonClass {
|
||||
constructor(type) {
|
||||
super();
|
||||
this.type;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
@@ -839,22 +980,22 @@ Outbound.ShadowsocksSettings = class extends CommonClass {
|
||||
}
|
||||
};
|
||||
Outbound.SocksSettings = class extends CommonClass {
|
||||
constructor(address, port, user, password) {
|
||||
constructor(address, port, user, pass) {
|
||||
super();
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
servers = json.servers;
|
||||
let servers = json.servers;
|
||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||
return new Outbound.SocksSettings(
|
||||
servers[0].address,
|
||||
servers[0].port,
|
||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
|
||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -863,28 +1004,28 @@ Outbound.SocksSettings = class extends CommonClass {
|
||||
servers: [{
|
||||
address: this.address,
|
||||
port: this.port,
|
||||
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
|
||||
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}],
|
||||
}],
|
||||
};
|
||||
}
|
||||
};
|
||||
Outbound.HttpSettings = class extends CommonClass {
|
||||
constructor(address, port, user, password) {
|
||||
constructor(address, port, user, pass) {
|
||||
super();
|
||||
this.address = address;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.pass = pass;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
servers = json.servers;
|
||||
let servers = json.servers;
|
||||
if(ObjectUtil.isArrEmpty(servers)) servers=[{users: [{}]}];
|
||||
return new Outbound.HttpSettings(
|
||||
servers[0].address,
|
||||
servers[0].port,
|
||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].user,
|
||||
ObjectUtil.isArrEmpty(servers[0].password) ? '' : servers[0].users[0].password,
|
||||
ObjectUtil.isArrEmpty(servers[0].users) ? '' : servers[0].users[0].pass,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -893,8 +1034,89 @@ Outbound.HttpSettings = class extends CommonClass {
|
||||
servers: [{
|
||||
address: this.address,
|
||||
port: this.port,
|
||||
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, password: this.password}],
|
||||
users: ObjectUtil.isEmpty(this.user) ? [] : [{user: this.user, pass: this.pass}],
|
||||
}],
|
||||
};
|
||||
}
|
||||
};
|
||||
Outbound.WireguardSettings = class extends CommonClass {
|
||||
constructor(
|
||||
mtu=1420, secretKey='',
|
||||
address='', workers=2, domainStrategy='', reserved='',
|
||||
peers=[new Outbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||
super();
|
||||
this.mtu = mtu;
|
||||
this.secretKey = secretKey;
|
||||
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
|
||||
this.address = address instanceof Array ? address.join(',') : address;
|
||||
this.workers = workers;
|
||||
this.domainStrategy = domainStrategy;
|
||||
this.reserved = reserved instanceof Array ? reserved.join(',') : reserved;
|
||||
this.peers = peers;
|
||||
this.kernelMode = kernelMode;
|
||||
}
|
||||
|
||||
addPeer() {
|
||||
this.peers.push(new Outbound.WireguardSettings.Peer());
|
||||
}
|
||||
|
||||
delPeer(index) {
|
||||
this.peers.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json={}){
|
||||
return new Outbound.WireguardSettings(
|
||||
json.mtu,
|
||||
json.secretKey,
|
||||
json.address,
|
||||
json.workers,
|
||||
json.domainStrategy,
|
||||
json.reserved,
|
||||
json.peers.map(peer => Outbound.WireguardSettings.Peer.fromJson(peer)),
|
||||
json.kernelMode,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
mtu: this.mtu?? undefined,
|
||||
secretKey: this.secretKey,
|
||||
address: this.address ? this.address.split(",") : [],
|
||||
workers: this.workers?? undefined,
|
||||
domainStrategy: WireguardDomainStrategy.includes(this.domainStrategy) ? this.domainStrategy : undefined,
|
||||
reserved: this.reserved ? this.reserved.split(",").map(Number) : undefined,
|
||||
peers: Outbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||
kernelMode: this.kernelMode,
|
||||
};
|
||||
}
|
||||
};
|
||||
Outbound.WireguardSettings.Peer = class extends CommonClass {
|
||||
constructor(publicKey='', psk='', allowedIPs=['0.0.0.0/0','::/0'], endpoint='', keepAlive=0) {
|
||||
super();
|
||||
this.publicKey = publicKey;
|
||||
this.psk = psk;
|
||||
this.allowedIPs = allowedIPs;
|
||||
this.endpoint = endpoint;
|
||||
this.keepAlive = keepAlive;
|
||||
}
|
||||
|
||||
static fromJson(json={}){
|
||||
return new Outbound.WireguardSettings.Peer(
|
||||
json.publicKey,
|
||||
json.preSharedKey,
|
||||
json.allowedIPs,
|
||||
json.endpoint,
|
||||
json.keepAlive
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
publicKey: this.publicKey,
|
||||
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
||||
allowedIPs: this.allowedIPs ? this.allowedIPs : undefined,
|
||||
endpoint: this.endpoint,
|
||||
keepAlive: this.keepAlive?? undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -24,13 +24,18 @@ class AllSetting {
|
||||
this.subListen = "";
|
||||
this.subPort = "2096";
|
||||
this.subPath = "/sub/";
|
||||
this.subJsonPath = "/json/";
|
||||
this.subDomain = "";
|
||||
this.subCertFile = "";
|
||||
this.subKeyFile = "";
|
||||
this.subUpdates = 0;
|
||||
this.subEncrypt = true;
|
||||
this.subShowInfo = false;
|
||||
this.subURI = '';
|
||||
this.subURI = "";
|
||||
this.subJsonURI = "";
|
||||
this.subJsonFragment = "";
|
||||
this.subJsonMux = "";
|
||||
this.subJsonRules = "";
|
||||
|
||||
this.timeLocation = "Asia/Tehran";
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const Protocols = {
|
||||
DOKODEMO: 'dokodemo-door',
|
||||
SOCKS: 'socks',
|
||||
HTTP: 'http',
|
||||
WIREGUARD: 'wireguard',
|
||||
};
|
||||
|
||||
const SSMethods = {
|
||||
@@ -205,15 +206,6 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
|
||||
this.headers.push({ name: name, value: value });
|
||||
}
|
||||
|
||||
getHeader(name) {
|
||||
for (const header of this.headers) {
|
||||
if (header.name.toLowerCase() === name.toLowerCase()) {
|
||||
return header.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
removeHeader(index) {
|
||||
this.headers.splice(index, 1);
|
||||
}
|
||||
@@ -229,6 +221,7 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
version: this.version,
|
||||
method: this.method,
|
||||
path: ObjectUtil.clone(this.path),
|
||||
headers: XrayCommonClass.toV2Headers(this.headers),
|
||||
@@ -330,10 +323,11 @@ class KcpStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class WsStreamSettings extends XrayCommonClass {
|
||||
constructor(acceptProxyProtocol=false, path='/', headers=[]) {
|
||||
constructor(acceptProxyProtocol=false, path='/', host='', headers=[]) {
|
||||
super();
|
||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
@@ -341,15 +335,6 @@ class WsStreamSettings extends XrayCommonClass {
|
||||
this.headers.push({ name: name, value: value });
|
||||
}
|
||||
|
||||
getHeader(name) {
|
||||
for (const header of this.headers) {
|
||||
if (header.name.toLowerCase() === name.toLowerCase()) {
|
||||
return header.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
removeHeader(index) {
|
||||
this.headers.splice(index, 1);
|
||||
}
|
||||
@@ -358,6 +343,7 @@ class WsStreamSettings extends XrayCommonClass {
|
||||
return new WsStreamSettings(
|
||||
json.acceptProxyProtocol,
|
||||
json.path,
|
||||
json.host,
|
||||
XrayCommonClass.toHeaders(json.headers),
|
||||
);
|
||||
}
|
||||
@@ -366,6 +352,7 @@ class WsStreamSettings extends XrayCommonClass {
|
||||
return {
|
||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
||||
};
|
||||
}
|
||||
@@ -406,7 +393,7 @@ class HttpStreamSettings extends XrayCommonClass {
|
||||
|
||||
class QuicStreamSettings extends XrayCommonClass {
|
||||
constructor(security='none',
|
||||
key='', type='none') {
|
||||
key=RandomUtil.randomSeq(10), type='none') {
|
||||
super();
|
||||
this.security = security;
|
||||
this.key = key;
|
||||
@@ -433,24 +420,62 @@ class QuicStreamSettings extends XrayCommonClass {
|
||||
}
|
||||
|
||||
class GrpcStreamSettings extends XrayCommonClass {
|
||||
constructor(serviceName="", multiMode=false) {
|
||||
constructor(serviceName='', authority='', multiMode=false) {
|
||||
super();
|
||||
this.serviceName = serviceName;
|
||||
this.authority = authority;
|
||||
this.multiMode = multiMode;
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new GrpcStreamSettings(json.serviceName, json.multiMode);
|
||||
return new GrpcStreamSettings(json.serviceName, json.authority, json.multiMode);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
serviceName: this.serviceName,
|
||||
authority: this.authority,
|
||||
multiMode: this.multiMode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HttpUpgradeStreamSettings extends XrayCommonClass {
|
||||
constructor(acceptProxyProtocol=false, path='/', host='', headers=[]) {
|
||||
super();
|
||||
this.acceptProxyProtocol = acceptProxyProtocol;
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
addHeader(name, value) {
|
||||
this.headers.push({ name: name, value: value });
|
||||
}
|
||||
|
||||
removeHeader(index) {
|
||||
this.headers.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json={}) {
|
||||
return new HttpUpgradeStreamSettings(
|
||||
json.acceptProxyProtocol,
|
||||
json.path,
|
||||
json.host,
|
||||
XrayCommonClass.toHeaders(json.headers),
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
acceptProxyProtocol: this.acceptProxyProtocol,
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
headers: XrayCommonClass.toV2Headers(this.headers, false),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TlsStreamSettings extends XrayCommonClass {
|
||||
constructor(serverName='',
|
||||
minVersion = TLS_VERSION_OPTION.TLS12,
|
||||
@@ -458,7 +483,7 @@ class TlsStreamSettings extends XrayCommonClass {
|
||||
cipherSuites = '',
|
||||
rejectUnknownSni = false,
|
||||
certificates=[new TlsStreamSettings.Cert()],
|
||||
alpn=[],
|
||||
alpn=[ALPN_OPTION.H2,ALPN_OPTION.HTTP1],
|
||||
settings=new TlsStreamSettings.Settings()) {
|
||||
super();
|
||||
this.sni = serverName;
|
||||
@@ -703,6 +728,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
httpSettings=new HttpStreamSettings(),
|
||||
quicSettings=new QuicStreamSettings(),
|
||||
grpcSettings=new GrpcStreamSettings(),
|
||||
httpupgradeSettings=new HttpUpgradeStreamSettings(),
|
||||
sockopt = undefined,
|
||||
) {
|
||||
super();
|
||||
@@ -717,6 +743,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
this.http = httpSettings;
|
||||
this.quic = quicSettings;
|
||||
this.grpc = grpcSettings;
|
||||
this.httpupgrade = httpupgradeSettings;
|
||||
this.sockopt = sockopt;
|
||||
}
|
||||
|
||||
@@ -765,6 +792,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
HttpStreamSettings.fromJson(json.httpSettings),
|
||||
QuicStreamSettings.fromJson(json.quicSettings),
|
||||
GrpcStreamSettings.fromJson(json.grpcSettings),
|
||||
HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
|
||||
SockoptStreamSettings.fromJson(json.sockopt),
|
||||
);
|
||||
}
|
||||
@@ -783,6 +811,7 @@ class StreamSettings extends XrayCommonClass {
|
||||
httpSettings: network === 'http' ? this.http.toJson() : undefined,
|
||||
quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
|
||||
grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
|
||||
httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
|
||||
sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
|
||||
};
|
||||
}
|
||||
@@ -812,7 +841,7 @@ class Sniffing extends XrayCommonClass {
|
||||
class Inbound extends XrayCommonClass {
|
||||
constructor(port=RandomUtil.randomIntRange(10000, 60000),
|
||||
listen='',
|
||||
protocol=Protocols.VMESS,
|
||||
protocol=Protocols.VLESS,
|
||||
settings=null,
|
||||
streamSettings=new StreamSettings(),
|
||||
tag='',
|
||||
@@ -887,13 +916,17 @@ class Inbound extends XrayCommonClass {
|
||||
return this.network === "http";
|
||||
}
|
||||
|
||||
get isHttpupgrade() {
|
||||
return this.network === "httpupgrade";
|
||||
}
|
||||
|
||||
// Shadowsocks
|
||||
get method() {
|
||||
switch (this.protocol) {
|
||||
case Protocols.SHADOWSOCKS:
|
||||
return this.settings.method;
|
||||
default:
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
}
|
||||
get isSSMultiUser() {
|
||||
@@ -906,16 +939,27 @@ class Inbound extends XrayCommonClass {
|
||||
get serverName() {
|
||||
if (this.stream.isTls) return this.stream.tls.sni;
|
||||
if (this.stream.isReality) return this.stream.reality.serverNames;
|
||||
return "";
|
||||
return '';
|
||||
}
|
||||
|
||||
getHeader(obj, name) {
|
||||
for (const header of obj.headers) {
|
||||
if (header.name.toLowerCase() === name.toLowerCase()) {
|
||||
return header.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get host() {
|
||||
if (this.isTcp) {
|
||||
return this.stream.tcp.request.getHeader("Host");
|
||||
} else if (this.isWs) {
|
||||
return this.stream.ws.getHeader("Host");
|
||||
return this.getHeader(this.stream.tcp.request, 'host');
|
||||
} else if (this.isH2) {
|
||||
return this.stream.http.host[0];
|
||||
} else if (this.isWs) {
|
||||
return this.stream.ws.host?.length>0 ? this.stream.ws.host : this.getHeader(this.stream.ws, 'host');
|
||||
} else if (this.isHttpupgrade) {
|
||||
return this.stream.httpupgrade.host;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -927,6 +971,8 @@ class Inbound extends XrayCommonClass {
|
||||
return this.stream.ws.path;
|
||||
} else if (this.isH2) {
|
||||
return this.stream.http.path;
|
||||
} else if (this.isHttpupgrade) {
|
||||
return this.stream.httpupgrade.path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -962,7 +1008,7 @@ class Inbound extends XrayCommonClass {
|
||||
|
||||
canEnableTls() {
|
||||
if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
|
||||
return ["tcp", "ws", "http", "quic", "grpc"].includes(this.network);
|
||||
return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.network);
|
||||
}
|
||||
|
||||
//this is used for xtls-rprx-vision
|
||||
@@ -982,10 +1028,6 @@ class Inbound extends XrayCommonClass {
|
||||
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||
}
|
||||
|
||||
canSniffing() {
|
||||
return [Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.port = RandomUtil.randomIntRange(10000, 60000);
|
||||
this.listen = '';
|
||||
@@ -1011,29 +1053,24 @@ class Inbound extends XrayCommonClass {
|
||||
type: 'none',
|
||||
tls: security,
|
||||
};
|
||||
let network = this.stream.network;
|
||||
const network = this.stream.network;
|
||||
if (network === 'tcp') {
|
||||
let tcp = this.stream.tcp;
|
||||
const tcp = this.stream.tcp;
|
||||
obj.type = tcp.type;
|
||||
if (tcp.type === 'http') {
|
||||
let request = tcp.request;
|
||||
const request = tcp.request;
|
||||
obj.path = request.path.join(',');
|
||||
let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
obj.host = request.headers[index].value;
|
||||
}
|
||||
const host = this.getHeader(request,'host');
|
||||
if (host) obj.host = host;
|
||||
}
|
||||
} else if (network === 'kcp') {
|
||||
let kcp = this.stream.kcp;
|
||||
const kcp = this.stream.kcp;
|
||||
obj.type = kcp.type;
|
||||
obj.path = kcp.seed;
|
||||
} else if (network === 'ws') {
|
||||
let ws = this.stream.ws;
|
||||
const ws = this.stream.ws;
|
||||
obj.path = ws.path;
|
||||
let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
|
||||
if (index >= 0) {
|
||||
obj.host = ws.headers[index].value;
|
||||
}
|
||||
obj.host = ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host');
|
||||
} else if (network === 'http') {
|
||||
obj.net = 'h2';
|
||||
obj.path = this.stream.http.path;
|
||||
@@ -1044,9 +1081,14 @@ class Inbound extends XrayCommonClass {
|
||||
obj.path = this.stream.quic.key;
|
||||
} else if (network === 'grpc') {
|
||||
obj.path = this.stream.grpc.serviceName;
|
||||
obj.authority = this.stream.grpc.authority;
|
||||
if (this.stream.grpc.multiMode){
|
||||
obj.type = 'multi'
|
||||
}
|
||||
} else if (network === 'httpupgrade') {
|
||||
const httpupgrade = this.stream.httpupgrade;
|
||||
obj.path = httpupgrade.path;
|
||||
obj.host = httpupgrade.host;
|
||||
}
|
||||
|
||||
if (security === 'tls') {
|
||||
@@ -1095,11 +1137,7 @@ class Inbound extends XrayCommonClass {
|
||||
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);
|
||||
}
|
||||
params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'));
|
||||
break;
|
||||
case "http":
|
||||
const http = this.stream.http;
|
||||
@@ -1115,10 +1153,16 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
params.set("authority", grpc.authority);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
case "httpupgrade":
|
||||
const httpupgrade = this.stream.httpupgrade;
|
||||
params.set("path", httpupgrade.path);
|
||||
params.set("host", httpupgrade.host);
|
||||
break;
|
||||
}
|
||||
|
||||
if (security === 'tls') {
|
||||
@@ -1138,7 +1182,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
if (security === 'reality') {
|
||||
else if (security === 'reality') {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||
@@ -1156,6 +1200,10 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
params.set("security", "none");
|
||||
}
|
||||
|
||||
const link = `vless://${uuid}@${address}:${port}`;
|
||||
const url = new URL(link);
|
||||
for (const [key, value] of params) {
|
||||
@@ -1193,11 +1241,7 @@ class Inbound extends XrayCommonClass {
|
||||
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);
|
||||
}
|
||||
params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'));
|
||||
break;
|
||||
case "http":
|
||||
const http = this.stream.http;
|
||||
@@ -1213,10 +1257,16 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
params.set("authority", grpc.authority);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
case "httpupgrade":
|
||||
const httpupgrade = this.stream.httpupgrade;
|
||||
params.set("path", httpupgrade.path);
|
||||
params.set("host", httpupgrade.host);
|
||||
break;
|
||||
}
|
||||
|
||||
if (security === 'tls') {
|
||||
@@ -1274,11 +1324,7 @@ class Inbound extends XrayCommonClass {
|
||||
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);
|
||||
}
|
||||
params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'));
|
||||
break;
|
||||
case "http":
|
||||
const http = this.stream.http;
|
||||
@@ -1294,10 +1340,16 @@ class Inbound extends XrayCommonClass {
|
||||
case "grpc":
|
||||
const grpc = this.stream.grpc;
|
||||
params.set("serviceName", grpc.serviceName);
|
||||
params.set("authority", grpc.authority);
|
||||
if(grpc.multiMode){
|
||||
params.set("mode", "multi");
|
||||
}
|
||||
break;
|
||||
case "httpupgrade":
|
||||
const httpupgrade = this.stream.httpupgrade;
|
||||
params.set("path", httpupgrade.path);
|
||||
params.set("host", httpupgrade.host);
|
||||
break;
|
||||
}
|
||||
|
||||
if (security === 'tls') {
|
||||
@@ -1314,7 +1366,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
if (security === 'reality') {
|
||||
else if (security === 'reality') {
|
||||
params.set("security", "reality");
|
||||
params.set("pbk", this.stream.reality.settings.publicKey);
|
||||
params.set("fp", this.stream.reality.settings.fingerprint);
|
||||
@@ -1328,6 +1380,9 @@ class Inbound extends XrayCommonClass {
|
||||
params.set("spx", this.stream.reality.settings.spiderX);
|
||||
}
|
||||
}
|
||||
else {
|
||||
params.set("security", "none");
|
||||
}
|
||||
|
||||
const link = `trojan://${clientPassword}@${address}:${port}`;
|
||||
const url = new URL(link);
|
||||
@@ -1338,6 +1393,28 @@ class Inbound extends XrayCommonClass {
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
getWireguardLink(address, port, remark, peerId) {
|
||||
let txt = `[Interface]\n`
|
||||
txt += `PrivateKey = ${this.settings.peers[peerId].privateKey}\n`
|
||||
txt += `Address = ${this.settings.peers[peerId].allowedIPs[0]}\n`
|
||||
txt += `DNS = 1.1.1.1, 9.9.9.9\n`
|
||||
if (this.settings.mtu) {
|
||||
txt += `MTU = ${this.settings.mtu}\n`
|
||||
}
|
||||
txt += `\n# ${remark}\n`
|
||||
txt += `[Peer]\n`
|
||||
txt += `PublicKey = ${this.settings.pubKey}\n`
|
||||
txt += `AllowedIPs = 0.0.0.0/0, ::/0\n`
|
||||
txt += `Endpoint = ${address}:${port}`
|
||||
if (this.settings.peers[peerId].psk) {
|
||||
txt += `\nPresharedKey = ${this.settings.peers[peerId].psk}`
|
||||
}
|
||||
if (this.settings.peers[peerId].keepAlive) {
|
||||
txt += `\nPersistentKeepalive = ${this.settings.peers[peerId].keepAlive}\n`
|
||||
}
|
||||
return txt;
|
||||
}
|
||||
|
||||
genLink(address='', port=this.port, forceTls='same', remark='', client) {
|
||||
switch (this.protocol) {
|
||||
case Protocols.VMESS:
|
||||
@@ -1361,7 +1438,7 @@ class Inbound extends XrayCommonClass {
|
||||
const orderChars = remarkModel.slice(1);
|
||||
let orders = {
|
||||
'i': remark,
|
||||
'e': client ? client.email : '',
|
||||
'e': email,
|
||||
'o': '',
|
||||
};
|
||||
if(ObjectUtil.isArrEmpty(this.stream.externalProxy)){
|
||||
@@ -1384,6 +1461,7 @@ class Inbound extends XrayCommonClass {
|
||||
}
|
||||
|
||||
genInboundLinks(remark = '', remarkModel = '-ieo') {
|
||||
let addr = !ObjectUtil.isEmpty(this.listen) && this.listen !== "0.0.0.0" ? this.listen : location.hostname;
|
||||
if(this.clients){
|
||||
let links = [];
|
||||
this.clients.forEach((client) => {
|
||||
@@ -1393,7 +1471,14 @@ class Inbound extends XrayCommonClass {
|
||||
});
|
||||
return links.join('\r\n');
|
||||
} else {
|
||||
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(this.listen, this.port, 'same', remark);
|
||||
if(this.protocol == Protocols.SHADOWSOCKS && !this.isSSMultiUser) return this.genSSLink(addr, this.port, 'same', remark);
|
||||
if(this.protocol == Protocols.WIREGUARD) {
|
||||
let links = [];
|
||||
this.settings.peers.forEach((p,index) => {
|
||||
links.push(this.getWireguardLink(addr,this.port,remark + remarkModel.charAt(0) + (index+1),index));
|
||||
});
|
||||
return links.join('\r\n');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1444,6 +1529,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
||||
case Protocols.DOKODEMO: return new Inbound.DokodemoSettings(protocol);
|
||||
case Protocols.SOCKS: return new Inbound.SocksSettings(protocol);
|
||||
case Protocols.HTTP: return new Inbound.HttpSettings(protocol);
|
||||
case Protocols.WIREGUARD: return new Inbound.WireguardSettings(protocol);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -1457,6 +1543,7 @@ Inbound.Settings = class extends XrayCommonClass {
|
||||
case Protocols.DOKODEMO: return Inbound.DokodemoSettings.fromJson(json);
|
||||
case Protocols.SOCKS: return Inbound.SocksSettings.fromJson(json);
|
||||
case Protocols.HTTP: return Inbound.HttpSettings.fromJson(json);
|
||||
case Protocols.WIREGUARD: return Inbound.WireguardSettings.fromJson(json);
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
@@ -1530,7 +1617,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||
);
|
||||
}
|
||||
get _expiryTime() {
|
||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||
if (this.expiryTime === 0 || this.expiryTime === '') {
|
||||
return null;
|
||||
}
|
||||
if (this.expiryTime < 0){
|
||||
@@ -1540,7 +1627,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
set _expiryTime(t) {
|
||||
if (t == null || t === "") {
|
||||
if (t == null || t === '') {
|
||||
this.expiryTime = 0;
|
||||
} else {
|
||||
this.expiryTime = t.valueOf();
|
||||
@@ -1623,7 +1710,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
get _expiryTime() {
|
||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||
if (this.expiryTime === 0 || this.expiryTime === '') {
|
||||
return null;
|
||||
}
|
||||
if (this.expiryTime < 0){
|
||||
@@ -1633,7 +1720,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
set _expiryTime(t) {
|
||||
if (t == null || t === "") {
|
||||
if (t == null || t === '') {
|
||||
this.expiryTime = 0;
|
||||
} else {
|
||||
this.expiryTime = t.valueOf();
|
||||
@@ -1648,7 +1735,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass {
|
||||
}
|
||||
};
|
||||
Inbound.VLESSSettings.Fallback = class extends XrayCommonClass {
|
||||
constructor(name="", alpn='', path='', dest='', xver=0) {
|
||||
constructor(name='', alpn='', path='', dest='', xver=0) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.alpn = alpn;
|
||||
@@ -1757,7 +1844,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
get _expiryTime() {
|
||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||
if (this.expiryTime === 0 || this.expiryTime === '') {
|
||||
return null;
|
||||
}
|
||||
if (this.expiryTime < 0){
|
||||
@@ -1767,7 +1854,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
set _expiryTime(t) {
|
||||
if (t == null || t === "") {
|
||||
if (t == null || t === '') {
|
||||
this.expiryTime = 0;
|
||||
} else {
|
||||
this.expiryTime = t.valueOf();
|
||||
@@ -1784,7 +1871,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass {
|
||||
};
|
||||
|
||||
Inbound.TrojanSettings.Fallback = class extends XrayCommonClass {
|
||||
constructor(name="", alpn='', path='', dest='', xver=0) {
|
||||
constructor(name='', alpn='', path='', dest='', xver=0) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.alpn = alpn;
|
||||
@@ -1899,7 +1986,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
get _expiryTime() {
|
||||
if (this.expiryTime === 0 || this.expiryTime === "") {
|
||||
if (this.expiryTime === 0 || this.expiryTime === '') {
|
||||
return null;
|
||||
}
|
||||
if (this.expiryTime < 0){
|
||||
@@ -1909,7 +1996,7 @@ Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass {
|
||||
}
|
||||
|
||||
set _expiryTime(t) {
|
||||
if (t == null || t === "") {
|
||||
if (t == null || t === '') {
|
||||
this.expiryTime = 0;
|
||||
} else {
|
||||
this.expiryTime = t.valueOf();
|
||||
@@ -2047,3 +2134,81 @@ Inbound.HttpSettings.HttpAccount = class extends XrayCommonClass {
|
||||
return new Inbound.HttpSettings.HttpAccount(json.user, json.pass);
|
||||
}
|
||||
};
|
||||
|
||||
Inbound.WireguardSettings = class extends XrayCommonClass {
|
||||
constructor(protocol, mtu=1420, secretKey=Wireguard.generateKeypair().privateKey, peers=[new Inbound.WireguardSettings.Peer()], kernelMode=false) {
|
||||
super(protocol);
|
||||
this.mtu = mtu;
|
||||
this.secretKey = secretKey;
|
||||
this.pubKey = secretKey.length>0 ? Wireguard.generateKeypair(secretKey).publicKey : '';
|
||||
this.peers = peers;
|
||||
this.kernelMode = kernelMode;
|
||||
}
|
||||
|
||||
addPeer() {
|
||||
this.peers.push(new Inbound.WireguardSettings.Peer(null,null,'',['10.0.0.' + (this.peers.length+2)]));
|
||||
}
|
||||
|
||||
delPeer(index) {
|
||||
this.peers.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json={}){
|
||||
return new Inbound.WireguardSettings(
|
||||
Protocols.WIREGUARD,
|
||||
json.mtu,
|
||||
json.secretKey,
|
||||
json.peers.map(peer => Inbound.WireguardSettings.Peer.fromJson(peer)),
|
||||
json.kernelMode,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
mtu: this.mtu?? undefined,
|
||||
secretKey: this.secretKey,
|
||||
peers: Inbound.WireguardSettings.Peer.toJsonArray(this.peers),
|
||||
kernelMode: this.kernelMode,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Inbound.WireguardSettings.Peer = class extends XrayCommonClass {
|
||||
constructor(privateKey, publicKey, psk='', allowedIPs=['10.0.0.2/32'], keepAlive=0) {
|
||||
super();
|
||||
this.privateKey = privateKey
|
||||
this.publicKey = publicKey;
|
||||
if (!this.publicKey){
|
||||
[this.publicKey, this.privateKey] = Object.values(Wireguard.generateKeypair())
|
||||
}
|
||||
this.psk = psk;
|
||||
allowedIPs.forEach((a,index) => {
|
||||
if (a.length>0 && !a.includes('/')) allowedIPs[index] += '/32';
|
||||
})
|
||||
this.allowedIPs = allowedIPs;
|
||||
this.keepAlive = keepAlive;
|
||||
}
|
||||
|
||||
static fromJson(json={}){
|
||||
return new Inbound.WireguardSettings.Peer(
|
||||
json.privateKey,
|
||||
json.publicKey,
|
||||
json.preSharedKey,
|
||||
json.allowedIPs,
|
||||
json.keepAlive
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
this.allowedIPs.forEach((a,index) => {
|
||||
if (a.length>0 && !a.includes('/')) this.allowedIPs[index] += '/32';
|
||||
});
|
||||
return {
|
||||
privateKey: this.privateKey,
|
||||
publicKey: this.publicKey,
|
||||
preSharedKey: this.psk.length>0 ? this.psk : undefined,
|
||||
allowedIPs: this.allowedIPs,
|
||||
keepAlive: this.keepAlive?? undefined,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -20,6 +20,14 @@ function sizeFormat(size) {
|
||||
}
|
||||
}
|
||||
|
||||
function cpuCoreFormat(cores) {
|
||||
if (cores === 1) {
|
||||
return "1 Core";
|
||||
} else {
|
||||
return cores + " Cores";
|
||||
}
|
||||
}
|
||||
|
||||
function base64(str) {
|
||||
return Base64.encode(str);
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ class RandomUtil {
|
||||
}
|
||||
|
||||
static randomShortId() {
|
||||
let shortIds = ['','','',''];
|
||||
for (var ii = 0; ii < 4; ii++) {
|
||||
for (var jj = 0; jj < this.randomInt(8); jj++){
|
||||
let shortIds = new Array(24).fill('');
|
||||
for (var ii = 0; ii < 24; ii++) {
|
||||
for (var jj = 0; jj < this.randomInt(16); jj++){
|
||||
let randomNum = this.randomInt(256);
|
||||
shortIds[ii] += ('0' + randomNum.toString(16)).slice(-2)
|
||||
}
|
||||
@@ -296,3 +296,190 @@ class ObjectUtil {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class Wireguard {
|
||||
static gf(init) {
|
||||
var r = new Float64Array(16);
|
||||
if (init) {
|
||||
for (var i = 0; i < init.length; ++i)
|
||||
r[i] = init[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static pack(o, n) {
|
||||
var b, m = this.gf(), t = this.gf();
|
||||
for (var i = 0; i < 16; ++i)
|
||||
t[i] = n[i];
|
||||
this.carry(t);
|
||||
this.carry(t);
|
||||
this.carry(t);
|
||||
for (var j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed;
|
||||
for (var i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
||||
m[i - 1] &= 0xffff;
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
||||
b = (m[15] >> 16) & 1;
|
||||
m[14] &= 0xffff;
|
||||
this.cswap(t, m, 1 - b);
|
||||
}
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff;
|
||||
o[2 * i + 1] = t[i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
static carry(o) {
|
||||
var c;
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
|
||||
o[i] &= 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
static cswap(p, q, b) {
|
||||
var t, c = ~(b - 1);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i]);
|
||||
p[i] ^= t;
|
||||
q[i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
static add(o, a, b) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] + b[i]) | 0;
|
||||
}
|
||||
|
||||
static subtract(o, a, b) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] - b[i]) | 0;
|
||||
}
|
||||
|
||||
static multmod(o, a, b) {
|
||||
var t = new Float64Array(31);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
for (var j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j];
|
||||
}
|
||||
for (var i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16];
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = t[i];
|
||||
this.carry(o);
|
||||
this.carry(o);
|
||||
}
|
||||
|
||||
static invert(o, i) {
|
||||
var c = this.gf();
|
||||
for (var a = 0; a < 16; ++a)
|
||||
c[a] = i[a];
|
||||
for (var a = 253; a >= 0; --a) {
|
||||
this.multmod(c, c, c);
|
||||
if (a !== 2 && a !== 4)
|
||||
this.multmod(c, c, i);
|
||||
}
|
||||
for (var a = 0; a < 16; ++a)
|
||||
o[a] = c[a];
|
||||
}
|
||||
|
||||
static clamp(z) {
|
||||
z[31] = (z[31] & 127) | 64;
|
||||
z[0] &= 248;
|
||||
}
|
||||
|
||||
static generatePublicKey(privateKey) {
|
||||
var r, z = new Uint8Array(32);
|
||||
var a = this.gf([1]),
|
||||
b = this.gf([9]),
|
||||
c = this.gf(),
|
||||
d = this.gf([1]),
|
||||
e = this.gf(),
|
||||
f = this.gf(),
|
||||
_121665 = this.gf([0xdb41, 1]),
|
||||
_9 = this.gf([9]);
|
||||
for (var i = 0; i < 32; ++i)
|
||||
z[i] = privateKey[i];
|
||||
this.clamp(z);
|
||||
for (var i = 254; i >= 0; --i) {
|
||||
r = (z[i >>> 3] >>> (i & 7)) & 1;
|
||||
this.cswap(a, b, r);
|
||||
this.cswap(c, d, r);
|
||||
this.add(e, a, c);
|
||||
this.subtract(a, a, c);
|
||||
this.add(c, b, d);
|
||||
this.subtract(b, b, d);
|
||||
this.multmod(d, e, e);
|
||||
this.multmod(f, a, a);
|
||||
this.multmod(a, c, a);
|
||||
this.multmod(c, b, e);
|
||||
this.add(e, a, c);
|
||||
this.subtract(a, a, c);
|
||||
this.multmod(b, a, a);
|
||||
this.subtract(c, d, f);
|
||||
this.multmod(a, c, _121665);
|
||||
this.add(a, a, d);
|
||||
this.multmod(c, c, a);
|
||||
this.multmod(a, d, f);
|
||||
this.multmod(d, b, _9);
|
||||
this.multmod(b, e, e);
|
||||
this.cswap(a, b, r);
|
||||
this.cswap(c, d, r);
|
||||
}
|
||||
this.invert(c, c);
|
||||
this.multmod(a, a, c);
|
||||
this.pack(z, a);
|
||||
return z;
|
||||
}
|
||||
|
||||
static generatePresharedKey() {
|
||||
var privateKey = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
static generatePrivateKey() {
|
||||
var privateKey = this.generatePresharedKey();
|
||||
this.clamp(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
static encodeBase64(dest, src) {
|
||||
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
|
||||
for (var i = 0; i < 4; ++i)
|
||||
dest[i] = input[i] + 65 +
|
||||
(((25 - input[i]) >> 8) & 6) -
|
||||
(((51 - input[i]) >> 8) & 75) -
|
||||
(((61 - input[i]) >> 8) & 15) +
|
||||
(((62 - input[i]) >> 8) & 3);
|
||||
}
|
||||
|
||||
static keyToBase64(key) {
|
||||
var i, base64 = new Uint8Array(44);
|
||||
for (i = 0; i < 32 / 3; ++i)
|
||||
this.encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
|
||||
this.encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
|
||||
base64[43] = 61;
|
||||
return String.fromCharCode.apply(null, base64);
|
||||
}
|
||||
|
||||
static keyFromBase64(encoded) {
|
||||
const binaryStr = atob(encoded);
|
||||
const bytes = new Uint8Array(binaryStr.length);
|
||||
for (let i = 0; i < binaryStr.length; i++) {
|
||||
bytes[i] = binaryStr.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static generateKeypair(secretKey='') {
|
||||
var privateKey = secretKey.length>0 ? this.keyFromBase64(secretKey) : this.generatePrivateKey();
|
||||
var publicKey = this.generatePublicKey(privateKey);
|
||||
return {
|
||||
publicKey: this.keyToBase64(publicKey),
|
||||
privateKey: secretKey.length>0 ? secretKey : this.keyToBase64(privateKey)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,81 +22,35 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/xui/API/inbounds")
|
||||
g.Use(a.checkLogin)
|
||||
|
||||
g.GET("/", a.inbounds)
|
||||
g.GET("/get/:id", a.inbound)
|
||||
g.GET("/getClientTraffics/:email", a.getClientTraffics)
|
||||
g.POST("/add", a.addInbound)
|
||||
g.POST("/del/:id", a.delInbound)
|
||||
g.POST("/update/:id", a.updateInbound)
|
||||
g.POST("/addClient", a.addInboundClient)
|
||||
g.POST("/:id/delClient/:clientId", a.delInboundClient)
|
||||
g.POST("/updateClient/:clientId", a.updateInboundClient)
|
||||
g.POST("/:id/resetClientTraffic/:email", a.resetClientTraffic)
|
||||
g.POST("/resetAllTraffics", a.resetAllTraffics)
|
||||
g.POST("/resetAllClientTraffics/:id", a.resetAllClientTraffics)
|
||||
g.POST("/delDepletedClients/:id", a.delDepletedClients)
|
||||
g.GET("/createbackup", a.createBackup)
|
||||
g.POST("/onlines", a.onlines)
|
||||
|
||||
a.inboundController = NewInboundController(g)
|
||||
}
|
||||
|
||||
func (a *APIController) inbounds(c *gin.Context) {
|
||||
a.inboundController.getInbounds(c)
|
||||
}
|
||||
inboundRoutes := []struct {
|
||||
Method string
|
||||
Path string
|
||||
Handler gin.HandlerFunc
|
||||
}{
|
||||
{"GET", "/createbackup", a.createBackup},
|
||||
{"GET", "/", a.inboundController.getInbounds},
|
||||
{"GET", "/get/:id", a.inboundController.getInbound},
|
||||
{"GET", "/getClientTraffics/:email", a.inboundController.getClientTraffics},
|
||||
{"POST", "/add", a.inboundController.addInbound},
|
||||
{"POST", "/del/:id", a.inboundController.delInbound},
|
||||
{"POST", "/update/:id", a.inboundController.updateInbound},
|
||||
{"POST", "/addClient", a.inboundController.addInboundClient},
|
||||
{"POST", "/:id/delClient/:clientId", a.inboundController.delInboundClient},
|
||||
{"POST", "/updateClient/:clientId", a.inboundController.updateInboundClient},
|
||||
{"POST", "/:id/resetClientTraffic/:email", a.inboundController.resetClientTraffic},
|
||||
{"POST", "/resetAllTraffics", a.inboundController.resetAllTraffics},
|
||||
{"POST", "/resetAllClientTraffics/:id", a.inboundController.resetAllClientTraffics},
|
||||
{"POST", "/delDepletedClients/:id", a.inboundController.delDepletedClients},
|
||||
{"POST", "/onlines", a.inboundController.onlines},
|
||||
}
|
||||
|
||||
func (a *APIController) inbound(c *gin.Context) {
|
||||
a.inboundController.getInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) getClientTraffics(c *gin.Context) {
|
||||
a.inboundController.getClientTraffics(c)
|
||||
}
|
||||
|
||||
func (a *APIController) addInbound(c *gin.Context) {
|
||||
a.inboundController.addInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) delInbound(c *gin.Context) {
|
||||
a.inboundController.delInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) updateInbound(c *gin.Context) {
|
||||
a.inboundController.updateInbound(c)
|
||||
}
|
||||
|
||||
func (a *APIController) addInboundClient(c *gin.Context) {
|
||||
a.inboundController.addInboundClient(c)
|
||||
}
|
||||
|
||||
func (a *APIController) delInboundClient(c *gin.Context) {
|
||||
a.inboundController.delInboundClient(c)
|
||||
}
|
||||
|
||||
func (a *APIController) updateInboundClient(c *gin.Context) {
|
||||
a.inboundController.updateInboundClient(c)
|
||||
}
|
||||
|
||||
func (a *APIController) resetClientTraffic(c *gin.Context) {
|
||||
a.inboundController.resetClientTraffic(c)
|
||||
}
|
||||
|
||||
func (a *APIController) resetAllTraffics(c *gin.Context) {
|
||||
a.inboundController.resetAllTraffics(c)
|
||||
}
|
||||
|
||||
func (a *APIController) resetAllClientTraffics(c *gin.Context) {
|
||||
a.inboundController.resetAllClientTraffics(c)
|
||||
}
|
||||
|
||||
func (a *APIController) delDepletedClients(c *gin.Context) {
|
||||
a.inboundController.delDepletedClients(c)
|
||||
for _, route := range inboundRoutes {
|
||||
g.Handle(route.Method, route.Path, route.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *APIController) createBackup(c *gin.Context) {
|
||||
a.Tgbot.SendBackupToAdmins()
|
||||
}
|
||||
|
||||
func (a *APIController) onlines(c *gin.Context) {
|
||||
a.inboundController.onlines(c)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/web/locale"
|
||||
"x-ui/web/session"
|
||||
@@ -9,13 +10,12 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BaseController struct {
|
||||
}
|
||||
type BaseController struct{}
|
||||
|
||||
func (a *BaseController) checkLogin(c *gin.Context) {
|
||||
if !session.IsLogin(c) {
|
||||
if isAjax(c) {
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||
pureJsonMsg(c, http.StatusUnauthorized, false, I18nWeb(c, "pages.login.loginAgain"))
|
||||
} else {
|
||||
c.Redirect(http.StatusTemporaryRedirect, c.GetString("base_path"))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"x-ui/database/model"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
@@ -63,6 +64,7 @@ func (a *InboundController) getInbound(c *gin.Context) {
|
||||
}
|
||||
jsonObj(c, inbound, nil)
|
||||
}
|
||||
|
||||
func (a *InboundController) getClientTraffics(c *gin.Context) {
|
||||
email := c.Param("email")
|
||||
clientTraffics, err := a.inboundService.GetClientTrafficByEmail(email)
|
||||
@@ -82,7 +84,11 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||
}
|
||||
user := session.GetLoginUser(c)
|
||||
inbound.UserId = user.Id
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
} else {
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
inbound, needRestart, err = a.inboundService.AddInbound(inbound)
|
||||
@@ -144,7 +150,7 @@ func (a *InboundController) addInboundClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client(s) added", nil)
|
||||
if err == nil && needRestart {
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
@@ -165,7 +171,7 @@ func (a *InboundController) delInboundClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client deleted", nil)
|
||||
if err == nil && needRestart {
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
@@ -188,7 +194,7 @@ func (a *InboundController) updateInboundClient(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "Client updated", nil)
|
||||
if err == nil && needRestart {
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
@@ -209,7 +215,7 @@ func (a *InboundController) resetClientTraffic(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
jsonMsg(c, "traffic reseted", nil)
|
||||
if err == nil && needRestart {
|
||||
if needRestart {
|
||||
a.xrayService.SetToNeedRestart()
|
||||
}
|
||||
}
|
||||
@@ -266,7 +272,11 @@ func (a *InboundController) importInbound(c *gin.Context) {
|
||||
user := session.GetLoginUser(c)
|
||||
inbound.Id = 0
|
||||
inbound.UserId = user.Id
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
} else {
|
||||
inbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||
}
|
||||
|
||||
for index := range inbound.ClientStats {
|
||||
inbound.ClientStats[index].Id = 0
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"x-ui/logger"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
@@ -47,15 +48,15 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
var form LoginForm
|
||||
err := c.ShouldBind(&form)
|
||||
if err != nil {
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.invalidFormData"))
|
||||
return
|
||||
}
|
||||
if form.Username == "" {
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyUsername"))
|
||||
return
|
||||
}
|
||||
if form.Password == "" {
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.emptyPassword"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ func (a *IndexController) login(c *gin.Context) {
|
||||
if user == nil {
|
||||
logger.Infof("wrong username or password: \"%s\" \"%s\"", form.Username, form.Password)
|
||||
a.tgbot.UserLoginNotify(form.Username, getRemoteIp(c), timeStr, 0)
|
||||
pureJsonMsg(c, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
pureJsonMsg(c, http.StatusOK, false, I18nWeb(c, "pages.login.toasts.wrongUsernameOrPassword"))
|
||||
return
|
||||
} else {
|
||||
logger.Infof("%s login success ,Ip Address: %s\n", form.Username, getRemoteIp(c))
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"x-ui/web/global"
|
||||
"x-ui/web/service"
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"x-ui/web/entity"
|
||||
"x-ui/web/service"
|
||||
"x-ui/web/session"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/logger"
|
||||
"x-ui/web/entity"
|
||||
@@ -48,18 +49,11 @@ func jsonMsgObj(c *gin.Context, msg string, obj interface{}, err error) {
|
||||
c.JSON(http.StatusOK, m)
|
||||
}
|
||||
|
||||
func pureJsonMsg(c *gin.Context, success bool, msg string) {
|
||||
if success {
|
||||
c.JSON(http.StatusOK, entity.Msg{
|
||||
Success: true,
|
||||
Msg: msg,
|
||||
})
|
||||
} else {
|
||||
c.JSON(http.StatusOK, entity.Msg{
|
||||
Success: false,
|
||||
Msg: msg,
|
||||
})
|
||||
}
|
||||
func pureJsonMsg(c *gin.Context, statusCode int, success bool, msg string) {
|
||||
c.JSON(statusCode, entity.Msg{
|
||||
Success: success,
|
||||
Msg: msg,
|
||||
})
|
||||
}
|
||||
|
||||
func html(c *gin.Context, name string, title string, data gin.H) {
|
||||
|
||||
@@ -26,6 +26,7 @@ func (a *XraySettingController) initRouter(g *gin.RouterGroup) {
|
||||
g.POST("/update", a.updateSetting)
|
||||
g.GET("/getXrayResult", a.getXrayResult)
|
||||
g.GET("/getDefaultJsonConfig", a.getDefaultXrayConfig)
|
||||
g.POST("/warp/:action", a.warp)
|
||||
}
|
||||
|
||||
func (a *XraySettingController) getXraySetting(c *gin.Context) {
|
||||
@@ -61,3 +62,24 @@ func (a *XraySettingController) getDefaultXrayConfig(c *gin.Context) {
|
||||
func (a *XraySettingController) getXrayResult(c *gin.Context) {
|
||||
jsonObj(c, a.XrayService.GetXrayResult(), nil)
|
||||
}
|
||||
|
||||
func (a *XraySettingController) warp(c *gin.Context) {
|
||||
action := c.Param("action")
|
||||
var resp string
|
||||
var err error
|
||||
switch action {
|
||||
case "data":
|
||||
resp, err = a.XraySettingService.GetWarp()
|
||||
case "config":
|
||||
resp, err = a.XraySettingService.GetWarpConfig()
|
||||
case "reg":
|
||||
skey := c.PostForm("privateKey")
|
||||
pkey := c.PostForm("publicKey")
|
||||
resp, err = a.XraySettingService.RegWarp(skey, pkey)
|
||||
case "license":
|
||||
license := c.PostForm("license")
|
||||
resp, err = a.XraySettingService.SetWarpLicence(license)
|
||||
}
|
||||
|
||||
jsonObj(c, resp, err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/util/common"
|
||||
)
|
||||
|
||||
@@ -46,6 +47,11 @@ type AllSetting struct {
|
||||
SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"`
|
||||
SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"`
|
||||
SubURI string `json:"subURI" form:"subURI"`
|
||||
SubJsonPath string `json:"subJsonPath" form:"subJsonPath"`
|
||||
SubJsonURI string `json:"subJsonURI" form:"subJsonURI"`
|
||||
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"`
|
||||
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"`
|
||||
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
|
||||
}
|
||||
|
||||
func (s *AllSetting) CheckValid() error {
|
||||
@@ -103,6 +109,13 @@ func (s *AllSetting) CheckValid() error {
|
||||
s.SubPath += "/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(s.SubJsonPath, "/") {
|
||||
s.SubJsonPath = "/" + s.SubJsonPath
|
||||
}
|
||||
if !strings.HasSuffix(s.SubJsonPath, "/") {
|
||||
s.SubJsonPath += "/"
|
||||
}
|
||||
|
||||
_, err := time.LoadLocation(s.TimeLocation)
|
||||
if err != nil {
|
||||
return common.NewError("time location not exist:", s.TimeLocation)
|
||||
|
||||
@@ -7,8 +7,10 @@ import (
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var webServer WebServer
|
||||
var subServer SubServer
|
||||
var (
|
||||
webServer WebServer
|
||||
subServer SubServer
|
||||
)
|
||||
|
||||
type WebServer interface {
|
||||
GetCron() *cron.Cron
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/ant-design-vue@1.7.8/antd.min.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/element-ui@2.15.0/theme-chalk/display.css">
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/css/custom.css?{{ .cur_ver }}">
|
||||
<link rel=”icon” type=”image/x-icon” href="{{ .base_path }}assets/favicon.ico">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="{{ .base_path }}assets/favicon.ico">
|
||||
<style>
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
@@ -30,4 +28,5 @@
|
||||
</style>
|
||||
<title>{{ .host }}-{{ i18n .title}}</title>
|
||||
</head>
|
||||
<div id="message"></div>
|
||||
{{end}}
|
||||
@@ -1,6 +1,7 @@
|
||||
{{define "promptModal"}}
|
||||
<a-modal id="prompt-modal" v-model="promptModal.visible" :title="promptModal.title"
|
||||
:closable="true" @ok="promptModal.ok" :mask-closable="false"
|
||||
:confirm-loading="promptModal.confirmLoading"
|
||||
:ok-text="promptModal.okText" cancel-text='{{ i18n "cancel" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-input id="prompt-modal-input" :type="promptModal.type"
|
||||
v-model="promptModal.value"
|
||||
@@ -17,6 +18,7 @@
|
||||
value: '',
|
||||
okText: '{{ i18n "sure"}}',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
keyEnter(e) {
|
||||
if (this.type !== 'textarea') {
|
||||
e.preventDefault();
|
||||
@@ -30,7 +32,6 @@
|
||||
}
|
||||
},
|
||||
ok() {
|
||||
promptModal.close();
|
||||
promptModal.confirm(promptModal.value);
|
||||
},
|
||||
confirm() {},
|
||||
@@ -53,7 +54,10 @@
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
}
|
||||
},
|
||||
loading(loading=true) {
|
||||
this.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
|
||||
const promptModalApp = new Vue({
|
||||
|
||||
@@ -5,13 +5,23 @@
|
||||
width="300px" :class="themeSwitcher.currentTheme">
|
||||
<a-tag color="green" style="margin-bottom: 10px;display: block;text-align: center;" >{{ i18n "pages.inbounds.clickOnQRcode" }}</a-tag>
|
||||
<template v-if="app.subSettings.enable && qrModal.subId">
|
||||
<a-divider>Subscription</a-divider>
|
||||
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))" id="qrCode-sub" style="width: 100%; height: 100%;"></canvas>
|
||||
<a-divider>{{ i18n "pages.settings.subSettings"}}</a-divider>
|
||||
<canvas @click="copyToClipboard('qrCode-sub',genSubLink(qrModal.client.subId))"
|
||||
id="qrCode-sub"
|
||||
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
|
||||
</canvas>
|
||||
<a-divider>{{ i18n "pages.settings.subSettings"}} Json</a-divider>
|
||||
<canvas @click="copyToClipboard('qrCode-subJson',genSubJsonLink(qrModal.client.subId))"
|
||||
id="qrCode-subJson"
|
||||
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;">
|
||||
</canvas>
|
||||
</template>
|
||||
<a-divider>{{ i18n "pages.inbounds.client" }}</a-divider>
|
||||
<template v-for="(row, index) in qrModal.qrcodes">
|
||||
<a-tag color="blue" style="margin: 10px 0; 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>
|
||||
<canvas @click="copyToClipboard('qrCode-'+index, row.link)"
|
||||
:id="'qrCode-'+index"
|
||||
style="width: 100%; height: 100%; display: flex; border-radius: 1rem;"></canvas>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
@@ -32,12 +42,21 @@
|
||||
this.client = client;
|
||||
this.subId = '';
|
||||
this.qrcodes = [];
|
||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
||||
this.qrcodes.push({
|
||||
remark: l.remark,
|
||||
link: l.link
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD){
|
||||
this.inbound.genInboundLinks(dbInbound.remark).split('\r\n').forEach((l,index) =>{
|
||||
this.qrcodes.push({
|
||||
remark: "Peer " + (index+1),
|
||||
link: l
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, client).forEach(l => {
|
||||
this.qrcodes.push({
|
||||
remark: l.remark,
|
||||
link: l.link
|
||||
});
|
||||
});
|
||||
}
|
||||
this.visible = true;
|
||||
},
|
||||
close: function () {
|
||||
@@ -70,12 +89,16 @@
|
||||
},
|
||||
genSubLink(subID) {
|
||||
return app.subSettings.subURI+subID+'?name='+subID;
|
||||
},
|
||||
genSubJsonLink(subID) {
|
||||
return app.subSettings.subJsonURI+subID;
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (qrModal.client && qrModal.client.subId) {
|
||||
qrModal.subId = qrModal.client.subId;
|
||||
this.setQrCode("qrCode-sub", this.genSubLink(qrModal.subId));
|
||||
this.setQrCode("qrCode-subJson", this.genSubJsonLink(qrModal.subId));
|
||||
}
|
||||
qrModal.qrcodes.forEach((element, index) => {
|
||||
this.setQrCode("qrCode-" + index, element.link);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
{{define "textModal"}}
|
||||
<a-modal id="text-modal" v-model="txtModal.visible" :title="txtModal.title"
|
||||
:closable="true" ok-text='{{ i18n "copy" }}' cancel-text='{{ i18n "close" }}'
|
||||
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}" :class="themeSwitcher.currentTheme">
|
||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
|
||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||
:download="txtModal.fileName">
|
||||
{{ i18n "download" }} [[ txtModal.fileName ]]
|
||||
</a-button>
|
||||
:closable="true"
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<template slot="footer">
|
||||
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" icon="download"
|
||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
|
||||
:download="txtModal.fileName">[[ txtModal.fileName ]]
|
||||
</a-button>
|
||||
<a-button type="primary" id="copy-btn">{{ i18n "copy" }}</a-button>
|
||||
</template>
|
||||
<a-input type="textarea" v-model="txtModal.content"
|
||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||
:autosize="{ minRows: 10, maxRows: 20}"></a-input>
|
||||
</a-modal>
|
||||
|
||||
<script>
|
||||
@@ -27,7 +29,7 @@
|
||||
this.visible = true;
|
||||
textModalApp.$nextTick(() => {
|
||||
if (this.clipboard === null) {
|
||||
this.clipboard = new ClipboardJS('#txt-modal-ok-btn', {
|
||||
this.clipboard = new ClipboardJS('#copy-btn', {
|
||||
text: () => this.content,
|
||||
});
|
||||
this.clipboard.on('success', () => {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
.title {
|
||||
font-size: 32px;
|
||||
@@ -36,7 +37,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
#login {
|
||||
animation: charge 0.5s both;
|
||||
background-color: #fff;
|
||||
border-radius: 2rem;
|
||||
padding: 3rem;
|
||||
@@ -45,24 +45,6 @@
|
||||
#login:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
}
|
||||
@keyframes charge {
|
||||
from {
|
||||
transform: translateY(5rem);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes wave {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.wave {
|
||||
opacity: 0.6;
|
||||
position: absolute;
|
||||
@@ -70,20 +52,20 @@
|
||||
left: 50%;
|
||||
width: 6000px;
|
||||
height: 6000px;
|
||||
background-color: rgba(14, 73, 181, 0.08);
|
||||
background-color: rgba(0, 135, 113, 0.08);
|
||||
margin-left: -3000px;
|
||||
transform-origin: 50% 48%;
|
||||
border-radius: 46%;
|
||||
animation: wave 72s infinite linear;
|
||||
pointer-events: none;
|
||||
rotate: 125deg;
|
||||
}
|
||||
.wave2 {
|
||||
animation: wave 88s infinite linear;
|
||||
opacity: 0.3;
|
||||
opacity: 0.4;
|
||||
rotate: 70deg;
|
||||
}
|
||||
.wave3 {
|
||||
animation: wave 80s infinite linear;
|
||||
opacity: 0.1;
|
||||
opacity: 0.2;
|
||||
rotate: 90deg;
|
||||
}
|
||||
.under {
|
||||
background-color: #dce9f5;
|
||||
@@ -100,120 +82,8 @@
|
||||
.dark h1 {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
.ant-btn-primary-login {
|
||||
color: #0e49b5;
|
||||
background-color: #edf4fa;
|
||||
border-color: #a9c5e7;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.12);
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
}
|
||||
.ant-btn-primary-login:focus,
|
||||
.ant-btn-primary-login:hover {
|
||||
color: #fff;
|
||||
background-color: #0c3f9d;
|
||||
border-color: #0e49b5;
|
||||
background-image: linear-gradient(
|
||||
270deg,
|
||||
rgba(123, 199, 77, 0) 30%,
|
||||
#2f67c2,
|
||||
rgba(123, 199, 77, 0) 100%
|
||||
);
|
||||
background-repeat: no-repeat;
|
||||
animation: ma-bg-move ease-in-out 5s infinite;
|
||||
background-position-x: -500px;
|
||||
width: 95%;
|
||||
animation-delay: -0.5s;
|
||||
box-shadow: 0 2px 0 rgba(0, 0, 0, 0.045);
|
||||
}
|
||||
.ant-btn-primary-login.active,
|
||||
.ant-btn-primary-login:active {
|
||||
color: #fff;
|
||||
background-color: #04308f;
|
||||
border-color: #04308f;
|
||||
}
|
||||
@keyframes ma-bg-move {
|
||||
0% {
|
||||
background-position: -500px 0;
|
||||
}
|
||||
50% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
}
|
||||
.wave-btn-bg {
|
||||
position: relative;
|
||||
border-radius: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
.dark .wave-btn-bg {
|
||||
color: #fff;
|
||||
position: relative;
|
||||
background-color: #0e49b5;
|
||||
border: 2px double transparent;
|
||||
background-origin: border-box;
|
||||
background-clip: padding-box, border-box;
|
||||
background-size: 300%;
|
||||
animation: wave-btn-tara 4s ease infinite;
|
||||
transition: all 0.5s ease;
|
||||
width: 100%;
|
||||
}
|
||||
.dark .wave-btn-bg-cl {
|
||||
background-image: linear-gradient(rgba(13, 14, 33, 0), rgba(13, 14, 33, 0)),
|
||||
radial-gradient(circle at left top, #0e49b5, #387eff, #0e49b5) !important;
|
||||
border-radius: 3em;
|
||||
}
|
||||
.dark .wave-btn-bg-cl:hover {
|
||||
width: 95%;
|
||||
}
|
||||
.dark .wave-btn-bg-cl:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
z-index: -1;
|
||||
background: inherit;
|
||||
background-size: inherit;
|
||||
border-radius: 4em;
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
}
|
||||
.dark .wave-btn-bg-cl:hover::before {
|
||||
opacity: 1;
|
||||
filter: blur(20px);
|
||||
animation: wave-btn-tara 8s linear infinite;
|
||||
}
|
||||
@keyframes wave-btn-tara {
|
||||
to {
|
||||
background-position: 300%;
|
||||
}
|
||||
}
|
||||
.dark .ant-btn-primary-login {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-image: linear-gradient(
|
||||
rgba(13, 14, 33, 0.45),
|
||||
rgba(13, 14, 33, 0.35)
|
||||
);
|
||||
border-radius: 2rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
height: 46px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
padding: 0 15px;
|
||||
width: 100%;
|
||||
animation: none;
|
||||
background-position-x: 0;
|
||||
box-shadow: none;
|
||||
.ant-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
@@ -227,7 +97,7 @@
|
||||
<a-col :xs="22" :sm="20" :md="14" :lg="10" :xl="8" :xxl="6" id="login" style="margin: 3rem 0;">
|
||||
<a-row type="flex" justify="center">
|
||||
<a-col>
|
||||
<h1 class="title">{{ i18n "pages.login.title" }}</h1>
|
||||
<h2 class="title" style="text-align: center;">{{ i18n "pages.login.title" }}</h2>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row type="flex" justify="center">
|
||||
@@ -246,12 +116,10 @@
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-row justify="center" class="centered">
|
||||
<div class="wave-btn-bg wave-btn-bg-cl">
|
||||
<a-button class="ant-btn-primary-login" type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
||||
:style="loading ? { width: '50px' } : { display: 'inline-block' }">
|
||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="primary" :loading="loading" @click="login" :icon="loading ? 'poweroff' : undefined"
|
||||
:style="{ fontWeight: 'bold', width: loading ? '50px' : '100%', display: 'inline-block' }">
|
||||
[[ loading ? '' : '{{ i18n "login" }}' ]]
|
||||
</a-button>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
|
||||
@@ -2,170 +2,105 @@
|
||||
<a-modal id="client-bulk-modal" v-model="clientsBulkModal.visible" :title="clientsBulkModal.title" @ok="clientsBulkModal.ok"
|
||||
:confirm-loading="clientsBulkModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-text="clientsBulkModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.method" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="0">Random</a-select-option>
|
||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>1">
|
||||
<td>{{ i18n "pages.client.first" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>1">
|
||||
<td>{{ i18n "pages.client.last" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>0">
|
||||
<td>{{ i18n "pages.client.prefix" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="clientsBulkModal.emailPrefix" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod>2">
|
||||
<td>{{ i18n "pages.client.postfix" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="clientsBulkModal.emailPostfix" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.emailMethod < 2">
|
||||
<td>{{ i18n "pages.client.clientCount" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||
<td>Flow</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="clientsBulkModal.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="app.subSettings.enable">
|
||||
<td>Subscription
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||
</template>
|
||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="clientsBulkModal.subId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="app.tgBotEnable">
|
||||
<td>Telegram ID
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="clientsBulkModal.tgId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="clientsBulkModal.expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="clientsBulkModal.expiryTime != 0">
|
||||
<td>
|
||||
<span>{{ i18n "pages.client.renew" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.client.method" }}'>
|
||||
<a-select v-model="clientsBulkModal.emailMethod" buttonStyle="solid" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="0">Random</a-select-option>
|
||||
<a-select-option :value="1">Random+Prefix</a-select-option>
|
||||
<a-select-option :value="2">Random+Prefix+Num</a-select-option>
|
||||
<a-select-option :value="3">Random+Prefix+Num+Postfix</a-select-option>
|
||||
<a-select-option :value="4">Prefix+Num+Postfix</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.first" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||
<a-input-number v-model="clientsBulkModal.firstNum" :min="1"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.last" }}' v-if="clientsBulkModal.emailMethod>1">
|
||||
<a-input-number v-model="clientsBulkModal.lastNum" :min="clientsBulkModal.firstNum"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.prefix" }}' v-if="clientsBulkModal.emailMethod>0">
|
||||
<a-input v-model="clientsBulkModal.emailPrefix"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.postfix" }}' v-if="clientsBulkModal.emailMethod>2">
|
||||
<a-input v-model="clientsBulkModal.emailPostfix"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.clientCount" }}' v-if="clientsBulkModal.emailMethod < 2">
|
||||
<a-input-number v-model="clientsBulkModal.quantity" :min="1" :max="100"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Flow' v-if="clientsBulkModal.inbound.canEnableTlsFlow()">
|
||||
<a-select v-model="clientsBulkModal.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.subSettings.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||
</template>
|
||||
Subscription
|
||||
<a-icon @click="clientsBulkModal.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="clientsBulkModal.subId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="app.tgBotEnable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||
</template>
|
||||
Telegram ID
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="clientsBulkModal.tgId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.totalFlow" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="clientsBulkModal.totalGB" :min="0"></a-input-number> GB
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||
<a-switch v-model="clientsBulkModal.delayedStart" @click="clientsBulkModal.expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.expireDays" }}' v-if="clientsBulkModal.delayedStart">
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.expireDate" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="clientsBulkModal.expiryTime"></a-date-picker>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="clientsBulkModal.expiryTime != 0">
|
||||
<template slot="label">
|
||||
<span>{{ i18n "pages.client.renew" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model.number="clientsBulkModal.reset" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
@@ -220,7 +155,7 @@
|
||||
}
|
||||
ObjectUtil.execute(clientsBulkModal.confirm, clients, clientsBulkModal.dbInbound.id);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||
show({ title='', okText='{{ i18n "confirm" }}', dbInbound=null, confirm=(inbound, dbInbound)=>{} }) {
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
@@ -254,7 +189,7 @@
|
||||
clientsBulkModal.visible = false;
|
||||
clientsBulkModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
loading(loading=true) {
|
||||
clientsBulkModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
@@ -276,4 +211,4 @@
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
ObjectUtil.execute(clientModal.confirm, clientModalApp.client, clientModal.dbInbound.id);
|
||||
}
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||
show({ title='', okText='{{ i18n "confirm" }}', index=null, dbInbound=null, confirm=()=>{}, isEdit=false }) {
|
||||
this.visible = true;
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
@@ -70,7 +70,7 @@
|
||||
clientModal.visible = false;
|
||||
clientModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
loading(loading=true) {
|
||||
clientModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{{define "menuItems"}}
|
||||
<a-menu-item key="{{ .base_path }}xui/">
|
||||
<a-icon type="dashboard"></a-icon>
|
||||
<span>{{ i18n "menu.dashboard"}}</span>
|
||||
<span><strong>{{ i18n "menu.dashboard"}}</strong></span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/inbounds">
|
||||
<a-icon type="user"></a-icon>
|
||||
<span>{{ i18n "menu.inbounds"}}</span>
|
||||
<span><strong>{{ i18n "menu.inbounds"}}</strong></span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/settings">
|
||||
<a-icon type="setting"></a-icon>
|
||||
<span>{{ i18n "menu.settings"}}</span>
|
||||
<span><strong>{{ i18n "menu.settings"}}</strong></span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}xui/xray">
|
||||
<a-icon type="tool"></a-icon>
|
||||
<span>{{ i18n "menu.xray"}}</span>
|
||||
<span><strong>{{ i18n "menu.xray"}}</strong></span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="{{ .base_path }}logout">
|
||||
<a-icon type="logout"></a-icon>
|
||||
<span>{{ i18n "menu.logout"}}</span>
|
||||
<span><strong>{{ i18n "menu.logout"}}</strong></span>
|
||||
</a-menu-item>
|
||||
{{end}}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<a-input :value="value" @input="$emit('input', $event.target.value)" :placeholder="placeholder"></a-input>
|
||||
</template>
|
||||
<template v-else-if="type === 'number'">
|
||||
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" :step="step" style="width: 100%;"></a-input-number>
|
||||
<a-input-number :value="value" @change="value => $emit('input', value)" :min="min" :max="max" :step="step" style="width: 100%;"></a-input-number>
|
||||
</template>
|
||||
<template v-else-if="type === 'switch'">
|
||||
<a-switch :checked="value" @change="value => $emit('input', value)"></a-switch>
|
||||
@@ -28,7 +28,7 @@
|
||||
{{define "component/setting"}}
|
||||
<script>
|
||||
Vue.component('setting-list-item', {
|
||||
props: ["type", "title", "desc", "value", "min", "step", "placeholder"],
|
||||
props: ["type", "title", "desc", "value", "min", "max", "step", "placeholder"],
|
||||
template: `{{template "component/settingListItem"}}`,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
toggleTheme() {
|
||||
this.isDarkTheme = !this.isDarkTheme;
|
||||
localStorage.setItem('dark-mode', this.isDarkTheme);
|
||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -29,6 +30,10 @@
|
||||
props: [],
|
||||
template: `{{template "component/themeSwitchTemplate"}}`,
|
||||
data: () => ({ themeSwitcher }),
|
||||
mounted() {
|
||||
this.$message.config({getContainer: () => document.getElementById('message')});
|
||||
document.getElementById('message').className = themeSwitcher.currentTheme;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
86
web/html/xui/dns_modal.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{{define "dnsModal"}}
|
||||
<a-modal id="dns-modal" v-model="dnsModal.visible" :title="dnsModal.title" @ok="dnsModal.ok"
|
||||
:closable="true" :mask-closable="false"
|
||||
:ok-text="dnsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.address" }}'>
|
||||
<a-input v-model.trim="dnsModal.dnsServer.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.domains" }}'>
|
||||
<a-button size="small" type="primary" @click="dnsModal.dnsServer.domains.push('')">+</a-button>
|
||||
<template v-for="(domain, index) in dnsModal.dnsServer.domains">
|
||||
<a-input v-model.trim="dnsModal.dnsServer.domains[index]">
|
||||
<a-button size="small" slot="addonAfter" @click="dnsModal.dnsServer.domains.splice(index,1)">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.dns.strategy" }}' v-if="isAdvanced">
|
||||
<a-select
|
||||
v-model="dnsModal.dnsServer.queryStrategy"
|
||||
style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="l" :label="l" v-for="l in ['UseIP', 'UseIPv4', 'UseIPv6']">
|
||||
[[ l ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
const dnsModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
dnsServer: {
|
||||
address: "localhost",
|
||||
domains: [],
|
||||
queryStrategy: 'UseIP',
|
||||
},
|
||||
ok() {
|
||||
domains = dnsModal.dnsServer.domains.filter(d => d.length>0);
|
||||
dnsModal.dnsServer.domains = domains;
|
||||
newDnsServer = domains.length > 0 ? dnsModal.dnsServer : dnsModal.dnsServer.address;
|
||||
ObjectUtil.execute(dnsModal.confirm, newDnsServer);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "confirm" }}', dnsServer, confirm=(dnsServer)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if(isEdit) {
|
||||
if (typeof dnsServer == 'object'){
|
||||
this.dnsServer = dnsServer;
|
||||
} else {
|
||||
this.dnsServer.address = dnsServer?? '';
|
||||
}
|
||||
} else {
|
||||
this.dnsServer = {
|
||||
address: "localhost",
|
||||
domains: [],
|
||||
queryStrategy: 'UseIP',
|
||||
}
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
},
|
||||
close() {
|
||||
dnsModal.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#dns-modal',
|
||||
data: {
|
||||
dnsModal: dnsModal,
|
||||
},
|
||||
computed: {
|
||||
isAdvanced: {
|
||||
get: function () { return dnsModal.dnsServer.domains.length>0 }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
57
web/html/xui/fakedns_modal.html
Normal file
@@ -0,0 +1,57 @@
|
||||
{{define "fakednsModal"}}
|
||||
<a-modal id="fakedns-modal" v-model="fakednsModal.visible" :title="fakednsModal.title" @ok="fakednsModal.ok"
|
||||
:closable="true" :mask-closable="false"
|
||||
:ok-text="fakednsModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.fakedns.ipPool" }}'>
|
||||
<a-input v-model.trim="fakednsModal.fakeDns.ipPool"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.fakedns.poolSize" }}'>
|
||||
<a-input type="number" min="1" v-model.trim="fakednsModal.fakeDns.poolSize"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
const fakednsModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
fakeDns: {
|
||||
ipPool: "198.18.0.0/16",
|
||||
poolSize: 65535,
|
||||
},
|
||||
ok() {
|
||||
ObjectUtil.execute(fakednsModal.confirm, fakednsModal.fakeDns);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "confirm" }}', fakeDns, confirm=(fakeDns)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if(isEdit) {
|
||||
this.fakeDns = fakeDns;
|
||||
} else {
|
||||
this.fakeDns = {
|
||||
ipPool: "198.18.0.0/16",
|
||||
poolSize: 65535,
|
||||
}
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
},
|
||||
close() {
|
||||
fakednsModal.visible = false;
|
||||
},
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#fakedns-modal',
|
||||
data: {
|
||||
fakednsModal: fakednsModal,
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -1,170 +1,125 @@
|
||||
{{define "form/client"}}
|
||||
<a-form layout="inline" v-if="client">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.enable" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.email" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.email" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||
<td>password
|
||||
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.password" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||
<td>ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon></td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.id" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.subSettings.enable">
|
||||
<td>Subscription
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||
</template>
|
||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.subId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.email && app.tgBotEnable">
|
||||
<td>Telegram ID
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="client.tgId" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.canEnableTlsFlow()">
|
||||
<td>Flow</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="client.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="isEdit && clientStats">
|
||||
<td>{{ i18n "usage" }}</td>
|
||||
<td>
|
||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||
[[ sizeFormat(clientStats.up) ]] /
|
||||
[[ sizeFormat(clientStats.down) ]]
|
||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||
</a-tag>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.client.delayedStart" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="delayedStart">
|
||||
<td>{{ i18n "pages.client.expireDays" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="client._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="client.expiryTime != 0">
|
||||
<td>
|
||||
<span>{{ i18n "pages.client.renew" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.client.renewDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form layout="horizontal" v-if="client" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }}'>
|
||||
<a-switch v-model="client.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.emailDesc" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.email" }}
|
||||
<a-icon type="sync" @click="client.email = RandomUtil.randomLowerAndNum(9)"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="client.email"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "password" }}
|
||||
<a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @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-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="client.password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.protocol === Protocols.VMESS || inbound.protocol === Protocols.VLESS">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
ID <a-icon @click="client.id = RandomUtil.randomUUID()" type="sync"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="client.id"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email && app.subSettings.enable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.subscriptionDesc" }}</span>
|
||||
</template>
|
||||
Subscription
|
||||
<a-icon @click="client.subId = RandomUtil.randomLowerAndNum(16)" type="sync"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="client.subId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.email && app.tgBotEnable">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.telegramDesc" }}</span>
|
||||
</template>
|
||||
Telegram ID
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="client.tgId"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.canEnableTlsFlow()" label='Flow'>
|
||||
<a-select v-model="client.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.totalFlow" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="client._totalGB" :min="0"></a-input-number> GB
|
||||
</a-form-item>
|
||||
<a-form-item v-if="isEdit && clientStats" label='{{ i18n "usage" }}'>
|
||||
<a-tag :color="clientUsageColor(clientStats, app.trafficDiff)">
|
||||
[[ sizeFormat(clientStats.up) ]] /
|
||||
[[ sizeFormat(clientStats.down) ]]
|
||||
([[ sizeFormat(clientStats.up + clientStats.down) ]])
|
||||
</a-tag>
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.resetTraffic" }}</template>
|
||||
<a-icon type="retweet" @click="resetClientTraffic(client.email,clientStats.inboundId,$event.target)" v-if="client.email.length > 0"></a-icon>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.client.delayedStart" }}'>
|
||||
<a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="delayedStart" label='{{ i18n "pages.client.expireDays" }}'>
|
||||
<a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item v-else>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</template>
|
||||
{{ i18n "pages.inbounds.expireDate" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="client._expiryTime"></a-date-picker>
|
||||
<a-tag color="red" v-if="isEdit && isExpiry">Expired</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="client.expiryTime != 0">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">{{ i18n "pages.client.renewDesc" }}</template>
|
||||
{{ i18n "pages.client.renew" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model.number="client.reset" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,91 +1,63 @@
|
||||
{{define "form/inbound"}}
|
||||
<!-- base -->
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "enable" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "remark" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="dbInbound.remark" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "protocol" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.protocol" style="width: 250px;" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "monitor" }}
|
||||
<a-tooltip>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "enable" }}'>
|
||||
<a-switch v-model="dbInbound.enable"></a-switch>
|
||||
</a-form-item>
|
||||
<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-select v-model="inbound.protocol" :disabled="isEdit" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="p in Protocols" :key="p" :value="p">[[ p ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.monitorDesc" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.listen" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.totalFlow" }}</span>(GB)
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ i18n "pages.inbounds.expireDate" }}</span>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="dbInbound._expiryTime" style="width: 250px;"></a-date-picker>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{ i18n "monitor" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.listen"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number v-model.number="inbound.port"></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.meansNoLimit" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.totalFlow" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input-number v-model="dbInbound.totalGB" :min="0"></a-input-number> GB
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.inbounds.expireDate" }}
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="dbInbound._expiryTime"></a-date-picker>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- vmess settings -->
|
||||
@@ -123,6 +95,11 @@
|
||||
{{template "form/http"}}
|
||||
</template>
|
||||
|
||||
<!-- wireguard -->
|
||||
<template v-if="inbound.protocol === Protocols.WIREGUARD">
|
||||
{{template "form/wireguard"}}
|
||||
</template>
|
||||
|
||||
<!-- stream settings -->
|
||||
<template v-if="inbound.canEnableStream()">
|
||||
{{template "form/streamSettings"}}
|
||||
@@ -135,7 +112,7 @@
|
||||
</template>
|
||||
|
||||
<!-- sniffing -->
|
||||
<template v-if="inbound.canSniffing()">
|
||||
<template>
|
||||
{{template "form/sniffing"}}
|
||||
</template>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -2,535 +2,455 @@
|
||||
<!-- base -->
|
||||
<a-tabs :active-key="outModal.activeKey" style="padding: 0; background-color: transparent;" @change="(activeKey) => {outModal.toggleJson(activeKey == '2'); }">
|
||||
<a-tab-pane key="1" tab="Form">
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "protocol" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
|
||||
<td>
|
||||
<a-form-item has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
|
||||
<a-input v-model.trim="outbound.tag" style="width: 250px" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "protocol" }}'>
|
||||
<a-select v-model="outbound.protocol" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x,y in Protocols" :value="x">[[ y ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}' has-feedback :validate-status="outModal.duplicateTag? 'warning' : 'success'">
|
||||
<a-input v-model.trim="outbound.tag" @change="outModal.check()" placeholder='{{ i18n "pages.xray.outbound.tagDesc" }}'></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.sendThrough" }}'>
|
||||
<a-input v-model="outbound.sendThrough"></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<!-- freedom settings-->
|
||||
<template v-if="outbound.protocol === Protocols.Freedom">
|
||||
<tr>
|
||||
<td>Strategy</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
v-model="outbound.settings.domainStrategy"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in outboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fragment</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch
|
||||
:checked="Object.keys(outbound.settings.fragment).length >0"
|
||||
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='Strategy'>
|
||||
<a-select
|
||||
v-model="outbound.settings.domainStrategy"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in OutboundDomainStrategies" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Fragment'>
|
||||
<a-switch
|
||||
:checked="Object.keys(outbound.settings.fragment).length >0"
|
||||
@change="checked => outbound.settings.fragment = checked ? new Outbound.FreedomSettings.Fragment() : {}">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="Object.keys(outbound.settings.fragment).length >0">
|
||||
<tr>
|
||||
<td>Packets</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
v-model="outbound.settings.fragment.packets"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Length</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.settings.fragment.length" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Interval</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.settings.fragment.interval" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='Packets'>
|
||||
<a-select
|
||||
v-model="outbound.settings.fragment.packets"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['1-3','tlshello']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Length'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.length"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Interval'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- blackhole settings -->
|
||||
<template v-if="outbound.protocol === Protocols.Blackhole">
|
||||
<tr>
|
||||
<td>Response Type</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
v-model="outbound.settings.type"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='Response Type'>
|
||||
<a-select
|
||||
v-model="outbound.settings.type"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['', 'none','http']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- dns settings -->
|
||||
<template v-if="outbound.protocol === Protocols.DNS">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
v-model="outbound.settings.network"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select
|
||||
v-model="outbound.settings.network"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['udp','tcp']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- wireguard settings -->
|
||||
<template v-if="outbound.protocol === Protocols.Wireguard">
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.xray.outbound.address" }} <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="outbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.xray.wireguard.secretKey" }}
|
||||
<a-icon type="sync"
|
||||
@click="[outbound.settings.pubKey, outbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
|
||||
</a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="outbound.settings.secretKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
|
||||
<a-input disabled v-model="outbound.settings.pubKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.wireguard.domainStrategy" }}'>
|
||||
<a-select v-model="outbound.settings.domainStrategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="wds in ['', ...WireguardDomainStrategy]" :value="wds">[[ wds ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="outbound.settings.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Workers'>
|
||||
<a-input-number min="0" v-model.number="outbound.settings.workers"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Mode'>
|
||||
<a-switch v-model="outbound.settings.kernelMode"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
Reserved <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model="outbound.settings.reserved"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Peers">
|
||||
<a-button type="primary" size="small" @click="outbound.settings.addPeer()">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form v-for="(peer, index) in outbound.settings.peers" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;">
|
||||
Peer [[ index + 1 ]]
|
||||
<a-icon v-if="outbound.settings.peers.length>1" type="delete" @click="() => outbound.settings.delPeer(index)"
|
||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||
</a-divider>
|
||||
|
||||
<a-form-item label='{{ i18n "pages.xray.wireguard.endpoint" }}'>
|
||||
<a-input v-model.trim="peer.endpoint"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
|
||||
<a-input v-model.trim="peer.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.wireguard.psk" }}'>
|
||||
<a-input v-model.trim="peer.psk"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
|
||||
</template>
|
||||
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
|
||||
<a-input v-model.trim="peer.allowedIPs[index]">
|
||||
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label='Keep Alive'>
|
||||
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- Address + Port -->
|
||||
<template v-if="outbound.hasAddressPort()">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.address" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.settings.address" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.port" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.address" }}'>
|
||||
<a-input v-model.trim="outbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number v-model.number="outbound.settings.port" :min="1" :max="65532"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Vnext (vless/vmess) settings -->
|
||||
<template v-if="[Protocols.VMess, Protocols.VLESS].includes(outbound.protocol)">
|
||||
<tr>
|
||||
<td>ID</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.settings.id" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='ID'>
|
||||
<a-input v-model.trim="outbound.settings.id"></a-input>
|
||||
</a-form-item>
|
||||
<!-- vless settings -->
|
||||
<template v-if="outbound.canEnableTlsFlow()">
|
||||
<tr>
|
||||
<td>Flow</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.settings.flow" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='Flow'>
|
||||
<a-select v-model="outbound.settings.flow" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="" selected>{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option v-for="key in TLS_FLOW_CONTROL" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- Servers (trojan/shadowsocks/socks/http) settings -->
|
||||
<template v-if="outbound.hasServers()">
|
||||
<tr v-if="outbound.hasUsername()">
|
||||
<td>{{ i18n "username" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.settings.user" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.settings.password" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- http / socks -->
|
||||
<template v-if="outbound.hasUsername()">
|
||||
<a-form-item label='{{ i18n "username" }}'>
|
||||
<a-input v-model.trim="outbound.settings.user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="outbound.settings.pass"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- trojan/shadowsocks -->
|
||||
<template v-if="[Protocols.Trojan, Protocols.Shadowsocks].includes(outbound.protocol)">
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="outbound.settings.password"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<!-- shadowsocks -->
|
||||
<template v-if="outbound.protocol === Protocols.Shadowsocks">
|
||||
<tr>
|
||||
<td>{{ i18n "encryption" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.settings.method" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>UDP over TCP</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<a-form-item label='{{ i18n "encryption" }}'>
|
||||
<a-select v-model="outbound.settings.method" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="(method, method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='UDP over TCP'>
|
||||
<a-switch v-model="outbound.settings.uot"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- stream settings -->
|
||||
<template v-if="outbound.canEnableStream()">
|
||||
<tr>
|
||||
<td>{{ i18n "transmission" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||
style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">KCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP2</a-select-option>
|
||||
<a-select-option value="quic">QUIC</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "transmission" }}'>
|
||||
<a-select v-model="outbound.stream.network" @change="streamNetworkChange"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP/2</a-select-option>
|
||||
<a-select-option value="quic">QUIC</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HttpUpgrade</a-select-option>
|
||||
</a-select>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.network === 'tcp'">
|
||||
<tr>
|
||||
<td>http {{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch
|
||||
:checked="outbound.stream.tcp.type === 'http'"
|
||||
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||
<a-switch :checked="outbound.stream.tcp.type === 'http'"
|
||||
@change="checked => outbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.tcp.type == 'http'">
|
||||
<tr>
|
||||
<td>{{ i18n "host" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.host"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input style="width: 250px;" v-model.trim="outbound.stream.tcp.path"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="outbound.stream.tcp.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.tcp.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- kcp -->
|
||||
<template v-if="outbound.stream.network === 'kcp'">
|
||||
<tr>
|
||||
<td>{{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="outbound.stream.kcp.seed" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mtu</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tti (ms)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uplink capacity (MB/S)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>downlink capacity (MB/S)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>congestion</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>read buffer size (MB)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>write buffer size (MB)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="outbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
<a-select-option value="dns">DNS</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model="outbound.stream.kcp.seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='TTI (ms)'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.tti"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Uplink (MB/s)'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Downlink (MB/s)'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.downCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Congestion'>
|
||||
<a-switch v-model="outbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Read Buffer (MB)'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.readBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Write Buffer (MB)'>
|
||||
<a-input-number v-model.number="outbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- ws -->
|
||||
<template v-if="outbound.stream.network === 'ws'">
|
||||
<tr>
|
||||
<td>{{ i18n "host" }}</td>
|
||||
<td><a-form-item><a-input style="width: 250px" v-model="outbound.stream.ws.host"></a-input></a-form-item></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td><a-form-item><a-input style="width: 250px;" v-model.trim="outbound.stream.ws.path"></a-input></a-form-item></td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model="outbound.stream.ws.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- http -->
|
||||
<template v-if="outbound.stream.network === 'http'">
|
||||
<tr>
|
||||
<td>{{ i18n "host" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.http.host" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.http.path" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- quic -->
|
||||
<template v-if="outbound.stream.network === 'quic'">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.quic.key" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="outbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-input v-model.trim="outbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="outbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- grpc -->
|
||||
<template v-if="outbound.stream.network === 'grpc'">
|
||||
<tr>
|
||||
<td>serviceName</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MultiMode</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='Service Name'>
|
||||
<a-input v-model.trim="outbound.stream.grpc.serviceName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Authority'>
|
||||
<a-input v-model.trim="outbound.stream.grpc.authority"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Multi Mode'>
|
||||
<a-switch v-model="outbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- httpupgrade -->
|
||||
<template v-if="outbound.stream.network === 'httpupgrade'">
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model="outbound.stream.httpupgrade.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- tls settings -->
|
||||
<template v-if="outbound.canEnableTls()">
|
||||
<tr>
|
||||
<td>{{ i18n "security" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="tls">TLS</a-radio-button>
|
||||
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "security" }}'>
|
||||
<a-radio-group v-model="outbound.stream.security" button-style="solid">
|
||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="tls">TLS</a-radio-button>
|
||||
<a-radio-button v-if="outbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.isTls">
|
||||
<tr>
|
||||
<td>SNI</td>
|
||||
<td>
|
||||
<a-form-item placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="outbound.stream.tls.serverName" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.stream.tls.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ALPN</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="width: 250px"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="outbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allow insecure</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="outbound.stream.tls.serverName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="outbound.stream.tls.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="ALPN">
|
||||
<a-select
|
||||
mode="multiple"
|
||||
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="outbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow Insecure">
|
||||
<a-switch v-model="outbound.stream.tls.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
<template v-if="outbound.stream.isReality">
|
||||
<tr>
|
||||
<td>{{ i18n "domainName" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.reality.serverName" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="outbound.stream.reality.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Short Id</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SpiderX</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Public Key</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="outbound.stream.reality.publicKey" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label="SNI">
|
||||
<a-input v-model.trim="outbound.stream.reality.serverName"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="outbound.stream.reality.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Short ID">
|
||||
<a-input v-model.trim="outbound.stream.reality.shortId" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="SpiderX">
|
||||
<a-input v-model.trim="outbound.stream.reality.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Public Key">
|
||||
<a-input v-model.trim="outbound.stream.reality.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
</table>
|
||||
|
||||
<!-- sockopt settings -->
|
||||
<a-form-item label="Sockopts">
|
||||
<a-switch v-model="outbound.stream.sockoptSwitch"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.stream.sockoptSwitch">
|
||||
<a-form-item label="Dialer Proxy">
|
||||
<a-select v-model="outbound.stream.sockopt.dialerProxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ['', ...outModal.tags]" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Fast Open">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Keep Alive Interval">
|
||||
<a-input-number v-model="outbound.stream.sockopt.tcpKeepAliveInterval" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP No-Delay">
|
||||
<a-switch v-model="outbound.stream.sockopt.tcpNoDelay"></a-switch>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- mux settings -->
|
||||
<template v-if="outbound.canEnableMux()">
|
||||
<a-form-item label="Mux">
|
||||
<a-switch v-model="outbound.mux.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="outbound.mux.enabled">
|
||||
<a-form-item label="Concurrency">
|
||||
<a-input-number v-model="outbound.mux.concurrency" :min="-1" :max="1024"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="xudp Concurrency">
|
||||
<a-input-number v-model="outbound.mux.xudpConcurrency" :min="-1" :max="1024"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="xudp UDP 443">
|
||||
<a-select v-model="outbound.mux.xudpProxyUDP443" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="c in ['reject', 'allow', 'skip']" :value="c">[[ c ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="JSON" force-render="true">
|
||||
@@ -541,4 +461,4 @@
|
||||
<textarea style="position:absolute; left: -800px;" id="outboundJson"></textarea>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,42 +1,20 @@
|
||||
{{define "form/dokodemo"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.targetAddress"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.destinationPort"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.network"}}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>FollowRedirect</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.targetAddress"}}'>
|
||||
<a-input v-model.trim="inbound.settings.address"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.destinationPort"}}'>
|
||||
<a-input-number v-model.number="inbound.settings.port"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network"}}'>
|
||||
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="udp">UDP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Follow Redirect'>
|
||||
<a-switch v-model="inbound.settings.followRedirect"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{define "form/http"}}
|
||||
<a-form layout="inline">
|
||||
<a-form>
|
||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||
<tr>
|
||||
<td width="45%">{{ i18n "username" }}</td>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{{define "form/shadowsocks"}}
|
||||
<a-form layout="inline">
|
||||
<template v-if="inbound.isSSMultiUser">
|
||||
<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-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
{{template "form/client"}}
|
||||
@@ -20,40 +19,32 @@
|
||||
</table>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</template>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "encryption" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.method" style="width: 250px;" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.isSS2022">
|
||||
<td>{{ i18n "password" }}
|
||||
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.password" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.settings.network" style="width: 100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp,udp">tcp+udp</a-select-option>
|
||||
<a-select-option value="tcp">tcp</a-select-option>
|
||||
<a-select-option value="udp">udp</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "encryption" }}'>
|
||||
<a-select v-model="inbound.settings.method" @change="SSMethodChange" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="(method,method_name) in SSMethods" :value="method">[[ method_name ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="inbound.isSS2022">
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "password" }}
|
||||
<a-icon @click="inbound.settings.password = RandomUtil.randomShadowsocksPassword()" type="sync"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.settings.password"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.network" }}'>
|
||||
<a-select v-model="inbound.settings.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp,udp">TCP+UDP</a-select-option>
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="udp">UDP</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
{{end}}
|
||||
|
||||
@@ -1,52 +1,33 @@
|
||||
{{define "form/socks"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td style="width: 30%;">{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.settings.auth === 'password'">
|
||||
<td colspan="2">
|
||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||
<tr>
|
||||
<td width="45%">{{ i18n "username" }}</td>
|
||||
<td width="45%">{{ i18n "password" }}</td>
|
||||
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.enable" }} udp</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="inbound.settings.udp">
|
||||
<td>IP</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.enable" }} UDP'>
|
||||
<a-switch v-model="inbound.settings.udp"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="IP" v-if="inbound.settings.udp">
|
||||
<a-input v-model.trim="inbound.settings.ip"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "password" }}'>
|
||||
<a-switch :checked="inbound.settings.auth === 'password'"
|
||||
@change="checked => inbound.settings.auth = checked ? 'password' : 'noauth'"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-if="inbound.settings.auth === 'password'">
|
||||
<table style="width: 100%; text-align: center; margin-bottom: 10px;">
|
||||
<tr>
|
||||
<td width="45%">{{ i18n "username" }}</td>
|
||||
<td width="45%">{{ i18n "password" }}</td>
|
||||
<td><a-button size="small" @click="inbound.settings.addAccount(new Inbound.SocksSettings.SocksAccount())">+</a-button></td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-input-group compact v-for="(account, index) in inbound.settings.accounts" style="margin-bottom: 10px;">
|
||||
<a-input style="width: 50%" v-model.trim="account.user" placeholder='{{ i18n "username" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="account.pass" placeholder='{{ i18n "password" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.settings.delAccount(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</template>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{{define "form/trojan"}}
|
||||
<a-form layout="inline">
|
||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.trojans.slice(0,1)" v-if="!isEdit">
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
{{template "form/client"}}
|
||||
@@ -32,55 +31,28 @@
|
||||
</a-form>
|
||||
|
||||
<!-- trojan fallbacks -->
|
||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;">
|
||||
fallback[[ index + 1 ]]
|
||||
Fallback [[ index + 1 ]]
|
||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||
</a-divider>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td style="width: 20%;">Name</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.name" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alpn</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.alpn" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Path</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.path" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dest</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>xVer</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form-item label='SNI'>
|
||||
<a-input v-model="fallback.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='ALPN'>
|
||||
<a-input v-model="fallback.alpn"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Path'>
|
||||
<a-input v-model="fallback.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Dest'>
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='xVer'>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
</template>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{{define "form/vless"}}
|
||||
<a-form layout="inline">
|
||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vlesses.slice(0,1)" v-if="!isEdit">
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
{{template "form/client"}}
|
||||
@@ -34,54 +33,27 @@
|
||||
</a-form>
|
||||
|
||||
<!-- vless fallbacks -->
|
||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" layout="inline">
|
||||
<a-form v-for="(fallback, index) in inbound.settings.fallbacks" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;">
|
||||
fallback[[ index + 1 ]]
|
||||
Fallback [[ index + 1 ]]
|
||||
<a-icon type="delete" @click="() => inbound.settings.delFallback(index)"
|
||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||
</a-divider>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td style="width: 20%;">Name</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.name" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alpn</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.alpn" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Path</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.path" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dest</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="fallback.dest" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>xVer</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form-item label='SNI'>
|
||||
<a-input v-model="fallback.name"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='ALPN'>
|
||||
<a-input v-model="fallback.alpn"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Path'>
|
||||
<a-input v-model="fallback.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Dest'>
|
||||
<a-input v-model="fallback.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='xVer'>
|
||||
<a-input-number v-model="fallback.xver" :min="0" :max="2"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{{define "form/vmess"}}
|
||||
<a-form layout="inline">
|
||||
<a-collapse activeKey="0" v-for="(client, index) in inbound.settings.vmesses.slice(0,1)" v-if="!isEdit">
|
||||
<a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'>
|
||||
{{template "form/client"}}
|
||||
|
||||
80
web/html/xui/form/protocol/wireguard.html
Normal file
@@ -0,0 +1,80 @@
|
||||
{{define "form/wireguard"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.xray.wireguard.secretKey" }}
|
||||
<a-icon type="sync"
|
||||
@click="[inbound.settings.pubKey, inbound.settings.secretKey] = Object.values(Wireguard.generateKeypair())">
|
||||
</a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.settings.secretKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.wireguard.publicKey" }}'>
|
||||
<a-input disabled v-model="inbound.settings.pubKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="inbound.settings.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Kernel Mode'>
|
||||
<a-switch v-model="inbound.settings.kernelMode"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Peers">
|
||||
<a-button type="primary" size="small" @click="inbound.settings.addPeer()">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form v-for="(peer, index) in inbound.settings.peers" :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;">
|
||||
Peer [[ index + 1 ]]
|
||||
<a-icon v-if="inbound.settings.peers.length>1" type="delete" @click="() => inbound.settings.delPeer(index)"
|
||||
style="color: rgb(255, 77, 79);cursor: pointer;"/>
|
||||
</a-divider>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.xray.wireguard.secretKey" }}
|
||||
<a-icon @click="[peer.publicKey, peer.privateKey] = Object.values(Wireguard.generateKeypair())"type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="peer.privateKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
{{ i18n "pages.xray.wireguard.publicKey" }}
|
||||
</template>
|
||||
<a-input v-model.trim="peer.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "pages.xray.wireguard.psk" }}
|
||||
<a-icon @click="peer.psk = Wireguard.keyToBase64(Wireguard.generatePresharedKey())"type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="peer.psk"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
{{ i18n "pages.xray.wireguard.allowedIPs" }} <a-button type="primary" size="small" @click="peer.allowedIPs.push('')">+</a-button>
|
||||
</template>
|
||||
<template v-for="(aip, index) in peer.allowedIPs" style="margin-bottom: 10px;">
|
||||
<a-input v-model.trim="peer.allowedIPs[index]">
|
||||
<a-button v-if="peer.allowedIPs.length>1" slot="addonAfter" size="small" @click="peer.allowedIPs.splice(index, 1)">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label='Keep Alive'>
|
||||
<a-input-number v-model.number="peer.keepAlive" :min="0"></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,19 +1,19 @@
|
||||
{{define "form/sniffing"}}
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
<a-form layout="inline">
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item>
|
||||
<span slot="label">
|
||||
sniffing
|
||||
Sniffing
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.inbounds.noRecommendKeepDefault" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-switch v-model="inbound.sniffing.enabled"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-checkbox-group v-model="inbound.sniffing.destOverride" v-if="inbound.sniffing.enabled">
|
||||
<a-checkbox v-for="key,value in SNIFFING_OPTION" :value="key">[[ value ]]</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
{{define "form/externalProxy"}}
|
||||
<a-form layout="inline">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
<a-form-item label="External Proxy">
|
||||
<a-switch v-model="externalProxy"></a-switch>
|
||||
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
|
||||
<a-switch v-model="externalProxy"></a-switch>
|
||||
<a-button v-if="externalProxy" type="primary" style="margin-left: 10px" size="small" @click="inbound.stream.externalProxy.push({forceTls: 'same', dest: '', port: 443, remark: ''})">+</a-button>
|
||||
</a-form-item>
|
||||
<table width="100%" class="ant-table-tbody" v-if="externalProxy" style="margin-bottom:5px">
|
||||
<tr style="line-height: 40px;">
|
||||
<td width="100%">
|
||||
<a-input-group style="margin: 0 5px;" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||
<template>
|
||||
<a-tooltip title="Force TLS">
|
||||
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option value="tls">TLS</a-select-option>
|
||||
</a-select>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 35%" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||
</a-tooltip>
|
||||
<a-input style="width: 20%" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||
</a-input-group>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-input-group style="margin: 5px 0;" compact v-for="(row, index) in inbound.stream.externalProxy">
|
||||
<template>
|
||||
<a-tooltip title="Force TLS">
|
||||
<a-select v-model="row.forceTls" style="width:20%; margin: 0px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="same">{{ i18n "pages.inbounds.same" }}</a-select-option>
|
||||
<a-select-option value="none">{{ i18n "none" }}</a-select-option>
|
||||
<a-select-option value="tls">TLS</a-select-option>
|
||||
</a-select>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input style="width: 35%; border-radius: 0;" v-model.trim="row.dest" placeholder='{{ i18n "host" }}'></a-input>
|
||||
<a-tooltip title='{{ i18n "pages.inbounds.port" }}'>
|
||||
<a-input-number style="width: 15%;" v-model.number="row.port" min="1" max="65531"></a-input-number>
|
||||
</a-tooltip>
|
||||
<a-input style="width: 20%; border-radius: 0;" v-model.trim="row.remark" placeholder='{{ i18n "remark" }}'></a-input>
|
||||
<a-button style="width: 10%; margin: 0px" @click="inbound.stream.externalProxy.splice(index, 1)">-</a-button>
|
||||
</a-input-group>
|
||||
</a-form>
|
||||
{{end}}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
{{define "form/streamGRPC"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>serviceName</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MultiMode</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Service Name">
|
||||
<a-input v-model.trim="inbound.stream.grpc.serviceName" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Authority">
|
||||
<a-input v-model.trim="inbound.stream.grpc.authority"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Multi Mode">
|
||||
<a-switch v-model="inbound.stream.grpc.multiMode"></a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
{{define "form/streamHTTP"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.http.path" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>host</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-row v-for="(host, index) in inbound.stream.http.host">
|
||||
<a-input v-model.trim="inbound.stream.http.host[index]" style="width: 250px;"></a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.http.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">{{ i18n "host" }}
|
||||
<a-button size="small" @click="inbound.stream.http.addHost()">+</a-button>
|
||||
</template>
|
||||
<template v-for="(host, index) in inbound.stream.http.host">
|
||||
<a-input v-model.trim="inbound.stream.http.host[index]">
|
||||
<a-button size="small" slot="addonAfter"
|
||||
@click="inbound.stream.http.removeHost(index)"
|
||||
v-if="inbound.stream.http.host.length>1">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
26
web/html/xui/form/stream/stream_httpupgrade.html
Normal file
@@ -0,0 +1,26 @@
|
||||
{{define "form/streamHTTPUPGRADE"}}
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="PROXY Protocol">
|
||||
<a-switch v-model="inbound.stream.httpupgrade.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.httpupgrade.path"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="inbound.stream.httpupgrade.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="inbound.stream.ws.removeHeader(index)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,85 +1,48 @@
|
||||
{{define "form/streamKCP"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.kcp.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="inbound.stream.kcp.seed" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mtu</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tti (ms)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uplink capacity (MB/S)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>downlink capacity (MB/S)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>congestion</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>read buffer size (MB)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>write buffer size (MB)</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.kcp.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
<a-select-option value="dns">DNS</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "password" }}
|
||||
<a-icon @click="inbound.stream.kcp.seed = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.kcp.seed"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='MTU'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.mtu"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='TTI (ms)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.tti"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Uplink (MB/s)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.upCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Downlink (MB/s)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.downCap"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Congestion'>
|
||||
<a-switch v-model="inbound.stream.kcp.congestion"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Read Buffer (MB)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.readBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='Write Buffer (MB)'>
|
||||
<a-input-number v-model.number="inbound.stream.kcp.writeBuffer"></a-input-number>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
{{define "form/streamQUIC"}}
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.quic.encryption" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.quic.security" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">aes-128-gcm</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">chacha20-poly1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.quic.key" style="width: 250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "camouflage" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.quic.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">none (not camouflage)</a-select-option>
|
||||
<a-select-option value="srtp">srtp (video call)</a-select-option>
|
||||
<a-select-option value="utp">utp (BT download)</a-select-option>
|
||||
<a-select-option value="wechat-video">wechat-video (WeChat video)</a-select-option>
|
||||
<a-select-option value="dtls">dtls (DTLS 1.2 packages)</a-select-option>
|
||||
<a-select-option value="wireguard">wireguard (wireguard packages)</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.quic.encryption" }}'>
|
||||
<a-select v-model="inbound.stream.quic.security" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="aes-128-gcm">AES-128-GCM</a-select-option>
|
||||
<a-select-option value="chacha20-poly1305">CHACHA20-POLY1305</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
{{ i18n "password" }}
|
||||
<a-icon @click="inbound.stream.quic.key = RandomUtil.randomSeq(10)"type="sync"> </a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.quic.key"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "camouflage" }}'>
|
||||
<a-select v-model="inbound.stream.quic.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="none">None</a-select-option>
|
||||
<a-select-option value="srtp">SRTP</a-select-option>
|
||||
<a-select-option value="utp">uTP</a-select-option>
|
||||
<a-select-option value="wechat-video">WeChat</a-select-option>
|
||||
<a-select-option value="dtls">DTLS 1.2</a-select-option>
|
||||
<a-select-option value="wireguard">WireGuard</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
{{define "form/streamSettings"}}
|
||||
<!-- select stream network -->
|
||||
<a-form layout="inline">
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="{{ i18n "transmission" }}">
|
||||
<a-select v-model="inbound.stream.network" @change="streamNetworkChange"
|
||||
style="width: 150px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="tcp">TCP</a-select-option>
|
||||
<a-select-option value="kcp">KCP</a-select-option>
|
||||
<a-select-option value="kcp">mKCP</a-select-option>
|
||||
<a-select-option value="ws">WebSocket</a-select-option>
|
||||
<a-select-option value="http">HTTP2</a-select-option>
|
||||
<a-select-option value="http">HTTP/2</a-select-option>
|
||||
<a-select-option value="quic">QUIC</a-select-option>
|
||||
<a-select-option value="grpc">gRPC</a-select-option>
|
||||
<a-select-option value="httpupgrade">HttpUpgrade</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -43,8 +44,14 @@
|
||||
<template v-if="inbound.stream.network === 'grpc'">
|
||||
{{template "form/streamGRPC"}}
|
||||
</template>
|
||||
|
||||
<!-- httpupgrade -->
|
||||
<template v-if="inbound.stream.network === 'httpupgrade'">
|
||||
{{template "form/streamHTTPUPGRADE"}}
|
||||
</template>
|
||||
|
||||
<!-- sockopt -->
|
||||
<template>
|
||||
{{template "form/streamSockopt"}}
|
||||
</template>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,46 +1,26 @@
|
||||
{{define "form/streamSockopt"}}
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Transparent Proxy">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="TPROXY">
|
||||
<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.currentTheme">
|
||||
<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>
|
||||
<template v-if="inbound.stream.sockoptSwitch">
|
||||
<a-form-item label="PROXY Protocol">
|
||||
<a-switch v-model="inbound.stream.sockopt.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="TCP Fast Open">
|
||||
<a-switch v-model.trim="inbound.stream.sockopt.tcpFastOpen"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Route Mark">
|
||||
<a-input-number v-model="inbound.stream.sockopt.mark" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TPROXY">
|
||||
<a-select v-model="inbound.stream.sockopt.tproxy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="off">Off</a-select-option>
|
||||
<a-select-option value="redirect">Redirect</a-select-option>
|
||||
<a-select-option value="tproxy">TPROXY</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,113 +1,82 @@
|
||||
{{define "form/streamTCP"}}
|
||||
<!-- tcp type -->
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Accept Proxy Protocol" v-if="inbound.canEnableTls()">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="PROXY Protocol" v-if="inbound.canEnableTls()">
|
||||
<a-switch v-model="inbound.stream.tcp.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="http {{ i18n "camouflage" }}">
|
||||
<a-switch
|
||||
:checked="inbound.stream.tcp.type === 'http'"
|
||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
<a-form-item label='HTTP {{ i18n "camouflage" }}'>
|
||||
<a-switch :checked="inbound.stream.tcp.type === 'http'"
|
||||
@change="checked => inbound.stream.tcp.type = checked ? 'http' : 'none'">
|
||||
</a-switch>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- tcp request -->
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'" layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.requestVersion" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.version" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.requestMethod" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.method" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="vertical-align: top; padding-top: 10px;">{{ i18n "pages.inbounds.stream.tcp.requestPath" }}
|
||||
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-row v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]" style="width: 200px;">
|
||||
<a-button size="small" slot="addonAfter"
|
||||
@click="inbound.stream.tcp.request.removePath(index)"
|
||||
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
|
||||
</a-input>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" width="100%">
|
||||
<a-form-item>
|
||||
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- tcp response -->
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.responseVersion" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.version" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatus" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.status" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.stream.tcp.responseStatusDescription" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.reason" style="width: 200px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" width="100%">
|
||||
<a-form-item>
|
||||
<span>{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}:</span>
|
||||
<a-button size="small" style="margin-left: 10px"
|
||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-form v-if="inbound.stream.tcp.type === 'http'" :colon="false" :label-col="{ md: {span:8} }"
|
||||
:wrapper-col="{ md: {span:14} }">
|
||||
<!-- tcp request -->
|
||||
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.request" }}</a-divider>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.version"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.method" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.method"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">{{ i18n "pages.inbounds.stream.tcp.path" }}
|
||||
<a-button size="small" @click="inbound.stream.tcp.request.addPath('/')">+</a-button>
|
||||
</template>
|
||||
<template v-for="(path, index) in inbound.stream.tcp.request.path">
|
||||
<a-input v-model.trim="inbound.stream.tcp.request.path[index]">
|
||||
<a-button size="small" slot="addonAfter" @click="inbound.stream.tcp.request.removePath(index)"
|
||||
v-if="inbound.stream.tcp.request.path.length>1">-</a-button>
|
||||
</a-input>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button size="small" @click="inbound.stream.tcp.request.addHeader('', '')">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.request.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small"
|
||||
@click="inbound.stream.tcp.request.removeHeader(index)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- tcp response -->
|
||||
<a-divider style="margin:0;">{{ i18n "pages.inbounds.stream.general.response" }}</a-divider>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.version" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.version"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.status" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.status"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.statusDescription" }}'>
|
||||
<a-input v-model.trim="inbound.stream.tcp.response.reason"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.responseHeader" }}'>
|
||||
<a-button size="small"
|
||||
@click="inbound.stream.tcp.response.addHeader('Content-Type', 'application/octet-stream')">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.tcp.response.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model.trim="header.value"
|
||||
placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<template slot="addonAfter">
|
||||
<a-button size="small" @click="inbound.stream.tcp.response.removeHeader(index)">-</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
@@ -1,16 +1,18 @@
|
||||
{{define "form/streamWS"}}
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="AcceptProxyProtocol">
|
||||
<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="PROXY Protocol">
|
||||
<a-switch v-model="inbound.stream.ws.acceptProxyProtocol"></a-switch>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item label='{{ i18n "path" }}'>
|
||||
<a-input v-model.trim="inbound.stream.ws.path"></a-input>
|
||||
</a-form-item>
|
||||
<br>
|
||||
<a-form-item style="width: 100%;">
|
||||
<span>{{ i18n "pages.inbounds.stream.general.requestHeader" }}:</span>
|
||||
<a-button size="small" style="margin-left: 10px" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||
<a-form-item label='{{ i18n "host" }}'>
|
||||
<a-input v-model.trim="inbound.stream.ws.host"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
|
||||
<a-button size="small" @click="inbound.stream.ws.addHeader()">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span:24}">
|
||||
<a-input-group compact v-for="(header, index) in inbound.stream.ws.headers">
|
||||
<a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
@@ -21,4 +23,4 @@
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,251 +1,135 @@
|
||||
{{define "form/tlsSettings"}}
|
||||
<!-- tls enable -->
|
||||
<a-form v-if="inbound.canEnableTls()" layout="inline">
|
||||
<a-form v-if="inbound.canEnableTls()" :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-divider style="margin:0;"></a-divider>
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-form-item label='{{ i18n "security" }}'>
|
||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="tls">TLS</a-radio-button>
|
||||
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "security" }}'>
|
||||
<a-radio-group v-model="inbound.stream.security" button-style="solid">
|
||||
<a-radio-button value="none">{{ i18n "none" }}</a-radio-button>
|
||||
<a-radio-button value="tls">TLS</a-radio-button>
|
||||
<a-radio-button v-if="inbound.canEnableReality()" value="reality">Reality</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- tls settings -->
|
||||
<template v-if="inbound.stream.isTls">
|
||||
<tr>
|
||||
<td>SNI</td>
|
||||
<td>
|
||||
<a-form-item placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="inbound.stream.tls.sni" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>CipherSuites</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="">auto</a-select-option>
|
||||
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Min/Max Version</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-group compact>
|
||||
<a-select style="width: 125px" v-model="inbound.stream.tls.minVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
<a-select style="width: 125px" v-model="inbound.stream.tls.maxVersion" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ALPN</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select
|
||||
mode="multiple"
|
||||
style="width: 250px"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="inbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Allow insecure</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Reject Unknown SNI</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label="SNI" placeholder="Server Name Indication">
|
||||
<a-input v-model.trim="inbound.stream.tls.sni"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="Cipher Suites">
|
||||
<a-select v-model="inbound.stream.tls.cipherSuites" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="">Auto</a-select-option>
|
||||
<a-select-option v-for="key,value in TLS_CIPHER_OPTION" :value="key">[[ value ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Min/Max Version">
|
||||
<a-input-group compact>
|
||||
<a-select v-model="inbound.stream.tls.minVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model="inbound.stream.tls.maxVersion" style="width:100px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in TLS_VERSION_OPTION" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="uTLS">
|
||||
<a-select v-model="inbound.stream.tls.settings.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value=''>None</a-select-option>
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="ALPN">
|
||||
<a-select
|
||||
mode="multiple"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
v-model="inbound.stream.tls.alpn">
|
||||
<a-select-option v-for="alpn in ALPN_OPTION" :value="alpn">[[ alpn ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Allow Insecure">
|
||||
<a-switch v-model="inbound.stream.tls.settings.allowInsecure"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="Reject Unknown SNI">
|
||||
<a-switch v-model="inbound.stream.tls.rejectUnknownSni"></a-switch>
|
||||
</a-form-item>
|
||||
<template v-for="cert,index in inbound.stream.tls.certs">
|
||||
<tr>
|
||||
<td>{{ i18n "certificate" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<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>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-if="cert.useFile">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.publicKeyPath" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.keyPath" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "certificate" }}'>
|
||||
<a-radio-group v-model="cert.useFile" button-style="solid">
|
||||
<a-radio-button :value="true">{{ i18n "pages.inbounds.certificatePath" }}</a-radio-button>
|
||||
<a-radio-button :value="false">{{ i18n "pages.inbounds.certificateContent" }}</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-button v-if="index === 0" type="primary" size="small" @click="inbound.stream.tls.addCert()" style="margin-left: 10px">+</a-button>
|
||||
<a-button v-if="inbound.stream.tls.certs.length>1" type="primary" size="small" @click="inbound.stream.tls.removeCert(index)" style="margin-left: 10px">-</a-button>
|
||||
</a-form-item>
|
||||
<template v-if="cert.useFile">
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyPath" }}'>
|
||||
<a-input v-model.trim="cert.certFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyPath" }}'>
|
||||
<a-input v-model.trim="cert.keyFile" style="width:250px;"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="setDefaultCertData(index)">{{ i18n "pages.inbounds.setDefaultCert" }}</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.publicKeyContent" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.inbounds.keyContent" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.publicKeyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.cert"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.inbounds.keyContent" }}'>
|
||||
<a-input type="textarea" :rows="3" style="width:250px;" v-model="cert.key"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<tr>
|
||||
<td>ocspStapling</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='OCSP stapling'>
|
||||
<a-input-number v-model.number="cert.ocspStapling" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- reality settings -->
|
||||
<template v-if="inbound.stream.isReality">
|
||||
<tr>
|
||||
<td>Show</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Xver</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>uTLS</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
style="width: 250px" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dest</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.dest" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Server Names</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Short Ids
|
||||
<a-form-item label='Show'>
|
||||
<a-switch v-model="inbound.stream.reality.show"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label='Xver'>
|
||||
<a-input-number v-model.number="inbound.stream.reality.xver" :min="0"></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label='uTLS'>
|
||||
<a-select v-model="inbound.stream.reality.settings.fingerprint"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in UTLS_FINGERPRINT" :value="key">[[ key ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Dest'>
|
||||
<a-input v-model.trim="inbound.stream.reality.dest"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SNI'>
|
||||
<a-input v-model.trim="inbound.stream.reality.serverNames"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "reset" }}</span>
|
||||
</template>
|
||||
Short IDs
|
||||
<a-icon @click="inbound.stream.reality.shortIds = RandomUtil.randomShortId().join(',')" type="sync">
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SpiderX</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Private Key</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.privateKey" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Public Key</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get new cert</a-button>
|
||||
</td>
|
||||
</tr>
|
||||
</a-icon>
|
||||
</template>
|
||||
<a-input v-model.trim="inbound.stream.reality.shortIds" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='SpiderX'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.spiderX" style="width:250px"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Private Key'>
|
||||
<a-input v-model.trim="inbound.stream.reality.privateKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Public Key'>
|
||||
<a-input v-model.trim="inbound.stream.reality.settings.publicKey"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" icon="import" @click="getNewX25519Cert">Get New Cert</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</table>
|
||||
</a-form>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -54,7 +54,9 @@
|
||||
<template v-else-if="!client.enable">{{ i18n "disabled" }}</template>
|
||||
<template v-else-if="isClientOnline(client.email)">{{ i18n "online" }}</template>
|
||||
</template>
|
||||
<a-badge :class="isClientOnline(client.email)? 'online-animation' : ''" :color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||
<a-badge
|
||||
:class="isClientOnline(client.email)? 'online-animation' : ''"
|
||||
:color="client.enable ? statsExpColor(record, client.email) : themeSwitcher.isDarkTheme ? '#2c3950' : '#bcbcbc'">
|
||||
</a-badge>
|
||||
</a-tooltip>
|
||||
[[ client.email ]]
|
||||
@@ -86,13 +88,12 @@
|
||||
<td width="120px" v-else-if="client.totalGB > 0">
|
||||
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||
:show-info="false"
|
||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:percent="statsProgress(record, client.email)"/>
|
||||
</td>
|
||||
<td width="120px" v-else class="infinite-bar">
|
||||
<a-progress
|
||||
:show-info="false"
|
||||
:status="isClientOnline(client.email)? 'active' : ''"
|
||||
:percent="100"></a-progress>
|
||||
</td>
|
||||
<td width="60px">
|
||||
@@ -117,7 +118,7 @@
|
||||
</td>
|
||||
<td width="120px" class="infinite-bar">
|
||||
<a-progress :show-info="false"
|
||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||
</td>
|
||||
<td width="60px">[[ client.reset + "d" ]]</td>
|
||||
@@ -202,14 +203,13 @@
|
||||
</template>
|
||||
<a-progress :stroke-color="clientStatsColor(record, client.email)"
|
||||
:show-info="false"
|
||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:percent="statsProgress(record, client.email)"/>
|
||||
</a-popover>
|
||||
</td>
|
||||
<td width="120px" v-else class="infinite-bar">
|
||||
<a-progress :stroke-color="themeSwitcher.isDarkTheme ? '#2c1e32':'#F2EAF1'"
|
||||
:show-info="false"
|
||||
:status="isClientOnline(client.email)? 'active' : ''"
|
||||
:percent="100"></a-progress>
|
||||
</td>
|
||||
<td width="80px">
|
||||
@@ -235,7 +235,7 @@
|
||||
<span v-else>[[ DateUtil.formatMillis(client._expiryTime) ]]</span>
|
||||
</template>
|
||||
<a-progress :show-info="false"
|
||||
:status="isClientOnline(client.email)? 'active' : isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:status="isClientEnabled(record, client.email)? 'exception' : ''"
|
||||
:percent="expireProgress(client.expiryTime, client.reset)"/>
|
||||
</a-popover>
|
||||
</td>
|
||||
|
||||
@@ -7,28 +7,40 @@
|
||||
width="600px"
|
||||
:class="themeSwitcher.currentTheme"
|
||||
>
|
||||
<table style="margin-bottom: 10px; width: 100%;">
|
||||
<tr><td>
|
||||
<a-row>
|
||||
<a-col :xs="24" :md="12">
|
||||
<table>
|
||||
<tr><td>{{ i18n "protocol" }}</td><td><a-tag color="purple">[[ dbInbound.protocol ]]</a-tag></td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td><a-tag>[[ dbInbound.address ]]</a-tag></td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.address" }}</td><td>
|
||||
<a-tooltip :title="[[ dbInbound.address ]]">
|
||||
<a-tag class="info-large-tag">[[ dbInbound.address ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</td></tr>
|
||||
<tr><td>{{ i18n "pages.inbounds.port" }}</td><td><a-tag>[[ dbInbound.port ]]</a-tag></td></tr>
|
||||
</table>
|
||||
</td>
|
||||
<td v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
</a-col>
|
||||
<a-col :xs="24" :md="12">
|
||||
<template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS">
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ i18n "transmission" }}</td><td><a-tag color="blue">[[ inbound.network ]]</a-tag></td>
|
||||
</tr>
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2">
|
||||
<template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade">
|
||||
<tr>
|
||||
<td>{{ i18n "host" }}</td>
|
||||
<td v-if="inbound.host"><a-tag>[[ inbound.host ]]</a-tag></td>
|
||||
<td v-if="inbound.host">
|
||||
<a-tooltip :title="[[ inbound.host ]]">
|
||||
<a-tag class="info-large-tag">[[ inbound.host ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td></tr>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "path" }}</td>
|
||||
<td v-if="inbound.path"><a-tag>[[ inbound.path ]]</a-tag></td>
|
||||
<td v-if="inbound.path">
|
||||
<a-tooltip :title="[[ inbound.path ]]">
|
||||
<a-tag class="info-large-tag">[[ inbound.path ]]</a-tag>
|
||||
</a-tooltip>
|
||||
<td v-else><a-tag color="orange">{{ i18n "none" }}</a-tag></td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -45,30 +57,35 @@
|
||||
</template>
|
||||
|
||||
<template v-if="inbound.isGrpc">
|
||||
<tr><td>grpc serviceName</td><td><a-tag>[[ inbound.serviceName ]]</a-tag></td></tr>
|
||||
<tr><td>grpc serviceName</td><td>
|
||||
<a-tooltip :title="[[ inbound.serviceName ]]">
|
||||
<a-tag class="info-large-tag">[[ inbound.serviceName ]]</a-tag>
|
||||
</a-tooltip>
|
||||
<tr><td>grpc multiMode</td><td><a-tag>[[ inbound.stream.grpc.multiMode ]]</a-tag></td></tr>
|
||||
</template>
|
||||
</table>
|
||||
</td></tr>
|
||||
<tr colspan="2" v-if="dbInbound.hasLink()">
|
||||
<td>
|
||||
{{ i18n "security" }}
|
||||
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||
<br />
|
||||
<template v-if="inbound.stream.security != 'none'">
|
||||
{{ i18n "domainName" }}
|
||||
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</template>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
</a-col>
|
||||
<template v-if="dbInbound.hasLink()">
|
||||
{{ i18n "security" }}
|
||||
<a-tag :color="inbound.stream.security == 'none' ? 'red' : 'green'">[[ inbound.stream.security ]]</a-tag>
|
||||
<br />
|
||||
<template v-if="inbound.stream.security != 'none'">
|
||||
{{ i18n "domainName" }}
|
||||
<a-tag :color="inbound.serverName ? 'green' : 'orange'">[[ inbound.serverName ? inbound.serverName : '' ]]</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<table v-if="dbInbound.isSS" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr>
|
||||
<td>{{ i18n "encryption" }}</td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.method ]]</a-tag></td>
|
||||
</tr><tr v-if="inbound.isSS2022">
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td><a-tag>[[ inbound.settings.password ]]</a-tag></td>
|
||||
<td>
|
||||
<a-tooltip :title="[[ inbound.settings.password ]]">
|
||||
<a-tag class="info-large-tag">[[ inbound.settings.password ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td>{{ i18n "pages.inbounds.network" }}</td>
|
||||
<td><a-tag color="blue">[[ inbound.settings.network ]]</a-tag></td>
|
||||
@@ -90,8 +107,12 @@
|
||||
<td><a-tag>[[ infoModal.clientSettings.flow ]]</a-tag></td>
|
||||
</tr>
|
||||
<tr v-if="infoModal.clientSettings.password">
|
||||
<td>Password</td>
|
||||
<td><a-tag>[[ infoModal.clientSettings.password ]]</a-tag></td>
|
||||
<td>{{ i18n "password" }}</td>
|
||||
<td>
|
||||
<a-tooltip :title="[[ infoModal.clientSettings.password ]]">
|
||||
<a-tag class="info-large-tag">[[ infoModal.clientSettings.password ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "status" }}</td>
|
||||
@@ -118,7 +139,7 @@
|
||||
<tr>
|
||||
<td>
|
||||
<a-tag v-if="infoModal.clientStats && infoModal.clientSettings.totalGB > 0" :color="statsColor(infoModal.clientStats)">
|
||||
[[ sizeFormat(infoModal.clientSettings.totalGB - infoModal.clientStats.up - infoModal.clientStats.down) ]]
|
||||
[[ getRemStats() ]]
|
||||
</a-tag>
|
||||
</td>
|
||||
<td>
|
||||
@@ -139,10 +160,10 @@
|
||||
</tr>
|
||||
</table>
|
||||
<template v-if="app.subSettings.enable && infoModal.clientSettings.subId">
|
||||
<a-divider>Subscription link</a-divider>
|
||||
<a-divider>Subscription URL</a-divider>
|
||||
<a-row>
|
||||
<a-col :span="22"><a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||
<a-col :span="2">
|
||||
<a-col :sx="24" :md="22">SUB: <a :href="[[ infoModal.subLink ]]" target="_blank">[[ infoModal.subLink ]]</a></a-col>
|
||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<button class="ant-btn ant-btn-primary" id="copy-sub-link" @click="copyToClipboard('copy-sub-link', infoModal.subLink)">
|
||||
<a-icon type="snippets"></a-icon>
|
||||
@@ -150,12 +171,22 @@
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :sx="24" :md="22">JSON: <a :href="[[ infoModal.subJsonLink ]]" target="_blank">[[ infoModal.subJsonLink ]]</a></a-col>
|
||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||
<a-tooltip title='{{ i18n "copy" }}'>
|
||||
<button class="ant-btn ant-btn-primary" id="copy-subJson-link" @click="copyToClipboard('copy-subJson-link', infoModal.subJsonLink)">
|
||||
<a-icon type="snippets"></a-icon>
|
||||
</button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template v-if="app.tgBotEnable && infoModal.clientSettings.tgId">
|
||||
<a-divider>Telegram ID</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-col :sx="24" :md="22"><a :href="[[ infoModal.tgLink ]]" target="_blank">@[[ infoModal.clientSettings.tgId ]]</a></a-col>
|
||||
<a-col :sx="24" :md="2" style="text-align: right;">
|
||||
<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>
|
||||
@@ -167,8 +198,8 @@
|
||||
<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="blue">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||
<a-col :span="2" style="text-align: right;">
|
||||
<a-col :sx="24" :md="22"><a-tag color="blue">[[ link.remark ]]</a-tag><br />[[ link.link ]]</a-col>
|
||||
<a-col :sx="24" :md="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>
|
||||
@@ -239,6 +270,71 @@
|
||||
<td><a-tag color="blue">[[ account.pass ]]</a-tag></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table v-if="dbInbound.isWireguard" style="margin-bottom: 10px; width: 100%;">
|
||||
<tr class="client-table-odd-row">
|
||||
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
|
||||
<td>[[ inbound.settings.secretKey ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
|
||||
<td>[[ inbound.settings.pubKey ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>MTU</td>
|
||||
<td>[[ inbound.settings.mtu ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kernel Mode</td>
|
||||
<td>[[ inbound.settings.kernelMode ]]</td>
|
||||
</tr>
|
||||
<template v-for="(peer, index) in inbound.settings.peers">
|
||||
<tr>
|
||||
<td colspan="2"><a-divider>Peer [[ index + 1 ]]</a-divider></td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>{{ i18n "pages.xray.wireguard.secretKey" }}</td>
|
||||
<td>[[ peer.privateKey ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.wireguard.publicKey" }}</td>
|
||||
<td>[[ peer.publicKey ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>{{ i18n "pages.xray.wireguard.psk" }}</td>
|
||||
<td>[[ peer.psk ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.wireguard.allowedIPs" }}</td>
|
||||
<td>[[ peer.allowedIPs.join(",") ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Keep Alive</td>
|
||||
<td>[[ peer.keepAlive ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-row>
|
||||
<a-col :span="22" style="overflow-wrap: anywhere;">
|
||||
<a-tag color="blue">Config</a-tag>
|
||||
<div
|
||||
v-html="infoModal.links[index].replaceAll(`\n`,`<br />`)"
|
||||
style="border-radius: 1rem; padding: 0.5rem;"
|
||||
class="client-table-odd-row"></div>
|
||||
</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, infoModal.links[index])">
|
||||
<a-icon type="snippets"></a-icon>
|
||||
</button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
</template>
|
||||
</a-modal>
|
||||
<script>
|
||||
@@ -255,6 +351,7 @@
|
||||
index: null,
|
||||
isExpired: false,
|
||||
subLink: '',
|
||||
subJsonLink: '',
|
||||
tgLink: '',
|
||||
show(dbInbound, index) {
|
||||
this.index = index;
|
||||
@@ -263,10 +360,15 @@
|
||||
this.clientSettings = this.inbound.clients ? this.inbound.clients[index] : null;
|
||||
this.isExpired = this.inbound.clients ? this.inbound.isExpiry(index): this.dbInbound.isExpiry;
|
||||
this.clientStats = this.inbound.clients ? this.dbInbound.clientStats.find(row => row.email === this.clientSettings.email) : [];
|
||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
||||
if (this.inbound.protocol == Protocols.WIREGUARD){
|
||||
this.links = this.inbound.genInboundLinks(dbInbound.remark).split('\r\n')
|
||||
} else {
|
||||
this.links = this.inbound.genAllLinks(this.dbInbound.remark, app.remarkModel, this.clientSettings);
|
||||
}
|
||||
if (this.clientSettings) {
|
||||
if (this.clientSettings.subId) {
|
||||
this.subLink = this.genSubLink(this.clientSettings.subId);
|
||||
this.subJsonLink = this.genSubJsonLink(this.clientSettings.subId);
|
||||
}
|
||||
if (this.clientSettings.tgId) {
|
||||
this.tgLink = "https://t.me/" + this.clientSettings.tgId;
|
||||
@@ -279,6 +381,9 @@
|
||||
},
|
||||
genSubLink(subID) {
|
||||
return app.subSettings.subURI+subID+'?name='+subID;
|
||||
},
|
||||
genSubJsonLink(subID) {
|
||||
return app.subSettings.subJsonURI+subID+'?name='+subID;;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -318,10 +423,14 @@
|
||||
},
|
||||
statsColor(stats) {
|
||||
return usageColor(stats.up + stats.down, app.trafficDiff, stats.total);
|
||||
}
|
||||
},
|
||||
getRemStats() {
|
||||
remained = this.infoModal.clientStats.total - this.infoModal.clientStats.up - this.infoModal.clientStats.down;
|
||||
return remained>0 ? sizeFormat(remained) : '-';
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
inbound: new Inbound(),
|
||||
@@ -18,7 +18,7 @@
|
||||
ok() {
|
||||
ObjectUtil.execute(inModal.confirm, inModal.inbound, inModal.dbInbound);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false }) {
|
||||
show({ title='', okText='{{ i18n "confirm" }}', inbound=null, dbInbound=null, confirm=(inbound, dbInbound)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
if (inbound) {
|
||||
@@ -39,7 +39,7 @@
|
||||
inModal.visible = false;
|
||||
inModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
loading(loading=true) {
|
||||
inModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,6 +43,10 @@
|
||||
0%, 50%, 100% { transform: scale(1); opacity: 1; }
|
||||
10% { transform: scale(1.5); opacity: .2; }
|
||||
}
|
||||
.info-large-tag {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
@@ -64,15 +68,15 @@
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalDownUp" }}:
|
||||
<strong>{{ i18n "pages.inbounds.totalDownUp" }}:</strong>
|
||||
<a-tag color="blue">[[ sizeFormat(total.up) ]] / [[ sizeFormat(total.down) ]]</a-tag>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.totalUsage" }}:
|
||||
<strong>{{ i18n "pages.inbounds.totalUsage" }}:</strong>
|
||||
<a-tag color="blue">[[ sizeFormat(total.up + total.down) ]]</a-tag>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
{{ i18n "pages.inbounds.inboundCount" }}:
|
||||
<strong>{{ i18n "pages.inbounds.inboundCount" }}:</strong>
|
||||
<a-tag color="blue">[[ dbInbounds.length ]]</a-tag>
|
||||
</a-col>
|
||||
<a-col :xs="24" :sm="24" :lg="12">
|
||||
@@ -80,7 +84,7 @@
|
||||
<div>
|
||||
<a-back-top :target="() => document.getElementById('content-layout')" visibility-height="200">
|
||||
</a-back-top>
|
||||
{{ i18n "clients" }}:
|
||||
<strong>{{ i18n "clients" }}:</strong>
|
||||
<a-tag color="blue">[[ total.clients ]]</a-tag>
|
||||
<a-popover title='{{ i18n "disabled" }}' :overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
@@ -133,6 +137,10 @@
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export" }} - {{ i18n "pages.settings.subSettings" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="resetInbounds">
|
||||
<a-icon type="reload"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllTraffic" }}
|
||||
@@ -141,7 +149,7 @@
|
||||
<a-icon type="file-done"></a-icon>
|
||||
{{ i18n "pages.inbounds.resetAllClientTraffics" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
@@ -160,9 +168,9 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; justify-content: flex-start;">
|
||||
<div :style="isMobile ? '' : 'display: flex; align-items: center; justify-content: flex-start;'">
|
||||
<a-switch v-model="enableFilter"
|
||||
style="margin-right: .5rem;"
|
||||
:style="isMobile ? 'margin-bottom: .5rem; display: flex;' : 'margin-right: .5rem;'"
|
||||
@change="toggleFilter">
|
||||
<a-icon slot="checkedChildren" type="search"></a-icon>
|
||||
<a-icon slot="unCheckedChildren" type="filter"></a-icon>
|
||||
@@ -177,7 +185,7 @@
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-back-top></a-back-top>
|
||||
<a-table :columns="isMobile ? mobileColums : columns" :row-key="dbInbound => dbInbound.id"
|
||||
<a-table :columns="isMobile ? mobileColumns : columns" :row-key="dbInbound => dbInbound.id"
|
||||
:data-source="searchedInbounds"
|
||||
:scroll="isMobile ? {} : { x: 1000 }"
|
||||
:pagination=pagination(searchedInbounds)
|
||||
@@ -195,7 +203,7 @@
|
||||
<a-icon type="edit"></a-icon>
|
||||
{{ i18n "edit" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="qrcode" v-if="dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser">
|
||||
<a-menu-item key="qrcode" v-if="(dbInbound.isSS && !dbInbound.toInbound().isSSMultiUser) || dbInbound.isWireguard">
|
||||
<a-icon type="qrcode"></a-icon>
|
||||
{{ i18n "qrCode" }}
|
||||
</a-menu-item>
|
||||
@@ -216,7 +224,11 @@
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients">
|
||||
<a-menu-item key="subs" v-if="subSettings.enable">
|
||||
<a-icon type="export"></a-icon>
|
||||
{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delDepletedClients" style="color: #FF4D4F;">
|
||||
<a-icon type="rest"></a-icon>
|
||||
{{ i18n "pages.inbounds.delDepletedClients" }}
|
||||
</a-menu-item>
|
||||
@@ -232,7 +244,7 @@
|
||||
</a-menu-item>
|
||||
<a-menu-item key="clipboard">
|
||||
<a-icon type="copy"></a-icon>
|
||||
{{ i18n "pages.inbounds.copyToClipboard" }}
|
||||
{{ i18n "pages.inbounds.exportInbound" }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="clone">
|
||||
<a-icon type="block"></a-icon> {{ i18n "pages.inbounds.clone"}}
|
||||
@@ -243,7 +255,7 @@
|
||||
</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="isMobile">
|
||||
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id)"></a-switch>
|
||||
<a-switch size="small" v-model="dbInbound.enable" @change="switchEnable(dbInbound.id,dbInbound.enable)"></a-switch>
|
||||
{{ i18n "pages.inbounds.enable" }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
@@ -310,7 +322,7 @@
|
||||
</a-popover>
|
||||
</template>
|
||||
<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,dbInbound.enable)"></a-switch>
|
||||
</template>
|
||||
<template slot="expiryTime" slot-scope="text, dbInbound">
|
||||
<a-popover v-if="dbInbound.expiryTime > 0" :overlay-class-name="themeSwitcher.currentTheme">
|
||||
@@ -490,7 +502,7 @@
|
||||
scopedSlots: { customRender: 'expiryTime' },
|
||||
}];
|
||||
|
||||
const mobileColums = [{
|
||||
const mobileColumns = [{
|
||||
title: "ID",
|
||||
align: 'right',
|
||||
dataIndex: "id",
|
||||
@@ -514,19 +526,73 @@
|
||||
}];
|
||||
|
||||
const innerColumns = [
|
||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 50, scopedSlots: { customRender: 'actions' } },
|
||||
{ title: '{{ i18n "pages.inbounds.enable" }}', width: 20, scopedSlots: { customRender: 'enable' } },
|
||||
{ title: '{{ i18n "online" }}', width: 20, scopedSlots: { customRender: 'online' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 80, scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.traffic" }}', width: 80, align: 'center', scopedSlots: { customRender: 'traffic' } },
|
||||
{ title: '{{ i18n "pages.inbounds.expireDate" }}', width: 80, align: 'center', scopedSlots: { customRender: 'expiryTime' } },
|
||||
];
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||
width: 50,
|
||||
scopedSlots: { customRender: 'actions' },
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.enable" }}',
|
||||
width: 20,
|
||||
scopedSlots: { customRender: 'enable' },
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "online" }}',
|
||||
width: 20,
|
||||
scopedSlots: { customRender: 'online' },
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.client" }}',
|
||||
dataIndex: 'email',
|
||||
width: 70,
|
||||
scopedSlots: { customRender: 'client' },
|
||||
sorter: (a, b) => {
|
||||
const clientA = a.email || '';
|
||||
const clientB = b.email || '';
|
||||
return clientA.localeCompare(clientB, undefined, { sensitivity: 'base' });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.traffic" }}',
|
||||
dataIndex: 'traffic',
|
||||
width: 80,
|
||||
align: 'center',
|
||||
scopedSlots: { customRender: 'traffic' },
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.expireDate" }}',
|
||||
width: 70,
|
||||
align: 'center',
|
||||
scopedSlots: { customRender: 'expiryTime' },
|
||||
},
|
||||
];
|
||||
|
||||
const innerMobileColumns = [
|
||||
{ title: '{{ i18n "pages.inbounds.operate" }}', width: 10, align: 'center', scopedSlots: { customRender: 'actionMenu' } },
|
||||
{ title: '{{ i18n "pages.inbounds.client" }}', width: 90, align: 'left', scopedSlots: { customRender: 'client' } },
|
||||
{ title: '{{ i18n "pages.inbounds.info" }}', width: 10, align: 'center', scopedSlots: { customRender: 'info' } },
|
||||
];
|
||||
const innerMobileColumns = [
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.operate" }}',
|
||||
width: 10,
|
||||
align: 'center',
|
||||
scopedSlots: { customRender: 'actionMenu' },
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.client" }}',
|
||||
dataIndex: 'email',
|
||||
width: 90,
|
||||
align: 'left',
|
||||
scopedSlots: { customRender: 'client' },
|
||||
sorter: (a, b) => {
|
||||
const clientA = a.email || '';
|
||||
const clientB = b.email || '';
|
||||
return clientA.localeCompare(clientB, undefined, { sensitivity: 'base' });
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '{{ i18n "pages.inbounds.info" }}',
|
||||
width: 10,
|
||||
align: 'center',
|
||||
scopedSlots: { customRender: 'info' },
|
||||
},
|
||||
];
|
||||
|
||||
const app = new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
@@ -552,7 +618,8 @@
|
||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
subSettings: {
|
||||
enable : false,
|
||||
subURI : ''
|
||||
subURI : '',
|
||||
subJsonURI : '',
|
||||
},
|
||||
remarkModel: '-ieo',
|
||||
tgBotEnable: false,
|
||||
@@ -597,7 +664,8 @@
|
||||
this.tgBotEnable = tgBotEnable;
|
||||
this.subSettings = {
|
||||
enable : subEnable,
|
||||
subURI: subURI
|
||||
subURI: subURI,
|
||||
subJsonURI: subJsonURI
|
||||
};
|
||||
this.pageSize = pageSize;
|
||||
this.remarkModel = remarkModel;
|
||||
@@ -634,8 +702,12 @@
|
||||
clientCount = clients.length;
|
||||
if (dbInbound.enable) {
|
||||
clients.forEach(client => {
|
||||
client.enable ? active.push(client.email) : deactive.push(client.email);
|
||||
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||
if (client.enable) {
|
||||
active.push(client.email);
|
||||
if(this.isClientOnline(client.email)) online.push(client.email);
|
||||
} else {
|
||||
deactive.push(client.email);
|
||||
}
|
||||
});
|
||||
clientStats.forEach(client => {
|
||||
if (!client.enable) {
|
||||
@@ -723,6 +795,9 @@
|
||||
case "export":
|
||||
this.exportAllLinks();
|
||||
break;
|
||||
case "subs":
|
||||
this.exportAllSubs();
|
||||
break;
|
||||
case "resetInbounds":
|
||||
this.resetAllTraffic();
|
||||
break;
|
||||
@@ -754,6 +829,9 @@
|
||||
case "export":
|
||||
this.inboundLinks(dbInbound.id);
|
||||
break;
|
||||
case "subs":
|
||||
this.exportSubs(dbInbound.id);
|
||||
break;
|
||||
case "clipboard":
|
||||
this.copyToClipboard(dbInbound.id);
|
||||
break;
|
||||
@@ -780,9 +858,7 @@
|
||||
okText: '{{ i18n "pages.inbounds.create"}}',
|
||||
cancelText: '{{ i18n "close" }}',
|
||||
confirm: async (inbound, dbInbound) => {
|
||||
inModal.loading();
|
||||
await this.addInbound(inbound, dbInbound);
|
||||
inModal.close();
|
||||
await this.addInbound(inbound, dbInbound, inModal);
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
@@ -797,9 +873,7 @@
|
||||
inbound: inbound,
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (inbound, dbInbound) => {
|
||||
inModal.loading();
|
||||
await this.updateInbound(inbound, dbInbound);
|
||||
inModal.close();
|
||||
},
|
||||
isEdit: true
|
||||
});
|
||||
@@ -832,8 +906,8 @@
|
||||
port: RandomUtil.randomIntRange(10000, 60000),
|
||||
protocol: baseInbound.protocol,
|
||||
settings: Inbound.Settings.getSettings(baseInbound.protocol).toString(),
|
||||
streamSettings: baseInbound.stream.toString(),
|
||||
sniffing: baseInbound.canSniffing() ? baseInbound.sniffing.toString() : '{}',
|
||||
streamSettings: baseInbound.stream.toString(),
|
||||
sniffing: baseInbound.sniffing.toString(),
|
||||
};
|
||||
await this.submit('/xui/inbound/add', data, inModal);
|
||||
},
|
||||
@@ -852,7 +926,7 @@
|
||||
settings: inbound.settings.toString(),
|
||||
};
|
||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||
data.sniffing = inbound.sniffing.toString();
|
||||
|
||||
await this.submit('/xui/inbound/add', data, inModal);
|
||||
},
|
||||
@@ -871,7 +945,7 @@
|
||||
settings: inbound.settings.toString(),
|
||||
};
|
||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||
if (inbound.canSniffing()) data.sniffing = inbound.sniffing.toString();
|
||||
data.sniffing = inbound.sniffing.toString();
|
||||
|
||||
await this.submit(`/xui/inbound/update/${dbInbound.id}`, data, inModal);
|
||||
},
|
||||
@@ -882,9 +956,7 @@
|
||||
okText: '{{ i18n "pages.client.submitAdd"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientModal.loading();
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientModal.close();
|
||||
await this.addClient(clients, dbInboundId, clientModal);
|
||||
},
|
||||
isEdit: false
|
||||
});
|
||||
@@ -896,9 +968,7 @@
|
||||
okText: '{{ i18n "pages.client.bulk"}}',
|
||||
dbInbound: dbInbound,
|
||||
confirm: async (clients, dbInboundId) => {
|
||||
clientsBulkModal.loading();
|
||||
await this.addClient(clients, dbInboundId);
|
||||
clientsBulkModal.close();
|
||||
await this.addClient(clients, dbInboundId, clientsBulkModal);
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -927,19 +997,19 @@
|
||||
default: return clients.findIndex(item => item.id === client.id && item.email === client.email);
|
||||
}
|
||||
},
|
||||
async addClient(clients, dbInboundId) {
|
||||
async addClient(clients, dbInboundId, modal) {
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + clients.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/addClient`, data);
|
||||
await this.submit(`/xui/inbound/addClient`, data, modal);
|
||||
},
|
||||
async updateClient(client, dbInboundId, clientId) {
|
||||
const data = {
|
||||
id: dbInboundId,
|
||||
settings: '{"clients": [' + client.toString() + ']}',
|
||||
};
|
||||
await this.submit(`/xui/inbound/updateClient/${clientId}`, data);
|
||||
await this.submit(`/xui/inbound/updateClient/${clientId}`, data, clientModal);
|
||||
},
|
||||
resetTraffic(dbInboundId) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
@@ -1026,8 +1096,9 @@
|
||||
newDbInbound = this.checkFallback(dbInbound);
|
||||
infoModal.show(newDbInbound, index);
|
||||
},
|
||||
switchEnable(dbInboundId) {
|
||||
switchEnable(dbInboundId,state) {
|
||||
dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
dbInbound.enable = state;
|
||||
this.submit(`/xui/inbound/update/${dbInboundId}`, dbInbound);
|
||||
},
|
||||
async switchEnableClient(dbInboundId, client) {
|
||||
@@ -1041,8 +1112,8 @@
|
||||
await this.updateClient(clients[index], dbInboundId, clientId);
|
||||
this.loading(false);
|
||||
},
|
||||
async submit(url, data) {
|
||||
const msg = await HttpUtil.postWithModal(url, data);
|
||||
async submit(url, data, modal) {
|
||||
const msg = await HttpUtil.postWithModal(url, data, modal);
|
||||
if (msg.success) {
|
||||
await this.getDBInbounds();
|
||||
}
|
||||
@@ -1089,7 +1160,7 @@
|
||||
title: '{{ i18n "pages.inbounds.delDepletedClientsTitle"}}',
|
||||
content: '{{ i18n "pages.inbounds.delDepletedClientsContent"}}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "reset"}}',
|
||||
okText: '{{ i18n "delete"}}',
|
||||
cancelText: '{{ i18n "cancel"}}',
|
||||
onOk: () => this.submit('/xui/inbound/delDepletedClients/' + dbInboundId),
|
||||
})
|
||||
@@ -1177,6 +1248,22 @@
|
||||
newDbInbound = this.checkFallback(dbInbound);
|
||||
txtModal.show('{{ i18n "pages.inbounds.export"}}', newDbInbound.genInboundLinks(this.remarkModel), newDbInbound.remark);
|
||||
},
|
||||
exportSubs(dbInboundId) {
|
||||
const dbInbound = this.dbInbounds.find(row => row.id === dbInboundId);
|
||||
const clients = this.getInboundClients(dbInbound);
|
||||
let subLinks = []
|
||||
if (clients != null){
|
||||
clients.forEach(c => {
|
||||
if (c.subId && c.subId.length>0){
|
||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
||||
}
|
||||
})
|
||||
}
|
||||
txtModal.show(
|
||||
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
|
||||
[...new Set(subLinks)].join('\n'),
|
||||
dbInbound.remark + "-Subs");
|
||||
},
|
||||
importInbound() {
|
||||
promptModal.open({
|
||||
title: '{{ i18n "pages.inbounds.importInbound" }}',
|
||||
@@ -1185,11 +1272,27 @@
|
||||
okText: '{{ i18n "pages.inbounds.import" }}',
|
||||
confirm: async (dbInboundText) => {
|
||||
await this.submit('/xui/inbound/import', {data: dbInboundText}, promptModal);
|
||||
promptModal.close();
|
||||
},
|
||||
});
|
||||
},
|
||||
exportAllLinks() {
|
||||
exportAllSubs() {
|
||||
let subLinks = []
|
||||
for (const dbInbound of this.dbInbounds) {
|
||||
const clients = this.getInboundClients(dbInbound);
|
||||
if (clients != null){
|
||||
clients.forEach(c => {
|
||||
if (c.subId && c.subId.length>0){
|
||||
subLinks.push(this.subSettings.subURI + c.subId + "?name=" + c.subId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
txtModal.show(
|
||||
'{{ i18n "pages.inbounds.export"}} - {{ i18n "pages.settings.subSettings" }}',
|
||||
[...new Set(subLinks)].join('\r\n'),
|
||||
'All-Inbounds-Subs');
|
||||
},
|
||||
exportAllLinks() {
|
||||
let copyText = [];
|
||||
for (const dbInbound of this.dbInbounds) {
|
||||
copyText.push(dbInbound.genInboundLinks(this.remarkModel));
|
||||
|
||||
@@ -44,14 +44,14 @@
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.cpu.color"
|
||||
:percent="status.cpu.percent"></a-progress>
|
||||
<div>CPU: ([[ status.cpuCount ]]core)</div>
|
||||
<div><strong>CPU:</strong> [[ cpuCoreFormat(status.cpuCount) ]]</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
<a-progress type="dashboard" status="normal"
|
||||
:stroke-color="status.mem.color"
|
||||
:percent="status.mem.percent"></a-progress>
|
||||
<div>
|
||||
{{ i18n "pages.index.memory"}}: [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||
<strong>{{ i18n "pages.index.memory"}}:</strong> [[ sizeFormat(status.mem.current) ]] / [[ sizeFormat(status.mem.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -63,7 +63,7 @@
|
||||
:stroke-color="status.swap.color"
|
||||
:percent="status.swap.percent"></a-progress>
|
||||
<div>
|
||||
Swap: [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||
<strong>Swap:</strong> [[ sizeFormat(status.swap.current) ]] / [[ sizeFormat(status.swap.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="12" style="text-align: center">
|
||||
@@ -71,7 +71,7 @@
|
||||
:stroke-color="status.disk.color"
|
||||
:percent="status.disk.percent"></a-progress>
|
||||
<div>
|
||||
{{ i18n "pages.index.hard"}}: [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||
<strong>{{ i18n "pages.index.hard"}}:</strong> [[ sizeFormat(status.disk.current) ]] / [[ sizeFormat(status.disk.total) ]]
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -84,85 +84,100 @@
|
||||
<a-row>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
X-UI: <a href="https://github.com/alireza0/x-ui/releases" target="_blank"><a-tag color="blue">{{ .cur_ver }}</a-tag></a>
|
||||
Xray:
|
||||
<strong>{{ i18n "pages.inbounds.stream.tcp.version" }}:</strong>
|
||||
<a href="https://github.com/alireza0/x-ui/releases" target="_blank">
|
||||
<a-tag color="purple" style="cursor: pointer;">X-UI {{ .cur_ver }}</a-tag>
|
||||
</a>
|
||||
<a-tooltip title='{{ i18n "pages.index.xraySwitch" }}'>
|
||||
<a-tag color="blue" style="cursor: pointer;" @click="openSelectV2rayVersion">[[ status.xray.version ]]</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openSelectV2rayVersion">Xray [[ status.xray.version ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
{{ i18n "pages.index.operationHours" }}:
|
||||
Xray
|
||||
<a-tag color="blue">[[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
OS
|
||||
<strong>{{ i18n "pages.index.operationHours" }}:</strong>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.xrayoperationHoursDesc" }}
|
||||
</template>
|
||||
<a-tag color="blue" style="margin-right: 3px;">Xray [[ formatSecond(status.appStats.uptime) ]]</a-tag>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.operationHoursDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<a-tag color="blue">OS [[ formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-tooltip>
|
||||
<a-tag color="blue">[[ formatSecond(status.uptime) ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
{{ i18n "pages.index.xrayStatus" }}:
|
||||
<a-tag :color="status.xray.color">[[ status.xray.state ]]</a-tag>
|
||||
<strong>{{ i18n "pages.index.xrayStatus" }}:</strong>
|
||||
<a-tag :color="status.xray.color" style="margin-right: 3px;"><strong>[[ status.xray.state ]]</strong></a-tag>
|
||||
<a-popover v-if="status.xray.state === State.Error"
|
||||
:overlay-class-name="themeSwitcher.currentTheme">
|
||||
<span slot="title" style="font-size: 12pt">Error in running xray-core
|
||||
<span slot="title" style="font-size: 12pt">An error occurred while running Xray
|
||||
<a-tag color="purple" style="cursor: pointer; float: right;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
</span>
|
||||
<template slot="content">
|
||||
<p style="max-width: 400px" v-for="line in status.xray.errorMsg.split('\n')">[[ line ]]</p>
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<a-icon type="exclamation-circle"></a-icon>
|
||||
</a-popover>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="stopXrayService">{{ i18n "pages.index.stopXray" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="restartXrayService">{{ i18n "pages.index.restartXray" }}</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
{{ i18n "menu.link" }}:
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||
<strong>{{ i18n "menu.link" }}:</strong>
|
||||
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="openLogs()">{{ i18n "pages.index.logs" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer; margin-right: 3px;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
|
||||
<a-tag color="purple" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
{{ 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>
|
||||
{{ 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>
|
||||
Host: [[ status.hostInfo.hostname ]] -
|
||||
<template v-if="status.hostInfo.ipv4">IPv4:
|
||||
<strong>{{ i18n "pages.index.systemLoad" }}:</strong>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
[[ status.hostInfo.ipv4 ]]
|
||||
{{ i18n "pages.index.systemLoadDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<a-tag color="blue">[[ status.loads[0] ]] | [[ status.loads[1] ]] | [[ status.loads[2] ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
<strong>{{ i18n "usage" }}:</strong>
|
||||
<a-tag color="blue" style="margin-right: 3px;">RAM [[ sizeFormat(status.appStats.mem) ]]</a-tag>
|
||||
<a-tag color="blue">Threads [[ status.appStats.threads ]]</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :sm="24" :md="12">
|
||||
<a-card hoverable>
|
||||
<strong>{{ i18n "pages.index.serverInfo" }}:</strong>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.hostname" }}
|
||||
</template>
|
||||
<a-tag color="blue" style="margin-right: 3px;">[[ status.hostInfo.hostname ]]</a-tag>
|
||||
</a-tooltip>
|
||||
<template v-if="status.hostInfo.ipv4">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
[[ status.hostInfo.ipv4 ]]
|
||||
</template>
|
||||
<a-tag color="blue" style="margin-right: 3px;">IPv4</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="status.hostInfo.ipv6">IPv6:
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
[[ status.hostInfo.ipv6 ]]
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
</a-tooltip>
|
||||
<template v-if="status.hostInfo.ipv6">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
[[ status.hostInfo.ipv6 ]]
|
||||
</template>
|
||||
<a-tag color="blue" style="margin-right: 3px;">IPv6</a-tag>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
@@ -170,21 +185,21 @@
|
||||
<a-card hoverable>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
TCP: [[ status.tcpCount ]]
|
||||
<a-icon type="swap"></a-icon>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.connectionTcpCountDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<strong>TCP:</Strong> <a-tag>[[ status.tcpCount ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
UDP: [[ status.udpCount ]]
|
||||
<a-icon type="swap"></a-icon>
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.connectionUdpCountDesc" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<strong>UDP:</strong> <a-tag>[[ status.udpCount ]]</a-tag>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -195,22 +210,20 @@
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-icon type="arrow-up"></a-icon>
|
||||
[[ sizeFormat(status.netIO.up) ]] / S
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.upSpeed" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<strong>Up:</strong> [[ sizeFormat(status.netIO.up) ]]/s
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-icon type="arrow-down"></a-icon>
|
||||
[[ sizeFormat(status.netIO.down) ]] / S
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.downSpeed" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<strong>Down:</strong> [[ sizeFormat(status.netIO.down) ]]/s
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -221,22 +234,20 @@
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-icon type="cloud-upload"></a-icon>
|
||||
[[ sizeFormat(status.netTraffic.sent) ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.totalSent" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<strong>Out:</strong> [[ sizeFormat(status.netTraffic.sent) ]]
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-icon type="cloud-download"></a-icon>
|
||||
[[ sizeFormat(status.netTraffic.recv) ]]
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.totalReceive" }}
|
||||
</template>
|
||||
<a-icon type="question-circle" theme="filled"></a-icon>
|
||||
<strong>In:</strong> [[ sizeFormat(status.netTraffic.recv) ]]
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -251,8 +262,10 @@
|
||||
:closable="true" @ok="() => versionModal.visible = false"
|
||||
:class="themeSwitcher.currentTheme"
|
||||
footer="">
|
||||
<h2>{{ i18n "pages.index.xraySwitchClick"}}</h2>
|
||||
<h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
|
||||
<a-alert type="warning" style="margin-bottom: 12px; width: fit-content"
|
||||
message='{{ i18n "pages.index.xraySwitchClickDesk" }}'
|
||||
show-icon
|
||||
></a-alert>
|
||||
<template v-for="version, index in versionModal.versions">
|
||||
<a-tag :color="index % 2 == 0 ? 'purple' : 'blue'"
|
||||
style="margin: 10px" @click="switchV2rayVersion(version)">
|
||||
@@ -261,52 +274,53 @@
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<a-modal id="log-modal" v-model="logModal.visible" title="Logs"
|
||||
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
|
||||
<a-modal id="log-modal" v-model="logModal.visible"
|
||||
:closable="true" @cancel="() => logModal.visible = false"
|
||||
:class="themeSwitcher.currentTheme"
|
||||
width="800px"
|
||||
footer="">
|
||||
width="800px" footer="">
|
||||
<template slot="title">
|
||||
{{ i18n "pages.index.logs" }}
|
||||
<a-icon :spin="logModal.loading"
|
||||
type="sync"
|
||||
style="vertical-align: middle; margin-left: 10px;"
|
||||
:disabled="logModal.loading"
|
||||
@click="openLogs()">
|
||||
</a-icon>
|
||||
</template>
|
||||
<a-form layout="inline">
|
||||
<a-form-item label="Count">
|
||||
<a-select v-model="logModal.rows"
|
||||
style="width: 80px"
|
||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="10">10</a-select-option>
|
||||
<a-select-option value="20">20</a-select-option>
|
||||
<a-select-option value="50">50</a-select-option>
|
||||
<a-select-option value="100">100</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="Log Level">
|
||||
<a-select v-model="logModal.level"
|
||||
style="width: 120px"
|
||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="debug">Debug</a-select-option>
|
||||
<a-select-option value="info">Info</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-input-group compact>
|
||||
<a-select v-model="logModal.rows" style="width:70px;"
|
||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="10">10</a-select-option>
|
||||
<a-select-option value="20">20</a-select-option>
|
||||
<a-select-option value="50">50</a-select-option>
|
||||
<a-select-option value="100">100</a-select-option>
|
||||
</a-select>
|
||||
<a-select v-model="logModal.level" style="width:100px;"
|
||||
@change="openLogs()" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="debug">Debug</a-select-option>
|
||||
<a-select-option value="info">Info</a-select-option>
|
||||
<a-select-option value="warning">Warning</a-select-option>
|
||||
<a-select-option value="err">Error</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button class="ant-btn ant-btn-primary" :loading="logModal.loading" @click="openLogs()"><a-icon :spin="logModal.loading" type="sync"></a-icon> Reload</a-button>
|
||||
<a-checkbox v-model="logModal.syslog" @change="openLogs()">SysLog</a-checkbox>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" style="margin-bottom: 10px;"
|
||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs)" download="x-ui.log">
|
||||
{{ i18n "download" }} x-ui.log
|
||||
<a-form-item style="float: right;">
|
||||
<a-button type="primary" icon="download"
|
||||
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(logModal.logs?.join('\n'))" download="x-ui.log">
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.logs"></div>
|
||||
</a-form>
|
||||
<div class="ant-input" style="height: auto; max-height: 500px; overflow: auto;" v-html="logModal.formattedLogs"></div>
|
||||
</a-modal>
|
||||
|
||||
<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
|
||||
:closable="true"
|
||||
:class="themeSwitcher.currentTheme"
|
||||
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
|
||||
:closable="true" footer=""
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-alert type="warning" style="margin-bottom: 10px; width: fit-content"
|
||||
:message="backupModal.description"
|
||||
show-icon
|
||||
@@ -328,9 +342,9 @@
|
||||
{{template "textModal"}}
|
||||
<script>
|
||||
const State = {
|
||||
Running: "running",
|
||||
Stop: "stop",
|
||||
Error: "error",
|
||||
Running: "Running",
|
||||
Stop: "Stop",
|
||||
Error: "Error",
|
||||
}
|
||||
Object.freeze(State);
|
||||
|
||||
@@ -398,7 +412,7 @@
|
||||
this.xray = data.xray;
|
||||
switch (this.xray.state) {
|
||||
case State.Running:
|
||||
this.xray.color = "blue";
|
||||
this.xray.color = 'blue';
|
||||
break;
|
||||
case State.Stop:
|
||||
this.xray.color = "orange";
|
||||
@@ -426,14 +440,15 @@
|
||||
|
||||
const logModal = {
|
||||
visible: false,
|
||||
logs: '',
|
||||
logs: [],
|
||||
rows: 20,
|
||||
level: 'info',
|
||||
syslog: false,
|
||||
loading: false,
|
||||
show(logs) {
|
||||
this.visible = true;
|
||||
this.logs = logs? this.formatLogs(logs) : "No Record...";
|
||||
this.logs = logs;
|
||||
this.formattedLogs = this.logs?.length > 0 ? this.formatLogs(this.logs) : "No Record...";
|
||||
},
|
||||
formatLogs(logs) {
|
||||
let formattedLogs = '';
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
}
|
||||
|
||||
.ant-tabs-bar {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -43,13 +44,17 @@
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||
<transition name="list" appear>
|
||||
<a-alert type="error" v-if="showAlert" style="margin-bottom: 10px"
|
||||
<a-alert type="error" v-if="confAlerts.length>0" style="margin: 10px 5px;"
|
||||
message='{{ i18n "secAlertTitle" }}'
|
||||
color="red"
|
||||
description='{{ i18n "secAlertSsl" }}'
|
||||
show-icon closable
|
||||
show-icon
|
||||
closable
|
||||
>
|
||||
</a-alert>
|
||||
<template slot="description">
|
||||
<b>{{ i18n "secAlertConf" }}</b>
|
||||
<ul><li v-for="a in confAlerts">[[ a ]]</li></ul>
|
||||
</template>
|
||||
</a-alert>
|
||||
</transition>
|
||||
<a-space direction="vertical">
|
||||
<a-card hoverable style="margin-bottom: .5rem;">
|
||||
@@ -131,45 +136,25 @@
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab='{{ i18n "pages.settings.userSettings"}}'>
|
||||
<a-form style="padding: 20px;" layout="inline">
|
||||
<table cellpadding="2">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.settings.oldUsername"}}:</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="user.oldUsername" style="width: 200px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.settings.currentPassword"}}:</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<password-input v-model="user.oldPassword" style="width: 200px"></password-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.settings.newUsername"}}:</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model="user.newUsername" style="width: 200px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.settings.newPassword"}}:</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<password-input v-model="user.newPassword" style="width: 200px"></password-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||
<a-form layout="horizontal" :colon="false" style="float: left; margin: 10px 0;" :label-col="{ md: {span:10} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.settings.oldUsername"}}'>
|
||||
<a-input v-model="user.oldUsername"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.settings.currentPassword"}}'>
|
||||
<password-input v-model="user.oldPassword"></password-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.settings.newUsername"}}'>
|
||||
<a-input v-model="user.newUsername"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.settings.newPassword"}}'>
|
||||
<password-input v-model="user.newPassword"></password-input>
|
||||
</a-form-item>
|
||||
<a-form-item label=" ">
|
||||
<a-button type="primary" @click="updateUser">{{ i18n "confirm" }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" tab='{{ i18n "pages.settings.TGBotSettings"}}'>
|
||||
@@ -186,7 +171,6 @@
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title="Telegram Bot Language" />
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="24" :xl="12">
|
||||
<template>
|
||||
<a-select
|
||||
@@ -220,6 +204,68 @@
|
||||
<setting-list-item type="number" title='{{ i18n "pages.settings.subUpdates"}}' desc='{{ i18n "pages.settings.subUpdatesDesc"}}' v-model="allSetting.subUpdates"></setting-list-item>
|
||||
</a-list>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="5" tab='{{ i18n "pages.settings.subSettings" }} Json' v-if="allSetting.subEnable">
|
||||
<a-list item-layout="horizontal">
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subPath"}}' desc='{{ i18n "pages.settings.subPathDesc"}}' v-model="allSetting.subJsonPath"></setting-list-item>
|
||||
<setting-list-item type="text" title='{{ i18n "pages.settings.subURI"}}' desc='{{ i18n "pages.settings.subURIDesc"}}' v-model="allSetting.subJsonURI" placeholder="(http|https)://domain[:port]/path/"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.settings.fragment"}}' desc='{{ i18n "pages.settings.fragmentDesc"}}' v-model="fragment"></setting-list-item>
|
||||
<setting-list-item type="switch" title='Mux' v-model="enableMux"></setting-list-item>
|
||||
<setting-list-item type="switch" title='{{ i18n "pages.xray.directCountryConfigs"}}' desc='{{ i18n "pages.xray.directCountryConfigsDesc"}}' v-model="enableDirect"></setting-list-item>
|
||||
</a-list>
|
||||
<a-collapse>
|
||||
<a-collapse-panel header='{{ i18n "pages.settings.fragment"}}' v-if="fragment">
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='Packets'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select
|
||||
v-model="fragmentPackets"
|
||||
style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p" :label="p" v-for="p in ['1-1', '1-3', 'tlshello']">
|
||||
[[ p ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
<setting-list-item type="text" title='Length' v-model="fragmentLength" placeholder="100-200"></setting-list-item>
|
||||
<setting-list-item type="text" title='Interval' v-model="fragmentInterval" placeholder="10-20"></setting-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='Mux' v-if="enableMux">
|
||||
<setting-list-item type="number" title='Concurrency' v-model="muxConcurrency" :min="-1" :max="1024"></setting-list-item>
|
||||
<setting-list-item type="number" title='xudp Concurrency' v-model="muxXudpConcurrency" :min="-1" :max="1024"></setting-list-item>
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-row>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-list-item-meta title='xudp UDP 443'/>
|
||||
</a-col>
|
||||
<a-col :lg="24" :xl="12">
|
||||
<a-select
|
||||
v-model="muxXudpProxyUDP443"
|
||||
style="width: 100%"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option :value="p" :label="p" v-for="p in ['reject', 'allow', 'skip']">
|
||||
[[ p ]]
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
<a-collapse-panel header='{{ i18n "pages.xray.directCountryConfigs"}}' v-if="enableDirect">
|
||||
<a-list-item style="padding: 20px">
|
||||
<a-checkbox-group
|
||||
v-model="directCountries"
|
||||
name="Countries"
|
||||
:options="countryOptions"
|
||||
/>
|
||||
</a-list-item>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-space>
|
||||
</a-spin>
|
||||
@@ -245,10 +291,61 @@
|
||||
saveBtnDisable: true,
|
||||
user: {},
|
||||
lang: getLang(),
|
||||
showAlert: false,
|
||||
remarkModels: {i:'Inbound',e:'Email',o:'Other'},
|
||||
remarkSeparators: [' ','-','_','@',':','~','|',',','.','/'],
|
||||
remarkSample: '',
|
||||
defaultFragment: {
|
||||
tag: "fragment",
|
||||
protocol: "freedom",
|
||||
settings: {
|
||||
domainStrategy: "AsIs",
|
||||
fragment: {
|
||||
packets: "tlshello",
|
||||
length: "100-200",
|
||||
interval: "10-20"
|
||||
}
|
||||
},
|
||||
streamSettings: {
|
||||
sockopt: {
|
||||
tcpKeepAliveIdle: 100,
|
||||
tcpNoDelay: true
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultMux: {
|
||||
enabled: true,
|
||||
concurrency: 8,
|
||||
xudpConcurrency: 16,
|
||||
xudpProxyUDP443: "reject"
|
||||
},
|
||||
defaultRules: [
|
||||
{
|
||||
type: "field",
|
||||
outboundTag: "direct",
|
||||
domain: [
|
||||
"geosite:category-ir",
|
||||
"geosite:cn"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
type: "field",
|
||||
outboundTag: "direct",
|
||||
ip: [
|
||||
"geoip:private",
|
||||
"geoip:ir",
|
||||
"geoip:cn"
|
||||
],
|
||||
enabled: true
|
||||
},
|
||||
],
|
||||
countryOptions: [
|
||||
{ label: 'Private IP/Domain', value: 'private' },
|
||||
{ label: '🇮🇷 Iran', value: 'ir' },
|
||||
{ label: '🇨🇳 China', value: 'cn' },
|
||||
{ label: '🇷🇺 Russia', value: 'ru' },
|
||||
{ label: '🇻🇳 Vietnam', value: 'vn' },
|
||||
],
|
||||
get remarkModel() {
|
||||
rm = this.allSetting.remarkModel;
|
||||
return rm.length>1 ? rm.substring(1).split('') : [];
|
||||
@@ -309,7 +406,7 @@
|
||||
title: '{{ i18n "pages.settings.restartPanel" }}',
|
||||
content: '{{ i18n "pages.settings.restartPanelDesc" }}',
|
||||
class: themeSwitcher.currentTheme,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
cancelText: '{{ i18n "cancel" }}',
|
||||
onOk: () => resolve(),
|
||||
});
|
||||
@@ -329,10 +426,117 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
if (window.location.protocol !== "https:") {
|
||||
this.showAlert = true;
|
||||
computed: {
|
||||
fragment: {
|
||||
get: function() { return this.allSetting?.subJsonFragment != ""; },
|
||||
set: function (v) {
|
||||
this.allSetting.subJsonFragment = v ? JSON.stringify(this.defaultFragment) : "";
|
||||
}
|
||||
},
|
||||
fragmentPackets: {
|
||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.packets : ""; },
|
||||
set: function(v) {
|
||||
if (v != ""){
|
||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||
newFragment.settings.fragment.packets = v;
|
||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||
}
|
||||
}
|
||||
},
|
||||
fragmentLength: {
|
||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.length : ""; },
|
||||
set: function(v) {
|
||||
if (v != ""){
|
||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||
newFragment.settings.fragment.length = v;
|
||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||
}
|
||||
}
|
||||
},
|
||||
fragmentInterval: {
|
||||
get: function() { return this.fragment ? JSON.parse(this.allSetting.subJsonFragment).settings.fragment.interval : ""; },
|
||||
set: function(v) {
|
||||
if (v != ""){
|
||||
newFragment = JSON.parse(this.allSetting.subJsonFragment);
|
||||
newFragment.settings.fragment.interval = v;
|
||||
this.allSetting.subJsonFragment = JSON.stringify(newFragment);
|
||||
}
|
||||
}
|
||||
},
|
||||
enableMux: {
|
||||
get: function() { return this.allSetting?.subJsonMux != ""; },
|
||||
set: function (v) {
|
||||
this.allSetting.subJsonMux = v ? JSON.stringify(this.defaultMux) : "";
|
||||
}
|
||||
},
|
||||
muxConcurrency: {
|
||||
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).concurrency : -1; },
|
||||
set: function(v) {
|
||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||
newMux.concurrency = v;
|
||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||
}
|
||||
},
|
||||
muxXudpConcurrency: {
|
||||
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpConcurrency : -1; },
|
||||
set: function(v) {
|
||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||
newMux.xudpConcurrency = v;
|
||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||
}
|
||||
},
|
||||
muxXudpProxyUDP443: {
|
||||
get: function() { return this.enableMux ? JSON.parse(this.allSetting.subJsonMux).xudpProxyUDP443 : "reject"; },
|
||||
set: function(v) {
|
||||
newMux = JSON.parse(this.allSetting.subJsonMux);
|
||||
newMux.xudpProxyUDP443 = v;
|
||||
this.allSetting.subJsonMux = JSON.stringify(newMux);
|
||||
}
|
||||
},
|
||||
enableDirect: {
|
||||
get: function() { return this.allSetting?.subJsonRules != ""; },
|
||||
set: function (v) {
|
||||
this.allSetting.subJsonRules = v ? JSON.stringify(this.defaultRules) : "";
|
||||
}
|
||||
},
|
||||
directCountries: {
|
||||
get: function() {
|
||||
if (!this.enableDirect) return [];
|
||||
rules = JSON.parse(this.allSetting.subJsonRules);
|
||||
return Array.isArray(rules) ? rules[1].ip.map(d => d.replace("geoip:","")) : [];
|
||||
},
|
||||
set: function (v) {
|
||||
rules = JSON.parse(this.allSetting.subJsonRules);
|
||||
if (!Array.isArray(rules)) return;
|
||||
rules[0].domain = [];
|
||||
rules[1].ip = [];
|
||||
v.forEach(d => {
|
||||
category = ["cn","private"].includes(d) ? "" : "category-";
|
||||
rules[0].domain.push("geosite:"+category+d);
|
||||
rules[1].ip.push("geoip:"+d);
|
||||
});
|
||||
this.allSetting.subJsonRules = JSON.stringify(rules);
|
||||
}
|
||||
},
|
||||
confAlerts: {
|
||||
get: function() {
|
||||
if (!this.allSetting) return [];
|
||||
var alerts = []
|
||||
if (window.location.protocol !== "https:") alerts.push('{{ i18n "secAlertSSL" }}');
|
||||
if (this.allSetting.webPort == 54321) alerts.push('{{ i18n "secAlertPanelPort" }}');
|
||||
panelPath = window.location.pathname.split('/').length<4
|
||||
if (panelPath && this.allSetting.webBasePath == '/') alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||
if (this.allSetting.subEnable) {
|
||||
subPath = this.allSetting.subURI.length >0 ? new URL(this.allSetting.subURI).pathname : this.allSetting.subPath;
|
||||
if (subPath == '/sub/') alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||
subJsonPath = this.allSetting.subJsonURI.length >0 ? new URL(this.allSetting.subJsonURI).pathname : this.allSetting.subJsonPath;
|
||||
if (subJsonPath == '/json/') alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||
}
|
||||
return alerts
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.getAllSetting();
|
||||
while (true) {
|
||||
await PromiseUtil.sleep(1000);
|
||||
@@ -342,4 +546,4 @@
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
204
web/html/xui/warp_modal.html
Normal file
@@ -0,0 +1,204 @@
|
||||
{{define "warpModal"}}
|
||||
<a-modal id="warp-modal" v-model="warpModal.visible" title="Cloudflare WARP"
|
||||
:confirm-loading="warpModal.confirmLoading" :closable="true" :mask-closable="true"
|
||||
:footer="null" :class="themeSwitcher.currentTheme">
|
||||
<template v-if="ObjectUtil.isEmpty(warpModal.warpData)">
|
||||
<a-button icon="api" @click="register" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.create" }}</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<table style="margin: 5px 0; width: 100%;">
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Access Token</td>
|
||||
<td>[[ warpModal.warpData.access_token ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Device ID</td>
|
||||
<td>[[ warpModal.warpData.device_id ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>License Key</td>
|
||||
<td>[[ warpModal.warpData.license_key ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Private Key</td>
|
||||
<td>[[ warpModal.warpData.private_key ]]</td>
|
||||
</tr>
|
||||
</table>
|
||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.settings" }}</a-divider>
|
||||
<a-collapse style="margin: 10px 0;">
|
||||
<a-collapse-panel header='WARP/WARP+ License Key'>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label="Key">
|
||||
<a-input v-model="warpPlus"></a-input>
|
||||
<a-button @click="updateLicense(warpPlus)" :disabled="warpPlus.length<26" :loading="warpModal.confirmLoading">{{ i18n "pages.inbounds.update" }}</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
<a-divider style="margin: 0;">{{ i18n "pages.xray.outbound.accountInfo" }}</a-divider>
|
||||
<a-button icon="sync" @click="getConfig" style="margin-top: 5px; margin-bottom: 10px;" :loading="warpModal.confirmLoading" type="primary">{{ i18n "info" }}</a-button>
|
||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig)">
|
||||
<table style="width: 100%">
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Device Name</td>
|
||||
<td>[[ warpModal.warpConfig.name ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Device Model</td>
|
||||
<td>[[ warpModal.warpConfig.model ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Device Enabled</td>
|
||||
<td>[[ warpModal.warpConfig.enabled ]]</td>
|
||||
</tr>
|
||||
<template v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account)">
|
||||
<tr>
|
||||
<td>Account Type</td>
|
||||
<td>[[ warpModal.warpConfig.account.account_type ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Role</td>
|
||||
<td>[[ warpModal.warpConfig.account.role ]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>WARP+ Data</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.premium_data) ]]</td>
|
||||
</tr>
|
||||
<tr class="client-table-odd-row">
|
||||
<td>Quota</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.quota) ]]</td>
|
||||
</tr>
|
||||
<tr v-if="!ObjectUtil.isEmpty(warpModal.warpConfig.account.usage)">
|
||||
<td>Usage</td>
|
||||
<td>[[ sizeFormat(warpModal.warpConfig.account.usage) ]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
<a-divider style="margin: 10px 0;">{{ i18n "pages.xray.outbound.outboundStatus" }}</a-divider>
|
||||
<a-form :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
|
||||
<template v-if="warpOutboundIndex>=0">
|
||||
<a-tag color="green" style="line-height: 31px;">{{ i18n "enabled" }}</a-tag>
|
||||
<a-button @click="resetOutbound" :loading="warpModal.confirmLoading" type="danger">{{ i18n "reset" }}</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-tag color="orange" style="line-height: 31px;">{{ i18n "disabled" }}</a-tag>
|
||||
<a-button @click="addOutbound" :loading="warpModal.confirmLoading" type="primary">{{ i18n "pages.xray.outbound.addOutbound" }}</a-button>
|
||||
</template>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
</a-modal>
|
||||
<script>
|
||||
|
||||
const warpModal = {
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
warpData: null,
|
||||
warpConfig: null,
|
||||
warpOutbound: null,
|
||||
show() {
|
||||
this.visible = true;
|
||||
this.warpConfig = null;
|
||||
this.getData();
|
||||
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
this.loading(false);
|
||||
},
|
||||
loading(loading=true) {
|
||||
this.confirmLoading = loading;
|
||||
},
|
||||
async getData(){
|
||||
this.loading(true);
|
||||
const msg = await HttpUtil.post('/xui/xray/warp/data');
|
||||
this.loading(false);
|
||||
if (msg.success) {
|
||||
this.warpData = msg.obj.length>0 ? JSON.parse(msg.obj): null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#warp-modal',
|
||||
data: {
|
||||
warpModal: warpModal,
|
||||
warpPlus: '',
|
||||
},
|
||||
methods: {
|
||||
collectConfig() {
|
||||
config = warpModal.warpConfig.config;
|
||||
peer = config.peers[0];
|
||||
if(config){
|
||||
warpModal.warpOutbound = Outbound.fromJson({
|
||||
tag: 'warp',
|
||||
protocol: Protocols.Wireguard,
|
||||
settings: {
|
||||
mtu: 1420,
|
||||
secretKey: warpModal.warpData.private_key,
|
||||
address: Object.values(config.interface.addresses),
|
||||
domainStrategy: 'ForceIP',
|
||||
peers: [{
|
||||
publicKey: peer.public_key,
|
||||
endpoint: peer.endpoint.host,
|
||||
}],
|
||||
kernelMode: false
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
async register(){
|
||||
warpModal.loading(true);
|
||||
keys = Wireguard.generateKeypair();
|
||||
const msg = await HttpUtil.post('/xui/xray/warp/reg',keys);
|
||||
if (msg.success) {
|
||||
resp = JSON.parse(msg.obj);
|
||||
warpModal.warpData = resp.data;
|
||||
warpModal.warpConfig = resp.config;
|
||||
this.collectConfig();
|
||||
}
|
||||
warpModal.loading(false);
|
||||
},
|
||||
async updateLicense(l){
|
||||
warpModal.loading(true);
|
||||
const msg = await HttpUtil.post('/xui/xray/warp/license',{license: l});
|
||||
if (msg.success) {
|
||||
warpModal.warpData = JSON.parse(msg.obj);
|
||||
warpModal.warpConfig = null;
|
||||
this.warpPlus = '';
|
||||
}
|
||||
warpModal.loading(false);
|
||||
},
|
||||
async getConfig(){
|
||||
warpModal.loading(true);
|
||||
const msg = await HttpUtil.post('/xui/xray/warp/config');
|
||||
warpModal.loading(false);
|
||||
if (msg.success) {
|
||||
warpModal.warpConfig = JSON.parse(msg.obj);
|
||||
this.collectConfig();
|
||||
}
|
||||
},
|
||||
addOutbound(){
|
||||
app.templateSettings.outbounds.push(warpModal.warpOutbound.toJson());
|
||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||
warpModal.close();
|
||||
},
|
||||
resetOutbound(){
|
||||
app.templateSettings.outbounds[this.warpOutboundIndex] = warpModal.warpOutbound.toJson();
|
||||
app.outboundSettings = JSON.stringify(app.templateSettings.outbounds);
|
||||
warpModal.close();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
warpOutboundIndex: {
|
||||
get: function() {
|
||||
return app.templateSettings ? app.templateSettings.outbounds.findIndex((o) => o.tag == 'warp') : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
114
web/html/xui/xray_balancer_modal.html
Normal file
@@ -0,0 +1,114 @@
|
||||
{{define "balancerModal"}}
|
||||
<a-modal
|
||||
id="balancer-modal"
|
||||
v-model="balancerModal.visible"
|
||||
:title="balancerModal.title"
|
||||
@ok="balancerModal.ok"
|
||||
:confirm-loading="balancerModal.confirmLoading"
|
||||
:ok-button-props="{ props: { disabled: !balancerModal.isValid } }"
|
||||
:closable="true"
|
||||
:mask-closable="false"
|
||||
:ok-text="balancerModal.okText"
|
||||
cancel-text='{{ i18n "close" }}'
|
||||
:class="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.balancer.tag" }}' has-feedback
|
||||
:validate-status="balancerModal.duplicateTag? 'warning' : 'success'">
|
||||
<a-input v-model.trim="balancerModal.balancer.tag" @change="balancerModal.check()"
|
||||
placeholder='{{ i18n "pages.xray.balancer.tagDesc" }}'></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerStrategy" }}'>
|
||||
<a-select v-model="balancerModal.balancer.strategy" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option value="random">Random</a-select-option>
|
||||
<a-select-option value="roundRobin">Round Robin</a-select-option>
|
||||
<a-select-option value="leastLoad">Least Load</a-select-option>
|
||||
<a-select-option value="leastPing">Least Ping</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.balancer.balancerSelectors" }}' has-feedback :validate-status="balancerModal.emptySelector? 'warning' : 'success'">
|
||||
<a-select v-model="balancerModal.balancer.selector" mode="tags" @change="balancerModal.checkSelector()"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in balancerModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
const balancerModal = {
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
duplicateTag: false,
|
||||
emptySelector: false,
|
||||
balancer: {
|
||||
tag: '',
|
||||
strategy: 'random',
|
||||
selector: []
|
||||
},
|
||||
outboundTags: [],
|
||||
balancerTags:[],
|
||||
ok() {
|
||||
if (balancerModal.balancer.selector.length == 0) {
|
||||
balancerModal.emptySelector = true;
|
||||
return;
|
||||
}
|
||||
balancerModal.emptySelector = false;
|
||||
ObjectUtil.execute(balancerModal.confirm, balancerModal.balancer);
|
||||
},
|
||||
show({ title = '', okText = '{{ i18n "sure" }}', balancerTags = [], balancer, confirm = (balancer) => { }, isEdit = false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
this.visible = true;
|
||||
if (isEdit) {
|
||||
balancerModal.balancer = balancer;
|
||||
} else {
|
||||
balancerModal.balancer = {
|
||||
tag: '',
|
||||
strategy: 'random',
|
||||
selector: []
|
||||
};
|
||||
}
|
||||
this.balancerTags = balancerTags.filter((tag) => tag != balancer.tag);
|
||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||
this.isEdit = isEdit;
|
||||
this.check();
|
||||
this.checkSelector();
|
||||
},
|
||||
close() {
|
||||
this.visible = false;
|
||||
this.loading(false);
|
||||
},
|
||||
loading(loading=true) {
|
||||
this.confirmLoading = loading;
|
||||
},
|
||||
check() {
|
||||
if (this.balancer.tag == '' || this.balancerTags.includes(this.balancer.tag)) {
|
||||
this.duplicateTag = true;
|
||||
this.isValid = false;
|
||||
} else {
|
||||
this.duplicateTag = false;
|
||||
this.isValid = true;
|
||||
}
|
||||
},
|
||||
checkSelector() {
|
||||
this.emptySelector = this.balancer.selector.length == 0;
|
||||
}
|
||||
};
|
||||
|
||||
new Vue({
|
||||
delimiters: ['[[', ']]'],
|
||||
el: '#balancer-modal',
|
||||
data: {
|
||||
balancerModal: balancerModal
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{{end}}
|
||||
@@ -11,7 +11,7 @@
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
outbound: new Outbound(),
|
||||
@@ -25,7 +25,7 @@
|
||||
ok() {
|
||||
ObjectUtil.execute(outModal.confirm, outModal.outbound.toJson());
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) {
|
||||
show({ title='', okText='{{ i18n "confirm" }}', outbound, confirm=(outbound)=>{}, isEdit=false, tags=[] }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
@@ -42,7 +42,7 @@
|
||||
outModal.visible = false;
|
||||
outModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
loading(loading=true) {
|
||||
outModal.confirmLoading = loading;
|
||||
},
|
||||
check(){
|
||||
|
||||
@@ -2,79 +2,42 @@
|
||||
<a-modal id="reverse-modal" v-model="reverseModal.visible" :title="reverseModal.title" @ok="reverseModal.ok"
|
||||
:confirm-loading="reverseModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-text="reverseModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.outbound.type" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="reverseModal.reverse.type" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.outbound.tag" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="reverseModal.reverse.tag" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.outbound.domain" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="reverseModal.reverse.domain" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.type" }}'>
|
||||
<a-select v-model="reverseModal.reverse.type" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x,y in reverseTypes" :value="y">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.tag" }}'>
|
||||
<a-input v-model.trim="reverseModal.reverse.tag"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.domain" }}'>
|
||||
<a-input v-model.trim="reverseModal.reverse.domain"></a-input>
|
||||
</a-form-item>
|
||||
<template v-if="reverseModal.reverse.type=='bridge'">
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.outbound.intercon" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="reverseModal.rules[0].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.rules.outbound" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="reverseModal.rules[1].outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||
<a-select v-model="reverseModal.rules[0].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.rules.outbound" }}'>
|
||||
<a-select v-model="reverseModal.rules[1].outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in reverseModal.outboundTags" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.outbound.intercon" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-checkbox-group
|
||||
v-model="reverseModal.rules[0].inboundTag"
|
||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ i18n "pages.xray.rules.inbound" }}</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-checkbox-group
|
||||
v-model="reverseModal.rules[1].inboundTag"
|
||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<a-form-item label='{{ i18n "pages.xray.outbound.intercon" }}'>
|
||||
<a-checkbox-group
|
||||
v-model="reverseModal.rules[0].inboundTag"
|
||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||
</a-form-item>
|
||||
<a-form-item label='{{ i18n "pages.xray.rules.inbound" }}'>
|
||||
<a-checkbox-group
|
||||
v-model="reverseModal.rules[1].inboundTag"
|
||||
:options="reverseModal.inboundTags"></a-checkbox-group>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<script>
|
||||
@@ -82,7 +45,7 @@
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
reverse: {
|
||||
@@ -110,7 +73,7 @@
|
||||
}
|
||||
ObjectUtil.execute(reverseModal.confirm, reverseModal.reverse, reverseModal.rules);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
|
||||
show({ title='', okText='{{ i18n "confirm" }}', reverse, rules, confirm=(reverse, rules)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
@@ -150,13 +113,14 @@
|
||||
this.isEdit = isEdit;
|
||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||
this.inboundTags.push(...app.inboundTags);
|
||||
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||
},
|
||||
close() {
|
||||
reverseModal.visible = false;
|
||||
reverseModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
loading(loading=true) {
|
||||
reverseModal.confirmLoading = loading;
|
||||
},
|
||||
};
|
||||
@@ -167,8 +131,6 @@
|
||||
data: {
|
||||
reverseModal: reverseModal,
|
||||
reverseTypes: { bridge: '{{ i18n "pages.xray.outbound.bridge" }}', portal:'{{ i18n "pages.xray.outbound.portal" }}'},
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,149 +2,124 @@
|
||||
<a-modal id="rule-modal" v-model="ruleModal.visible" :title="ruleModal.title" @ok="ruleModal.ok"
|
||||
:confirm-loading="ruleModal.confirmLoading" :closable="true" :mask-closable="false"
|
||||
:ok-text="ruleModal.okText" cancel-text='{{ i18n "close" }}' :class="themeSwitcher.currentTheme">
|
||||
<a-form layout="inline">
|
||||
<table width="100%" class="ant-table-tbody">
|
||||
<tr>
|
||||
<td style="width: 30%;">Domain Matcher</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="ruleModal.rule.domainMatcher" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-form :colon="false" :label-col="{ md: {span:6} }" :wrapper-col="{ md: {span:14} }">
|
||||
<a-form-item label='Domain Matcher'>
|
||||
<a-select v-model="ruleModal.rule.domainMatcher" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="dm in ['','hybrid','linear']" :value="dm">[[ dm ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Source IPs
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
Source IPs <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="ruleModal.rule.source" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Source Port
|
||||
</template>
|
||||
<a-input v-model.trim="ruleModal.rule.source"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
Source Port <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="ruleModal.rule.sourcePort" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Network</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="ruleModal.rule.network" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['','tcp','tdp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Protocol</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="ruleModal.rule.protocol" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['','http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a-form-item>
|
||||
<span>Attributes</span>
|
||||
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
|
||||
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
||||
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP
|
||||
</template>
|
||||
<a-input v-model.trim="ruleModal.rule.sourcePort"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Network'>
|
||||
<a-select v-model="ruleModal.rule.network" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['','tcp','udp','tcp,udp']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Protocol'>
|
||||
<a-select v-model="ruleModal.rule.protocol" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="x in ['http','tls','bittorrent']" :value="x">[[ x ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Attributes'>
|
||||
<a-button size="small" style="margin-left: 10px" @click="ruleModal.rule.attrs.push(['', ''])">+</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item :wrapper-col="{span: 24}">
|
||||
<a-input-group compact v-for="(attr,index) in ruleModal.rule.attrs">
|
||||
<a-input style="width: 50%" v-model="attr[0]" placeholder='{{ i18n "pages.inbounds.stream.general.name" }}'>
|
||||
<template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
|
||||
</a-input>
|
||||
<a-input style="width: 50%" v-model="attr[1]" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
|
||||
<a-button slot="addonAfter" size="small" @click="ruleModal.rule.attrs.splice(index,1)">-</a-button>
|
||||
</a-input>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
IP <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="ruleModal.rule.ip" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Domain
|
||||
</template>
|
||||
<a-input v-model.trim="ruleModal.rule.ip"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
Domain <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="ruleModal.rule.domain" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Port
|
||||
</template>
|
||||
<a-input v-model.trim="ruleModal.rule.domain"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
<a-icon type="question-circle"></a-icon>
|
||||
User <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-input v-model.trim="ruleModal.rule.port" style="width: 250px"></a-input>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Inbound Tags</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Outbound Tag</td>
|
||||
<td>
|
||||
<a-form-item>
|
||||
<a-select v-model="ruleModal.rule.outboundTag" style="width: 250px;" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<a-input v-model.trim="ruleModal.rule.user"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.rules.useComma" }}</span>
|
||||
</template>
|
||||
Port <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-input v-model.trim="ruleModal.rule.port"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Inbound Tags'>
|
||||
<a-select v-model="ruleModal.rule.inboundTag" mode="multiple" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.inboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label='Outbound Tag'>
|
||||
<a-select v-model="ruleModal.rule.outboundTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.outboundTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<template slot="label">
|
||||
<a-tooltip>
|
||||
<template slot="title">
|
||||
<span>{{ i18n "pages.xray.balancer.balancerDesc" }}</span>
|
||||
</template>
|
||||
Balancer Tag <a-icon type="question-circle"></a-icon>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<a-select v-model="ruleModal.rule.balancerTag" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="tag in ruleModal.balancerTags" :value="tag">[[ tag ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</table>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
@@ -154,7 +129,7 @@
|
||||
title: '',
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
okText: '{{ i18n "sure" }}',
|
||||
okText: '{{ i18n "confirm" }}',
|
||||
isEdit: false,
|
||||
confirm: null,
|
||||
rule: {
|
||||
@@ -171,6 +146,7 @@
|
||||
protocol: [],
|
||||
attrs: [],
|
||||
outboundTag: "",
|
||||
balancerTag: "",
|
||||
},
|
||||
inboundTags: [],
|
||||
outboundTags: [],
|
||||
@@ -180,7 +156,7 @@
|
||||
newRule = ruleModal.getResult();
|
||||
ObjectUtil.execute(ruleModal.confirm, newRule);
|
||||
},
|
||||
show({ title='', okText='{{ i18n "sure" }}', rule, confirm=(rule)=>{}, isEdit=false }) {
|
||||
show({ title='', okText='{{ i18n "confirm" }}', rule, confirm=(rule)=>{}, isEdit=false }) {
|
||||
this.title = title;
|
||||
this.okText = okText;
|
||||
this.confirm = confirm;
|
||||
@@ -198,6 +174,7 @@
|
||||
this.rule.protocol = rule.protocol;
|
||||
this.rule.attrs = rule.attrs ? Object.entries(rule.attrs) : [];
|
||||
this.rule.outboundTag = rule.outboundTag;
|
||||
this.rule.balancerTag = rule.balancerTag ? rule.balancerTag : "";
|
||||
} else {
|
||||
this.rule = {
|
||||
domainMatcher: "",
|
||||
@@ -212,24 +189,29 @@
|
||||
protocol: [],
|
||||
attrs: [],
|
||||
outboundTag: "",
|
||||
balancerTag: "",
|
||||
}
|
||||
}
|
||||
this.isEdit = isEdit;
|
||||
this.inboundTags = app.templateSettings.inbounds.filter((i) => !ObjectUtil.isEmpty(i.tag)).map(obj => obj.tag);
|
||||
this.inboundTags.push(...app.inboundTags);
|
||||
this.outboundTags = app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag);
|
||||
if (app.enableDNS && !ObjectUtil.isEmpty(app.dnsTag)) this.inboundTags.push(app.dnsTag)
|
||||
this.outboundTags = ["", ...app.templateSettings.outbounds.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
||||
if(app.templateSettings.reverse){
|
||||
if(app.templateSettings.reverse.bridges) {
|
||||
this.inboundTags.push(...app.templateSettings.reverse.bridges.map(b => b.tag));
|
||||
}
|
||||
if(app.templateSettings.reverse.portals) this.outboundTags.push(...app.templateSettings.reverse.portals.map(b => b.tag));
|
||||
}
|
||||
if (app.templateSettings.routing && app.templateSettings.routing.balancers) {
|
||||
this.balancerTags = [ "", ...app.templateSettings.routing.balancers.filter((o) => !ObjectUtil.isEmpty(o.tag)).map(obj => obj.tag)];
|
||||
}
|
||||
},
|
||||
close() {
|
||||
ruleModal.visible = false;
|
||||
ruleModal.loading(false);
|
||||
},
|
||||
loading(loading) {
|
||||
loading(loading=true) {
|
||||
ruleModal.confirmLoading = loading;
|
||||
},
|
||||
getResult() {
|
||||
@@ -248,7 +230,8 @@
|
||||
rule.inboundTag = value.inboundTag;
|
||||
rule.protocol = value.protocol;
|
||||
rule.attrs = Object.fromEntries(value.attrs);
|
||||
rule.outboundTag = value.outboundTag;
|
||||
rule.outboundTag = value.outboundTag == "" ? undefined : value.outboundTag;
|
||||
rule.balancerTag = value.balancerTag == "" ? undefined : value.balancerTag;
|
||||
|
||||
for (const [key, value] of Object.entries(rule)) {
|
||||
if (
|
||||
|
||||
@@ -3,6 +3,7 @@ package job
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"x-ui/web/service"
|
||||
|
||||
"github.com/shirou/gopsutil/v3/cpu"
|
||||
|
||||
@@ -31,5 +31,4 @@ func (j *XrayTrafficJob) Run() {
|
||||
if needRestart {
|
||||
j.xrayService.SetToNeedRestart()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"strings"
|
||||
|
||||
"x-ui/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -12,9 +13,11 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var i18nBundle *i18n.Bundle
|
||||
var LocalizerWeb *i18n.Localizer
|
||||
var LocalizerBot *i18n.Localizer
|
||||
var (
|
||||
i18nBundle *i18n.Bundle
|
||||
LocalizerWeb *i18n.Localizer
|
||||
LocalizerBot *i18n.Localizer
|
||||
)
|
||||
|
||||
type I18nType string
|
||||
|
||||
@@ -79,7 +82,6 @@ func I18n(i18nType I18nType, key string, params ...string) string {
|
||||
MessageID: key,
|
||||
TemplateData: templateData,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to localize message: %v", err)
|
||||
return ""
|
||||
@@ -135,7 +137,6 @@ func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
|
||||
_, err = i18nBundle.ParseMessageFileBytes(data, path)
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
@@ -38,9 +39,25 @@ func (s *InboundService) GetAllInbounds() ([]*model.Inbound, error) {
|
||||
return inbounds, nil
|
||||
}
|
||||
|
||||
func (s *InboundService) checkPortExist(port int, ignoreId int) (bool, error) {
|
||||
func (s *InboundService) checkPortExist(listen string, port int, ignoreId int) (bool, error) {
|
||||
db := database.GetDB()
|
||||
db = db.Model(model.Inbound{}).Where("port = ?", port)
|
||||
if listen == "" || listen == "0.0.0.0" || listen == "::" || listen == "::0" {
|
||||
db = db.Model(model.Inbound{}).Where("port = ?", port)
|
||||
} else {
|
||||
db = db.Model(model.Inbound{}).
|
||||
Where("port = ?", port).
|
||||
Where(
|
||||
db.Model(model.Inbound{}).Where(
|
||||
"listen = ?", listen,
|
||||
).Or(
|
||||
"listen = \"\"",
|
||||
).Or(
|
||||
"listen = \"0.0.0.0\"",
|
||||
).Or(
|
||||
"listen = \"::\"",
|
||||
).Or(
|
||||
"listen = \"::0\""))
|
||||
}
|
||||
if ignoreId > 0 {
|
||||
db = db.Where("id != ?", ignoreId)
|
||||
}
|
||||
@@ -74,7 +91,6 @@ func (s *InboundService) getAllEmails() ([]string, error) {
|
||||
FROM inbounds,
|
||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
||||
`).Scan(&emails).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +151,7 @@ func (s *InboundService) checkEmailExistForInbound(inbound *model.Inbound) (stri
|
||||
}
|
||||
|
||||
func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
|
||||
exist, err := s.checkPortExist(inbound.Port, 0)
|
||||
exist, err := s.checkPortExist(inbound.Listen, inbound.Port, 0)
|
||||
if err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
@@ -156,6 +172,23 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
return inbound, false, err
|
||||
}
|
||||
|
||||
// Secure client ID
|
||||
for _, client := range clients {
|
||||
if inbound.Protocol == "trojan" {
|
||||
if client.Password == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
if client.Email == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
} else {
|
||||
if client.ID == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
tx := db.Begin()
|
||||
defer func() {
|
||||
@@ -238,7 +271,7 @@ func (s *InboundService) GetInbound(id int) (*model.Inbound, error) {
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound, bool, error) {
|
||||
exist, err := s.checkPortExist(inbound.Port, inbound.Id)
|
||||
exist, err := s.checkPortExist(inbound.Listen, inbound.Port, inbound.Id)
|
||||
if err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
@@ -281,7 +314,11 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
oldInbound.Settings = inbound.Settings
|
||||
oldInbound.StreamSettings = inbound.StreamSettings
|
||||
oldInbound.Sniffing = inbound.Sniffing
|
||||
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
|
||||
oldInbound.Tag = fmt.Sprintf("inbound-%v", inbound.Port)
|
||||
} else {
|
||||
oldInbound.Tag = fmt.Sprintf("inbound-%v:%v", inbound.Listen, inbound.Port)
|
||||
}
|
||||
|
||||
needRestart := false
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
@@ -379,6 +416,23 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Secure client ID
|
||||
for _, client := range clients {
|
||||
if oldInbound.Protocol == "trojan" {
|
||||
if client.Password == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
} else if oldInbound.Protocol == "shadowsocks" {
|
||||
if client.Email == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
} else {
|
||||
if client.ID == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var oldSettings map[string]interface{}
|
||||
err = json.Unmarshal([]byte(oldInbound.Settings), &oldSettings)
|
||||
if err != nil {
|
||||
@@ -462,9 +516,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
client_key = "email"
|
||||
}
|
||||
|
||||
inerfaceClients := settings["clients"].([]interface{})
|
||||
interfaceClients := settings["clients"].([]interface{})
|
||||
var newClients []interface{}
|
||||
for _, client := range inerfaceClients {
|
||||
for _, client := range interfaceClients {
|
||||
c := client.(map[string]interface{})
|
||||
c_id := c[client_key].(string)
|
||||
if c_id == clientId {
|
||||
@@ -474,6 +528,10 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) (bool,
|
||||
}
|
||||
}
|
||||
|
||||
if len(newClients) == 0 {
|
||||
return false, common.NewError("no client remained in Inbound")
|
||||
}
|
||||
|
||||
settings["clients"] = newClients
|
||||
newSettings, err := json.MarshalIndent(settings, "", " ")
|
||||
if err != nil {
|
||||
@@ -529,15 +587,19 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
}
|
||||
|
||||
oldEmail := ""
|
||||
newClientId := ""
|
||||
clientIndex := 0
|
||||
for index, oldClient := range oldClients {
|
||||
oldClientId := ""
|
||||
if oldInbound.Protocol == "trojan" {
|
||||
oldClientId = oldClient.Password
|
||||
newClientId = clients[0].Password
|
||||
} else if oldInbound.Protocol == "shadowsocks" {
|
||||
oldClientId = oldClient.Email
|
||||
newClientId = clients[0].Email
|
||||
} else {
|
||||
oldClientId = oldClient.ID
|
||||
newClientId = clients[0].ID
|
||||
}
|
||||
if clientId == oldClientId {
|
||||
oldEmail = oldClient.Email
|
||||
@@ -546,6 +608,11 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
||||
}
|
||||
}
|
||||
|
||||
// Validate new client ID
|
||||
if newClientId == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
|
||||
if len(clients[0].Email) > 0 && clients[0].Email != oldEmail {
|
||||
existEmail, err := s.checkEmailsExistForClients(clients)
|
||||
if err != nil {
|
||||
@@ -917,7 +984,7 @@ func (s *InboundService) disableInvalidInbounds(tx *gorm.DB) (bool, int64, error
|
||||
s.xrayApi.Init(p.GetAPIPort())
|
||||
for _, tag := range tags {
|
||||
err1 := s.xrayApi.DelInbound(tag)
|
||||
if err == nil {
|
||||
if err1 == nil {
|
||||
logger.Debug("Inbound disabled by api:", tag)
|
||||
} else {
|
||||
logger.Debug("Error in disabling inbound by api:", err1)
|
||||
@@ -997,10 +1064,7 @@ func (s *InboundService) AddClientStat(tx *gorm.DB, inboundId int, client *model
|
||||
clientTraffic.Reset = client.Reset
|
||||
result := tx.Create(&clientTraffic)
|
||||
err := result.Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *model.Client) error {
|
||||
@@ -1011,13 +1075,12 @@ func (s *InboundService) UpdateClientStat(tx *gorm.DB, email string, client *mod
|
||||
"email": client.Email,
|
||||
"total": client.TotalGB,
|
||||
"expiry_time": client.ExpiryTime,
|
||||
"reset": client.Reset})
|
||||
"reset": client.Reset,
|
||||
})
|
||||
err := result.Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *InboundService) DelClientStat(tx *gorm.DB, email string) error {
|
||||
return tx.Where("email = ?", email).Delete(xray.ClientTraffic{}).Error
|
||||
}
|
||||
@@ -1098,11 +1161,7 @@ func (s *InboundService) ResetAllClientTraffics(id int) error {
|
||||
Updates(map[string]interface{}{"enable": true, "up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *InboundService) ResetAllTraffics() error {
|
||||
@@ -1113,11 +1172,7 @@ func (s *InboundService) ResetAllTraffics() error {
|
||||
Updates(map[string]interface{}{"up": 0, "down": 0})
|
||||
|
||||
err := result.Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *InboundService) DelDepletedClients(id int) (err error) {
|
||||
@@ -1316,7 +1371,7 @@ func (s *InboundService) MigrationRequirements() {
|
||||
json.Unmarshal([]byte(inbounds[inbound_index].Settings), &settings)
|
||||
clients, ok := settings["clients"].([]interface{})
|
||||
if ok {
|
||||
// Fix Clinet configuration problems
|
||||
// Fix Client configuration problems
|
||||
var newClients []interface{}
|
||||
for client_index := range clients {
|
||||
c := clients[client_index].(map[string]interface{})
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"x-ui/logger"
|
||||
)
|
||||
|
||||
type PanelService struct {
|
||||
}
|
||||
type PanelService struct{}
|
||||
|
||||
func (s *PanelService) RestartPanel(delay time.Duration) error {
|
||||
p, err := os.FindProcess(syscall.Getpid())
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/logger"
|
||||
@@ -33,9 +34,9 @@ import (
|
||||
type ProcessState string
|
||||
|
||||
const (
|
||||
Running ProcessState = "running"
|
||||
Stop ProcessState = "stop"
|
||||
Error ProcessState = "error"
|
||||
Running ProcessState = "Running"
|
||||
Stop ProcessState = "Stop"
|
||||
Error ProcessState = "Error"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
@@ -250,7 +251,6 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
|
||||
}
|
||||
|
||||
func (s *ServerService) StopXrayService() (string error) {
|
||||
|
||||
err := s.xrayService.StopXray()
|
||||
if err != nil {
|
||||
logger.Error("stop xray failed:", err)
|
||||
@@ -261,7 +261,6 @@ func (s *ServerService) StopXrayService() (string error) {
|
||||
}
|
||||
|
||||
func (s *ServerService) RestartXrayService() (string error) {
|
||||
|
||||
s.xrayService.StopXray()
|
||||
defer func() {
|
||||
err := s.xrayService.RestartXray(true)
|
||||
@@ -365,7 +364,6 @@ func (s *ServerService) UpdateXray(version string) error {
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (s *ServerService) GetLogs(count string, level string, syslog string) []string {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
@@ -55,10 +56,15 @@ var defaultValueMap = map[string]string{
|
||||
"subEncrypt": "true",
|
||||
"subShowInfo": "false",
|
||||
"subURI": "",
|
||||
"subJsonPath": "/json/",
|
||||
"subJsonURI": "",
|
||||
"subJsonFragment": "",
|
||||
"subJsonMux": "",
|
||||
"subJsonRules": "",
|
||||
"warp": "",
|
||||
}
|
||||
|
||||
type SettingService struct {
|
||||
}
|
||||
type SettingService struct{}
|
||||
|
||||
func (s *SettingService) GetAllSetting() (*entity.AllSetting, error) {
|
||||
db := database.GetDB()
|
||||
@@ -352,17 +358,11 @@ func (s *SettingService) GetSubPort() (int, error) {
|
||||
}
|
||||
|
||||
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
|
||||
return s.getString("subPath")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonPath() (string, error) {
|
||||
return s.getString("subJsonPath")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubDomain() (string, error) {
|
||||
@@ -377,8 +377,8 @@ func (s *SettingService) GetSubKeyFile() (string, error) {
|
||||
return s.getString("subKeyFile")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubUpdates() (int, error) {
|
||||
return s.getInt("subUpdates")
|
||||
func (s *SettingService) GetSubUpdates() (string, error) {
|
||||
return s.getString("subUpdates")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubEncrypt() (bool, error) {
|
||||
@@ -397,6 +397,30 @@ func (s *SettingService) GetSubURI() (string, error) {
|
||||
return s.getString("subURI")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonURI() (string, error) {
|
||||
return s.getString("subJsonURI")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonFragment() (string, error) {
|
||||
return s.getString("subJsonFragment")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonMux() (string, error) {
|
||||
return s.getString("subJsonMux")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetSubJsonRules() (string, error) {
|
||||
return s.getString("subJsonRules")
|
||||
}
|
||||
|
||||
func (s *SettingService) GetWarp() (string, error) {
|
||||
return s.getString("warp")
|
||||
}
|
||||
|
||||
func (s *SettingService) SetWarp(data string) error {
|
||||
return s.setString("warp", data)
|
||||
}
|
||||
|
||||
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {
|
||||
if err := allSetting.CheckValid(); err != nil {
|
||||
return err
|
||||
@@ -438,6 +462,7 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
"tgBotEnable": func() (interface{}, error) { return s.GetTgbotenabled() },
|
||||
"subEnable": func() (interface{}, error) { return s.GetSubEnable() },
|
||||
"subURI": func() (interface{}, error) { return s.GetSubURI() },
|
||||
"subJsonURI": func() (interface{}, error) { return s.GetSubJsonURI() },
|
||||
"remarkModel": func() (interface{}, error) { return s.GetRemarkModel() },
|
||||
}
|
||||
|
||||
@@ -451,10 +476,11 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
if result["subEnable"].(bool) && result["subURI"].(string) == "" {
|
||||
if result["subEnable"].(bool) && (result["subURI"].(string) == "" || result["subJsonURI"].(string) == "") {
|
||||
subURI := ""
|
||||
subPort, _ := s.GetSubPort()
|
||||
subPath, _ := s.GetSubPath()
|
||||
subJsonPath, _ := s.GetSubJsonPath()
|
||||
subDomain, _ := s.GetSubDomain()
|
||||
subKeyFile, _ := s.GetSubKeyFile()
|
||||
subCertFile, _ := s.GetSubCertFile()
|
||||
@@ -475,12 +501,12 @@ func (s *SettingService) GetDefaultSettings(host string) (interface{}, error) {
|
||||
} else {
|
||||
subURI += fmt.Sprintf("%s:%d", subDomain, subPort)
|
||||
}
|
||||
if subPath[0] == byte('/') {
|
||||
subURI += subPath
|
||||
} else {
|
||||
subURI += "/" + subPath
|
||||
if result["subURI"].(string) == "" {
|
||||
result["subURI"] = subURI + subPath
|
||||
}
|
||||
if result["subJsonURI"].(string) == "" {
|
||||
result["subJsonURI"] = subURI + subJsonPath
|
||||
}
|
||||
result["subURI"] = subURI
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"x-ui/config"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
@@ -19,10 +20,12 @@ import (
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
var bot *tgbotapi.BotAPI
|
||||
var adminIds []int64
|
||||
var isRunning bool
|
||||
var hostname string
|
||||
var (
|
||||
bot *tgbotapi.BotAPI
|
||||
adminIds []int64
|
||||
isRunning bool
|
||||
hostname string
|
||||
)
|
||||
|
||||
type LoginStatus byte
|
||||
|
||||
@@ -132,7 +135,7 @@ func (t *Tgbot) OnReceive() {
|
||||
isAdmin := checkAdmin(tgId)
|
||||
if update.Message == nil {
|
||||
if update.CallbackQuery != nil {
|
||||
t.asnwerCallback(update.CallbackQuery, isAdmin)
|
||||
t.asnwerCallback(update.CallbackQuery)
|
||||
}
|
||||
} else {
|
||||
if update.Message.IsCommand() {
|
||||
@@ -193,7 +196,7 @@ func (t *Tgbot) answerCommand(message *tgbotapi.Message, chatId int64, isAdmin b
|
||||
t.SendAnswer(chatId, msg, isAdmin)
|
||||
}
|
||||
|
||||
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery, isAdmin bool) {
|
||||
func (t *Tgbot) asnwerCallback(callbackQuery *tgbotapi.CallbackQuery) {
|
||||
// Respond to the callback query, telling Telegram to show the user
|
||||
// a message with the data received.
|
||||
callback := tgbotapi.NewCallback(callbackQuery.ID, callbackQuery.Data)
|
||||
|
||||